diff --git a/Cargo.lock b/Cargo.lock index 6b715eb18..666068664 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,6 +97,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "asn1-rs" version = "0.5.2" @@ -152,7 +158,7 @@ dependencies = [ "pin-utils", "pkg-config", "tokio", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -457,6 +463,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "c_linked_list" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b" + [[package]] name = "camino" version = "1.1.6" @@ -501,7 +513,7 @@ dependencies = [ "systemd-rs", "timer", "widestring 0.4.3", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -889,7 +901,7 @@ checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -912,7 +924,7 @@ dependencies = [ "dlopen_derive", "lazy_static", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -1044,6 +1056,15 @@ dependencies = [ "backtrace", ] +[[package]] +name = "etherparse" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "827292ea592108849932ad8e30218f8b1f21c0dfd0696698a18b5d0aed62d990" +dependencies = [ + "arrayvec", +] + [[package]] name = "exitcode" version = "1.1.2" @@ -1219,6 +1240,12 @@ dependencies = [ "slab", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generate-openapi" version = "0.0.0" @@ -1260,6 +1287,28 @@ dependencies = [ "zeroize", ] +[[package]] +name = "get_if_addrs" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abddb55a898d32925f3148bd281174a68eeb68bbfd9a5938a57b18f506ee4ef7" +dependencies = [ + "c_linked_list", + "get_if_addrs-sys", + "libc", + "winapi 0.2.8", +] + +[[package]] +name = "get_if_addrs-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04f9fb746cf36b191c00f3ede8bde9c8e64f9f4b05ae2694a9ccf5e3f5ab48" +dependencies = [ + "gcc", + "libc", +] + [[package]] name = "getrandom" version = "0.2.11" @@ -1410,7 +1459,7 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2017,10 +2066,12 @@ name = "network-scanner" version = "0.0.0" dependencies = [ "anyhow", + "get_if_addrs", "network-scanner-net", "network-scanner-proto", "socket2", "tokio", + "tokio-stream", "tracing", "tracing-subscriber", ] @@ -2030,6 +2081,8 @@ name = "network-scanner-net" version = "0.0.0" dependencies = [ "anyhow", + "etherparse", + "network-scanner-proto", "socket2", "tokio", "tracing", @@ -2116,7 +2169,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2126,7 +2179,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ "overload", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -2766,7 +2819,7 @@ dependencies = [ "core-foundation", "system-configuration-sys", "url", - "winapi", + "winapi 0.3.9", "winreg 0.9.0", ] @@ -2978,7 +3031,7 @@ dependencies = [ "spin 0.5.2", "untrusted 0.7.1", "web-sys", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -3558,7 +3611,7 @@ dependencies = [ "tracing", "url", "uuid", - "winapi", + "winapi 0.3.9", "windows 0.51.1", "windows-sys 0.48.0", "winreg 0.51.0", @@ -3633,7 +3686,7 @@ dependencies = [ "libc", "ntapi", "once_cell", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -4471,6 +4524,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -4734,7 +4793,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16cdb3898397cf7f624c294948669beafaeebc5577d5ec53d0afb76633593597" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] 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;