Skip to content

Commit 26ad768

Browse files
authored
Add unix socket methods to SockAddr
To enable determining what type of AF_UNIX address it is, if any. Enable retrieving the pathname or abstract address of the socket, if applicable. Adds the following API: * SockAddr::is_unix * SockAddr::is_unnamed * SockAddr::as_pathname * SockAddr::as_abstract_namespace Also, fix some typos in CONTRIBUTING.
1 parent eb87f41 commit 26ad768

File tree

4 files changed

+152
-7
lines changed

4 files changed

+152
-7
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ platforms the `all` feature is used, to indicate to the user that they're using
3939
API that might is not available on all platforms.
4040

4141
The main `Socket` type is defined in `src/socket.rs` with additional methods
42-
defined on in the the `src/sys/*.rs` files, as per above. The methods on
42+
defined in the `src/sys/*.rs` files, as per above. The methods on
4343
`Socket` are split into multiple `impl` blocks. The first `impl` block contains
4444
a collection of system calls for creating and using the socket, e.g.
4545
`socket(2)`, `bind(2)`, `listen(2)`, etc. The other implementation blocks are
@@ -51,7 +51,7 @@ such as `Socket::freebind` which is (at the time of writing) only available on
5151
Android, Linux and Fuchsia, which is defined in the `src/sys/*.rs` files.
5252

5353
Other types are mostly defined in `src/lib.rs`, except for `SockAddr` and
54-
`SockRef` which have there own file. These types follow the same structure as
54+
`SockRef` which have their own file. These types follow the same structure as
5555
`Socket`, where OS specific methods are defined in `src/sys/*.rs`, e.g.
5656
`Type::cloexec`.
5757

src/sockaddr.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use windows_sys::Win32::Networking::WinSock::SOCKADDR_IN6_0;
99

1010
use crate::sys::{
1111
c_int, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6, sockaddr_storage, socklen_t, AF_INET,
12-
AF_INET6,
12+
AF_INET6, AF_UNIX,
1313
};
1414
use crate::Domain;
1515

@@ -184,6 +184,12 @@ impl SockAddr {
184184
self.storage.ss_family == AF_INET6 as sa_family_t
185185
}
186186

187+
/// Returns true if this address is of a unix socket (for local interprocess communication),
188+
/// i.e. it is from the `AF_UNIX` family, false otherwise.
189+
pub fn is_unix(&self) -> bool {
190+
self.storage.ss_family == AF_UNIX as sa_family_t
191+
}
192+
187193
/// Returns a raw pointer to the address storage.
188194
#[cfg(all(unix, not(target_os = "redox")))]
189195
pub(crate) const fn as_storage_ptr(&self) -> *const sockaddr_storage {
@@ -351,6 +357,8 @@ mod tests {
351357
let std = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876);
352358
let addr = SockAddr::from(std);
353359
assert!(addr.is_ipv4());
360+
assert!(!addr.is_ipv6());
361+
assert!(!addr.is_unix());
354362
assert_eq!(addr.family(), AF_INET as sa_family_t);
355363
assert_eq!(addr.domain(), Domain::IPV4);
356364
assert_eq!(addr.len(), size_of::<sockaddr_in>() as socklen_t);
@@ -364,6 +372,11 @@ mod tests {
364372
assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std)));
365373
assert_eq!(addr.as_socket_ipv4(), Some(std));
366374
assert!(addr.as_socket_ipv6().is_none());
375+
#[cfg(unix)]
376+
{
377+
assert!(addr.as_pathname().is_none());
378+
assert!(addr.as_abstract_namespace().is_none());
379+
}
367380
}
368381

369382
#[test]
@@ -372,6 +385,8 @@ mod tests {
372385
let std = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12);
373386
let addr = SockAddr::from(std);
374387
assert!(addr.is_ipv6());
388+
assert!(!addr.is_ipv4());
389+
assert!(!addr.is_unix());
375390
assert_eq!(addr.family(), AF_INET6 as sa_family_t);
376391
assert_eq!(addr.domain(), Domain::IPV6);
377392
assert_eq!(addr.len(), size_of::<sockaddr_in6>() as socklen_t);
@@ -385,6 +400,11 @@ mod tests {
385400
assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std)));
386401
assert!(addr.as_socket_ipv4().is_none());
387402
assert_eq!(addr.as_socket_ipv6(), Some(std));
403+
#[cfg(unix)]
404+
{
405+
assert!(addr.as_pathname().is_none());
406+
assert!(addr.as_abstract_namespace().is_none());
407+
}
388408
}
389409

390410
#[test]

src/sys/unix.rs

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// except according to those terms.
88

99
use std::cmp::min;
10+
use std::ffi::OsStr;
1011
#[cfg(not(target_os = "redox"))]
1112
use std::io::IoSlice;
1213
use std::marker::PhantomData;
@@ -569,6 +570,13 @@ impl<'a> MaybeUninitSlice<'a> {
569570
}
570571
}
571572

573+
/// Returns the offset of the `sun_path` member of the passed unix socket address.
574+
pub(crate) fn offset_of_path(storage: &libc::sockaddr_un) -> usize {
575+
let base = storage as *const _ as usize;
576+
let path = ptr::addr_of!(storage.sun_path) as usize;
577+
path - base
578+
}
579+
572580
#[allow(unsafe_op_in_unsafe_fn)]
573581
pub(crate) fn unix_sockaddr(path: &Path) -> io::Result<SockAddr> {
574582
// SAFETY: a `sockaddr_storage` of all zeros is valid.
@@ -603,9 +611,7 @@ pub(crate) fn unix_sockaddr(path: &Path) -> io::Result<SockAddr> {
603611
);
604612
}
605613

606-
let base = storage as *const _ as usize;
607-
let path = ptr::addr_of!(storage.sun_path) as usize;
608-
let sun_path_offset = path - base;
614+
let sun_path_offset = offset_of_path(storage);
609615
sun_path_offset
610616
+ bytes.len()
611617
+ match bytes.first() {
@@ -659,6 +665,91 @@ impl SockAddr {
659665
None
660666
}
661667
}
668+
669+
/// Returns true if this address is an unnamed address from the `AF_UNIX` family (for local
670+
/// interprocess communication), false otherwise.
671+
pub fn is_unnamed(&self) -> bool {
672+
self.as_sockaddr_un()
673+
.map(|storage| {
674+
self.len() == offset_of_path(storage) as u32
675+
// On some non-linux platforms a zeroed path is returned for unnamed.
676+
// Abstract addresses only exist on Linux.
677+
// NOTE: although Fuchsia does define `AF_UNIX` it's not actually implemented.
678+
// See https://github.com/rust-lang/socket2/pull/403#discussion_r1123557978
679+
|| (cfg!(not(any(target_os = "linux", target_os = "android")))
680+
&& storage.sun_path[0] == 0)
681+
})
682+
.unwrap_or_default()
683+
}
684+
685+
/// Returns the underlying `sockaddr_un` object if this addres is from the `AF_UNIX` family,
686+
/// otherwise returns `None`.
687+
pub(crate) fn as_sockaddr_un(&self) -> Option<&libc::sockaddr_un> {
688+
self.is_unix().then(|| {
689+
// SAFETY: if unix socket, i.e. the `ss_family` field is `AF_UNIX` then storage must be
690+
// a `sockaddr_un`.
691+
unsafe { &*self.as_ptr().cast::<libc::sockaddr_un>() }
692+
})
693+
}
694+
695+
/// Get the length of the path bytes of the address, not including the terminating or initial
696+
/// (for abstract names) null byte.
697+
///
698+
/// Should not be called on unnamed addresses.
699+
fn path_len(&self, storage: &libc::sockaddr_un) -> usize {
700+
debug_assert!(!self.is_unnamed());
701+
self.len() as usize - offset_of_path(storage) - 1
702+
}
703+
704+
/// Get a u8 slice for the bytes of the pathname or abstract name.
705+
///
706+
/// Should not be called on unnamed addresses.
707+
fn path_bytes(&self, storage: &libc::sockaddr_un, abstract_name: bool) -> &[u8] {
708+
debug_assert!(!self.is_unnamed());
709+
// SAFETY: the pointed objects of type `i8` have the same memory layout as `u8`. The path is
710+
// the last field in the storage and so its length is equal to
711+
// TOTAL_LENGTH - OFFSET_OF_PATH -1
712+
// Where the 1 is either a terminating null if we have a pathname address, or the initial
713+
// null byte, if it's an abstract name address. In the latter case, the path bytes start
714+
// after the initial null byte, hence the `offset`.
715+
// There is no safe way to convert a `&[i8]` to `&[u8]`
716+
unsafe {
717+
slice::from_raw_parts(
718+
(storage.sun_path.as_ptr() as *const u8).offset(abstract_name as isize),
719+
self.path_len(storage),
720+
)
721+
}
722+
}
723+
724+
/// Returns this address as a `Path` reference if it is an `AF_UNIX` pathname address, otherwise
725+
/// returns `None`.
726+
pub fn as_pathname(&self) -> Option<&Path> {
727+
self.as_sockaddr_un().and_then(|storage| {
728+
(self.len() > offset_of_path(storage) as u32 && storage.sun_path[0] != 0).then(|| {
729+
let path_slice = self.path_bytes(storage, false);
730+
Path::new::<OsStr>(OsStrExt::from_bytes(path_slice))
731+
})
732+
})
733+
}
734+
735+
/// Returns this address as a slice of bytes representing an abstract address if it is an
736+
/// `AF_UNIX` abstract address, otherwise returns `None`.
737+
///
738+
/// Abstract addresses are a Linux extension, so this method returns `None` on all non-Linux
739+
/// platforms.
740+
pub fn as_abstract_namespace(&self) -> Option<&[u8]> {
741+
// NOTE: although Fuchsia does define `AF_UNIX` it's not actually implemented.
742+
// See https://github.com/rust-lang/socket2/pull/403#discussion_r1123557978
743+
#[cfg(any(target_os = "linux", target_os = "android"))]
744+
{
745+
self.as_sockaddr_un().and_then(|storage| {
746+
(self.len() > offset_of_path(storage) as u32 && storage.sun_path[0] == 0)
747+
.then(|| self.path_bytes(storage, true))
748+
})
749+
}
750+
#[cfg(not(any(target_os = "linux", target_os = "android")))]
751+
None
752+
}
662753
}
663754

664755
pub(crate) type Socket = c_int;

tests/socket.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ use std::num::NonZeroUsize;
3838
use std::os::unix::io::AsRawFd;
3939
#[cfg(windows)]
4040
use std::os::windows::io::AsRawSocket;
41+
#[cfg(unix)]
42+
use std::path::Path;
4143
use std::str;
4244
use std::thread;
4345
use std::time::Duration;
@@ -144,17 +146,49 @@ fn socket_address_unix() {
144146
let addr = SockAddr::unix(string).unwrap();
145147
assert!(addr.as_socket_ipv4().is_none());
146148
assert!(addr.as_socket_ipv6().is_none());
149+
assert!(!addr.is_ipv4());
150+
assert!(!addr.is_ipv6());
151+
assert!(addr.is_unix());
152+
assert_eq!(addr.domain(), Domain::UNIX);
153+
#[cfg(unix)]
154+
{
155+
assert!(!addr.is_unnamed());
156+
assert_eq!(addr.as_pathname(), Some(Path::new(string)));
157+
assert_eq!(addr.as_abstract_namespace(), None);
158+
}
159+
}
160+
161+
#[test]
162+
fn socket_address_unix_unnamed() {
163+
let addr = SockAddr::unix("").unwrap();
164+
assert!(addr.as_socket_ipv4().is_none());
165+
assert!(addr.as_socket_ipv6().is_none());
166+
assert!(!addr.is_ipv4());
167+
assert!(!addr.is_ipv6());
168+
assert!(addr.is_unix());
169+
assert_eq!(addr.domain(), Domain::UNIX);
170+
#[cfg(unix)]
171+
{
172+
assert!(addr.is_unnamed());
173+
assert_eq!(addr.as_pathname(), None);
174+
assert_eq!(addr.as_abstract_namespace(), None);
175+
}
147176
}
148177

149178
#[test]
150179
#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "all"))]
151180
fn socket_address_unix_abstract_namespace() {
152181
let path = "\0h".repeat(108 / 2);
153-
let addr = SockAddr::unix(path).unwrap();
182+
let addr = SockAddr::unix(&path).unwrap();
154183
assert_eq!(
155184
addr.len() as usize,
156185
std::mem::size_of::<libc::sockaddr_un>()
157186
);
187+
assert!(!addr.is_unnamed());
188+
// The first byte is the opening null bytes of an abstract address, should not be included.
189+
assert_eq!(addr.as_abstract_namespace(), Some(&path.as_bytes()[1..]));
190+
assert!(addr.as_pathname().is_none());
191+
assert!(!addr.is_unnamed());
158192
}
159193

160194
#[test]

0 commit comments

Comments
 (0)