Skip to content

Commit

Permalink
Add unix socket methods to SockAddr
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
t4lz authored Mar 4, 2023
1 parent eb87f41 commit 26ad768
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 7 deletions.
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")))
&& 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),
)
}
}

/// 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"))]
{
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

0 comments on commit 26ad768

Please sign in to comment.