ping-tokio 0.2.0

Async ICMP ping library for Rust, built on Tokio and raw sockets.
Documentation

ping-tokio

Async ICMP ping library for Rust, built on Tokio and raw sockets.

Supports ICMPv4 and ICMPv6. Requires the process to have permission to open raw sockets (either CAP_NET_RAW on Linux or running as root).


Library

Cargo.toml

[dependencies]
ping-tokio = "0.2"
tokio = { version = "1", features = ["rt", "macros"] }

High-level API

ping resolves the destination, opens the appropriate raw socket, sends count ICMP echo requests, and returns aggregate statistics.

use std::time::Duration;
use ping_tokio::ping;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let stats = ping("0.0.0.0", "8.8.8.8", 5, Duration::from_secs(1), 64).await?;
    println!(
        "{} tx / {} rx, rtt min/avg/max = {:.3}/{:.3}/{:.3} ms",
        stats.packets_tx,
        stats.packets_rx,
        stats.rtt_min.as_secs_f64() * 1000.0,
        stats.rtt_avg.as_secs_f64() * 1000.0,
        stats.rtt_max.as_secs_f64() * 1000.0,
    );
    Ok(())
}

ping accepts any type that implements ToIpAddr for both src and dest: Ipv4Addr, Ipv6Addr, IpAddr, &str, or String.

The size parameter is the total ICMP payload size in bytes. The first 8 bytes of the payload are reserved for an internal timestamp used to measure RTT; size must therefore be greater than 8.

PingStats fields

Field Description
packets_tx Number of echo requests sent
packets_rx Number of echo replies received
rtt_min Minimum RTT across received replies
rtt_avg Mean RTT
rtt_max Maximum RTT
rtt_std_dev Population standard deviation of RTT samples

Probes that time out are counted in packets_tx but not packets_rx. They do not cause the function to return an error.


Low-level API

For per-probe control, use IcmpSocket together with send_icmp_echo_v4 / send_icmp_echo_v6 directly.

use std::{net::Ipv4Addr, time::Duration};
use ping_tokio::{IcmpSocket, generate_payload, send_icmp_echo_v4};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let sock = IcmpSocket::bind(Ipv4Addr::UNSPECIFIED).await?;
    sock.connect("8.8.8.8").await?;

    let payload = generate_payload(56); // 56 bytes of application data
    let reply = send_icmp_echo_v4(&sock, &payload, 1, Duration::from_secs(5)).await?;

    println!(
        "reply from {}: seq={} ttl={} rtt={:.3} ms",
        reply.src_addr,
        reply.seq,
        reply.ttl,
        reply.rtt.as_secs_f64() * 1000.0
    );
    Ok(())
}

send_icmp_echo_v4 / send_icmp_echo_v6 each:

  1. Build and send an ICMP echo request with an embedded timestamp.
  2. Loop reading from the socket, filtering by type, ID, and sequence, until a matching reply arrives or the timeout elapses.
  3. Return the reply metadata on success, or ErrorKind::TimedOut on timeout.

Binary (ping)

A command-line ping utility is included behind the bin feature flag.

Build

cargo build --release --features bin

The binary is placed at target/release/ping.

Usage

ping <destination> [-c count] [-s size] [-W timeout_secs]
Flag Default Description
-c 5 Number of echo requests to send
-s 56 Total ICMP payload size (must be > 8)
-W 1 Per-probe timeout in seconds

Example output

$ ping 127.0.0.1 -c 5 -s 1500
PING 127.0.0.1 (127.0.0.1): 1500 data bytes
1508 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.106 ms
1508 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.139 ms
1508 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.102 ms
1508 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.152 ms
1508 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.132 ms

--- 127.0.0.1 ping statistics ---
5 packets transmitted, 5 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.102/0.126/0.152/0.019 ms

The reported byte count (1508) is ICMP header (8) + payload (size).


Permissions

Raw sockets require elevated privileges:

Linux — grant CAP_NET_RAW to the binary:

sudo setcap cap_net_raw+ep target/release/ping

Or run with sudo.

macOS / BSD — run as root or use sudo.


Platform support

CI exercises the crate on Linux and macOS. Most Unix-like platforms that provide POSIX raw sockets and the recvmsg / cmsghdr ancillary-data APIs (the BSDs, illumos, etc.) are expected to work, though they are not covered by automated tests. Windows is not supported — the implementation relies on POSIX socket semantics (AsyncFd, IPV6_RECVHOPLIMIT, CMSG_*) that have no direct Windows equivalent.


Implementation notes

This implementation derives from BSD ping, originally written by Mike Muuss in 1983, and follows several of its design choices:

  • Embedded timestamps. The send time is written into the first 8 bytes of the echo payload and read back from the reply. This avoids maintaining a hashtable (or similar side data) keyed on sequence number to compute RTT — the timing information travels with the packet itself.
  • Raw sockets, not unprivileged ICMP. Although Linux offers unprivileged IPPROTO_ICMP datagram sockets (SOCK_DGRAM), this crate uses raw sockets (SOCK_RAW) so that the IPv4 Don't Fragment (DF) bit can be set on outgoing probes. This is required for path-MTU style diagnostics and matches the behavior of the system ping utility.
  • IPv6 hop limit exposure. For IPv6 the received hop limit is not present in the ICMPv6 payload, so the socket is configured with IPV6_RECVHOPLIMIT and the value is recovered from the recvmsg ancillary data (cmsghdr / CMSG_*). This mirrors ping6 and surfaces a TTL-equivalent field for network diagnostics.

Dependencies

Crate Purpose
tokio Async runtime, AsyncFd for non-blocking socket I/O
socket2 Raw socket creation and recvmsg for ancillary data
libc cmsghdr / CMSG_* for IPv6 hop-limit extraction

Minimum Rust version

1.84