From 931bc5a9dde2f06041837dca0708360e2a1e059a Mon Sep 17 00:00:00 2001 From: Johnathan Corgan Date: Wed, 29 Jul 2026 17:10:47 +0000 Subject: [PATCH] Say which syscall failed when a connected UDP socket cannot open open_connected_fd returned a bare last_os_error() from both the bind and the connect path, and its caller wrapped both identically, so a field report of "Address already in use" could not be attributed to either. The two have entirely different causes: on Linux a UDP connect(2) to a 4-tuple another socket already holds returns EADDRINUSE, which is not the same fault as bind refusing the local address. A node at roughly 245 peers is emitting this three times a second across nine peers and the report cannot be diagnosed as it stands. Name the syscall and the address it was operating on in each error, the local address for bind and the peer address for connect. The address is what makes the next step possible: the identity that can collide is the resolved SocketAddr, not the configured transport address, and nothing in the log carried it on the failure path. This is diagnosis only and fixes nothing. The unbounded per-tick retry, the process-global failure counter that hides it, and the sockets that outlive their peers are separate changes. Note that the resolved peer address now appears at warn level on the failure path, alongside the peer's node address, where previously it appeared only in the success-path debug line. raw_os_error() on the returned error is now None, since wrapping produces a custom error. No caller reads it: the sole consumer flattens the error to a string, and the errno text survives in the message. Recorded as a known gap rather than a defect. --- src/transport/udp/io.rs | 53 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/transport/udp/io.rs b/src/transport/udp/io.rs index 95190af..8ef74fc 100644 --- a/src/transport/udp/io.rs +++ b/src/transport/udp/io.rs @@ -900,7 +900,7 @@ mod connected { ) }; if bind_r < 0 { - return Err(io::Error::last_os_error()); + return Err(syscall_err("bind", local_addr)); } // Connect to the peer — locks in the per-packet kernel route. @@ -913,12 +913,17 @@ mod connected { ) }; if conn_r < 0 { - return Err(io::Error::last_os_error()); + return Err(syscall_err("connect", peer_addr)); } Ok(owned) } + fn syscall_err(syscall: &str, addr: SocketAddr) -> io::Error { + let err = io::Error::last_os_error(); + io::Error::new(err.kind(), format!("{syscall} {addr}: {err}")) + } + #[cfg(not(target_os = "linux"))] fn set_nonblocking_cloexec(fd: RawFd) -> io::Result<()> { let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) }; @@ -1005,6 +1010,50 @@ mod connected { ) }; } + + #[cfg(all(test, target_os = "linux"))] + mod tests { + use super::*; + use std::net::UdpSocket; + + const BUF: usize = 1 << 20; + + #[test] + fn bind_failure_names_bind_and_local_addr() { + // A plain socket without SO_REUSEPORT holds the address, so the + // SO_REUSEPORT bind below is refused with EADDRINUSE. + let holder = UdpSocket::bind("127.0.0.1:0").expect("holder bind"); + let holder_addr = holder.local_addr().expect("holder addr"); + + let err = open_connected_fd(holder_addr, "127.0.0.1:9".parse().unwrap(), BUF, BUF) + .expect_err("bind must fail against a non-reuseport holder"); + + assert_eq!(err.kind(), io::ErrorKind::AddrInUse, "{err}"); + let msg = err.to_string(); + assert!(msg.starts_with("bind "), "{msg}"); + assert!(msg.contains(&holder_addr.to_string()), "{msg}"); + assert!(!msg.contains("connect"), "{msg}"); + } + + #[test] + fn connect_failure_names_connect_and_peer_addr() { + // connect(2) to the broadcast address without SO_BROADCAST fails + // synchronously with EACCES; the bind before it succeeds. + let err = open_connected_fd( + "127.0.0.1:0".parse().unwrap(), + "255.255.255.255:9999".parse().unwrap(), + BUF, + BUF, + ) + .expect_err("connect to broadcast without SO_BROADCAST must fail"); + + assert_eq!(err.kind(), io::ErrorKind::PermissionDenied, "{err}"); + let msg = err.to_string(); + assert!(msg.starts_with("connect "), "{msg}"); + assert!(msg.contains("255.255.255.255:9999"), "{msg}"); + assert!(!msg.contains("bind"), "{msg}"); + } + } } #[cfg(any(target_os = "linux", target_os = "macos"))]