Skip to content
Merged
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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ fn main() {
Ok(_) => println!("Ping successful with custom options!"),
Err(e) => eprintln!("Ping failed: {}", e),
}

}
```

To perform a ping using a domain name instead of an IP address, you can use any 3rd-party DNS resolver or [`ToSocketAddrs`](https://doc.rust-lang.org/std/net/trait.ToSocketAddrs.html) from the standard library:

```rust
use std::net::ToSocketAddrs;

fn main() {
let address = "www.google.com:0" // use any port, we only need the IP
.to_socket_addrs() // convert domain name to socket address iterator
Expand Down
6 changes: 6 additions & 0 deletions src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
use thiserror::Error;

/// Errors that can occur while sending a ping or decoding its reply.
#[derive(Debug, Error)]
pub enum Error {
/// The target address used an unsupported protocol.
#[error("invalid procotol")]
InvalidProtocol,
/// An internal error, such as failing to encode the request packet.
#[error("internal error")]
InternalError,
/// The ICMP echo reply could not be decoded.
#[error("Decode echo reply error occurred while processing the ICMP echo reply.")]
DecodeEchoReplyError,
/// An underlying I/O error. A timeout is reported here with kind
/// [`ErrorKind::TimedOut`](std::io::ErrorKind::TimedOut).
#[error("io error: {error}")]
IoError {
#[from]
Expand Down
51 changes: 51 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,54 @@
//! An ICMP echo ("ping") implementation for IPv4 and IPv6.
//!
//! Send an ICMP echo request to a target [`IpAddr`] and wait for the reply or
//! a timeout.
//!
//! # Quick start
//!
//! The main entry point is the [`Ping`] builder, created with [`new`].
//! Configure the options you need and call [`Ping::send`], which returns a
//! [`PingResult`] describing the reply.
//!
//! ```no_run
//! use std::time::Duration;
//!
//! let target = "8.8.8.8".parse().unwrap();
//! let result = ping::new(target)
//! .timeout(Duration::from_secs(2))
//! .ttl(64)
//! .send()
//! .expect("ping failed");
//!
//! println!("round-trip time: {:?}", result.rtt);
//! ```
//!
//! # Pinging a host name
//!
//! Only an [`IpAddr`] is accepted. To ping a host name, resolve it first with
//! [`ToSocketAddrs`](std::net::ToSocketAddrs).
//!
//! ```no_run
//! use std::net::ToSocketAddrs;
//!
//! // The port is irrelevant, we only need the resolved IP.
//! let addr = "www.google.com:0"
//! .to_socket_addrs()
//! .unwrap()
//! .next()
//! .unwrap()
//! .ip();
//!
//! ping::new(addr).send().expect("ping failed");
//! ```
//!
//! # Socket types
//!
//! Sending ICMP traffic over a [`RAW`] socket needs elevated privileges, while
//! a [`DGRAM`] socket works unprivileged on most systems. See [`SocketType`]
//! for the per-platform default and how to override it.
//!
//! [`IpAddr`]: std::net::IpAddr

mod errors;
mod packet;
mod ping;
Expand Down
58 changes: 58 additions & 0 deletions src/ping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@ const TOKEN_SIZE: usize = 24;
const ECHO_REQUEST_BUFFER_SIZE: usize = ICMP_HEADER_SIZE + TOKEN_SIZE;
type Token = [u8; TOKEN_SIZE];

/// The kind of socket used to send the ICMP request.
///
/// The default depends on the platform. On Windows [`Ping::new`] uses
/// [`RAW`](SocketType::RAW), elsewhere it uses [`DGRAM`](SocketType::DGRAM).
/// Override it with [`Ping::socket_type`].
#[derive(Clone, Copy, Debug)]
pub enum SocketType {
/// Raw socket. Needs elevated privileges (root, or `CAP_NET_RAW` on Linux).
RAW,
/// Datagram socket. Works without elevated privileges on most systems, but
/// some Linux distributions disable it by default.
DGRAM,
}

Expand All @@ -26,9 +34,12 @@ impl From<SocketType> for Type {
}
}

/// The outcome of a successful ping, returned by [`Ping::send`].
#[derive(Debug)]
#[non_exhaustive]
pub struct PingResult {
/// The measured round-trip time between sending the request and receiving
/// the matching reply.
pub rtt: Duration,
/// The ICMP identifier observed in the reply.
///
Expand All @@ -38,10 +49,14 @@ pub struct PingResult {
/// reply, and therefore this field, carries the kernel-chosen value rather
/// than the requested one.
pub ident: u16,
/// The sequence number echoed back in the reply.
pub seq_cnt: u16,
/// The payload token echoed back in the reply, used to match it to the
/// request.
pub payload: Vec<u8>,
/// The actual source IP address from the reply packet.
pub source: IpAddr,
/// The target address passed to the ping.
#[deprecated(since = "0.7.1", note = "use `source` instead")]
pub target: IpAddr,
/// The TTL from the reply IP header. Only available for IPv4 RAW sockets;
Expand Down Expand Up @@ -234,6 +249,16 @@ pub fn ping(
Ok(())
}

/// Builder for a single ping.
///
/// Create one with [`Ping::new`] or [`new`], set any options, then call
/// [`send`](Ping::send). All options are optional and have sensible defaults.
///
/// ```no_run
/// let target = "8.8.8.8".parse().unwrap();
/// let result = ping::new(target).send().expect("ping failed");
/// println!("{:?}", result.rtt);
/// ```
#[derive(Debug, Clone)]
pub struct Ping<'a> {
socket_type: SocketType,
Expand All @@ -248,6 +273,9 @@ pub struct Ping<'a> {
}

impl<'a> Ping<'a> {
/// Creates a builder targeting `addr`, with the default socket type for
/// the current platform ([`RAW`](SocketType::RAW) on Windows,
/// [`DGRAM`](SocketType::DGRAM) elsewhere).
pub fn new(addr: IpAddr) -> Self {
let socket_type = if std::env::consts::OS == "windows" {
SocketType::RAW
Expand All @@ -267,6 +295,8 @@ impl<'a> Ping<'a> {
};
}

/// 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;
Expand All @@ -288,11 +318,19 @@ impl<'a> Ping<'a> {
)
}

/// Sets how long [`send`](Ping::send) waits for a reply before failing.
///
/// When unset, the timeout defaults to 4 seconds. On timeout, `send`
/// returns an [`Error::IoError`] whose kind is
/// [`ErrorKind::TimedOut`](std::io::ErrorKind::TimedOut).
pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
self.timeout = Some(timeout);
return 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;
Expand All @@ -312,27 +350,47 @@ impl<'a> Ping<'a> {
return 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;
}

/// Sets the 24-byte payload token carried by the request.
///
/// The reply is matched to the request by this token, so it acts as the
/// 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;
}

/// Binds the socket to a network interface by name (e.g. `"eth0"`), so the
/// request is sent from that interface.
///
/// Only available on Linux and Android.
#[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;
}

/// Sends the echo request and blocks until a matching reply arrives or the
/// timeout elapses.
///
/// On success returns a [`PingResult`]. A timeout is reported as an
/// [`Error::IoError`] with kind
/// [`ErrorKind::TimedOut`](std::io::ErrorKind::TimedOut).
pub fn send(&self) -> Result<PingResult, Error> {
self.ping_with_socket(self.socket_type.into())
}
}

/// Creates a [`Ping`] builder targeting `addr`.
///
/// Shorthand for [`Ping::new`].
pub fn new<'a>(addr: IpAddr) -> Ping<'a> {
return Ping::new(addr);
}
Loading