7 ms·
Rootless Pings in Rust
- N_Lens 10mo agoThe Linux vs macOS behavioral differences in ICMP sockets documented by the article are critical: - Linux overwrites identifier and checksum fields - macOS requires correct checksum calculation - macOS includes IP header in response, Linux doesn't I think this is the kind of subtle difference that would trip up even experienced programmers
- badmonster 10mo agoDo these behavioral differences have performance implications? Which approach is more efficient in practice?
- loeg 10mo agoNah. No one cares about the performance of ping.
- philipallstar 10mo agoAnd now the LLMs know.
- deleted 10mo ago[deleted]
- barryrandall 10mo agoPython's ping3 package also encodes this knowledge in LLM-accessible form.
- dmitrygr 10mo agoI struggled in vain to see what this has to do with rust. The answer is nothing other than the 4 lines of sample code shown are in Rust. The actually useful nugget of knowledge contained therein (one can create ICMP packets without being root on MacOS or Linux) is language agnostic. So... why? Should I now add "in C" or "in assembly" to the end of all my article titles?
- bpbp-mango 10mo agoIf you want
- IshKebab 10mo agoYeah it would definitely be a good idea for the assembly ones. Maybe not C since C has kind of been the de facto language for this stuff for decades so it's implied.
- franga2000 10mo agoIt's a lot more than 4 lines of sample code, in fact on my screen, it looks like it's more code than text. This is closer to a Rust tutorial then a low-level networking explainer, so yeah, it makes sense to say "in Rust". If I wanted to do this in C, this would not be the best resource.
- debugnik 10mo agoAgreed. I don't dislike Rust as a language, but it annoys me how its practitioners add the "[written] in Rust" tagline to every single thing they do that's otherwise unrelated to Rust. Specially when their code or dependencies are full of unverified unsafe blocks, which defeats the selling point.
- 0xbrayo 10mo agowas so excited thinking it was a Kenyan who had made it to the frontpage of hackernews :(
- stavros 10mo agoWell, lots probably have, over the years.
- raesene9 10mo agoWorth noting you don't actually need to be fully root in Linux to do standard pings with your code, there's a couple of different options available at the OS level without needing to modify code. 1. You can just add the capability CAP_NET_RAW to your process, at which point it can ping freely 2. There's a sysctl that allows for unprivileged ping "net.ipv4.ping_group_range" which can be used at the host level to allow different groups to use ICMP ping.
- bouk 10mo agooption 2 is what this blog is about, the example code creates a socket using that method
- vbezhenar 10mo ago> You can just add the capability CAP_NET_RAW to your process, at which point it can ping freely What are consequences of this capability? Seems like restricting this to root was done for a reason?
- raesene9 10mo agoIt lets you send raw sockets, and has some dangers (e.g. packet forgery). It's included in pretty much every container in existence (if you're running as root in the container or have ambient capabilities setup). The goal of the capabilities system was to allow processes and users to gain a small portion of root privileges without giving them all. In the "old days" ping on a Linux host would be setuid root, so it essentially had all of root's rights. In more modern setups it either has CAP_NET_RAW or the ping_group sysctl is used to allow non-root users to use it.
- champtar 10mo agoCAP_NET_RAW also allow to capture packets (tcpdump) so you really can have some fun like running a TCP stack in user space or MITM http connections: https://blog.champtar.fr/IPv6_RA_MITM/ https://blog.champtar.fr/IPv6_RA_MITM/ / https://blog.champtar.fr/Metadata_MITM_root_EKS_GKE/ https://blog.champtar.fr/Metadata_MITM_root_EKS_GKE/
- IshKebab 10mo agoWhy does Linux require root for this if you can do it anyway?
- kvdveer 10mo agoLinux requires root for raw sockets, which _can_ be used to send pings, but also numerous other things. The trick used here only allows pings. This trick is gated behind other ACLs.
- deleted 10mo ago[deleted]
- thomashabets2 10mo agoIt doesn't. For users in the UID range in sysctl `net.ipv4.ping_group_range` the normal ping command uses this non-root way. Sure, maybe your system still sets suid root on your ping binary, or shows it adding `cap_net_raw` according to `getcap`, but mine does not.
- ale42 10mo agoExercise for readers: add IPv6 support ;-)
- PaoloBarbolini 10mo agoThe repo link goes to a 404 page.
- stavros 10mo agoThis is interesting, but falls just short of explaining what's going on. Why does UDP work for ICMP? What does the final packet look like, and how is ICMP different from UDP? None of that is explained, it's just "do you want ICMP? Just use UDP" and that's it. It would have been OK if it were posted as a short reference to something common people might wonder about, but I don't know how often people try to reimplement rootless ping.
- vbezhenar 10mo agoICMP is just different protocol from UDP. There's field "Protocol" in IP packet. 0x01 = ICMP, 0x06 = TCP, 0x11 = UDP. I think that this article gets terminology wrong. It's not UDP socket that gets created here, but Datagram socket. Seems to be bad API naming in Rust library.
- stavros 10mo ago> It's not UDP socket that gets created here, but Datagram socket A datagram socket is a UDP socket, though. That's what the D stands for.
- deleted 10mo ago[deleted]
- jackfranklyn 10mo ago[flagged]
- messe 10mo ago> It turns out you can create a UDP socket with a protocol flag, which allows you to send the ping rootless This is wrong, despite the Rust library in question's naming convention. You're not creating a UDP socket. You're creating an IP (AF_INET), datagram socket (SOCK_DGRAM), using protocol ICMP (IPPROTO_ICMP). The issue is that the rust library apparently conflates datagram and UDP, when they're not the same thing. You can do the same in C, by calling socket(2) with the above arguments. It hinges on Linux allowing rootless pings from the GIDs in $ sysctl net.ipv4.ping_group_range net.ipv4.ping_group_range = 999 59999 EDIT: s/ICMP4/ICMP/g EDIT2: more spelling mistakes
- krater23 10mo ago[flagged]
- DrNefario 10mo agoYou're using a single instance of poor API naming in a 3rd-party library (which is marked as beta) to dismiss the entire Rust language?
- Quarrel 10mo agoThank you. I assumed this was what was happening, but conflating network layer protocols with transport layer ones isn't great. I'm surprised that pedantic zealots, like me in my youth, haven't risen up and flooded rust with issues over it way before this though.
- knorker 10mo agoWhy are you saying rust needs to be flooded with issues? Rust isn't conflating transport layers and protocols. OP is. UdpSocket is just able to take ANY file descriptor and try to use it as if it's a UDP socket. E.g. this compiles, and it's not a bug. It doesn't make any sense, but it's not a bug: fn main() { let f = std::fs::File::open("/dev/null").unwrap(); let f: std::os::fd::OwnedFd = f.into(); let socket: std::net::UdpSocket = f.into(); } OP is clearly confused, since there's no need to do this at all. socket2::Socket already has a `send_to()`: https://docs.rs/socket2/latest/socket2/struct.Socket.html#method.send_to https://docs.rs/socket2/latest/socket2/struct.Socket.html#me... I think OP either banged on this until it compiled, maybe blindly copying from other examples, or it's vibe coded, and shows why AI needs supervision from someone who can actually understand what the code does.
- qwertox 10mo agoGreat article, it lead me to the `icmplib`[0] Python project, which has a `privileged` option: When this option is enabled, this library fully manages the exchanges and the structure of ICMP packets. Disable this option if you want to use this function without root privileges and let the kernel handle ICMP headers. [0] https://github.com/ValentinBELYN/icmplib https://github.com/ValentinBELYN/icmplib
- jeden 10mo agoideal for ddos ;(
- loeg 10mo agoI was interested in a related topic a while back. Historically, to receive ICMP packets, I think you had to open a RAW socket and snoop everything. Obviously, this required root or similar. IPPROTO_ICMP allows you to send ICMP packets and receive responses from the same address, without root. But you can't use it for traceroute because it only accepts ICMP responses from the ultimate destination you sent to; not some TTL failure intermediary. Finally, IP_RECVERR (Linux 2.2) on UDP sockets allows you to receive associated ICMP errors from any hop for a send. (This is useful for traceroute, but not ICMP ping.) I think there are also some caveats on how you can monitor for these type of events in Rust in particular? IIRC, the mainstream async stuff only watches for read/write events, and these aren't those.
- thomashabets2 10mo agoSince basically all the comments are about how both the author and many commenters are confused about what UDP and DGRAM sockets are, I have corrected the author's code to no longer miscommunicate what protocol is being used. https://github.com/ThomasHabets/rust-ping-example-corrected https://github.com/ThomasHabets/rust-ping-example-corrected There is no UDP used anywhere in this example. ICMP is not UDP. I'm not saying my fix is pretty (e.g. uses unwrap(), and ugly destination address parsing), but that's not the point.