diff --git a/Cargo.toml b/Cargo.toml index 2d052ec..c667663 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ socket2 = { version = "0.6", features = ["all"] } thiserror = ">=1.0, <=2.1" rand = ">=0.8, <=0.9" tokio = { version = "1", features = ["net", "rt", "time"], optional = true } +libc = "0.2" [dev-dependencies] libc = "0.2" diff --git a/src/packet/icmp.rs b/src/packet/icmp.rs index f049dfd..453a801 100644 --- a/src/packet/icmp.rs +++ b/src/packet/icmp.rs @@ -53,7 +53,7 @@ impl<'a> EchoRequest<'a> { buffer[6] = (self.seq_cnt >> 8) as u8; buffer[7] = self.seq_cnt as u8; - if let Err(_) = (&mut buffer[8..]).write(self.payload) { + if (&mut buffer[8..]).write(self.payload).is_err() { return Err(Error::InvalidSize); } diff --git a/src/packet/ipv4.rs b/src/packet/ipv4.rs index 9cd0c2a..d8b6b37 100644 --- a/src/packet/ipv4.rs +++ b/src/packet/ipv4.rs @@ -60,7 +60,7 @@ impl<'a> IpV4Packet<'a> { let ttl = data[8]; Ok(Self { - protocol: protocol, + protocol, ttl, data: &data[header_size..], }) diff --git a/src/ping.rs b/src/ping.rs index 46a399a..87416cd 100644 --- a/src/ping.rs +++ b/src/ping.rs @@ -1,4 +1,6 @@ +use std::io; use std::net::{IpAddr, SocketAddr}; +use std::os::fd::AsRawFd; use std::time::{Duration, Instant}; use rand::random; @@ -49,6 +51,7 @@ fn create_socket( addr: IpAddr, ttl: Option, bind_device: Option<&str>, + fwmark: Option, ) -> Result { let socket = if addr.is_ipv4() { Socket::new(Domain::IPV4, socket_type, Some(Protocol::ICMPV4))? @@ -71,6 +74,25 @@ fn create_socket( eprintln!("Warning: bind_device is only supported on Linux and Android platforms"); } + if let Some(fwmark) = fwmark { + #[cfg(any(target_os = "linux", target_os = "android"))] + if unsafe { + libc::setsockopt( + socket.as_raw_fd(), + libc::SOL_SOCKET, + libc::SO_MARK, + &fwmark as *const _ as *const _, + size_of_val(&fwmark) as libc::socklen_t, + ) + } != 0 + { + return Err(From::from(io::Error::last_os_error())); + } + + #[cfg(not(any(target_os = "linux", target_os = "android")))] + eprintln!("Warning: fwmark is only supported on Linux and Android platforms"); + } + Ok(socket) } @@ -150,7 +172,7 @@ pub struct PingResult { pub ttl: Option, } -#[allow(deprecated)] +#[allow(deprecated, clippy::too_many_arguments)] fn ping_with_socktype( socket_type: Type, addr: IpAddr, @@ -160,6 +182,7 @@ fn ping_with_socktype( seq_cnt: Option, payload: Option<&Token>, bind_device: Option<&str>, + fwmark: Option, ) -> Result { let timeout = match timeout { Some(timeout) => timeout, @@ -168,7 +191,7 @@ fn ping_with_socktype( let dest = SocketAddr::new(addr, 0); let (request_bytes, request_payload) = prepare_request(addr, ident, seq_cnt, payload)?; - let socket = create_socket(socket_type, addr, ttl, bind_device)?; + let socket = create_socket(socket_type, addr, ttl, bind_device, fwmark)?; socket.set_write_timeout(Some(timeout))?; @@ -219,7 +242,17 @@ pub mod rawsock { seq_cnt: Option, payload: Option<&Token>, ) -> Result<(), Error> { - ping_with_socktype(Type::RAW, addr, timeout, ttl, ident, seq_cnt, payload, None)?; + ping_with_socktype( + Type::RAW, + addr, + timeout, + ttl, + ident, + seq_cnt, + payload, + None, + None, + )?; Ok(()) } } @@ -243,6 +276,7 @@ pub mod dgramsock { seq_cnt, payload, None, + None, )?; Ok(()) } @@ -285,6 +319,8 @@ pub struct Ping<'a> { payload: Option<&'a Token>, #[cfg(any(target_os = "linux", target_os = "android"))] bind_device: Option<&'a str>, + #[cfg(any(target_os = "linux", target_os = "android"))] + fwmark: Option, } impl<'a> Ping<'a> { @@ -297,7 +333,7 @@ impl<'a> Ping<'a> { } else { SocketType::DGRAM }; - return Ping { + Ping { socket_type, addr, timeout: None, @@ -307,14 +343,16 @@ impl<'a> Ping<'a> { payload: None, #[cfg(any(target_os = "linux", target_os = "android"))] bind_device: None, - }; + #[cfg(any(target_os = "linux", target_os = "android"))] + fwmark: None, + } } /// Overrides the [`SocketType`] used to send the request, replacing the /// platform default chosen by [`Ping::new`]. pub fn socket_type(&mut self, socket_type: SocketType) -> &mut Self { self.socket_type = socket_type; - return self; + self } fn ping_with_socket(&self, sock_type: Type) -> Result { @@ -330,6 +368,10 @@ impl<'a> Ping<'a> { self.bind_device, #[cfg(not(any(target_os = "linux", target_os = "android")))] None, + #[cfg(any(target_os = "linux", target_os = "android"))] + self.fwmark, + #[cfg(not(any(target_os = "linux", target_os = "android")))] + None, ) } @@ -340,7 +382,7 @@ impl<'a> Ping<'a> { /// [`ErrorKind::TimedOut`](std::io::ErrorKind::TimedOut). pub fn timeout(&mut self, timeout: Duration) -> &mut Self { self.timeout = Some(timeout); - return self; + self } /// Sets the IP time-to-live (hop limit) of the request. @@ -348,7 +390,7 @@ impl<'a> Ping<'a> { /// Defaults to 64 when unset. pub fn ttl(&mut self, ttl: u32) -> &mut Self { self.ttl = Some(ttl); - return self; + self } /// Sets the ICMP identifier to send. @@ -362,7 +404,7 @@ impl<'a> Ping<'a> { /// on Windows, or when selected via [`Ping::socket_type`]). pub fn ident(&mut self, ident: u16) -> &mut Self { self.ident = Some(ident); - return self; + self } /// Sets the ICMP sequence number of the request. @@ -370,7 +412,7 @@ impl<'a> Ping<'a> { /// Defaults to 1 when unset. pub fn seq_cnt(&mut self, seq_cnt: u16) -> &mut Self { self.seq_cnt = Some(seq_cnt); - return self; + self } /// Sets the 24-byte payload token carried by the request. @@ -379,7 +421,7 @@ impl<'a> Ping<'a> { /// correlation id. When unset, a random token is generated for each ping. pub fn payload(&mut self, payload: &'a Token) -> &mut Self { self.payload = Some(payload); - return self; + self } /// Binds the socket to a network interface by name (e.g. `"eth0"`), so the @@ -389,7 +431,16 @@ impl<'a> Ping<'a> { #[cfg(any(target_os = "linux", target_os = "android"))] pub fn bind_device(&mut self, device: &'a str) -> &mut Self { self.bind_device = Some(device); - return self; + self + } + + /// Sets the fwmark on the socket. + /// + /// Only available on Linux and Android. + #[cfg(any(target_os = "linux", target_os = "android"))] + pub fn fwmark(&mut self, fwmark: u32) -> &mut Self { + self.fwmark = Some(fwmark); + self } /// Sends the echo request and blocks until a matching reply arrives or the @@ -407,5 +458,5 @@ impl<'a> Ping<'a> { /// /// Shorthand for [`Ping::new`]. pub fn new<'a>(addr: IpAddr) -> Ping<'a> { - return Ping::new(addr); + Ping::new(addr) } diff --git a/tests/tests.rs b/tests/tests.rs index b25a443..81cd04a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -143,6 +143,20 @@ fn bind_device() { .unwrap(); } +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn fwmark() { + let addr = "127.0.0.1".parse().unwrap(); + let timeout = Duration::from_secs(1); + ping::new(addr) + .timeout(timeout) + .ttl(42) + .fwmark(12345) + .socket_type(ping::SocketType::RAW) + .send() + .unwrap(); +} + #[test] fn duration() { // Ensure that the duration returned is less than the rtt