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::trace;
12
13#[cfg(target_os = "linux")]
14mod ancillary;
15
16#[cfg(target_os = "linux")]
17mod linux;
18#[cfg(target_os = "linux")]
19pub use self::linux::enable_uds_socket_credentials;
20#[cfg(target_os = "linux")]
21use self::linux::uds_recvmsg;
22
23#[cfg(not(target_os = "linux"))]
24mod non_linux;
25#[cfg(not(target_os = "linux"))]
26pub use self::non_linux::enable_uds_socket_credentials;
27#[cfg(not(target_os = "linux"))]
28use self::non_linux::uds_recvmsg;
29use super::addr::ConnectionAddress;
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 is 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(super) 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(super) 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/// Receives data from the Unix domain socket.
85///
86/// This function is specifically for Unix domain sockets in stream mode (i.e. SOCK_STREAM), which are represented via
87/// `UnixStream` in `tokio`.
88///
89/// On success, returns the number of bytes read and the address from whence the data came.
90///
91/// ## Errors
92///
93/// If the underlying system call fails, an error is returned.
94pub async fn unix_recvmsg<B: BufMut>(socket: &mut UnixStream, buf: &mut B) -> io::Result<(usize, ConnectionAddress)> {
95 // TODO: We technically don't need to do this for SOCK_STREAM because we can do it once when the
96 // connection is accepted, and then just do "normal" reads after that. We do still need to do it
97 // for SOCK_DGRAM, though... so we might want to actually consider updating our `socket2` PR to
98 // include support for even setting SO_PASSCRED (or the OS-specific equivalent) to also include
99 // macOS and FreeBSD (*BSD, really) so that this also works correctly for all
100 // `cfg(unix)`-capable platforms.
101
102 // We manually call `recvmsg` on our domain socket as stdlib/`mio`/`tokio` don't yet expose a way to do out-of-band
103 // reads to get ancillary data such as the socket credentials used to shuttle origin detection information.
104 socket
105 .async_io(tokio::io::Interest::READABLE, || uds_recvmsg(socket, buf))
106 .await
107}
108
109/// Receives data from the Unix domain socket.
110///
111/// This function is specifically for Unix domain sockets in datagram mode (i.e. SOCK_DGRAM), which are represented via
112/// `UnixDatagram` in `tokio`.
113///
114/// On success, returns the number of bytes read and the address from whence the data came.
115///
116/// ## Errors
117///
118/// If the underlying system call fails, an error is returned.
119pub async fn unixgram_recvmsg<B: BufMut>(
120 socket: &mut UnixDatagram, buf: &mut B,
121) -> io::Result<(usize, ConnectionAddress)> {
122 // We manually call `recvmsg` on our domain socket as stdlib/`mio`/`tokio` don't yet expose a way to do out-of-band
123 // reads to get ancillary data such as the socket credentials used to shuttle origin detection information.
124 socket
125 .async_io(tokio::io::Interest::READABLE, || uds_recvmsg(socket, buf))
126 .await
127}