Skip to content

Commit

Permalink
net/linux_kernel: add unnamed Unix-domain addresses
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Kijewski committed Dec 16, 2024
1 parent 74bcc35 commit 4c5dc02
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 13 deletions.
36 changes: 31 additions & 5 deletions src/backend/libc/net/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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::<u8>(), 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")))]
Expand Down
37 changes: 30 additions & 7 deletions src/backend/linux_raw/net/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _,
Expand Down Expand Up @@ -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::<u8>(), bytes.len()) };

Some(bytes)
Some(unsafe { slice::from_raw_parts(bytes.as_ptr().cast::<u8>(), 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
Expand Down
26 changes: 25 additions & 1 deletion tests/net/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
2 changes: 2 additions & 0 deletions tests/net/unix_alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit 4c5dc02

Please sign in to comment.