Skip to content

Commit

Permalink
Auto merge of #85379 - mdaverde:uds-abstract, r=joshtriplett
Browse files Browse the repository at this point in the history
Add abstract namespace support for Unix domain sockets

Hello! The other day I wanted to mess around with UDS in Rust and found that abstract namespaces ([unix(7)](https://man7.org/linux/man-pages/man7/unix.7.html)) on Linux still needed development. I took the approach of adding `_addr` specific public functions to reduce conflicts.

Feature name: `unix_socket_abstract`
Tracking issue: #85410
Further context: #42048

## Non-platform specific additions

`UnixListener::bind_addr(&SocketAddr) -> Result<UnixListener>`

`UnixStream::connect_addr(&SocketAddr) -> Result<()>`

`UnixDatagram::bind_addr(&SocketAddr) -> Result<UnixDatagram>`

`UnixDatagram::connect_addr(&SocketAddr) -> Result<()>`

`UnixDatagram::send_to_addr(&self, &[u8], &SocketAddr) -> Result<usize>`

## Platform-specific (Linux) additions

`SocketAddr::from_abstract_namespace(&[u8]) -> SocketAddr`

`SockerAddr::as_abstract_namespace() -> Option<&[u8]>`

## Example

```rust
#![feature(unix_socket_abstract)]
use std::os::unix::net::{UnixListener, SocketAddr};

fn main() -> std::io::Result<()> {
    let addr = SocketAddr::from_abstract_namespace(b"namespace")?; // Linux only
    let listener = match UnixListener::bind_addr(&addr) {
        Ok(sock) => sock,
        Err(err) => {
            println!("Couldn't bind: {:?}", err);
            return Err(err);
        }
    };
    Ok(())
}
```

## Further Details

The main inspiration for the implementation came from the [nix-rust](https://github.com/nix-rust/nix/blob/master/src/sys/socket/addr.rs#L558) crate but there are also other [historical](c4db068) [attempts](https://github.com/tormol/uds/blob/master/src/addr.rs#L324) with similar approaches.

A comment I did have was with this change, we now allow a `SocketAddr` to be constructed explicitly rather than just used almost as a handle for the return of `peer_addr` and `local_addr`. We could consider adding other explicit constructors (e.g. `SocketAddr::from_pathname`, `SockerAddr::from_unnamed`).

Cheers!
  • Loading branch information
bors committed Oct 15, 2021
2 parents c102653 + 15b1198 commit 6cc0a76
Show file tree
Hide file tree
Showing 5 changed files with 416 additions and 4 deletions.
88 changes: 86 additions & 2 deletions library/std/src/os/unix/net/addr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@ impl<'a> fmt::Display for AsciiEscaped<'a> {
#[derive(Clone)]
#[stable(feature = "unix_socket", since = "1.10.0")]
pub struct SocketAddr {
addr: libc::sockaddr_un,
len: libc::socklen_t,
pub(super) addr: libc::sockaddr_un,
pub(super) len: libc::socklen_t,
}

impl SocketAddr {
Expand Down Expand Up @@ -198,6 +198,31 @@ impl SocketAddr {
if let AddressKind::Pathname(path) = self.address() { Some(path) } else { None }
}

/// Returns the contents of this address if it is an abstract namespace
/// without the leading null byte.
///
/// # Examples
///
/// ```no_run
/// #![feature(unix_socket_abstract)]
/// use std::os::unix::net::{UnixListener, SocketAddr};
///
/// fn main() -> std::io::Result<()> {
/// let namespace = b"hidden";
/// let namespace_addr = SocketAddr::from_abstract_namespace(&namespace[..])?;
/// let socket = UnixListener::bind_addr(&namespace_addr)?;
/// let local_addr = socket.local_addr().expect("Couldn't get local address");
/// assert_eq!(local_addr.as_abstract_namespace(), Some(&namespace[..]));
/// Ok(())
/// }
/// ```
#[doc(cfg(any(target_os = "android", target_os = "linux")))]
#[cfg(any(doc, target_os = "android", target_os = "linux",))]
#[unstable(feature = "unix_socket_abstract", issue = "85410")]
pub fn as_abstract_namespace(&self) -> Option<&[u8]> {
if let AddressKind::Abstract(name) = self.address() { Some(name) } else { None }
}

fn address(&self) -> AddressKind<'_> {
let len = self.len as usize - sun_path_offset(&self.addr);
let path = unsafe { mem::transmute::<&[libc::c_char], &[u8]>(&self.addr.sun_path) };
Expand All @@ -214,6 +239,65 @@ impl SocketAddr {
AddressKind::Pathname(OsStr::from_bytes(&path[..len - 1]).as_ref())
}
}

/// Creates an abstract domain socket address from a namespace
///
/// An abstract address does not create a file unlike traditional path-based
/// Unix sockets. The advantage of this is that the address will disappear when
/// the socket bound to it is closed, so no filesystem clean up is required.
///
/// The leading null byte for the abstract namespace is automatically added.
///
/// This is a Linux-specific extension. See more at [`unix(7)`].
///
/// [`unix(7)`]: https://man7.org/linux/man-pages/man7/unix.7.html
///
/// # Errors
///
/// This will return an error if the given namespace is too long
///
/// # Examples
///
/// ```no_run
/// #![feature(unix_socket_abstract)]
/// use std::os::unix::net::{UnixListener, SocketAddr};
///
/// fn main() -> std::io::Result<()> {
/// let addr = SocketAddr::from_abstract_namespace(b"hidden")?;
/// let listener = match UnixListener::bind_addr(&addr) {
/// Ok(sock) => sock,
/// Err(err) => {
/// println!("Couldn't bind: {:?}", err);
/// return Err(err);
/// }
/// };
/// Ok(())
/// }
/// ```
#[doc(cfg(any(target_os = "android", target_os = "linux")))]
#[cfg(any(doc, target_os = "android", target_os = "linux",))]
#[unstable(feature = "unix_socket_abstract", issue = "85410")]
pub fn from_abstract_namespace(namespace: &[u8]) -> io::Result<SocketAddr> {
unsafe {
let mut addr: libc::sockaddr_un = mem::zeroed();
addr.sun_family = libc::AF_UNIX as libc::sa_family_t;

if namespace.len() + 1 > addr.sun_path.len() {
return Err(io::Error::new_const(
io::ErrorKind::InvalidInput,
&"namespace must be shorter than SUN_LEN",
));
}

crate::ptr::copy_nonoverlapping(
namespace.as_ptr(),
addr.sun_path.as_mut_ptr().offset(1) as *mut u8,
namespace.len(),
);
let len = (sun_path_offset(&addr) + 1 + namespace.len()) as libc::socklen_t;
SocketAddr::from_parts(addr, len)
}
}
}

#[stable(feature = "unix_socket", since = "1.10.0")]
Expand Down
108 changes: 107 additions & 1 deletion library/std/src/os/unix/net/datagram.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,41 @@ impl UnixDatagram {
}
}

/// Creates a Unix datagram socket bound to an address.
///
/// # Examples
///
/// ```no_run
/// #![feature(unix_socket_abstract)]
/// use std::os::unix::net::{UnixDatagram};
///
/// fn main() -> std::io::Result<()> {
/// let sock1 = UnixDatagram::bind("path/to/socket")?;
/// let addr = sock1.local_addr()?;
///
/// let sock2 = match UnixDatagram::bind_addr(&addr) {
/// Ok(sock) => sock,
/// Err(err) => {
/// println!("Couldn't bind: {:?}", err);
/// return Err(err);
/// }
/// };
/// Ok(())
/// }
/// ```
#[unstable(feature = "unix_socket_abstract", issue = "85410")]
pub fn bind_addr(socket_addr: &SocketAddr) -> io::Result<UnixDatagram> {
unsafe {
let socket = UnixDatagram::unbound()?;
cvt(libc::bind(
socket.as_raw_fd(),
&socket_addr.addr as *const _ as *const _,
socket_addr.len as _,
))?;
Ok(socket)
}
}

/// Creates a Unix Datagram socket which is not bound to any address.
///
/// # Examples
Expand Down Expand Up @@ -156,7 +191,7 @@ impl UnixDatagram {
Ok((UnixDatagram(i1), UnixDatagram(i2)))
}

/// Connects the socket to the specified address.
/// Connects the socket to the specified path address.
///
/// The [`send`] method may be used to send data to the specified address.
/// [`recv`] and [`recv_from`] will only receive data from that address.
Expand Down Expand Up @@ -192,6 +227,41 @@ impl UnixDatagram {
Ok(())
}

/// Connects the socket to an address.
///
/// # Examples
///
/// ```no_run
/// #![feature(unix_socket_abstract)]
/// use std::os::unix::net::{UnixDatagram};
///
/// fn main() -> std::io::Result<()> {
/// let bound = UnixDatagram::bind("/path/to/socket")?;
/// let addr = bound.local_addr()?;
///
/// let sock = UnixDatagram::unbound()?;
/// match sock.connect_addr(&addr) {
/// Ok(sock) => sock,
/// Err(e) => {
/// println!("Couldn't connect: {:?}", e);
/// return Err(e)
/// }
/// };
/// Ok(())
/// }
/// ```
#[unstable(feature = "unix_socket_abstract", issue = "85410")]
pub fn connect_addr(&self, socket_addr: &SocketAddr) -> io::Result<()> {
unsafe {
cvt(libc::connect(
self.as_raw_fd(),
&socket_addr.addr as *const _ as *const _,
socket_addr.len,
))?;
}
Ok(())
}

/// Creates a new independently owned handle to the underlying socket.
///
/// The returned `UnixDatagram` is a reference to the same socket that this
Expand Down Expand Up @@ -473,6 +543,42 @@ impl UnixDatagram {
}
}

/// Sends data on the socket to the specified [SocketAddr].
///
/// On success, returns the number of bytes written.
///
/// [SocketAddr]: crate::os::unix::net::SocketAddr
///
/// # Examples
///
/// ```no_run
/// #![feature(unix_socket_abstract)]
/// use std::os::unix::net::{UnixDatagram};
///
/// fn main() -> std::io::Result<()> {
/// let bound = UnixDatagram::bind("/path/to/socket")?;
/// let addr = bound.local_addr()?;
///
/// let sock = UnixDatagram::unbound()?;
/// sock.send_to_addr(b"bacon egg and cheese", &addr).expect("send_to_addr function failed");
/// Ok(())
/// }
/// ```
#[unstable(feature = "unix_socket_abstract", issue = "85410")]
pub fn send_to_addr(&self, buf: &[u8], socket_addr: &SocketAddr) -> io::Result<usize> {
unsafe {
let count = cvt(libc::sendto(
self.as_raw_fd(),
buf.as_ptr() as *const _,
buf.len(),
MSG_NOSIGNAL,
&socket_addr.addr as *const _ as *const _,
socket_addr.len,
))?;
Ok(count as usize)
}
}

/// Sends data on the socket to the socket's peer.
///
/// The peer address may be set by the `connect` method, and this method
Expand Down
38 changes: 38 additions & 0 deletions library/std/src/os/unix/net/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,44 @@ impl UnixListener {
}
}

/// Creates a new `UnixListener` bound to the specified [`socket address`].
///
/// [`socket address`]: crate::os::unix::net::SocketAddr
///
/// # Examples
///
/// ```no_run
/// #![feature(unix_socket_abstract)]
/// use std::os::unix::net::{UnixListener};
///
/// fn main() -> std::io::Result<()> {
/// let listener1 = UnixListener::bind("path/to/socket")?;
/// let addr = listener1.local_addr()?;
///
/// let listener2 = match UnixListener::bind_addr(&addr) {
/// Ok(sock) => sock,
/// Err(err) => {
/// println!("Couldn't bind: {:?}", err);
/// return Err(err);
/// }
/// };
/// Ok(())
/// }
/// ```
#[unstable(feature = "unix_socket_abstract", issue = "85410")]
pub fn bind_addr(socket_addr: &SocketAddr) -> io::Result<UnixListener> {
unsafe {
let inner = Socket::new_raw(libc::AF_UNIX, libc::SOCK_STREAM)?;
cvt(libc::bind(
inner.as_raw_fd(),
&socket_addr.addr as *const _ as *const _,
socket_addr.len as _,
))?;
cvt(libc::listen(inner.as_raw_fd(), 128))?;
Ok(UnixListener(inner))
}
}

/// Accepts a new incoming connection to this listener.
///
/// This function will block the calling thread until a new Unix connection
Expand Down
37 changes: 37 additions & 0 deletions library/std/src/os/unix/net/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,43 @@ impl UnixStream {
}
}

/// Connects to the socket specified by [`address`].
///
/// [`address`]: crate::os::unix::net::SocketAddr
///
/// # Examples
///
/// ```no_run
/// #![feature(unix_socket_abstract)]
/// use std::os::unix::net::{UnixListener, UnixStream};
///
/// fn main() -> std::io::Result<()> {
/// let listener = UnixListener::bind("/path/to/the/socket")?;
/// let addr = listener.local_addr()?;
///
/// let sock = match UnixStream::connect_addr(&addr) {
/// Ok(sock) => sock,
/// Err(e) => {
/// println!("Couldn't connect: {:?}", e);
/// return Err(e)
/// }
/// };
/// Ok(())
/// }
/// ````
#[unstable(feature = "unix_socket_abstract", issue = "85410")]
pub fn connect_addr(socket_addr: &SocketAddr) -> io::Result<UnixStream> {
unsafe {
let inner = Socket::new_raw(libc::AF_UNIX, libc::SOCK_STREAM)?;
cvt(libc::connect(
inner.as_raw_fd(),
&socket_addr.addr as *const _ as *const _,
socket_addr.len,
))?;
Ok(UnixStream(inner))
}
}

/// Creates an unnamed pair of connected sockets.
///
/// Returns two `UnixStream`s which are connected to each other.
Expand Down

0 comments on commit 6cc0a76

Please sign in to comment.