Skip to content

Commit b77cbc8

Browse files
authored
net/linux_kernel: add unnamed Unix-domain addresses (#1242)
* 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 * net: unify `SocketAddrUnix` value extraction
1 parent 06ad93d commit b77cbc8

4 files changed

Lines changed: 116 additions & 39 deletions

File tree

src/backend/libc/net/addr.rs

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,26 @@ impl SocketAddrUnix {
7676
})
7777
}
7878

79+
/// Construct a new unnamed address.
80+
///
81+
/// The kernel will assign an abstract Unix-domain address to the socket when you call
82+
/// [`bind_unix()`][crate::net::bind_unix]. You can inspect the assigned name with
83+
/// [`getsockname`][crate::net::getsockname].
84+
///
85+
/// # References
86+
/// - [Linux]
87+
///
88+
/// [Linux]: https://www.man7.org/linux/man-pages/man7/unix.7.html
89+
#[cfg(linux_kernel)]
90+
#[inline]
91+
pub fn new_unnamed() -> Self {
92+
Self {
93+
unix: Self::init(),
94+
#[cfg(not(any(bsd, target_os = "haiku")))]
95+
len: offsetof_sun_path() as _,
96+
}
97+
}
98+
7999
const fn init() -> c::sockaddr_un {
80100
c::sockaddr_un {
81101
#[cfg(any(
@@ -103,19 +123,10 @@ impl SocketAddrUnix {
103123
/// For a filesystem path address, return the path.
104124
#[inline]
105125
pub fn path(&self) -> Option<&CStr> {
106-
let len = self.len();
107-
if len != 0 && self.unix.sun_path[0] != 0 {
108-
let end = len as usize - offsetof_sun_path();
109-
let bytes = &self.unix.sun_path[..end];
110-
// SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`.
111-
// And `from_bytes_with_nul_unchecked` since the string is
112-
// NUL-terminated.
113-
unsafe {
114-
Some(CStr::from_bytes_with_nul_unchecked(slice::from_raw_parts(
115-
bytes.as_ptr().cast(),
116-
bytes.len(),
117-
)))
118-
}
126+
let bytes = self.bytes()?;
127+
if !bytes.is_empty() && bytes[0] != 0 {
128+
// SAFETY: `from_bytes_with_nul_unchecked` since the string is NUL-terminated.
129+
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) })
119130
} else {
120131
None
121132
}
@@ -125,17 +136,20 @@ impl SocketAddrUnix {
125136
#[cfg(linux_kernel)]
126137
#[inline]
127138
pub fn abstract_name(&self) -> Option<&[u8]> {
128-
let len = self.len();
129-
if len != 0 && self.unix.sun_path[0] == 0 {
130-
let end = len as usize - offsetof_sun_path();
131-
let bytes = &self.unix.sun_path[1..end];
132-
// SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`.
133-
unsafe { Some(slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len())) }
139+
if let [0, ref bytes @ ..] = self.bytes()? {
140+
Some(bytes)
134141
} else {
135142
None
136143
}
137144
}
138145

146+
/// `true` if the socket address is unnamed.
147+
#[cfg(linux_kernel)]
148+
#[inline]
149+
pub fn is_unnamed(&self) -> bool {
150+
self.bytes() == Some(&[])
151+
}
152+
139153
#[inline]
140154
pub(crate) fn addr_len(&self) -> c::socklen_t {
141155
#[cfg(not(any(bsd, target_os = "haiku")))]
@@ -152,6 +166,18 @@ impl SocketAddrUnix {
152166
pub(crate) fn len(&self) -> usize {
153167
self.addr_len() as usize
154168
}
169+
170+
#[inline]
171+
fn bytes(&self) -> Option<&[u8]> {
172+
let len = self.len() as usize;
173+
if len != 0 {
174+
let bytes = &self.unix.sun_path[..len - offsetof_sun_path()];
175+
// SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`.
176+
Some(unsafe { slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len()) })
177+
} else {
178+
None
179+
}
180+
}
155181
}
156182

157183
#[cfg(unix)]

src/backend/linux_raw/net/addr.rs

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,26 @@ impl SocketAddrUnix {
6464
}
6565
}
6666

67+
/// Construct a new unnamed address.
68+
///
69+
/// The kernel will assign an abstract Unix-domain address to the socket when you call
70+
/// [`bind_unix()`][crate::net::bind_unix]. You can inspect the assigned name with
71+
/// [`getsockname`][crate::net::getsockname].
72+
///
73+
/// # References
74+
/// - [Linux]
75+
///
76+
/// [Linux]: https://www.man7.org/linux/man-pages/man7/unix.7.html
77+
#[cfg(linux_kernel)]
78+
#[inline]
79+
pub fn new_unnamed() -> Self {
80+
Self {
81+
unix: Self::init(),
82+
#[cfg(not(any(bsd, target_os = "haiku")))]
83+
len: offsetof_sun_path() as _,
84+
}
85+
}
86+
6787
const fn init() -> c::sockaddr_un {
6888
c::sockaddr_un {
6989
sun_family: c::AF_UNIX as _,
@@ -74,17 +94,10 @@ impl SocketAddrUnix {
7494
/// For a filesystem path address, return the path.
7595
#[inline]
7696
pub fn path(&self) -> Option<&CStr> {
77-
let len = self.len();
78-
if len != 0 && self.unix.sun_path[0] as u8 != b'\0' {
79-
let end = len as usize - offsetof_sun_path();
80-
let bytes = &self.unix.sun_path[..end];
81-
82-
// SAFETY: Convert `&[c_char]` to `&[u8]`.
83-
let bytes = unsafe { slice::from_raw_parts(bytes.as_ptr().cast::<u8>(), bytes.len()) };
84-
85-
// SAFETY: `from_bytes_with_nul_unchecked` since the string is
86-
// NUL-terminated.
87-
unsafe { Some(CStr::from_bytes_with_nul_unchecked(bytes)) }
97+
let bytes = self.bytes()?;
98+
if !bytes.is_empty() && bytes[0] != 0 {
99+
// SAFETY: `from_bytes_with_nul_unchecked` since the string is NUL-terminated.
100+
Some(unsafe { CStr::from_bytes_with_nul_unchecked(bytes) })
88101
} else {
89102
None
90103
}
@@ -93,20 +106,20 @@ impl SocketAddrUnix {
93106
/// For an abstract address, return the identifier.
94107
#[inline]
95108
pub fn abstract_name(&self) -> Option<&[u8]> {
96-
let len = self.len();
97-
if len != 0 && self.unix.sun_path[0] as u8 == b'\0' {
98-
let end = len as usize - offsetof_sun_path();
99-
let bytes = &self.unix.sun_path[1..end];
100-
101-
// SAFETY: Convert `&[c_char]` to `&[u8]`.
102-
let bytes = unsafe { slice::from_raw_parts(bytes.as_ptr().cast::<u8>(), bytes.len()) };
103-
109+
if let [0, ref bytes @ ..] = self.bytes()? {
104110
Some(bytes)
105111
} else {
106112
None
107113
}
108114
}
109115

116+
/// `true` if the socket address is unnamed.
117+
#[cfg(linux_kernel)]
118+
#[inline]
119+
pub fn is_unnamed(&self) -> bool {
120+
self.bytes() == Some(&[])
121+
}
122+
110123
#[inline]
111124
pub(crate) fn addr_len(&self) -> c::socklen_t {
112125
self.len
@@ -116,6 +129,18 @@ impl SocketAddrUnix {
116129
pub(crate) fn len(&self) -> usize {
117130
self.addr_len() as usize
118131
}
132+
133+
#[inline]
134+
fn bytes(&self) -> Option<&[u8]> {
135+
let len = self.len() as usize;
136+
if len != 0 {
137+
let bytes = &self.unix.sun_path[..len - offsetof_sun_path()];
138+
// SAFETY: `from_raw_parts` to convert from `&[c_char]` to `&[u8]`.
139+
Some(unsafe { slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len()) })
140+
} else {
141+
None
142+
}
143+
}
119144
}
120145

121146
impl PartialEq for SocketAddrUnix {

tests/net/unix.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ fn test_abstract_unix_msg_unconnected() {
423423

424424
#[cfg(feature = "pipe")]
425425
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
426+
#[cfg(feature = "pipe")]
426427
#[test]
427428
fn test_unix_msg_with_scm_rights() {
428429
crate::init();
@@ -658,7 +659,7 @@ fn test_unix_peercred_explicit() {
658659
/// Like `test_unix_peercred_explicit`, but relies on the fact that
659660
/// `set_socket_passcred` enables passing of the credentials implicitly
660661
/// instead of passing an explicit message to `sendmsg`.
661-
#[cfg(all(feature = "process", linux_kernel))]
662+
#[cfg(all(feature = "pipe", feature = "process", linux_kernel))]
662663
#[test]
663664
fn test_unix_peercred_implicit() {
664665
crate::init();
@@ -719,6 +720,7 @@ fn test_unix_peercred_implicit() {
719720
/// over multiple control messages.
720721
#[cfg(feature = "pipe")]
721722
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
723+
#[cfg(feature = "pipe")]
722724
#[test]
723725
fn test_unix_msg_with_combo() {
724726
crate::init();
@@ -930,3 +932,25 @@ fn test_unix_msg_with_combo() {
930932
client.join().unwrap();
931933
server.join().unwrap();
932934
}
935+
936+
/// Bind socket to an unnamed Unix-domain address, and assert that an abstract Unix-domain name was
937+
/// assigned by the kernel.
938+
#[cfg(linux_kernel)]
939+
#[test]
940+
fn test_bind_unnamed_address() {
941+
let address = SocketAddrUnix::new_unnamed();
942+
assert!(address.is_unnamed());
943+
assert_eq!(address.abstract_name(), None);
944+
assert_eq!(address.path(), None);
945+
let sock = socket(AddressFamily::UNIX, SocketType::DGRAM, None).unwrap();
946+
bind_unix(&sock, &address).unwrap();
947+
948+
let address = rustix::net::getsockname(&sock).unwrap();
949+
let address = match address {
950+
rustix::net::SocketAddrAny::Unix(address) => address,
951+
address => panic!("expected Unix address, got {address:?}"),
952+
};
953+
assert!(!address.is_unnamed());
954+
assert_ne!(address.abstract_name(), None);
955+
assert_eq!(address.path(), None);
956+
}

tests/net/unix_alloc.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ fn test_abstract_unix_msg_unconnected() {
421421

422422
#[cfg(feature = "pipe")]
423423
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
424+
#[cfg(feature = "pipe")]
424425
#[test]
425426
fn test_unix_msg_with_scm_rights() {
426427
crate::init();
@@ -660,6 +661,7 @@ fn test_unix_peercred() {
660661
/// over multiple control messages.
661662
#[cfg(feature = "pipe")]
662663
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
664+
#[cfg(feature = "pipe")]
663665
#[test]
664666
fn test_unix_msg_with_combo() {
665667
crate::init();

0 commit comments

Comments
 (0)