Live data from Hacker News

Rootless Pings in Rust

bou.ke

21–30 of 87 posts

Re: Rootless Pings in Rust

#21
post #16

This 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…

ICMP 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.

Re: Rootless Pings in Rust

#22
post #16

This 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…

The BSD socket API has 3 parameters when creating a socket with socket(), the family (e.g. inet) the kind (datagram in this case) and the protocol (often 0, but IPPROTO_ICMP in this case).

Because when the protocol is 0 it means a UDP socket Rust has called its API for creating any(?) datagram sockets UdpSocket, partly resulting in this confusion.

The kernel patch introducing the API also explains it was partly based on the UDP code, due to obviously sharing a lot of properties with it. https://lwn.net/Articles/420800/

Re: Rootless Pings in Rust

#23
post #16

This 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…

So in fairness, this doesn't actually use UDP at all (SOCK_DGRAM does not mean UDP!).

The actual protocol in use, and what's supported, it matched by all of the address family (IPV4), the socket type (DGRAM), and the protocol (ICMP). The match structure for IPV4 is here in Linux at least: https://elixir.bootlin.com/linux/v6.18/source/net/ipv4/af_in...

So ultimately, it's not even UDP, it's just creating a standard ICMP socket.

Re: Rootless Pings in Rust

#24
post #9

Worth 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…

> 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?

Re: Rootless Pings in Rust

#25
> 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

Re: Rootless Pings in Rust

#26
post #16

This 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…

The semantic wrappers around file descriptors (File, UdpSocket, PidFd, PipeReader, etc.) are advisory and generally interconvertible. Since there's no dedicated IcmpSocket they're using UdpSocket which happens to provide the right functions to invoke the syscalls they need.

Re: Rootless Pings in Rust

#27
post #16

This 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…

ICMP 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.

> 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.

Re: Rootless Pings in Rust

#28
post #9

Worth 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…

> 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?

It 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.

Re: Rootless Pings in Rust

#29
post #22
post #16

This 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…

The BSD socket API has 3 parameters when creating a socket with socket(), the family (e.g. inet) the kind (datagram in this case) and the protocol (often 0, but IPPROTO_ICMP in this case). Because when the protocol is 0 it means a UDP socket Rust has called its API for creating any(?) datagram sockets UdpSocket, partly resulting in this confusion. The kernel patch introducing the API also explains it was partly based…

Thanks, that's quite the misnomer then.

Re: Rootless Pings in Rust

#30
post #22
post #16

This 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…

The BSD socket API has 3 parameters when creating a socket with socket(), the family (e.g. inet) the kind (datagram in this case) and the protocol (often 0, but IPPROTO_ICMP in this case). Because when the protocol is 0 it means a UDP socket Rust has called its API for creating any(?) datagram sockets UdpSocket, partly resulting in this confusion. The kernel patch introducing the API also explains it was partly based…

The std api can only create UdpSockets, the trick here is that you use Socket2 which allows more kinds of sockets and then you tell UdpSocket that some raw file descriptor is a upd socket through a unsafe api with no checks and I guess it works because they use the same api on posix.

Edit: It is possible in safe rust as well, see child comment.

The macro used by socket2: https://docs.rs/socket2/0.6.1/src/socket2/lib.rs.html#108

The FromRawFd trait: https://doc.rust-lang.org/stable/std/os/fd/trait.FromRawFd.h...

Post reply on HN