diff --git a/crates/network-scanner-net/Cargo.toml b/crates/network-scanner-net/Cargo.toml index 285b0b839..195a4b5b2 100644 --- a/crates/network-scanner-net/Cargo.toml +++ b/crates/network-scanner-net/Cargo.toml @@ -9,6 +9,10 @@ publish = false [dependencies] anyhow = "1.0.79" +etherparse = "0.13.0" socket2 = { version = "0.5.5", features = ["all"] } tokio = { version = "1.35.1", features = ["io-util","rt","sync"] } tracing = "0.1.40" + +[dev-dependencies] +network-scanner-proto = { path = "../network-scanner-proto" } \ No newline at end of file diff --git a/crates/network-scanner-net/examples/block_raw.rs b/crates/network-scanner-net/examples/block_raw.rs new file mode 100644 index 000000000..655f6c594 --- /dev/null +++ b/crates/network-scanner-net/examples/block_raw.rs @@ -0,0 +1,41 @@ +use etherparse::{icmpv4, IcmpEchoHeader, Icmpv4Header}; +use socket2::{Domain, Protocol, SockAddr, Socket, Type}; +use std::{ + io::{self, Write}, + mem::MaybeUninit, + net::SocketAddr, + thread, +}; + +#[derive(Debug)] +#[repr(u8)] +pub enum IpProtocol { + Icmp = 1, + Igmp = 2, + Tcp = 6, + Udp = 17, + Ipv6 = 41, + Icmpv6 = 58, + UdpLite = 136, +} + +fn main() -> io::Result<()> { + let socket = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?; + + let buf = [8, 0, 246, 78, 0, 0, 0, 0, 0, 0, 0, 0, 101, 157, 156, 19]; + let addr = socket2::SockAddr::from(SocketAddr::from(([127, 0, 0, 1], 0))); + socket.send_to(&buf, &addr).unwrap(); + + let mut buf = [MaybeUninit::::uninit(); 8096]; + let (size, addr) = socket.recv_from(&mut buf)?; + + let inited_buf = buf[..size].as_ref(); + let buffer = inited_buf + .iter() + .map(|u| unsafe { u.assume_init() }) + .collect::>(); + println!("Received {} bytes from write socket", size); + println!("{:?}", buffer); + + Ok(()) +} diff --git a/crates/network-scanner-net/examples/send_all.rs b/crates/network-scanner-net/examples/send_all.rs new file mode 100644 index 000000000..9a02a1b00 --- /dev/null +++ b/crates/network-scanner-net/examples/send_all.rs @@ -0,0 +1,51 @@ +use etherparse::{icmpv4, IcmpEchoHeader, Icmpv4Header}; +use socket2::{Domain, Protocol, SockAddr, Socket, Type}; +use std::{ + io::{self, Write}, + mem::MaybeUninit, + net::SocketAddr, + thread, +}; + +#[derive(Debug)] +#[repr(u8)] +pub enum IpProtocol { + Icmp = 1, + Igmp = 2, + Tcp = 6, + Udp = 17, + Ipv6 = 41, + Icmpv6 = 58, + UdpLite = 136, +} + +fn main() -> io::Result<()> { + println!("Sending packet"); + let send_socket = Socket::new(Domain::IPV4, Type::RAW, Some(Protocol::ICMPV4))?; + let addr = SocketAddr::from(([192, 168, 1, 255], 1)); + + let time = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + + send_socket.set_broadcast(true)?; + // send_socket.connect(&addr.into()).expect("Failed to connect"); + let echo_message = network_scanner_proto::icmp_v4::Icmpv4Message::Echo { + identifier: 0, + sequence: 0, + payload: time.to_be_bytes().to_vec(), + }; + + let packet = network_scanner_proto::icmp_v4::Icmpv4Packet::from_message(echo_message); + // send_socket.write(&packet.to_bytes(true)).expect("Failed to write"); + send_socket + .send_to(&packet.to_bytes(true), &SockAddr::from(addr)) + .unwrap(); + println!("packet sent"); + let mut buf = [MaybeUninit::::uninit(); 8096]; + let res = send_socket.recv(&mut buf).expect("Failed to receive"); + println!("Received {} bytes from write socket", res); + + Ok(()) +} diff --git a/crates/network-scanner-net/src/broadcast.rs b/crates/network-scanner-net/src/broadcast.rs new file mode 100644 index 000000000..b7ccefed7 --- /dev/null +++ b/crates/network-scanner-net/src/broadcast.rs @@ -0,0 +1,11 @@ +// pub struct ResultStream{ + +// } + +// impl tokio_stream::Stream for ResultStream{ +// type Item = &[u8]; + +// } + +// pub async fn boardcast(ip: Ipv4Addr) -> std::io::Result<()>{ +// } diff --git a/crates/network-scanner-net/src/lib.rs b/crates/network-scanner-net/src/lib.rs index 8540f98d7..3faa8afcd 100644 --- a/crates/network-scanner-net/src/lib.rs +++ b/crates/network-scanner-net/src/lib.rs @@ -1 +1,2 @@ +pub mod broadcast; pub mod tokio_raw_socket; diff --git a/crates/network-scanner-net/src/tokio_raw_socket.rs b/crates/network-scanner-net/src/tokio_raw_socket.rs index 48f58c3ab..6f2de719d 100644 --- a/crates/network-scanner-net/src/tokio_raw_socket.rs +++ b/crates/network-scanner-net/src/tokio_raw_socket.rs @@ -18,7 +18,6 @@ impl TokioRawSocket { ty: socket2::Type, protocol: Option, ) -> std::io::Result { - let socket = socket2::Socket::new(domain, ty, protocol)?; let socket = Arc::new(Mutex::new(socket)); Ok(TokioRawSocket { socket }) diff --git a/crates/network-scanner-proto/src/icmp_v4.rs b/crates/network-scanner-proto/src/icmp_v4.rs index f6d6fd3ed..bf77c9794 100644 --- a/crates/network-scanner-proto/src/icmp_v4.rs +++ b/crates/network-scanner-proto/src/icmp_v4.rs @@ -17,6 +17,7 @@ pub enum Icmpv4MessageType { InformationReply = 16, } +#[derive(Debug)] pub enum Icmpv4Message { EchoReply { // type 0 @@ -213,6 +214,8 @@ impl Icmpv4Message { bytes } } + +#[derive(Debug)] pub struct Icmpv4Packet { pub code: u8, pub checksum: u16, diff --git a/crates/network-scanner/Cargo.toml b/crates/network-scanner/Cargo.toml index af1413f3b..d0af5d9a8 100644 --- a/crates/network-scanner/Cargo.toml +++ b/crates/network-scanner/Cargo.toml @@ -8,10 +8,12 @@ publish = false [dependencies] anyhow = "1.0.79" +get_if_addrs = "0.5.3" network-scanner-net ={ path = "../network-scanner-net" } network-scanner-proto ={ path = "../network-scanner-proto" } socket2 = "0.5.5" tokio = { version = "1.35.1", features = ["io-util"] } +tokio-stream = "0.1.14" tracing = "0.1.40" [dev-dependencies] diff --git a/crates/network-scanner/examples/block_boardcast.rs b/crates/network-scanner/examples/block_boardcast.rs new file mode 100644 index 000000000..a6dfb3a91 --- /dev/null +++ b/crates/network-scanner/examples/block_boardcast.rs @@ -0,0 +1,36 @@ +use std::{process::exit, time::Duration}; + +use network_scanner::boardcast::block_broadcast; + +pub fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt::SubscriberBuilder::default() + .with_max_level(tracing::Level::TRACE) + .init(); + + let ip = std::net::Ipv4Addr::new(192, 168, 1, 255); + + let iterator = block_broadcast(ip, Some(Duration::from_secs(1)))?; + + for result in iterator { + match result { + Ok(ping_res) => { + println!("received result {:?}", &ping_res); + } + Err(e) => match e.downcast_ref::() { + Some(e) => match e.kind() { + std::io::ErrorKind::TimedOut => { + tracing::info!("Timed out"); + exit(0); + } + _ => { + tracing::error!("Failed to receive broadcast packet: {}", e); + break; + } + }, + None => break, + }, + } + } + + Ok(()) +} diff --git a/crates/network-scanner/examples/block_ping.rs b/crates/network-scanner/examples/block_ping.rs new file mode 100644 index 000000000..60dc1b4a6 --- /dev/null +++ b/crates/network-scanner/examples/block_ping.rs @@ -0,0 +1,10 @@ +use network_scanner::ping::block_ping; + +pub fn main() { + tracing_subscriber::fmt::SubscriberBuilder::default() + .with_max_level(tracing::Level::TRACE) + .init(); + + let ip = std::net::Ipv4Addr::new(127, 0, 0, 1); + block_ping(ip).expect("Failed to ping"); +} diff --git a/crates/network-scanner/examples/boardcast.rs b/crates/network-scanner/examples/boardcast.rs new file mode 100644 index 000000000..169fbe021 --- /dev/null +++ b/crates/network-scanner/examples/boardcast.rs @@ -0,0 +1,35 @@ +use std::{process::exit, time::Duration}; + +use network_scanner::boardcast::boardcast; +use tokio_stream::StreamExt; + +#[tokio::main] +pub async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt::SubscriberBuilder::default() + .with_max_level(tracing::Level::TRACE) + .init(); + + let ip = std::net::Ipv4Addr::new(192, 168, 1, 255); + + let mut iterator = boardcast(ip, Some(Duration::from_secs(1))).await?; + + while let Some(result) = iterator.next().await { + match result { + Ok(res) => { + tracing::info!("received result {:?}", &res) + } + Err(e) => { + if let Some(e) = e.downcast_ref::() { + // if is timeout, say timeout then break + if let std::io::ErrorKind::TimedOut = e.kind() { + tracing::info!("timed out"); + exit(0) + } + } + return Err(e); + } + } + } + + Ok(()) +} diff --git a/crates/network-scanner/src/boardcast.rs b/crates/network-scanner/src/boardcast.rs new file mode 100644 index 000000000..f60472cfa --- /dev/null +++ b/crates/network-scanner/src/boardcast.rs @@ -0,0 +1,216 @@ +use std::{ + io::ErrorKind, + mem::MaybeUninit, + net::{Ipv4Addr, SocketAddr}, + time::Duration, +}; + +use anyhow::Context; +use network_scanner_net::tokio_raw_socket::TokioRawSocket; +use network_scanner_proto::icmp_v4; +use socket2::SockAddr; + +use crate::create_echo_request; + +#[derive(Debug)] +pub struct PingResponse { + pub addr: Ipv4Addr, + pub packet: icmp_v4::Icmpv4Packet, +} + +impl PingResponse { + pub(crate) unsafe fn from_raw( + addr: socket2::SockAddr, + payload: &[MaybeUninit], + size: usize, + ) -> anyhow::Result { + let addr = addr + .as_socket_ipv4() + .with_context(|| format!("sock addr is not ipv4"))? + .ip() + .clone(); // ip is private + + let payload = payload[..size] + .as_ref() + .iter() + .map(|u| unsafe { u.assume_init() }) + .collect::>(); + + let packet = icmp_v4::Icmpv4Packet::parse(payload.as_slice())?; + + Ok(PingResponse { addr, packet }) + } + + pub fn verify(&self, verifier: &[u8]) -> bool { + if let icmp_v4::Icmpv4Message::EchoReply { payload, .. } = &self.packet.message { + payload == verifier + } else { + false + } + } +} + +pub struct BroadcastStream { + receiver: tokio::sync::mpsc::Receiver>, SockAddr), std::io::Error>>, + verifier: Vec, + should_verify: bool, +} + +impl BroadcastStream { + pub fn should_verify(&mut self, should_verify: bool) { + self.should_verify = should_verify; + } +} + +impl tokio_stream::Stream for BroadcastStream { + type Item = anyhow::Result; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let ready_res = match self.receiver.poll_recv(cx) { + std::task::Poll::Ready(res) => res, + std::task::Poll::Pending => return std::task::Poll::Pending, + }; + + let not_none_res = match ready_res { + Some(res) => res, + None => return std::task::Poll::Ready(None), + }; + + let (buf, addr) = match not_none_res { + Ok(res) => res, + Err(e) => return std::task::Poll::Ready(Some(Err(e.into()))), + }; + + let ping_result = unsafe { PingResponse::from_raw(addr, &buf, buf.len()) }; + + let ping_result = match ping_result { + Ok(a) => a, + Err(e) => return std::task::Poll::Ready(Some(Err(e.into()))), + }; + + if self.should_verify && !ping_result.verify(&self.verifier) { + return std::task::Poll::Ready(Some(Err(anyhow::anyhow!("failed to verify ping response")))); + } + + std::task::Poll::Ready(Some(Ok(ping_result))) + } +} + +/// Broadcasts a ping to the given ip address +/// caller need to make sure that the ip address is a broadcast address +pub async fn boardcast(ip: Ipv4Addr, read_time_out: Option) -> anyhow::Result { + let socket = TokioRawSocket::new( + socket2::Domain::IPV4, + socket2::Type::RAW, + Some(socket2::Protocol::ICMPV4), + )?; + socket.set_broadcast(true).await?; + if let Some(time_out) = read_time_out { + socket.set_read_timeout(time_out).await?; + } + let (packet, verifier) = create_echo_request!(); + socket + .send_to(&packet.to_bytes(true), SockAddr::from(SocketAddr::new(ip.into(), 0))) + .await?; + let (sender, receiver) = tokio::sync::mpsc::channel(255); + tokio::task::spawn(async move { + let mut buffer = [MaybeUninit::uninit(); icmp_v4::ICMPV4_MTU]; + loop { + let result = socket.recv_from(&mut buffer).await; + let (size, addr) = match result { + Ok(res) => res, + Err(e) => { + if sender.send(Err(e)).await.is_err() { + tracing::error!("channel falied, sending Err to receiver"); + } + break; + } + }; + + let buffer_copy = buffer[..size].as_ref().to_vec(); + sender.send(Ok((buffer_copy, addr))).await.unwrap(); + } + }); + + Ok(BroadcastStream { + receiver, + verifier, + should_verify: true, + }) +} + +pub struct BorcastBlockStream { + socket: socket2::Socket, + verifier: Vec, + should_verify: bool, +} + +impl Iterator for BorcastBlockStream { + type Item = anyhow::Result; + + fn next(&mut self) -> Option { + let mut buffer = [MaybeUninit::uninit(); icmp_v4::ICMPV4_MTU]; + let res = self.socket.recv_from(&mut buffer); + + let (size, addr) = match res { + Ok(res) => res, + Err(e) => { + return Some(Err(e.into())); + } + }; + + if size == 0 { + return None; + } + + let ping_result = unsafe { PingResponse::from_raw(addr, &buffer, size) }; + + let ping_result = match ping_result { + Ok(a) => a, + Err(e) => return Some(Err(e)), + }; + + if self.should_verify && !ping_result.verify(&self.verifier) { + return Some(Err(anyhow::anyhow!("failed to verify ping response"))); + } + + Some(Ok(ping_result)) + } +} + +impl BorcastBlockStream { + pub fn should_verify(&mut self, should_verify: bool) { + self.should_verify = should_verify; + } +} + +pub fn block_broadcast(ip: Ipv4Addr, read_time_out: Option) -> anyhow::Result { + let socket = socket2::Socket::new( + socket2::Domain::IPV4, + socket2::Type::RAW, + Some(socket2::Protocol::ICMPV4), + )?; + socket.set_broadcast(true)?; + + if let Some(time_out) = read_time_out { + socket.set_read_timeout(Some(time_out))?; + } + + let addr = SocketAddr::new(ip.into(), 0); + + let (packet, verifier) = create_echo_request!(); + + tracing::trace!(?packet, "sending packet"); + socket + .send_to(&packet.to_bytes(true), &addr.into()) + .with_context(|| format!("Failed to send packet to {}", ip))?; + + Ok(BorcastBlockStream { + socket, + verifier, + should_verify: true, + }) +} diff --git a/crates/network-scanner/src/lib.rs b/crates/network-scanner/src/lib.rs index a766209cf..e7de18d5c 100644 --- a/crates/network-scanner/src/lib.rs +++ b/crates/network-scanner/src/lib.rs @@ -1 +1,2 @@ +pub mod boardcast; pub mod ping;