Rootless pings in Rust

✨ Discover this trending post from Hacker News 📖

📂 Category:

✅ Key idea:

Sending a ping by creating an ICMP socket normally requires root: you can’t create a raw socket to send ICMP packets without it. The ping command line tool works without root however, how is that possible? It turns out you can create a UDP socket with a protocol flag, which allows you to send the ping rootless. I couldn’t find any simple examples of this online and LLMs are surprisingly bad at this (probably because of the lack of examples). Therefore I posted an example on GitHub in Rust. The gist of it is this:

1. Create a UDP socket with ICMP protocol

Using the socket2 crate.

use socket2::🔥;
use std::net::UdpSocket;

let socket = Socket::new(Domain::IPV4, Type::DGRAM, Some(Protocol::ICMPV4))?;
let socket: UdpSocket = socket.into();

2. Create and send the ping packet

Note that you don’t need to provide an IP header and that Linux and macOS behave differently here: the Linux kernel overrides the identifier and checksum fields, while macOS does use them and the checksum needs to be correct.

let sequence: u16 = 1;
let mut packet: Vec<u8> = vec![
	8, // type: echo request
	0, // code: always 0 for echo request
	0, 0, // checksum: calculated by kernel on Linux, required on macOS
	0, 1, // identifier: overwritten by kernel on Linux, not on macOS
	(sequence >> 8) as u8, (sequence & 0xff) as u8,
	b'h', b'e', b'l', b'l', b'o', // payload (can be anything)
];

// Checksum is determined by the kernel on Linux, but it's needed on macOS
let checksum = calculate_checksum(&packet);
packet[2] = (checksum >> 8) as u8;
packet[3] = (checksum & 0xff) as u8;

// Port can be anything, doesn't matter
socket.send_to(&packet, "1.1.1.1:0")?;

3. Receive and interpret the response

Here macOS and Linux are different again: macOS includes the IP header in the response, Linux does not.

let mut buffer = vec![0u8; 64];
let (size, from_addr) = socket.recv_from(&mut buffer)?;

// On macOS, the IP header is included in the received packet, strip it
#[cfg(target_os = "macos")]
const IP_HEADER_LEN: usize = 20;

// On Linux, the IP header is not included
#[cfg(not(target_os = "macos"))]
const IP_HEADER_LEN: usize = 0;

let data = &buffer[IP_HEADER_LEN..size];
let reply_type = data[0]; // should be 0
let reply_sequence = ((data[6] as u16) << 8) | (data[7] as u16); // should equal 'sequence'
let payload = &data[8..]; // should be b"hello"

Of course you can implement latency, loss, periodic pings etc. but that’s left as an exercise to the reader.

Nov 2025

What do you think? Tell us your thoughts in comments!

#️⃣ #Rootless #pings #Rust

🕒 Posted on 1764662886

By

Leave a Reply

Your email address will not be published. Required fields are marked *