Skip to main content

saluki_io/net/unix/
mod.rs

1//! Unix-specific helpers.
2use std::{
3    fs::Permissions,
4    io,
5    os::unix::fs::{FileTypeExt as _, PermissionsExt as _},
6    path::Path,
7};
8
9use bytes::BufMut;
10use tokio::net::{UnixDatagram, UnixStream};
11use tracing::{debug, trace};
12
13#[cfg(target_os = "linux")]
14mod ancillary;
15
16#[cfg(target_os = "linux")]
17mod linux;
18#[cfg(target_os = "linux")]
19use self::linux::uds_recvmsg;
20#[cfg(target_os = "linux")]
21pub use self::linux::{enable_uds_socket_credentials, socket_reuseport_supported, uds_sendmsg_with_creds};
22
23#[cfg(not(target_os = "linux"))]
24mod non_linux;
25#[cfg(not(target_os = "linux"))]
26use self::non_linux::uds_recvmsg;
27#[cfg(not(target_os = "linux"))]
28pub use self::non_linux::{enable_uds_socket_credentials, socket_reuseport_supported};
29use super::addr::{ConnectionAddress, ProcessCredentialsError, ProcessIdentity};
30
31/// Ensures that the given path is read for use as a UNIX socket.
32///
33/// If the path already exists, and is a UNIX socket, it will be removed. If it's not a UNIX socket, an error will be
34/// returned.
35///
36/// ## Errors
37///
38/// If the underlying system call fails, an error is returned.
39pub async fn ensure_unix_socket_free<P: AsRef<Path>>(path: P) -> io::Result<()> {
40    let path = path.as_ref();
41
42    // If the socket file already exists, we need to make sure it's already a UNIX socket, which means we're "clear" to
43    // remove it before trying to claim it again when we create our listener.
44    match tokio::fs::metadata(path).await {
45        Ok(metadata) => {
46            if !metadata.file_type().is_socket() {
47                return Err(io::Error::new(
48                    io::ErrorKind::AlreadyExists,
49                    "path already exists and is not a Unix domain socket",
50                ));
51            }
52
53            tokio::fs::remove_file(path).await?;
54
55            trace!(
56                socket_path = path.to_string_lossy().as_ref(),
57                "Cleared existing Unix domain socket."
58            );
59        }
60        Err(err) => {
61            // If we can't find the file, that's good: nothing to clean up.
62            //
63            // Otherwise, forward the error.
64            if err.kind() != io::ErrorKind::NotFound {
65                return Err(err);
66            }
67        }
68    }
69
70    Ok(())
71}
72
73/// Sets the UNIX socket at the given path to be write-only by non-owners.
74///
75/// This ensures that normal clients can write to the socket but not read from it.
76///
77/// ## Errors
78///
79/// If the underlying system call fails, an error is returned.
80pub async fn set_unix_socket_write_only<P: AsRef<Path>>(path: P) -> io::Result<()> {
81    tokio::fs::set_permissions(path, Permissions::from_mode(0o722)).await
82}
83
84/// Returns the identity of the process on the other end of a connected Unix domain socket.
85///
86/// The kernel captures the peer's credentials when it connects (`SO_PEERCRED` on Linux), so they're fixed for the life of
87/// the connection.
88pub(crate) fn unix_peer_identity(socket: &UnixStream) -> ProcessIdentity {
89    match socket.peer_cred() {
90        Ok(creds) => match creds.pid() {
91            Some(pid) => ProcessIdentity::new(pid, creds.uid(), creds.gid()),
92            None => ProcessIdentity::Error(ProcessCredentialsError::UnsupportedPlatform),
93        },
94        Err(e) => {
95            debug!(error = %e, "Failed to look up peer credentials for Unix domain socket connection.");
96            ProcessIdentity::Error(ProcessCredentialsError::PeerLookupFailed)
97        }
98    }
99}
100
101/// Receives data from the Unix domain socket.
102///
103/// This function is specifically for Unix domain sockets in stream mode (that's, SOCK_STREAM), which are represented via
104/// `UnixStream` in `tokio`.
105///
106/// On success, returns the number of bytes read and the address from whence the data came.
107///
108/// ## Errors
109///
110/// If the underlying system call fails, an error is returned.
111pub async fn unix_recvmsg<B: BufMut>(socket: &mut UnixStream, buf: &mut B) -> io::Result<(usize, ConnectionAddress)> {
112    // TODO: We technically don't need to do this for SOCK_STREAM because we can do it once when the
113    // connection is accepted, and then just do "normal" reads after that. We do still need to do it
114    // for SOCK_DGRAM, though... so we might want to actually consider updating our `socket2` PR to
115    // include support for even setting SO_PASSCRED (or the OS-specific equivalent) to also include
116    // macOS and FreeBSD (*BSD, really) so that this also works correctly for all
117    // `cfg(unix)`-capable platforms.
118
119    // We manually call `recvmsg` on our domain socket as stdlib/`mio`/`tokio` don't yet expose a way to do out-of-band
120    // reads to get ancillary data such as the socket credentials used to shuttle origin detection information.
121    socket
122        .async_io(tokio::io::Interest::READABLE, || uds_recvmsg(socket, buf))
123        .await
124}
125
126/// Receives data from the Unix domain socket.
127///
128/// This function is specifically for Unix domain sockets in datagram mode (that's, SOCK_DGRAM), which are represented via
129/// `UnixDatagram` in `tokio`.
130///
131/// On success, returns the number of bytes read and the address from whence the data came.
132///
133/// ## Errors
134///
135/// If the underlying system call fails, an error is returned.
136pub async fn unixgram_recvmsg<B: BufMut>(socket: &UnixDatagram, buf: &mut B) -> io::Result<(usize, ConnectionAddress)> {
137    // We manually call `recvmsg` on our domain socket as stdlib/`mio`/`tokio` don't yet expose a way to do out-of-band
138    // reads to get ancillary data such as the socket credentials used to shuttle origin detection information.
139    socket
140        .async_io(tokio::io::Interest::READABLE, || uds_recvmsg(socket, buf))
141        .await
142}