Skip to content

Commit

Permalink
apply suggestion and improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
jjnicola committed Jan 22, 2025
1 parent 0fdda51 commit 17a6712
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 115 deletions.
219 changes: 107 additions & 112 deletions rust/src/alive_test/alive_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,76 +19,78 @@ use std::{
use pcap::{Active, Capture, Inactive, PacketCodec, PacketStream};
use pnet::packet::{
self,
icmp::*,
icmp::{IcmpCode, IcmpTypes, MutableIcmpPacket, *},
ip::IpNextHeaderProtocol,
ipv4::{checksum, Ipv4Packet},
ipv4::{checksum, Ipv4Packet, MutableIpv4Packet},
};

use socket2::{Domain, Protocol, Socket};

use tracing::debug;

/// Define IPPROTO_RAW
const IPPROTO_RAW: i32 = 255;

/// Default timeout
const DEFAULT_TIMEOUT: u64 = 5000;

const DEFAULT_TIMEOUT_MS: u64 = 5000;
const ICMP_LENGTH: usize = 8;
const IP_LENGTH: usize = 20;
const HEADER_LENGTH: u8 = 5;
const DEFAULT_TTL: u8 = 255;
const MIN_ALLOWED_PACKET_LEN: usize = 16;
enum AliveTestCtl {
Stop,
// (IP and succesful detection method)
Alive(String, AliveTestMethods),
Alive {
ip: String,
detection_method: AliveTestMethods,
},
}
fn make_mut_icmp_packet(buf: &mut Vec<u8>) -> Result<MutableIcmpPacket, AliveTestError> {
MutableIcmpPacket::new(buf).ok_or_else(|| AliveTestError::CreateIcmpPacket)
}

fn new_raw_socket() -> Result<Socket, AliveTestError> {
match Socket::new_raw(
Socket::new_raw(
Domain::IPV4,
socket2::Type::RAW,
Some(Protocol::from(IPPROTO_RAW)),
) {
Ok(s) => Ok(s),
Err(_) => Err(AliveTestError::NoSocket("no socket".to_string())),
}
)
.map_err(|e| AliveTestError::NoSocket(e.to_string()))
}

async fn forge_icmp(dst: IpAddr) -> Result<Vec<u8>, AliveTestError> {
fn forge_icmp(dst: IpAddr) -> Result<Vec<u8>, AliveTestError> {
if dst.is_ipv6() {
return Err(AliveTestError::InvalidDestinationAddr(
"Invalid destination address".to_string(),
));
return Err(AliveTestError::InvalidDestinationAddr);
}

let mut buf = vec![0; 8]; // icmp length
let mut icmp_pkt = packet::icmp::MutableIcmpPacket::new(&mut buf)
.ok_or_else(|| AliveTestError::CreateIcmpPacket("No icmp packet".to_string()))?;
// Create an icmp packet from a buffer and modify it.
let mut buf = vec![0; ICMP_LENGTH];
let mut icmp_pkt = make_mut_icmp_packet(&mut buf)?;
icmp_pkt.set_icmp_type(IcmpTypes::EchoRequest);
icmp_pkt.set_icmp_code(IcmpCode::new(0u8));

icmp_pkt.set_icmp_type(packet::icmp::IcmpTypes::EchoRequest);
icmp_pkt.set_icmp_code(packet::icmp::IcmpCode::new(0u8));
let icmp_aux = IcmpPacket::new(&buf)
.ok_or_else(|| AliveTestError::CreateIcmpPacket("No icmp packet".to_string()))?;
// Require an unmutable ICMP packet for checksum calculation.
// We create an unmutable from the buffer for this purpose
let icmp_aux = IcmpPacket::new(&buf).ok_or_else(|| AliveTestError::CreateIcmpPacket)?;
let chksum = pnet::packet::icmp::checksum(&icmp_aux);

let mut icmp_pkt = packet::icmp::MutableIcmpPacket::new(&mut buf)
.ok_or_else(|| AliveTestError::CreateIcmpPacket("No icmp packet".to_string()))?;
// Because the buffer of original mutable icmp packet is borrowed,
// create a new mutable icmp packet to set the checksum in the original buffer.
let mut icmp_pkt = make_mut_icmp_packet(&mut buf)?;
icmp_pkt.set_checksum(chksum);

let mut ip_buf = vec![0; 20]; //IP length
// We do now the same as above for the IPv4 packet, appending the icmp packet as payload
let mut ip_buf = vec![0; IP_LENGTH];
ip_buf.append(&mut buf);
let total_length = ip_buf.len();
let mut pkt = packet::ipv4::MutableIpv4Packet::new(&mut ip_buf)
.ok_or_else(|| AliveTestError::CreateIcmpPacket("No icmp packet".to_string()))?;
let mut pkt =
MutableIpv4Packet::new(&mut ip_buf).ok_or_else(|| AliveTestError::CreateIcmpPacket)?;

pkt.set_header_length(5_u8);
pkt.set_next_level_protocol(IpNextHeaderProtocol(0x01));
pkt.set_ttl(255_u8);
pkt.set_header_length(HEADER_LENGTH);
pkt.set_next_level_protocol(IpNextHeaderProtocol(IpNextHeaderProtocols::Icmp.0));
pkt.set_ttl(DEFAULT_TTL);
match dst.to_string().parse::<Ipv4Addr>() {
Ok(ip) => {
pkt.set_destination(ip);
}
Err(_) => {
return Err(AliveTestError::InvalidDestinationAddr(
"Invalid destination address".to_string(),
));
return Err(AliveTestError::InvalidDestinationAddr);
}
};

Expand All @@ -101,27 +103,20 @@ async fn forge_icmp(dst: IpAddr) -> Result<Vec<u8>, AliveTestError> {
}

/// Send an icmp packet
async fn alive_test_send_icmp_packet(icmp: Vec<u8>) -> Result<(), AliveTestError> {
fn alive_test_send_icmp_packet(icmp: Vec<u8>) -> Result<(), AliveTestError> {
tracing::debug!("starting sending packet");
let soc = new_raw_socket()?;

if let Err(_) = soc.set_header_included_v4(true) {
return Err(AliveTestError::NoSocket("no socket".to_string()));
};
let sock = new_raw_socket()?;
sock.set_header_included_v4(true)
.map_err(|e| AliveTestError::NoSocket(e.to_string()))?;

let icmp_raw = &icmp as &[u8];
let packet = packet::ipv4::Ipv4Packet::new(icmp_raw).ok_or_else(|| {
AliveTestError::CreateIcmpPacket("Not possible to create icmp packet".to_string())
})?;

let sock_str = format!("{}:{}", &packet.get_destination().to_string().as_str(), 0);
let sockaddr = SocketAddr::from_str(&sock_str)
.map_err(|_| AliveTestError::NoSocket("no socket".to_string()))?;
let sockaddr = socket2::SockAddr::from(sockaddr);
let packet =
packet::ipv4::Ipv4Packet::new(icmp_raw).ok_or_else(|| AliveTestError::CreateIcmpPacket)?;

match soc.send_to(icmp_raw, &sockaddr) {
let sockaddr = SocketAddr::new(IpAddr::V4(packet.get_destination()), 0);
match sock.send_to(icmp_raw, &sockaddr.into()) {
Ok(b) => {
debug!("Sent {} bytes", b);
tracing::debug!("Sent {} bytes", b);
}
Err(e) => {
return Err(AliveTestError::SendPacket(e.to_string()));
Expand All @@ -146,7 +141,7 @@ fn pkt_stream(
let cap = capture_inactive
.promisc(true)
.immediate_mode(true)
.timeout(5000)
.timeout(DEFAULT_TIMEOUT_MS as i32)
.immediate_mode(true)
.open()?
.setnonblock()?;
Expand All @@ -167,47 +162,44 @@ impl TryFrom<&[u8]> for EtherTypes {
fn try_from(val: &[u8]) -> Result<Self, Self::Error> {
match val {
&[0x08, 0x00] => Ok(EtherTypes::EtherTypeIp),
&[0x08, 0x06] => Ok(EtherTypes::EtherTypeIp),
&[0x08, 0xDD] => Ok(EtherTypes::EtherTypeIp),
&[0x08, 0x06] => Ok(EtherTypes::EtherTypeArp),
&[0x08, 0xDD] => Ok(EtherTypes::EtherTypeIp6),
_ => Err(AliveTestError::InvalidEtherType(
"Invalid EtherType".to_string(),
)),
}
}
}

fn process_ip_packet(packet: &[u8]) -> Result<Option<AliveTestCtl>, AliveTestError> {
let pkt = Ipv4Packet::new(&packet[16..]).ok_or_else(|| AliveTestError::CreateIcmpPacket)?;
let hl = pkt.get_header_length() as usize;
if pkt.get_next_level_protocol() == IpNextHeaderProtocols::Icmp {
let icmp_pkt =
IcmpPacket::new(&packet[hl..]).ok_or_else(|| AliveTestError::CreateIcmpPacket)?;
if icmp_pkt.get_icmp_type() == IcmpTypes::EchoReply {
if pkt.get_next_level_protocol() == IpNextHeaderProtocols::Icmp {
return Ok(Some(AliveTestCtl::Alive {
ip: pkt.get_source().to_string(),
detection_method: AliveTestMethods::Icmp,
}));
}
}
}
Ok(None)
}

fn process_packet(packet: &[u8]) -> Result<Option<AliveTestCtl>, AliveTestError> {
if packet.len() <= 16 {
return Err(AliveTestError::CreateIcmpPacket(
"Invalid IP packet".to_string(),
));
if packet.len() <= MIN_ALLOWED_PACKET_LEN {
return Err(AliveTestError::WrongPacketLenght);
};
let ether_type = &packet[14..16];
let ether_type = EtherTypes::try_from(ether_type)?;
match ether_type {
EtherTypes::EtherTypeIp => {
let pkt = Ipv4Packet::new(&packet[16..])
.ok_or_else(|| AliveTestError::CreateIcmpPacket("Invalid IP packet".to_string()))?;
let hl = pkt.get_header_length() as usize;
if pkt.get_next_level_protocol() == IpNextHeaderProtocols::Icmp {
let icmp_pkt = IcmpPacket::new(&packet[hl..]).ok_or_else(|| {
AliveTestError::CreateIcmpPacket("invalid icmp reply".to_string())
})?;
if icmp_pkt.get_icmp_type() == IcmpTypes::EchoReply {
if pkt.get_next_level_protocol() == IpNextHeaderProtocols::Icmp {
return Ok(Some(AliveTestCtl::Alive(
pkt.get_source().to_string(),
AliveTestMethods::Icmp,
)));
}
}
}
}
EtherTypes::EtherTypeIp => process_ip_packet(&packet),
EtherTypes::EtherTypeIp6 => unimplemented!(),
EtherTypes::EtherTypeArp => unimplemented!(),
}

Ok(None)
}

pub struct Scanner {
Expand All @@ -230,65 +222,55 @@ impl Scanner {
let mut alive = Vec::<(String, String)>::new();

if self.methods.contains(&AliveTestMethods::ConsiderAlive) {
self.target.iter().for_each(|t| {
for t in self.target.iter() {
alive.push((t.clone(), AliveTestMethods::ConsiderAlive.to_string()));
println!("{t} via {}", AliveTestMethods::ConsiderAlive.to_string())
});

}
return Ok(());
}
};

let capture_inactive = Capture::from_device("any").unwrap();
let capture_inactive = Capture::from_device("any")
.map_err(|e| AliveTestError::NoValidInterface(e.to_string()))?;
let trgt = self.target.clone();

let (tx_ctl, mut rx_ctl): (mpsc::Sender<AliveTestCtl>, mpsc::Receiver<AliveTestCtl>) =
mpsc::channel(1024);
let (tx_msg, mut rx_msg): (mpsc::Sender<AliveTestCtl>, mpsc::Receiver<AliveTestCtl>) =
mpsc::channel(1024);
let (tx_ctl, mut rx_ctl) = mpsc::channel(1024);
let (tx_msg, mut rx_msg) = mpsc::channel(1024);

// spawn the process for reading packets from the interfaces.
let worker_capture_handle = tokio::spawn(async move {
let capture_handle = tokio::spawn(async move {
let mut stream = pkt_stream(capture_inactive).expect("Failed to create stream");
tracing::debug!("Start capture loop");

loop {
tokio::select! {
packet = stream.next() => { // packet is Option<Result<Box>>
if let Some(Ok(data)) = packet {
if let Ok(Some(AliveTestCtl::Alive(addr, method))) = process_packet(&data) {
tx_msg.send(AliveTestCtl::Alive(addr, method)).await.unwrap()
if let Ok(Some(AliveTestCtl::Alive{ip: addr, detection_method: method})) = process_packet(&data) {
tx_msg.send(AliveTestCtl::Alive{ip: addr, detection_method: method}).await.unwrap()
}
}
},
Some(ctl) = rx_ctl.recv() => {
match ctl {
AliveTestCtl::Stop =>{
break;
},
_ => ()
ctl = rx_ctl.recv() => {
if let Some(AliveTestCtl::Stop) = ctl {
break;
};
},
}
}
tracing::debug!("leaving the capture thread");
Ok(())
});

let timeout = if self.timeout.is_some() {
self.timeout.unwrap()
} else {
DEFAULT_TIMEOUT
};

let timeout = self.timeout.unwrap_or(DEFAULT_TIMEOUT_MS);
let methods = self.methods.clone();
let worker_handle = tokio::spawn(async move {
let send_handle = tokio::spawn(async move {
let mut count = 0;

if methods.contains(&AliveTestMethods::Icmp) {
for t in trgt.iter() {
count += 1;
let dst_ip = IpAddr::from_str(t).unwrap();
let icmp = forge_icmp(dst_ip).await.unwrap();
let _ = alive_test_send_icmp_packet(icmp).await;
let dst_ip = IpAddr::from_str(t).expect("Valid IP address");
let icmp = forge_icmp(dst_ip).expect("Valid ICMP packet");
let _ = alive_test_send_icmp_packet(icmp);
}
}
if methods.contains(&AliveTestMethods::TcpSyn) {
Expand All @@ -304,15 +286,28 @@ impl Scanner {
tracing::debug!("Finished sending {count} packets");
sleep(Duration::from_millis(timeout)).await;
let _ = tx_ctl.send(AliveTestCtl::Stop).await;
Ok(())
});

while let Some(AliveTestCtl::Alive(addr, method)) = rx_msg.recv().await {
while let Some(AliveTestCtl::Alive {
ip: addr,
detection_method: method,
}) = rx_msg.recv().await
{
alive.push((addr.clone(), method.to_string()));
println!("{addr} via {method:?}");
}

let _ = worker_handle.await;
let _ = worker_capture_handle.await;
match send_handle.await {
Ok(Ok(())) => (),
Ok(Err(e)) => return Err(e),
Err(e) => return Err(AliveTestError::JoinError(e.to_string())),
};
match capture_handle.await {
Ok(Ok(())) => (),
Ok(Err(e)) => return Err(e),
Err(e) => return Err(AliveTestError::JoinError(e.to_string())),
};

Ok(())
}
Expand Down
12 changes: 9 additions & 3 deletions rust/src/alive_test/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ pub enum Error {
/// Not possible to create a socket
#[error("Not possible to create a socket")]
NoSocket(String),
#[error("Not possible to create an ICMP packet")]
CreateIcmpPacket(String),
#[error("Wrong buffer size. Not possible to create an ICMP packet")]
CreateIcmpPacket,
#[error("Invalid destination Address")]
InvalidDestinationAddr(String),
InvalidDestinationAddr,
#[error("Route unavailable")]
UnavailableRoute(String),
#[error("There was an error")]
Expand All @@ -24,4 +24,10 @@ pub enum Error {
SendPacket(String),
#[error("Invalid EtherType")]
InvalidEtherType(String),
#[error("Wrong packet length")]
WrongPacketLenght,
#[error("No valid interface")]
NoValidInterface(String),
#[error("Fail spawning the task")]
JoinError(String),
}
1 change: 1 addition & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: GPL-2.0-or-later WITH x11vnc-openssl-exception

#[cfg(feature = "nasl-builtin-raw-ip")]
pub mod alive_test;
pub mod feed;
pub mod models;
Expand Down

0 comments on commit 17a6712

Please sign in to comment.