Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion src/packet/icmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion src/packet/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl<'a> IpV4Packet<'a> {
let ttl = data[8];

Ok(Self {
protocol: protocol,
protocol,
ttl,
data: &data[header_size..],
})
Expand Down
77 changes: 64 additions & 13 deletions src/ping.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -49,6 +51,7 @@ fn create_socket(
addr: IpAddr,
ttl: Option<u32>,
bind_device: Option<&str>,
fwmark: Option<u32>,
) -> Result<Socket, Error> {
let socket = if addr.is_ipv4() {
Socket::new(Domain::IPV4, socket_type, Some(Protocol::ICMPV4))?
Expand All @@ -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)
}

Expand Down Expand Up @@ -150,7 +172,7 @@ pub struct PingResult {
pub ttl: Option<u8>,
}

#[allow(deprecated)]
#[allow(deprecated, clippy::too_many_arguments)]
fn ping_with_socktype(
socket_type: Type,
addr: IpAddr,
Expand All @@ -160,6 +182,7 @@ fn ping_with_socktype(
seq_cnt: Option<u16>,
payload: Option<&Token>,
bind_device: Option<&str>,
fwmark: Option<u32>,
) -> Result<PingResult, Error> {
let timeout = match timeout {
Some(timeout) => timeout,
Expand All @@ -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))?;

Expand Down Expand Up @@ -219,7 +242,17 @@ pub mod rawsock {
seq_cnt: Option<u16>,
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(())
}
}
Expand All @@ -243,6 +276,7 @@ pub mod dgramsock {
seq_cnt,
payload,
None,
None,
)?;
Ok(())
}
Expand Down Expand Up @@ -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<u32>,
}

impl<'a> Ping<'a> {
Expand All @@ -297,7 +333,7 @@ impl<'a> Ping<'a> {
} else {
SocketType::DGRAM
};
return Ping {
Ping {
socket_type,
addr,
timeout: None,
Expand All @@ -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<PingResult, Error> {
Expand All @@ -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,
)
}

Expand All @@ -340,15 +382,15 @@ 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.
///
/// 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.
Expand All @@ -362,15 +404,15 @@ 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.
///
/// 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.
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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)
}
14 changes: 14 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading