From 4c5dc02c0b3b7d92876d74231af3a87fef879793 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Mon, 16 Dec 2024 11:32:14 +0100 Subject: [PATCH] net/linux_kernel: add unnamed Unix-domain addresses I think it would be useful to have unnamed Unix-domain addressed in rustix. This PR adds the methods `SocketAddrUnix::new_unnamed()` and `SocketAddrUnix::is_unnamed()`. In C it is possible to have an [unnamed Unix-domain] socket name, when you set `len` = 2 = `sizeof(c::socklen_t)`. Then the kernel will choose an abstract Unix-domain name for you when you bind the socket. The same feature present also in Python, when you call [`sock.bind("")`]. Invoking [`SocketAddrUnix::new_abstract_name(b"")`] gives you an empty abstract socket address, i.e. `SocketAddrUnix::len == 3`. The kernel will keep this empty abstract name on calling `bind()`. [unnamed Unix-domain]: https://manpages.debian.org/bookworm/manpages/unix.7.en.html#unnamed [`sock.bind("")`]: https://docs.python.org/3.13/library/socket.html#socket.socket.bind [`SocketAddrUnix::new_abstract_name(b"")`]: https://docs.rs/rustix/0.38.42/rustix/net/struct.SocketAddrUnix.html#method.new_abstract_name --- src/backend/libc/net/addr.rs | 36 +++++++++++++++++++++++++----- src/backend/linux_raw/net/addr.rs | 37 +++++++++++++++++++++++++------ tests/net/unix.rs | 26 +++++++++++++++++++++- tests/net/unix_alloc.rs | 2 ++ 4 files changed, 88 insertions(+), 13 deletions(-) diff --git a/src/backend/libc/net/addr.rs b/src/backend/libc/net/addr.rs index 7a26eea7b..7280087ce 100644 --- a/src/backend/libc/net/addr.rs +++ b/src/backend/libc/net/addr.rs @@ -74,6 +74,26 @@ impl SocketAddrUnix { }) } + /// Construct a new unnamed address. + /// + /// The kernel will assign an abstract Unix-domain address to the socket when you call + /// [`bind_unix()`][crate::net::bind_unix]. You can inspect the assigned name with + /// [`getsockname`][crate::net::getsockname]. + /// + /// # References + /// - [Linux] + /// + /// [Linux]: https://www.man7.org/linux/man-pages/man7/unix.7.html + #[cfg(linux_kernel)] + #[inline] + pub fn new_unnamed() -> Self { + Self { + unix: Self::init(), + #[cfg(not(any(bsd, target_os = "haiku")))] + len: offsetof_sun_path() as _, + } + } + const fn init() -> c::sockaddr_un { c::sockaddr_un { #[cfg(any( @@ -123,17 +143,23 @@ impl SocketAddrUnix { #[cfg(linux_kernel)] #[inline] pub fn abstract_name(&self) -> Option<&[u8]> { - let len = self.len(); - if len != 0 && self.unix.sun_path[0] == 0 { - let end = len as usize - offsetof_sun_path(); + let end = self.len().saturating_sub(offsetof_sun_path()); + if end > 0 && self.unix.sun_path[0] as u8 == b'\0' { let bytes = &self.unix.sun_path[1..end]; - // SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`. - unsafe { Some(slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len())) } + // SAFETY: Convert `&[c_char]` to `&[u8]`. + Some(unsafe { slice::from_raw_parts(bytes.as_ptr().cast::(), bytes.len()) }) } else { None } } + /// `true` if the socket address is unnamed. + #[cfg(linux_kernel)] + #[inline] + pub fn is_unnamed(&self) -> bool { + self.len() == offsetof_sun_path() + } + #[inline] pub(crate) fn addr_len(&self) -> c::socklen_t { #[cfg(not(any(bsd, target_os = "haiku")))] diff --git a/src/backend/linux_raw/net/addr.rs b/src/backend/linux_raw/net/addr.rs index 5bc84ed69..b5a4fe7bb 100644 --- a/src/backend/linux_raw/net/addr.rs +++ b/src/backend/linux_raw/net/addr.rs @@ -62,6 +62,26 @@ impl SocketAddrUnix { } } + /// Construct a new unnamed address. + /// + /// The kernel will assign an abstract Unix-domain address to the socket when you call + /// [`bind_unix()`][crate::net::bind_unix]. You can inspect the assigned name with + /// [`getsockname`][crate::net::getsockname]. + /// + /// # References + /// - [Linux] + /// + /// [Linux]: https://www.man7.org/linux/man-pages/man7/unix.7.html + #[cfg(linux_kernel)] + #[inline] + pub fn new_unnamed() -> Self { + Self { + unix: Self::init(), + #[cfg(not(any(bsd, target_os = "haiku")))] + len: offsetof_sun_path() as _, + } + } + const fn init() -> c::sockaddr_un { c::sockaddr_un { sun_family: c::AF_UNIX as _, @@ -91,20 +111,23 @@ impl SocketAddrUnix { /// For an abstract address, return the identifier. #[inline] pub fn abstract_name(&self) -> Option<&[u8]> { - let len = self.len(); - if len != 0 && self.unix.sun_path[0] as u8 == b'\0' { - let end = len as usize - offsetof_sun_path(); + let end = self.len().saturating_sub(offsetof_sun_path()); + if end > 0 && self.unix.sun_path[0] as u8 == b'\0' { let bytes = &self.unix.sun_path[1..end]; - // SAFETY: Convert `&[c_char]` to `&[u8]`. - let bytes = unsafe { slice::from_raw_parts(bytes.as_ptr().cast::(), bytes.len()) }; - - Some(bytes) + Some(unsafe { slice::from_raw_parts(bytes.as_ptr().cast::(), bytes.len()) }) } else { None } } + /// `true` if the socket address is unnamed. + #[cfg(linux_kernel)] + #[inline] + pub fn is_unnamed(&self) -> bool { + self.len() == offsetof_sun_path() + } + #[inline] pub(crate) fn addr_len(&self) -> c::socklen_t { self.len diff --git a/tests/net/unix.rs b/tests/net/unix.rs index 27d0ff1d0..f2fc2aa28 100644 --- a/tests/net/unix.rs +++ b/tests/net/unix.rs @@ -419,6 +419,7 @@ fn test_abstract_unix_msg_unconnected() { } #[cfg(not(any(target_os = "redox", target_os = "wasi")))] +#[cfg(feature = "pipe")] #[test] fn test_unix_msg_with_scm_rights() { crate::init(); @@ -649,7 +650,7 @@ fn test_unix_peercred_explicit() { /// Like `test_unix_peercred_explicit`, but relies on the fact that /// `set_socket_passcred` enables passing of the credentials implicitly /// instead of passing an explicit message to `sendmsg`. -#[cfg(all(feature = "process", linux_kernel))] +#[cfg(all(feature = "pipe", feature = "process", linux_kernel))] #[test] fn test_unix_peercred_implicit() { crate::init(); @@ -707,6 +708,7 @@ fn test_unix_peercred_implicit() { /// Like `test_unix_msg_with_scm_rights`, but with multiple file descriptors /// over multiple control messages. #[cfg(not(any(target_os = "redox", target_os = "wasi")))] +#[cfg(feature = "pipe")] #[test] fn test_unix_msg_with_combo() { crate::init(); @@ -915,3 +917,25 @@ fn test_unix_msg_with_combo() { client.join().unwrap(); server.join().unwrap(); } + +/// Bind socket to an unnamed Unix-domain address, and assert that an abstract Unix-domain name was +/// assigned by the kernel. +#[cfg(linux_kernel)] +#[test] +fn test_bind_unnamed_address() { + let address = SocketAddrUnix::new_unnamed(); + assert!(address.is_unnamed()); + assert_eq!(address.abstract_name(), None); + assert_eq!(address.path(), None); + let sock = socket(AddressFamily::UNIX, SocketType::DGRAM, None).unwrap(); + bind_unix(&sock, &address).unwrap(); + + let address = rustix::net::getsockname(&sock).unwrap(); + let address = match address { + rustix::net::SocketAddrAny::Unix(address) => address, + address => panic!("expected Unix address, got {address:?}"), + }; + assert!(!address.is_unnamed()); + assert_ne!(address.abstract_name(), None); + assert_eq!(address.path(), None); +} diff --git a/tests/net/unix_alloc.rs b/tests/net/unix_alloc.rs index aedc9b736..222c5439d 100644 --- a/tests/net/unix_alloc.rs +++ b/tests/net/unix_alloc.rs @@ -417,6 +417,7 @@ fn test_abstract_unix_msg_unconnected() { } #[cfg(not(any(target_os = "redox", target_os = "wasi")))] +#[cfg(feature = "pipe")] #[test] fn test_unix_msg_with_scm_rights() { crate::init(); @@ -652,6 +653,7 @@ fn test_unix_peercred() { /// Like `test_unix_msg_with_scm_rights`, but with multiple file descriptors /// over multiple control messages. #[cfg(not(any(target_os = "redox", target_os = "wasi")))] +#[cfg(feature = "pipe")] #[test] fn test_unix_msg_with_combo() { crate::init();