[HN Gopher] Rootless Pings in Rust
___________________________________________________________________
Rootless Pings in Rust
Author : bouk
Score : 111 points
Date : 2025-12-02 07:01 UTC (16 hours ago)
(HTM) web link (bou.ke)
(TXT) w3m dump (bou.ke)
| N_Lens wrote:
| 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 wrote:
| Do these behavioral differences have performance implications?
| Which approach is more efficient in practice?
| loeg wrote:
| Nah. No one cares about the performance of ping.
| philipallstar wrote:
| And now the LLMs know.
| barryrandall wrote:
| Python's ping3 package also encodes this knowledge in LLM-
| accessible form.
| dmitrygr wrote:
| 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?
| bpbp-mango wrote:
| If you want
| IshKebab wrote:
| 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.
| franga2000 wrote:
| 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 wrote:
| 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.
| 0xbrayo wrote:
| was so excited thinking it was a Kenyan who had made it to the
| frontpage of hackernews :(
| stavros wrote:
| Well, lots probably have, over the years.
| raesene9 wrote:
| 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 wrote:
| option 2 is what this blog is about, the example code creates a
| socket using that method
| vbezhenar wrote:
| > 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 wrote:
| 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.
| champtar wrote:
| CAP_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/Metadata_MITM_root_EKS_GKE/
| octoberfranklin wrote:
| > There's a sysctl that allows for unprivileged ping
| "net.ipv4.ping_group_range"
|
| What are the risks of enabling this for all groups (i.e. sysctl
| net.ipv4.ping_group_range='0 4294967294')?
|
| Note this allows unprivileged ICMP sockets, _not_ unprivileged
| RAW sockets.
| IshKebab wrote:
| Why does Linux require root for this if you can do it anyway?
| kvdveer wrote:
| 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.
| thomashabets2 wrote:
| It 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 wrote:
| Exercise for readers: add IPv6 support ;-)
| PaoloBarbolini wrote:
| The repo link goes to a 404 page.
| stavros wrote:
| 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.
| vbezhenar wrote:
| 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 wrote:
| > 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 wrote:
| Wrong way around: UDP sockets are datagram sockets, there
| are datagram sockets that are not UDP.
| jdndbdbd wrote:
| No? Why would you think a datagram socket is UDP?
| stavros wrote:
| What a reasonable question to be asked today.
| messe wrote:
| 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).
| cstrahan wrote:
| Let me rephrase GP into (I hope) a more useful analogy.
| -- actually, here's the whole analogous exchange:
|
| "A rectangle is an equal-sided rectangle (i.e. "square")
| though. That's what the R stands for."
|
| "No? Why would you think a rectangle is a square?"
|
| Just as not all rectangles are squares (squares are a
| specific subset of rectangles), not all datagram
| protocols are UDP (UDP is just one particular datagram
| protocol).
| stavros wrote:
| The obvious answer is "I didn't know datagrams were a
| superset of UDP". I don't really understand how "how do
| you not know this" is a reasonable or useful question to
| ask.
| Quarrel wrote:
| 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.)
| meindnoch wrote:
| Not every cola is Coca-Cola, even though "Cola" stands for
| cola.
| pwdisswordfishy wrote:
| In related news, all rectangles are squares and all animals
| are dogs.
| dgl wrote:
| 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/
| stavros wrote:
| Thanks, that's quite the misnomer then.
| erk__ wrote:
| 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 wrote:
| From/Into conversion via OwnedFd is the safe API, RawFd is
| the older and lower-level one.
| erk__ wrote:
| 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 wrote:
| 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 wrote:
| That happens when someones learning project ("I rewrite a
| library in the new language I want to learn") ends up in
| productive code.
| IshKebab wrote:
| 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 wrote:
| 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.
| IshKebab wrote:
| We're talking about UdpSocket: https://doc.rust-
| lang.org/stable/std/net/struct.UdpSocket.ht...
| Sharlin wrote:
| Yes, I know, but the point is that the standard UdpSocket
| is correctly named as it doesn't represent any other
| datagram socket. Uh, we're pribably in agreement here
| actually.
| IshKebab wrote:
| Yeah exactly! :-D
| knorker wrote:
| Sure it does. let f =
| std::fs::File::open("/dev/null").unwrap(); let f:
| std::os::fd::OwnedFd = f.into(); let socket:
| std::net::UdpSocket = f.into();
|
| This is really no different. In this example it's not
| even a socket.
| slugonamission wrote:
| 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.
| the8472 wrote:
| 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.
| jeroenhd wrote:
| The rust API in use lets you feed an fd into a UdpSocket which
| calls the necessary send/recv/etc on it.
|
| The socket itself is an ICMP socket, but the ICMP shaped API
| just happened to fit into the UDP shaped hole. I'm sure some
| advanced UDP socket options will break or have weird side
| effects if your code tries to apply them.
| jackfranklyn wrote:
| 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.
| messe wrote:
| > 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 wrote:
| 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.
| knorker wrote:
| Why 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#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.
| GTP wrote:
| 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 wrote:
| 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...
| seba_dos1 wrote:
| Think of it as a protocol for sending custom ("user")
| datagrams. There are other datagram protocols too.
|
| The kernel API defaults to UDP when you pass 0 as the
| protocol for a datagram socket, which may be the source of
| the confusion.
| IgorPartola wrote:
| As the other commenter pointed out, UDP is transport
| protocol, not a packet level protocol.
|
| Think of it like this:
|
| Ethernet sends data frames to a MAC address. It has a sender
| and a receiver address. See here for structure:
| https://en.wikipedia.org/wiki/Ethernet_frame
|
| Internet Protocols (v6 and v4) send packets via Ethernet (or
| WiFi or Bluetooth or anything else) from an IP address to an
| IP address. For structure see
| https://en.wikipedia.org/wiki/IPv6_packet or if for some
| reason you still need the legacy version see
| https://en.wikipedia.org/wiki/IPv4#Packet_structure (aside
| but notice how much complexity was removed from the legacy
| version). Notably, IP does not have any mechanism for
| reliability. It is essentially writing your address and a
| destination address on a brick and tossing over your fence to
| the neighbor's yard and asking them to pass it along. If your
| neighbor isn't home your brick is not moving along.
|
| TCP and UDP send streams and datagrams respectively and use
| the concept of application ports. A TCP stream is what it
| sounds like: a continuous stream of bytes with no length or
| predefined stopping point. TCP takes your stream and chunks
| it into IP packets, the size of which is determined by the
| lowest Ethernet (or whatever data link protocol) data frame
| size. Typically this is 1500 but don't forget to account for
| header sizes so useful payload size is smaller. TCP is
| complex because it guarantees that your stream will
| eventually be delivered in the exact order in which it was
| sent. Eventually here could mean at t = infinity. UDP simply
| has source and destination port numbers in its header (which
| follows the IP header in the data frame), and guarantees
| nothing: not ordering not guaranteed delivery, etc. If an IP
| packet is a brick with two house addresses, a UDP datagram is
| a brick with two house addresses and an ATTN: application X
| added. An address represents an computer (this is very
| approximate in the world where any machine can have N
| addresses and run M VMs or containers which themselves can
| have O addresses), and a port represents a specific process
| on that computer.
|
| ICMP does not use ports. ICMP is meant for essentially
| network and host telemetry so you are still sending and
| receiving only at an IP address level. But it has a number of
| message types. You can see them here:
| https://en.wikipedia.org/wiki/ICMPv6. Note that ping and pong
| are just two of the types. Others are actually responsible
| for things like communicating what raw IP cannot. For example
| Packet Too Large type tells you that an IP packet you tried
| to send was hitting a part of its path where the datagram
| size did not allow it to fit and it's used for IP MTU path
| discovery (you keep sending different size packets to find
| what is the largest that will go through).
|
| There are other protocols that run directly on top of IP
| (6in4 for example, or SCTP). Most are way less popular than
| the three mentioned above. Some use datagrams (discrete
| "bricks" of data), some use streams (endless "tapes" of
| data), which is the difference in protocol family: datagrams
| vs stream. You can also go a level deeper and just craft raw
| IP packets directly but for that you typically must be the
| root user since you can for example send a packet with the
| source port set to 22 even though you are not the ssh daemon.
|
| Since ICMP has no concept of a port, when you send a ping to
| a remote host and it returns a ping to you, how does your
| kernel know to hand the response to your process and not some
| other one? In the ICMP header there is an ICMP identifier
| (often the process PID) and when the reply comes back it has
| the same identifier (but with source and destination IPs
| swapped and type updated to echo reply). This is what the
| kernel uses to find the process to which it will deliver the
| ICMP packet.
|
| I hope this clears up some of this.
| meindnoch wrote:
| When I read about animals I always think about elephants and
| though it was just a different way of saying the same thing.
| kbolino wrote:
| There's actually a lot of combinations of (domain, type,
| protocol) that are available. It is not always the case that
| the protocol implies the type.
|
| In IP land (domains AF_INET and AF_INET6), we have the well
| known UDP and TCP protocols, of course. UDP is always
| datagram (SOCK_DGRAM) and TCP is always stream (SOCK_STREAM).
| Besides datagram-only ICMP, there's also SCTP, which lets you
| choose between stream and sequential-packet (SOCK_SEQPACKET)
| types. A sequential-packet socket provides in-order delivery
| of packet-sized messages, so it sits somewhere between
| datagram and stream in terms of features.
|
| In AF_UNIX land, there are no protocols (the protocol field
| is always 0), but all 3 of the aforementioned types are
| available. You just have to pick the same type on both sides.
|
| Footnotes: SCTP is not widely usable because Windows doesn't
| natively support it and many routers will drop or reject it
| instead of forwarding it. Also, AF_UNIX is now supported on
| Windows, but only with SOCK_STREAM type.
| scottlamb wrote:
| > The issue is that the rust library apparently conflates
| datagram and UDP, when they're not the same thing.
|
| It comes down to these two lines (using full items paths for
| clarity): let socket =
| socket2::Socket::new(Domain::IPV4, Type::DGRAM,
| Some(Protocol::ICMPV4))?; let socket:
| std::net::UdpSocket = socket.into();
|
| The latter is using this impl:
| https://docs.rs/socket2/0.6.1/socket2/struct.Socket.html#imp...
|
| Basically the `socket2` crate lets you convert the fd it
| produces into a `UdpSocket`. It doesn't verify it really is a
| UDP socket first; that's up to you. If you do it blindly, you
| can get something with the wrong name, but it's probably
| harmless. (At the very least, it doesn't violate memory safety
| guarantees, which is what Rust code tends to be very strict
| about.)
|
| `UdpSocket` itself has a `From<OwnedFd>` impl that similarly
| doesn't check it really is a UDP socket; you could convert the
| `socket2::Socket` to an `OwnedFd` then that to a `UdpSocket`.
| https://doc.rust-lang.org/stable/std/net/struct.UdpSocket.ht...
| https://docs.rs/socket2/0.6.1/socket2/struct.Socket.html#imp...
| antonvs wrote:
| It may be memory safe but it's not using the type system to
| represent the domain very well.
|
| One could imagine a more type-friendly design in which we
| could write that first line as follows: let
| socket: Socket<IPv4, Datagram, IcmpV4> = Socket::new()?;
|
| Now, the specifics of socket types will be statically
| checked.
|
| Edit: I realized that the issue here is actually the
| conversion, and that UdpSocket on its own is actually a type-
| safe representation of a UDP socket, not a general datagram
| socket. But the fact that this dubiously-safe conversion is
| possible and even useful suggests that an improved design is
| possible. For example, a method like UdpSocket's
| `set_broadcast` can't work with a socket like the above, and
| from a type safety perspective, it shouldn't be possible to
| call it on such a socket.
| scottlamb wrote:
| One could, but one probably doesn't want to have separate
| types for TCP-over-IPv4 vs TCP-over-IPv6 for example, even
| if they accept/produce different forms of addresses. That'd
| force a lot of code bloat with monomorphization.
|
| So now one is making one's own enumeration which is
| different than the OS one and mapping between them, which
| can get into a mess of all the various protocols Linux and
| other OSs support, and I'm not sure it's solving a major
| problem. Opinions vary, but I prefer to use complex types
| sparingly.
|
| I think there are likely a bunch of other cases where it's
| useful to choose these values more dynamically too.
| Networking gets weird!
| antonvs wrote:
| It's precisely because networking gets weird that a good
| representation at the type level could be useful. But I
| agree that it'd need to be done carefully to avoid
| creating usability issues.
| knorker wrote:
| > dubiously-safe
|
| No, it's perfectly safe. Except if you expand the scope of
| "safe" by a lot.
|
| OP turned the socket into an (almost) raw file descriptor,
| and created an UDP socket from it. Weird, yes, but since
| it's perfectly memory safe and invalid operations would
| correctly error, it's not "dubiously-safe". It's safe.
|
| I mean, either your language has the ability to do raw
| (technically Owned in this case) file descriptors, or it
| doesn't.
|
| Maybe you'd prefer Rust had a third mode? Safe, `unsafe
| {}`, and `are_you_sure_you_understand_this {}`, the last
| one also being 'safe', but just... odd.
| antonvs wrote:
| It's dubiously safe because it allows invalid
| combinations, i.e. calling UDP-related methods on non-UDP
| sockets. I'm using "safe" in the general English sense
| here, "protected from or not exposed to danger or risk."
|
| > invalid operations would correctly error
|
| At runtime, yes. I'm pointing out that Rust makes it
| possible to do better, and catch such issues at compile
| time.
| knorker wrote:
| Of course it allows invalid combinations.
|
| This also compiles: let f =
| std::fs::File::open("/dev/null").unwrap(); let f:
| std::os::fd::OwnedFd = f.into(); let socket:
| std::net::UdpSocket = f.into();
|
| If you convert a high level object into a low level one,
| and then back up as another type, then what exactly do
| you expect the language to do about that?
|
| > "protected from or not exposed to danger or risk."
|
| A computer will do what you tell it to do, not what you
| intend it to do. Opening a file is way more dangerous
| than risking errors because "this syscall doesn't work on
| that fd".
|
| There's also always risk that a syscall will fail at
| runtime, whether the type of fd is correct or not.
|
| It sounds like _you_ would prefer if UdpSocket From
| <OwnedFD> should run getsockname() or something to
| confirm it's of the expected type, but I would prefer
| not. Indeed, in the general case some perfectly coded
| `unsafe` code could `dup2()` over the fd, so any checking
| at UdpSocket creation time is moot; you still don't get
| the safety you are asking for.
| scottlamb wrote:
| I agree with everything you wrote except for this:
|
| > Indeed, in the general case some perfectly coded
| `unsafe` code could `dup2()` over the fd, so any checking
| at UdpSocket creation time is moot; you still don't get
| the safety you are asking for.
|
| If `unsafe` code breaks safe code's soundness guarantees
| (let's assume for a second an alternate world in which
| "fd is of the correct type" is a soundness guarantee Rust
| makes), the bug is in the `unsafe` code.
| knorker wrote:
| Sure, I would strongly recommend against doing something
| like that. But I would expect it to work in the obvious
| way, and not be undefined behavior.
|
| E.g. if UdpSocket were to dup() internally its fd A into
| a fd B, and as_fd() returned B, but all actual recv/send
| is on fd A, then that would cause worse problems than
| this.
|
| But say an OS has a sockopt that turns an IPv4 UDP socket
| into a IPv6 UDP socket. Would it be OK for me to call
| that on UdpSocket's underlying fd? I'd say yes.
|
| Now if I _closed_ the fd for a UdpSocket from underneath
| it, I would expect that to be basically UB, if not by
| spec, then in practice impossible to reason about.
|
| As for unsound, sure. I could be convinced of calling the
| dup2 thing unsound. Not sure unsound is well enough
| defined, but basically: don't do it.
|
| Is it unsound to create a UdpSocket from a non-UDP file
| descriptor? Not in a way that can trigger unsafe, no.
| antonvs wrote:
| There are two issues here, and you're talking about a
| different one from the one I'm interested in. Your main
| issue seems to be this:
|
| > If you convert a high level object into a low level
| one, and then back up as another type, then what exactly
| do you expect the language to do about that?
|
| One answer to this would be "prevent it entirely". That's
| probably not practical for a language like Rust today,
| though, and I don't really care about that.
|
| What I care about is that it's necessary to do this in
| the first place. The fact that doing this can be useful
| and necessary in a case like this suggests that it would
| be possible to design the types involved so that you
| don't need these low-level and runtime-unsafe conversions
| to get the job done.
|
| > It sounds like you would prefer if UdpSocket
| From<OwnedFD> should run getsockname() or something to
| confirm it's of the expected type
|
| No, I'm saying the types could be designed to prevent the
| need for doing this in the first place.
| knorker wrote:
| > What I care about is that it's necessary to do this in
| the first place.
|
| I don't think it is. socket2::Socket has send_to() just
| as much as UdpSocket does.
|
| (disclaimer: I only looked up the docs, I didn't try to
| modify the code to strip out needless UdpSocket)
|
| > runtime-unsafe
|
| That's not a thing. You always need to check for errors.
| seccomp could be blocking your syscalls. Hard drives
| break such that reads return error.
|
| Getting an Err() from a function does not make it
| "unsafe", runtime or not.
|
| > the types could be designed to prevent the need for
| doing this in the first place.
|
| If your type system does not allow you to "bring your own
| fd (to be managed)", then it's not fit for purpose for
| the kind of problems Rust aims to solve.
|
| A systems language needs to be able to receive a file
| descriptor from a C library, and work with it.
| antonvs wrote:
| > > runtime-unsafe
|
| > That's not a thing.
|
| Yes, it is. See all the work on the subject of "make
| illegal states unrepresentable". Search for that phrase,
| it's originally from Yaron Minsky at Jane Street Capital,
| but it's mainly a pithy characterization of a common goal
| for languages with strong type systems, like Rust,
| Haskell, or the ML family. Another way this is expressed
| is as "static debugging" - the idea that you can debug a
| significant proportion of a program's bugs statically,
| using the type system.
|
| That's what I'm referring to here. If it's unfamiliar to
| you, it will likely take some time to get used to,
| because it's a significantly different paradigm from the
| common approach of debugging by running programs,
| encountering runtime errors, and trying to resolve them.
| But, instead of getting indignant and objecting to what
| I'm saying, consider that there might be something for
| you to learn here.
|
| > You always need to check for errors. seccomp could be
| blocking your syscalls. Hard drives break such that reads
| return error.
|
| What do you believe the relevance of this is? You need to
| check for errors that can't be checked for at compile
| time. That doesn't mean we should abandon the idea of
| checking for errors at compile time, or improving the
| scope of issues that we can detect at compile time.
|
| Errors that are prevented at compile time _cannot occur
| at runtime in principle_ , and that's an extremely
| powerful invariant in software development, one that any
| serious software developer should be aware of, and be
| able to take advantage of.
|
| Type systems allow you to prove properties of programs
| that otherwise would need to be debugged and tested at
| runtime, but to take advantage of that, the types need to
| be designed appropriately.
|
| > If your type system does not allow you to "bring your
| own fd (to be managed)", then it's not fit for purpose
| for the kind of problems Rust aims to solve.
|
| This is a failure of imagination, nothing more. An
| appropriate type schema for this domain will be able to
| handle the requirements of the domain.
| qwertox wrote:
| 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
| jeden wrote:
| ideal for ddos ;(
| loeg wrote:
| I 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 wrote:
| Since 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
|
| 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.
___________________________________________________________________
(page generated 2025-12-02 23:01 UTC)