Rootless pings in Rust
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 ::{ Domain , Protocol , Socket , Type }; 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.
... continue reading