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}