From aa9455082d71c00df85dc9e5fde3ced910172fdc Mon Sep 17 00:00:00 2001 From: 1ndahous3 <9205230+1ndahous3@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:28:39 +0300 Subject: [PATCH] Support Netlink socket addresses (#9206) Assisted-by: Codex:GPT-6 --- crates/stdlib/src/socket.rs | 37 +++++++++++++++++++++++++++ extra_tests/snippets/stdlib_socket.py | 24 +++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/crates/stdlib/src/socket.rs b/crates/stdlib/src/socket.rs index b3b2d10dd..06cb76a86 100644 --- a/crates/stdlib/src/socket.rs +++ b/crates/stdlib/src/socket.rs @@ -1716,6 +1716,36 @@ mod _socket { ) }) } + #[cfg(any(target_os = "linux", target_os = "android"))] + c::AF_NETLINK => { + let tuple: PyTupleRef = addr.downcast().map_err(|obj| { + vm.new_type_error(format!( + "{caller}(): AF_NETLINK address must be tuple, not {}", + obj.class().name() + )) + })?; + let [pid, groups] = tuple.as_slice() else { + return Err(vm + .new_type_error("AF_NETLINK address must be a pair (pid, groups)") + .into()); + }; + let pid = pid.clone().try_index(vm)?.as_u32_mask(); + let groups = groups.clone().try_index(vm)?.as_u32_mask(); + let mut storage = host_socket::raw::SockAddrStorage::zeroed(); + // SAFETY: SockAddrStorage has sufficient size and alignment for + // sockaddr_nl; all its fields and padding start initialized to zero. + let netlink_addr = unsafe { storage.view_as::() }; + netlink_addr.nl_family = c::AF_NETLINK as c::sa_family_t; + netlink_addr.nl_pid = pid; + netlink_addr.nl_groups = groups; + // SAFETY: The family, address fields, and length describe sockaddr_nl. + Ok(unsafe { + host_socket::raw::SockAddr::new( + storage, + core::mem::size_of::() as c::socklen_t, + ) + }) + } #[cfg(target_os = "linux")] c::AF_ALG => { let tuple: PyTupleRef = addr.downcast().map_err(|obj| { @@ -2916,6 +2946,13 @@ mod _socket { let path = ffi::OsStr::from_bytes(&path[..nul_pos]); return vm.fsdecode(path).into(); } + #[cfg(any(target_os = "linux", target_os = "android"))] + if addr.family() == c::AF_NETLINK as c::sa_family_t { + // SAFETY: Socket addresses supplied by the OS use sockaddr_nl for AF_NETLINK; + // SockAddr owns storage with the required size and alignment. + let netlink_addr = unsafe { &*addr.as_ptr().cast::() }; + return (netlink_addr.nl_pid, netlink_addr.nl_groups).to_pyobject(vm); + } #[cfg(target_os = "linux")] { let family = addr.family(); diff --git a/extra_tests/snippets/stdlib_socket.py b/extra_tests/snippets/stdlib_socket.py index a6a6b86ce..d81d57157 100644 --- a/extra_tests/snippets/stdlib_socket.py +++ b/extra_tests/snippets/stdlib_socket.py @@ -272,3 +272,27 @@ if sys.platform.startswith("linux"): tcp_truncation_preserves_buffer() if hasattr(socket, "IPPROTO_MPTCP"): tcp_truncation_preserves_buffer(socket.IPPROTO_MPTCP) + + +if hasattr(socket, "NETLINK_USERSOCK"): + with ( + socket.socket( + socket.AF_NETLINK, socket.SOCK_DGRAM, socket.NETLINK_USERSOCK + ) as sender, + socket.socket( + socket.AF_NETLINK, socket.SOCK_DGRAM, socket.NETLINK_USERSOCK + ) as receiver, + ): + sender.settimeout(2) + receiver.settimeout(2) + # AF_NETLINK parses unsigned 32-bit pid/group values, including masking. + sender.bind((2**32, 0)) + receiver.bind((0, 0)) + with assert_raises(TypeError): + sender.sendto(b"message", (0,)) + with assert_raises(TypeError): + sender.sendto(b"message", ("pid", 0)) + sender.sendto(b"message", receiver.getsockname()) + assert receiver.recvfrom(32) == (b"message", sender.getsockname()) + sender.connect(receiver.getsockname()) + assert sender.getpeername() == receiver.getsockname()