messe 4 hours 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

  • Quarrel 30 minutes ago

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

  • GTP 22 minutes ago

    Could you please explain me the difference? As UDP is the "User Datagram Protocol" when I read about datagrams I always think about UDP and though it was just a different way of saying the same thing. Maybe "datagram" is supposed to be the packet itself, but you're still sending it via UDP, right?

    • cetra3 5 minutes ago

      UDP and TCP are Layer 3 protocols, and so is ICMP. They all fill the same bits within network packets, like at the same level. So sending an ICMP packet (protocol 1) is not the same as sending a UDP packet (protocol 17).

      You can see a list of network protocols in /etc/protocols actually, or here: https://www.iana.org/assignments/protocol-numbers/protocol-n...

  • krater23 3 hours ago

    [flagged]

    • DrNefario 42 minutes ago

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

stavros 4 hours ago

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

  • dgl 4 hours ago

    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/

    • erk__ 4 hours ago

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

      • the8472 2 hours ago

        From/Into conversion via OwnedFd is the safe API, RawFd is the older and lower-level one.

        • erk__ 2 hours ago

          Ahh I guess that means that its possible in safe rust to cast a file descriptor to a different type. I was just looking at how socket2 did it and forgot to have a proper look.

    • stingraycharles 4 hours ago

      So UdpSocket should really be called DatagramSocket, UDP being the protocol that operates on these datagrams?

      Surprising that they got such a fundamental thing wrong.

      • krater23 3 hours ago

        That happens when someones learning project ("I rewrite a library in the new language I want to learn") ends up in productive code.

        • IshKebab 2 hours ago

          This is in the standard library; it's not a learning project. And it also isn't even incorrect - see erk__'s comment.

          Rust is an excellent language and fully capable of production use.

          • Sharlin an hour ago

            It's not, it's the `socket2` library. The standard sockets don't allow (ab)using actual `UdpSocket`s as a different kind of datagram socket.

    • stavros 4 hours ago

      Thanks, that's quite the misnomer then.

  • the8472 4 hours ago

    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.

  • vbezhenar 4 hours ago

    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.

    • stavros 4 hours 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.

      • jmgao 3 hours ago

        Wrong way around: UDP sockets are datagram sockets, there are datagram sockets that are not UDP.

      • Quarrel 25 minutes ago

        To give a more nuanced reply versus the "you're wrong" ones already here, the difference is that UDP adds send and receive ports, enabling most modern users (& uses) of UDP. Hence, it is the "User" datagram protocol.

        (it also adds a checksum, which used to be more important than it is nowadays, but still well worth it imho.)

      • jdndbdbd 2 hours ago

        No? Why would you think a datagram socket is UDP?

        • stavros 2 hours ago

          What a reasonable question to be asked today.

          • messe 2 hours ago

            What networks are you using without ICMP?

            Presumably you're also using systems that don't support Unix Domain Sockets which can be configured as SOCK_STREAM, SOCK_DGRAM, and even gasp SOCK_SEQPACKET (equivalent to SOCK_DGRAM in this case admittedly).

  • slugonamission 4 hours ago

    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.

qwertox 2 hours ago

Great 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
raesene9 5 hours ago

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 different groups to use ICMP ping.

  • bouk 5 hours ago

    option 2 is what this blog is about, the example code creates a socket using that method

  • vbezhenar 4 hours 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 4 hours ago

      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.

jackfranklyn 4 hours ago

The unprivileged DGRAM approach is a lifesaver for container environments. Ran into this building a health check service - spent ages wondering why my ping code needed --privileged when the system ping worked fine as a normal user. Turns out the default ping binary has setuid, which isn't an option in a minimal container image.

The cross-platform checksum difference is a pain though. Linux handling it for you is convenient until you test on macOS and everything breaks silently.

ale42 5 hours ago

Exercise for readers: add IPv6 support ;-)

jeden 20 minutes ago

ideal for ddos ;(

N_Lens 5 hours ago

The 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 4 hours ago

    Do these behavioral differences have performance implications? Which approach is more efficient in practice?

IshKebab 5 hours ago

Why does Linux require root for this if you can do it anyway?

  • kvdveer 5 hours ago

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

0xbrayo 5 hours ago

was so excited thinking it was a Kenyan who had made it to the frontpage of hackernews :(

  • stavros 4 hours ago

    Well, lots probably have, over the years.

dmitrygr 5 hours ago

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

  • franga2000 5 hours ago

    It'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 2 hours ago

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

  • IshKebab 5 hours ago

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