Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Match the standard library's interface for unix sockets on unix. #403

Merged
merged 17 commits into from
Mar 4, 2023
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ platforms the `all` feature is used, to indicate to the user that they're using
API that might is not available on all platforms.

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

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

Expand Down
22 changes: 21 additions & 1 deletion src/sockaddr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use windows_sys::Win32::Networking::WinSock::SOCKADDR_IN6_0;

use crate::sys::{
c_int, sa_family_t, sockaddr, sockaddr_in, sockaddr_in6, sockaddr_storage, socklen_t, AF_INET,
AF_INET6,
AF_INET6, AF_UNIX,
};
use crate::Domain;

Expand Down Expand Up @@ -184,6 +184,12 @@ impl SockAddr {
self.storage.ss_family == AF_INET6 as sa_family_t
}

/// Returns true if this address is of a unix socket (for local interprocess communication),
/// i.e. it is from the `AF_UNIX` family, false otherwise.
pub fn is_unix(&self) -> bool {
self.storage.ss_family == AF_UNIX as sa_family_t
}

/// Returns a raw pointer to the address storage.
#[cfg(all(unix, not(target_os = "redox")))]
pub(crate) const fn as_storage_ptr(&self) -> *const sockaddr_storage {
Expand Down Expand Up @@ -351,6 +357,8 @@ mod tests {
let std = SocketAddrV4::new(Ipv4Addr::new(1, 2, 3, 4), 9876);
let addr = SockAddr::from(std);
assert!(addr.is_ipv4());
assert!(!addr.is_ipv6());
assert!(!addr.is_unix());
assert_eq!(addr.family(), AF_INET as sa_family_t);
assert_eq!(addr.domain(), Domain::IPV4);
assert_eq!(addr.len(), size_of::<sockaddr_in>() as socklen_t);
Expand All @@ -364,6 +372,11 @@ mod tests {
assert_eq!(addr.as_socket(), Some(SocketAddr::V4(std)));
assert_eq!(addr.as_socket_ipv4(), Some(std));
assert!(addr.as_socket_ipv6().is_none());
#[cfg(unix)]
{
assert!(addr.as_pathname().is_none());
assert!(addr.as_abstract_namespace().is_none());
}
}

#[test]
Expand All @@ -372,6 +385,8 @@ mod tests {
let std = SocketAddrV6::new(Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8), 9876, 11, 12);
let addr = SockAddr::from(std);
assert!(addr.is_ipv6());
assert!(!addr.is_ipv4());
assert!(!addr.is_unix());
assert_eq!(addr.family(), AF_INET6 as sa_family_t);
assert_eq!(addr.domain(), Domain::IPV6);
assert_eq!(addr.len(), size_of::<sockaddr_in6>() as socklen_t);
Expand All @@ -385,6 +400,11 @@ mod tests {
assert_eq!(addr.as_socket(), Some(SocketAddr::V6(std)));
assert!(addr.as_socket_ipv4().is_none());
assert_eq!(addr.as_socket_ipv6(), Some(std));
#[cfg(unix)]
{
assert!(addr.as_pathname().is_none());
assert!(addr.as_abstract_namespace().is_none());
}
}

#[test]
Expand Down
97 changes: 94 additions & 3 deletions src/sys/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// except according to those terms.

use std::cmp::min;
use std::ffi::OsStr;
#[cfg(not(target_os = "redox"))]
use std::io::IoSlice;
use std::marker::PhantomData;
Expand Down Expand Up @@ -569,6 +570,13 @@ impl<'a> MaybeUninitSlice<'a> {
}
}

/// Returns the offset of the `sun_path` member of the passed unix socket address.
pub(crate) fn offset_of_path(storage: &libc::sockaddr_un) -> usize {
let base = storage as *const _ as usize;
let path = ptr::addr_of!(storage.sun_path) as usize;
path - base
}

#[allow(unsafe_op_in_unsafe_fn)]
pub(crate) fn unix_sockaddr(path: &Path) -> io::Result<SockAddr> {
// SAFETY: a `sockaddr_storage` of all zeros is valid.
Expand Down Expand Up @@ -603,9 +611,7 @@ pub(crate) fn unix_sockaddr(path: &Path) -> io::Result<SockAddr> {
);
}

let base = storage as *const _ as usize;
let path = ptr::addr_of!(storage.sun_path) as usize;
let sun_path_offset = path - base;
let sun_path_offset = offset_of_path(storage);
sun_path_offset
+ bytes.len()
+ match bytes.first() {
Expand Down Expand Up @@ -659,6 +665,91 @@ impl SockAddr {
None
}
}

/// Returns true if this address is an unnamed address from the `AF_UNIX` family (for local
/// interprocess communication), false otherwise.
pub fn is_unnamed(&self) -> bool {
self.as_sockaddr_un()
.map(|storage| {
self.len() == offset_of_path(storage) as u32
// On some non-linux platforms a zeroed path is returned for unnamed.
// Abstract addresses only exist on Linux.
// NOTE: although Fuchsia does define `AF_UNIX` it's not actually implemented.
// See https://github.com/rust-lang/socket2/pull/403#discussion_r1123557978
|| (cfg!(not(any(target_os = "linux", target_os = "android")))
t4lz marked this conversation as resolved.
Show resolved Hide resolved
&& storage.sun_path[0] == 0)
})
.unwrap_or_default()
}

/// Returns the underlying `sockaddr_un` object if this addres is from the `AF_UNIX` family,
/// otherwise returns `None`.
pub(crate) fn as_sockaddr_un(&self) -> Option<&libc::sockaddr_un> {
self.is_unix().then(|| {
// SAFETY: if unix socket, i.e. the `ss_family` field is `AF_UNIX` then storage must be
// a `sockaddr_un`.
unsafe { &*self.as_ptr().cast::<libc::sockaddr_un>() }
})
}

/// Get the length of the path bytes of the address, not including the terminating or initial
/// (for abstract names) null byte.
///
/// Should not be called on unnamed addresses.
fn path_len(&self, storage: &libc::sockaddr_un) -> usize {
debug_assert!(!self.is_unnamed());
self.len() as usize - offset_of_path(storage) - 1
}

/// Get a u8 slice for the bytes of the pathname or abstract name.
///
/// Should not be called on unnamed addresses.
fn path_bytes(&self, storage: &libc::sockaddr_un, abstract_name: bool) -> &[u8] {
debug_assert!(!self.is_unnamed());
// SAFETY: the pointed objects of type `i8` have the same memory layout as `u8`. The path is
// the last field in the storage and so its length is equal to
// TOTAL_LENGTH - OFFSET_OF_PATH -1
// Where the 1 is either a terminating null if we have a pathname address, or the initial
// null byte, if it's an abstract name address. In the latter case, the path bytes start
// after the initial null byte, hence the `offset`.
// There is no safe way to convert a `&[i8]` to `&[u8]`
unsafe {
slice::from_raw_parts(
(storage.sun_path.as_ptr() as *const u8).offset(abstract_name as isize),
self.path_len(storage),
Thomasdezeeuw marked this conversation as resolved.
Show resolved Hide resolved
)
}
}

/// Returns this address as a `Path` reference if it is an `AF_UNIX` pathname address, otherwise
/// returns `None`.
pub fn as_pathname(&self) -> Option<&Path> {
self.as_sockaddr_un().and_then(|storage| {
(self.len() > offset_of_path(storage) as u32 && storage.sun_path[0] != 0).then(|| {
let path_slice = self.path_bytes(storage, false);
Path::new::<OsStr>(OsStrExt::from_bytes(path_slice))
})
})
}

/// Returns this address as a slice of bytes representing an abstract address if it is an
/// `AF_UNIX` abstract address, otherwise returns `None`.
///
/// Abstract addresses are a Linux extension, so this method returns `None` on all non-Linux
/// platforms.
pub fn as_abstract_namespace(&self) -> Option<&[u8]> {
// NOTE: although Fuchsia does define `AF_UNIX` it's not actually implemented.
// See https://github.com/rust-lang/socket2/pull/403#discussion_r1123557978
#[cfg(any(target_os = "linux", target_os = "android"))]
t4lz marked this conversation as resolved.
Show resolved Hide resolved
{
self.as_sockaddr_un().and_then(|storage| {
(self.len() > offset_of_path(storage) as u32 && storage.sun_path[0] == 0)
.then(|| self.path_bytes(storage, true))
})
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
None
}
}

pub(crate) type Socket = c_int;
Expand Down
36 changes: 35 additions & 1 deletion tests/socket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ use std::num::NonZeroUsize;
use std::os::unix::io::AsRawFd;
#[cfg(windows)]
use std::os::windows::io::AsRawSocket;
#[cfg(unix)]
use std::path::Path;
use std::str;
use std::thread;
use std::time::Duration;
Expand Down Expand Up @@ -144,17 +146,49 @@ fn socket_address_unix() {
let addr = SockAddr::unix(string).unwrap();
assert!(addr.as_socket_ipv4().is_none());
assert!(addr.as_socket_ipv6().is_none());
assert!(!addr.is_ipv4());
assert!(!addr.is_ipv6());
assert!(addr.is_unix());
assert_eq!(addr.domain(), Domain::UNIX);
#[cfg(unix)]
{
assert!(!addr.is_unnamed());
assert_eq!(addr.as_pathname(), Some(Path::new(string)));
assert_eq!(addr.as_abstract_namespace(), None);
}
}

#[test]
fn socket_address_unix_unnamed() {
let addr = SockAddr::unix("").unwrap();
assert!(addr.as_socket_ipv4().is_none());
assert!(addr.as_socket_ipv6().is_none());
assert!(!addr.is_ipv4());
assert!(!addr.is_ipv6());
assert!(addr.is_unix());
assert_eq!(addr.domain(), Domain::UNIX);
#[cfg(unix)]
{
assert!(addr.is_unnamed());
assert_eq!(addr.as_pathname(), None);
assert_eq!(addr.as_abstract_namespace(), None);
}
}

#[test]
#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "all"))]
fn socket_address_unix_abstract_namespace() {
let path = "\0h".repeat(108 / 2);
let addr = SockAddr::unix(path).unwrap();
let addr = SockAddr::unix(&path).unwrap();
assert_eq!(
addr.len() as usize,
std::mem::size_of::<libc::sockaddr_un>()
);
assert!(!addr.is_unnamed());
// The first byte is the opening null bytes of an abstract address, should not be included.
assert_eq!(addr.as_abstract_namespace(), Some(&path.as_bytes()[1..]));
assert!(addr.as_pathname().is_none());
assert!(!addr.is_unnamed());
}

#[test]
Expand Down