Skip to main content

net_io/
local.rs

1//! Local sockets: Unix domain sockets (feature `local`). Windows named pipes and
2//! Windows AF_UNIX arrive in `0.5.0`; until then the cross-platform entry points
3//! return `Unsupported` there.
4
5use std::io::{self, Read, Write};
6use std::net::Shutdown;
7#[cfg(unix)]
8use std::path::{Path, PathBuf};
9#[cfg(unix)]
10use std::sync::{Arc, Mutex, PoisonError};
11#[cfg(unix)]
12use std::time::{Duration, Instant};
13
14#[cfg(unix)]
15use std::os::fd::{AsFd, BorrowedFd, OwnedFd};
16
17#[cfg(unix)]
18use crate::accept::{Accepted, Acceptor};
19#[cfg(unix)]
20use crate::driver::DriverKind;
21use crate::error::NetError;
22#[cfg(unix)]
23use crate::error::{ConfigReason, LimitKind, TimeoutKind};
24use crate::gate::KillHandle;
25#[cfg(unix)]
26use crate::id::{ConnectionId, ListenerId};
27use crate::info::ConnectionInfo;
28#[cfg(unix)]
29use crate::info::{Endpoint, Origin, PeerCredentials, Transport, UnixPath};
30use crate::limits::Limits;
31#[cfg(unix)]
32use crate::link::{ClockRef, Got, Link, WakeSource};
33#[cfg(unix)]
34use crate::opts::LocalOptions;
35#[cfg(unix)]
36use crate::proxy::ProxyMode;
37#[cfg(unix)]
38use crate::session::Phase;
39#[cfg(unix)]
40use crate::sys::{self, Interest, KillSlot, Ready};
41#[cfg(unix)]
42use crate::tcp::{ListenerInfo, TcpConnection};
43
44/// How long `connect` waits for a listener whose backlog is full.
45#[cfg(unix)]
46const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
47
48/// Descriptors received with [`UnixConnection::recv_descriptors`]: up to 64,
49/// owned, close-on-exec. Descriptors not taken are closed when this is dropped
50/// or reused.
51///
52/// ```
53/// let fds = net_io::Descriptors::new();
54/// assert!(fds.is_empty());
55/// ```
56#[cfg(unix)]
57pub struct Descriptors {
58    slots: [Option<OwnedFd>; sys::MAX_FDS],
59    len: usize,
60}
61
62#[cfg(unix)]
63impl Descriptors {
64    /// Room for 64 descriptors.
65    #[must_use]
66    pub fn new() -> Descriptors {
67        Descriptors {
68            slots: [const { None }; sys::MAX_FDS],
69            len: 0,
70        }
71    }
72
73    /// How many descriptors are held.
74    #[must_use]
75    pub fn len(&self) -> usize {
76        self.len
77    }
78
79    /// Whether none are held.
80    #[must_use]
81    pub fn is_empty(&self) -> bool {
82        self.len == 0
83    }
84
85    /// Takes the descriptors, in the order they were sent.
86    pub fn take(&mut self) -> impl Iterator<Item = OwnedFd> + '_ {
87        self.len = 0;
88        self.slots.iter_mut().filter_map(Option::take)
89    }
90
91    fn clear(&mut self) {
92        for slot in &mut self.slots {
93            *slot = None;
94        }
95        self.len = 0;
96    }
97}
98
99#[cfg(unix)]
100impl Default for Descriptors {
101    fn default() -> Descriptors {
102        Descriptors::new()
103    }
104}
105
106#[cfg(unix)]
107impl std::fmt::Debug for Descriptors {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        f.debug_struct("Descriptors")
110            .field("len", &self.len)
111            .finish()
112    }
113}
114
115/// The identity of a file: its device and inode.
116#[cfg(unix)]
117type FileId = (u64, u64);
118
119#[cfg(unix)]
120fn file_id(path: &Path) -> Option<(FileId, bool)> {
121    use std::os::unix::fs::{FileTypeExt, MetadataExt};
122    // lstat: a symbolic link is reported as itself, never followed.
123    let meta = std::fs::symlink_metadata(path).ok()?;
124    Some(((meta.dev(), meta.ino()), meta.file_type().is_socket()))
125}
126
127/// Checks a socket path: no NUL byte, and short enough for `sockaddr_un`.
128#[cfg(unix)]
129fn path_bytes(path: &Path) -> Result<&[u8], NetError> {
130    let bytes = path.as_os_str().as_encoded_bytes();
131    if bytes.is_empty() {
132        return Err(NetError::InvalidConfig {
133            field: "path",
134            reason: ConfigReason::Zero,
135        });
136    }
137    if bytes.contains(&0) {
138        return Err(NetError::InvalidConfig {
139            field: "path",
140            reason: ConfigReason::Malformed,
141        });
142    }
143    if bytes.len() >= sys::UNIX_PATH_MAX {
144        return Err(NetError::PathTooLong {
145            max: sys::UNIX_PATH_MAX,
146        });
147    }
148    Ok(bytes)
149}
150
151#[cfg(unix)]
152fn os_error(op: &'static str, e: io::Error) -> NetError {
153    match e.kind() {
154        io::ErrorKind::PermissionDenied => NetError::PermissionDenied { what: op },
155        io::ErrorKind::NotFound => NetError::NotFound,
156        _ => NetError::Os { op, source: e },
157    }
158}
159
160/// Whether something accepts connections on `path` (a probe connect).
161#[cfg(unix)]
162fn live(path: &[u8]) -> Result<bool, NetError> {
163    let sock = sys::unix_socket().map_err(|e| NetError::from_os("socket", e))?;
164    let probe = std::os::unix::net::UnixStream::from(sock);
165    // A full backlog would make the probe wait (Linux); a second is plenty.
166    let _ = probe.set_write_timeout(Some(Duration::from_secs(1)));
167    match sys::unix_connect(probe.as_fd(), path) {
168        Ok(()) => Ok(true),
169        Err(e) if e.kind() == io::ErrorKind::ConnectionRefused => Ok(false),
170        Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(false),
171        // A full backlog: someone is listening.
172        Err(e)
173            if matches!(
174                e.kind(),
175                io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut
176            ) =>
177        {
178            Ok(true)
179        }
180        Err(e) => Err(os_error("connect", e)),
181    }
182}
183
184/// A Unix domain socket listener. Dropping it removes its socket file, but only
185/// while the path still names the file this listener created.
186///
187/// ```no_run
188/// let listener = net_io::UnixListener::bind("/run/myapp/control.sock")?;
189/// let (mut conn, info) = listener.accept()?;
190/// println!("uid {:?}", info.peer_credentials.and_then(|c| c.uid));
191/// conn.send_all(b"ready\n")?;
192/// # Ok::<(), net_io::NetError>(())
193/// ```
194#[cfg(unix)]
195pub struct UnixListener {
196    inner: std::os::unix::net::UnixListener,
197    path: PathBuf,
198    created: Option<FileId>,
199    /// Set when the socket was released for a hand-off: the file stays.
200    keep: std::sync::atomic::AtomicBool,
201    shared_path: Arc<UnixPath>,
202    info: ListenerInfo,
203    require_uid: Option<u32>,
204    acceptor: Mutex<Acceptor>,
205}
206
207#[cfg(unix)]
208impl UnixListener {
209    /// Binds `path` with [`LocalOptions::new`]: mode `0600`, a stale socket file removed.
210    ///
211    /// # Errors
212    ///
213    /// `PathTooLong`, `StaleSocketInUse`, `PermissionDenied`, `NotFound` (no such
214    /// directory), `InvalidConfig { field: "path" }`, `Os`.
215    pub fn bind<P: AsRef<Path>>(path: P) -> Result<UnixListener, NetError> {
216        UnixListener::bind_with(path, LocalOptions::new())
217    }
218
219    /// Binds `path` with `options`: the socket is bound, given its mode and owner,
220    /// and only then listens, so no connection is accepted under the umask's
221    /// permissions.
222    ///
223    /// # Errors
224    ///
225    /// As [`UnixListener::bind`].
226    pub fn bind_with<P: AsRef<Path>>(
227        path: P,
228        options: LocalOptions,
229    ) -> Result<UnixListener, NetError> {
230        UnixListener::create(path.as_ref(), &options, false)
231    }
232
233    /// As [`UnixListener::bind_with`], but bound under a temporary name in the
234    /// same directory and renamed into place once it listens with its final
235    /// mode: the path never names a socket that refuses or is not ready.
236    ///
237    /// # Errors
238    ///
239    /// As [`UnixListener::bind`].
240    pub fn bind_atomic<P: AsRef<Path>>(
241        path: P,
242        options: LocalOptions,
243    ) -> Result<UnixListener, NetError> {
244        UnixListener::create(path.as_ref(), &options, true)
245    }
246
247    fn create(path: &Path, options: &LocalOptions, atomic: bool) -> Result<UnixListener, NetError> {
248        let bytes = path_bytes(path)?;
249        let shared_path = Arc::new(UnixPath::new(bytes).ok_or(NetError::PathTooLong {
250            max: sys::UNIX_PATH_MAX,
251        })?);
252        // An existing file: a live listener's socket, a stale one, or not a socket.
253        if let Some((_, is_socket)) = file_id(path) {
254            let stale = is_socket && !live(bytes)?;
255            if !stale {
256                return Err(NetError::StaleSocketInUse {
257                    path: path.to_path_buf(),
258                });
259            }
260            if !options.remove_stale {
261                return Err(NetError::StaleSocketInUse {
262                    path: path.to_path_buf(),
263                });
264            }
265            if !atomic {
266                // The rename of `bind_atomic` replaces it in one step instead.
267                std::fs::remove_file(path).map_err(|e| os_error("unlink", e))?;
268            }
269        }
270        let target = if atomic {
271            temporary_name(path)?
272        } else {
273            path.to_path_buf()
274        };
275        let target_bytes = path_bytes(&target)?;
276        let sock = sys::unix_socket().map_err(|e| NetError::from_os("socket", e))?;
277        if let Err(e) = sys::unix_bind(sock.as_fd(), target_bytes) {
278            return Err(match e.kind() {
279                io::ErrorKind::AddrInUse => NetError::StaleSocketInUse {
280                    path: path.to_path_buf(),
281                },
282                _ => os_error("bind", e),
283            });
284        }
285        let result = (|| {
286            let created = match file_id(&target) {
287                Some((id, true)) => id,
288                _ => return Err(replaced(&target)),
289            };
290            set_access(&target, options)?;
291            // The mode and owner were set on the file this socket created.
292            if file_id(&target) != Some((created, true)) {
293                return Err(replaced(&target));
294            }
295            sys::unix_listen(sock.as_fd(), options.backlog)
296                .map_err(|e| NetError::from_os("listen", e))?;
297            if atomic {
298                std::fs::rename(&target, path).map_err(|e| os_error("rename", e))?;
299            }
300            Ok(created)
301        })();
302        let created = match result {
303            Ok(id) => id,
304            Err(e) => {
305                if file_id(&target).is_some_and(|(_, sock)| sock) {
306                    let _ = std::fs::remove_file(&target);
307                }
308                return Err(e);
309            }
310        };
311        let inner = std::os::unix::net::UnixListener::from(sock);
312        // Accepts wait in poll, like TCP listeners.
313        inner
314            .set_nonblocking(true)
315            .map_err(|e| NetError::from_os("ioctl", e))?;
316        Ok(UnixListener {
317            inner,
318            path: path.to_path_buf(),
319            created: Some(created),
320            keep: std::sync::atomic::AtomicBool::new(false),
321            info: ListenerInfo {
322                id: ListenerId(0),
323                name: None,
324                local: Endpoint::Unix(shared_path.clone()),
325                backlog: if cfg!(target_os = "linux") {
326                    crate::tcp::effective_backlog(options.backlog)
327                } else {
328                    None
329                },
330                transport: Transport::Unix,
331                proxy: ProxyMode::Off,
332                tls: false,
333            },
334            shared_path,
335            require_uid: options.require_uid,
336            acceptor: Mutex::new(Acceptor::new()),
337        })
338    }
339
340    /// Blocks until a connection arrives. Peer credentials are read once and
341    /// stored in `ConnectionInfo`; with `require_uid`, other users' connections
342    /// are closed before any read and accepting continues.
343    ///
344    /// # Errors
345    ///
346    /// `Limit { which: Descriptors }` when exhaustion persists past one backoff step; `Os`.
347    pub fn accept(&self) -> Result<(UnixConnection, ConnectionInfo), NetError> {
348        match self.accept_until(None)? {
349            Some(pair) => Ok(pair),
350            None => Err(NetError::TimedOut {
351                kind: TimeoutKind::Read,
352            }),
353        }
354    }
355
356    /// As [`UnixListener::accept`], but `Ok(None)` when nothing arrived within `timeout`.
357    ///
358    /// # Errors
359    ///
360    /// As [`UnixListener::accept`].
361    pub fn accept_timeout(
362        &self,
363        timeout: Duration,
364    ) -> Result<Option<(UnixConnection, ConnectionInfo)>, NetError> {
365        self.accept_until(Some(Instant::now() + timeout))
366    }
367
368    fn accept_until(
369        &self,
370        deadline: Option<Instant>,
371    ) -> Result<Option<(UnixConnection, ConnectionInfo)>, NetError> {
372        let mut paused_once = false;
373        loop {
374            let outcome = self
375                .acceptor
376                .lock()
377                .unwrap_or_else(PoisonError::into_inner)
378                .accept_unix(&self.inner);
379            match outcome {
380                Accepted::Conn(stream) => {
381                    let credentials = credentials_of(&stream);
382                    if !self.permits(credentials) {
383                        continue;
384                    }
385                    let info = ConnectionInfo::new_unix(
386                        crate::id::issue_single(),
387                        Some(self.info.id),
388                        Origin::Accepted,
389                        Endpoint::Unix(self.shared_path.clone()),
390                        Endpoint::Unnamed,
391                        credentials,
392                        Instant::now(),
393                    );
394                    let conn = UnixConnection::from_stream(stream, info.clone(), Phase::Idle);
395                    return Ok(Some((conn, info)));
396                }
397                Accepted::Skipped(_) => continue,
398                Accepted::Again => {
399                    let timeout = match deadline {
400                        None => None,
401                        Some(d) => {
402                            let left = d.saturating_duration_since(Instant::now());
403                            if left.is_zero() {
404                                return Ok(None);
405                            }
406                            Some(left)
407                        }
408                    };
409                    match sys::wait(self.inner.as_fd(), Interest::Read, None, timeout) {
410                        Ok(Ready::TimedOut) => return Ok(None),
411                        Ok(_) => continue,
412                        Err(e) => return Err(NetError::from_os("poll", e)),
413                    }
414                }
415                Accepted::Pause(backoff) => {
416                    if paused_once {
417                        return Err(NetError::Limit {
418                            which: LimitKind::Descriptors,
419                        });
420                    }
421                    paused_once = true;
422                    std::thread::sleep(backoff);
423                }
424                Accepted::Failed(e) => return Err(NetError::from_os("accept", e)),
425            }
426        }
427    }
428
429    /// A listening Unix socket this process was given (inherited, received in a
430    /// hand-off). Its file is never removed by this listener.
431    pub(crate) fn adopt(sock: OwnedFd) -> Result<UnixListener, NetError> {
432        let inner = std::os::unix::net::UnixListener::from(sock);
433        inner
434            .set_nonblocking(true)
435            .map_err(|e| NetError::from_os("ioctl", e))?;
436        let path = inner
437            .local_addr()
438            .ok()
439            .and_then(|a| a.as_pathname().map(Path::to_path_buf))
440            .unwrap_or_default();
441        let shared_path = Arc::new(UnixPath::new(path.as_os_str().as_encoded_bytes()).ok_or(
442            NetError::PathTooLong {
443                max: sys::UNIX_PATH_MAX,
444            },
445        )?);
446        let local = if path.as_os_str().is_empty() {
447            Endpoint::Unnamed
448        } else {
449            Endpoint::Unix(shared_path.clone())
450        };
451        Ok(UnixListener {
452            inner,
453            path,
454            created: None,
455            keep: std::sync::atomic::AtomicBool::new(true),
456            info: ListenerInfo {
457                id: ListenerId(0),
458                name: None,
459                local,
460                backlog: None,
461                transport: Transport::Unix,
462                proxy: ProxyMode::Off,
463                tls: false,
464            },
465            shared_path,
466            require_uid: None,
467            acceptor: Mutex::new(Acceptor::new()),
468        })
469    }
470
471    /// Leaves the socket file in place when this listener is dropped.
472    pub(crate) fn keep_file(&self) {
473        self.keep.store(true, std::sync::atomic::Ordering::Release);
474    }
475
476    /// Whether a peer with these credentials may stay (`require_uid`).
477    pub(crate) fn permits(&self, credentials: Option<PeerCredentials>) -> bool {
478        match self.require_uid {
479            None => true,
480            Some(uid) => credentials.and_then(|c| c.uid) == Some(uid),
481        }
482    }
483
484    pub(crate) fn std(&self) -> &std::os::unix::net::UnixListener {
485        &self.inner
486    }
487
488    pub(crate) fn shared_path(&self) -> &Arc<UnixPath> {
489        &self.shared_path
490    }
491
492    pub(crate) fn set_identity(&mut self, id: ListenerId, name: Option<String>) {
493        self.info.id = id;
494        self.info.name = name;
495    }
496
497    pub(crate) fn set_proxy_mode(&mut self, mode: ProxyMode) {
498        self.info.proxy = mode;
499    }
500
501    pub(crate) fn set_tls(&mut self, on: bool) {
502        self.info.tls = on;
503    }
504
505    /// Facts about this listener.
506    #[must_use]
507    pub fn info(&self) -> &ListenerInfo {
508        &self.info
509    }
510
511    /// The path this listener was bound to (recorded at bind, not read back:
512    /// decision D-058); for an adopted socket, the path the OS reports.
513    #[must_use]
514    pub fn path(&self) -> Option<&Path> {
515        (!self.path.as_os_str().is_empty()).then_some(self.path.as_path())
516    }
517}
518
519#[cfg(unix)]
520impl Drop for UnixListener {
521    /// Removes the socket file only if the path still names the file this
522    /// listener created, never a replacement someone else bound.
523    fn drop(&mut self) {
524        if self.keep.load(std::sync::atomic::Ordering::Acquire) {
525            return;
526        }
527        if let Some(created) = self.created {
528            if file_id(&self.path) == Some((created, true)) {
529                let _ = std::fs::remove_file(&self.path);
530            }
531        }
532    }
533}
534
535#[cfg(unix)]
536impl AsFd for UnixListener {
537    fn as_fd(&self) -> BorrowedFd<'_> {
538        self.inner.as_fd()
539    }
540}
541
542#[cfg(unix)]
543impl std::fmt::Debug for UnixListener {
544    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
545        f.debug_struct("UnixListener")
546            .field("path", &self.path)
547            .field("info", &self.info)
548            .finish()
549    }
550}
551
552#[cfg(unix)]
553fn replaced(path: &Path) -> NetError {
554    NetError::StaleSocketInUse {
555        path: path.to_path_buf(),
556    }
557}
558
559/// Sets the socket file's mode, then its owner and group (`lchown`: a symbolic
560/// link put in its place is never followed).
561#[cfg(unix)]
562fn set_access(path: &Path, options: &LocalOptions) -> Result<(), NetError> {
563    use std::os::unix::fs::PermissionsExt;
564    std::fs::set_permissions(path, std::fs::Permissions::from_mode(options.mode))
565        .map_err(|e| os_error("chmod", e))?;
566    if options.owner.is_some() || options.group.is_some() {
567        std::os::unix::fs::lchown(path, options.owner, options.group)
568            .map_err(|e| os_error("chown", e))?;
569    }
570    Ok(())
571}
572
573/// A temporary name next to `path`: `.<name>.<pid>.<n>`.
574#[cfg(unix)]
575fn temporary_name(path: &Path) -> Result<PathBuf, NetError> {
576    use std::sync::atomic::{AtomicU32, Ordering};
577    static NEXT: AtomicU32 = AtomicU32::new(0);
578    let file = path.file_name().ok_or(NetError::InvalidConfig {
579        field: "path",
580        reason: ConfigReason::Malformed,
581    })?;
582    let mut name = std::ffi::OsString::from(".");
583    name.push(file);
584    name.push(format!(
585        ".{}.{}",
586        std::process::id(),
587        NEXT.fetch_add(1, Ordering::Relaxed)
588    ));
589    Ok(path.with_file_name(name))
590}
591
592#[cfg(unix)]
593pub(crate) fn credentials_of(stream: &std::os::unix::net::UnixStream) -> Option<PeerCredentials> {
594    sys::peer_credentials(stream.as_fd())
595        .ok()
596        .map(|(pid, uid, gid)| PeerCredentials {
597            pid,
598            uid: Some(uid),
599            gid: Some(gid),
600        })
601}
602
603/// A connection on a Unix domain socket, with the same limits, deadlines,
604/// and close behaviour as [`TcpConnection`].
605///
606/// ```no_run
607/// let mut conn = net_io::UnixConnection::connect("/run/myapp/control.sock")?;
608/// conn.send_all(b"status\n")?;
609/// # Ok::<(), net_io::NetError>(())
610/// ```
611#[cfg(unix)]
612pub struct UnixConnection {
613    inner: TcpConnection,
614}
615
616#[cfg(unix)]
617impl UnixConnection {
618    /// Connects to the socket at `path`; waits up to 10 s when its backlog is full.
619    ///
620    /// # Errors
621    ///
622    /// `NotFound` (no socket, or nobody listening), `PermissionDenied`,
623    /// `TimedOut { kind: Connect }`, `PathTooLong`, `InvalidConfig { field: "path" }`, `Os`.
624    pub fn connect<P: AsRef<Path>>(path: P) -> Result<UnixConnection, NetError> {
625        let path = path.as_ref();
626        let bytes = path_bytes(path)?;
627        let shared = Arc::new(UnixPath::new(bytes).ok_or(NetError::PathTooLong {
628            max: sys::UNIX_PATH_MAX,
629        })?);
630        let sock = sys::unix_socket().map_err(|e| NetError::from_os("socket", e))?;
631        let stream = std::os::unix::net::UnixStream::from(sock);
632        stream
633            .set_write_timeout(Some(CONNECT_TIMEOUT))
634            .map_err(|e| NetError::from_os("setsockopt", e))?;
635        if let Err(e) = sys::unix_connect(stream.as_fd(), bytes) {
636            return Err(match e.kind() {
637                io::ErrorKind::NotFound | io::ErrorKind::ConnectionRefused => NetError::NotFound,
638                io::ErrorKind::WouldBlock | io::ErrorKind::TimedOut => NetError::TimedOut {
639                    kind: TimeoutKind::Connect,
640                },
641                _ => os_error("connect", e),
642            });
643        }
644        stream
645            .set_write_timeout(None)
646            .map_err(|e| NetError::from_os("setsockopt", e))?;
647        if sys::WAIT_BEFORE_RECV {
648            stream
649                .set_nonblocking(true)
650                .map_err(|e| NetError::from_os("ioctl", e))?;
651        }
652        let info = ConnectionInfo::new_unix(
653            crate::id::issue_single(),
654            None,
655            Origin::Dialed,
656            Endpoint::Unnamed,
657            Endpoint::Unix(shared),
658            credentials_of(&stream),
659            Instant::now(),
660        );
661        Ok(UnixConnection::from_stream(stream, info, Phase::Receiving))
662    }
663
664    fn from_stream(
665        stream: std::os::unix::net::UnixStream,
666        info: ConnectionInfo,
667        phase: Phase,
668    ) -> UnixConnection {
669        let limits = Limits::default().effective(DriverKind::Blocking);
670        UnixConnection {
671            inner: TcpConnection::from_link(Link::new(
672                sys::Stream::Unix(stream),
673                info,
674                limits,
675                ClockRef::Monotonic,
676                Arc::new(KillSlot::new()),
677                WakeSource::Thread,
678                phase,
679            )),
680        }
681    }
682
683    /// One receive; `Ok(0)` means the peer closed its side.
684    ///
685    /// # Errors
686    ///
687    /// As [`TcpConnection::recv`].
688    pub fn recv(&mut self, buf: &mut [u8]) -> Result<usize, NetError> {
689        self.inner.recv(buf)
690    }
691
692    /// Receives until `buf` is full.
693    ///
694    /// # Errors
695    ///
696    /// As [`TcpConnection::recv_exact`].
697    pub fn recv_exact(&mut self, buf: &mut [u8]) -> Result<(), NetError> {
698        self.inner.recv_exact(buf)
699    }
700
701    /// One send after the gate check; may be partial.
702    ///
703    /// # Errors
704    ///
705    /// As [`TcpConnection::send`].
706    pub fn send(&mut self, buf: &[u8]) -> Result<usize, NetError> {
707        self.inner.send(buf)
708    }
709
710    /// Sends all of `buf`.
711    ///
712    /// # Errors
713    ///
714    /// As [`TcpConnection::send_all`].
715    pub fn send_all(&mut self, buf: &[u8]) -> Result<(), NetError> {
716        self.inner.send_all(buf)
717    }
718
719    /// Half-close.
720    ///
721    /// # Errors
722    ///
723    /// `Closed`, `Os`.
724    pub fn shutdown(&mut self, how: Shutdown) -> Result<(), NetError> {
725        self.inner.shutdown(how)
726    }
727
728    /// Graceful close, as [`TcpConnection::close`].
729    ///
730    /// # Errors
731    ///
732    /// None in this version.
733    pub fn close(self) -> Result<(), NetError> {
734        self.inner.close()
735    }
736
737    /// Closes at once without the graceful linger.
738    pub fn abort(self) {
739        self.inner.abort();
740    }
741
742    fn close_in_place(&mut self) -> Result<(), NetError> {
743        match self.inner.take_link() {
744            Some(link) => TcpConnection::from_link(link).close(),
745            None => Ok(()),
746        }
747    }
748
749    fn abort_in_place(&mut self) {
750        if let Some(link) = self.inner.take_link() {
751            TcpConnection::from_link(link).abort();
752        }
753    }
754
755    /// What net-io knows about this connection.
756    #[must_use]
757    pub fn info(&self) -> &ConnectionInfo {
758        self.inner.info()
759    }
760
761    /// The connection's id.
762    #[must_use]
763    pub fn id(&self) -> ConnectionId {
764        self.inner.id()
765    }
766
767    /// A handle other threads can use to abort this connection.
768    #[must_use]
769    pub fn kill_handle(&self) -> KillHandle {
770        self.inner.kill_handle()
771    }
772
773    /// Replaces the limits from the next operation on.
774    pub fn set_limits(&mut self, limits: Limits) {
775        self.inner.set_limits(limits);
776    }
777
778    /// Chooses which deadlines apply.
779    pub fn set_phase(&mut self, phase: Phase) {
780        self.inner.set_phase(phase);
781    }
782
783    /// The deadline for the current exchange (`None` clears it).
784    pub fn set_exchange_deadline(&mut self, at: Option<Instant>) {
785        self.inner.set_exchange_deadline(at);
786    }
787
788    /// The peer's process, user, and group, as read when the connection was made.
789    ///
790    /// # Errors
791    ///
792    /// `Unsupported` when the OS did not report them.
793    pub fn peer_credentials(&self) -> Result<PeerCredentials, NetError> {
794        self.info().peer_credentials.ok_or(NetError::Unsupported {
795            feature: "peer credentials",
796            platform: std::env::consts::OS,
797        })
798    }
799
800    /// Sends all of `body` (not empty) with up to 64 descriptors attached to its
801    /// first byte (`SCM_RIGHTS`). The descriptors stay open here; the receiver
802    /// gets its own copies.
803    ///
804    /// # Errors
805    ///
806    /// `InvalidConfig` (empty body, more than 64 descriptors), as [`UnixConnection::send`].
807    pub fn send_descriptors(
808        &mut self,
809        body: &[u8],
810        descriptors: &[BorrowedFd<'_>],
811    ) -> Result<(), NetError> {
812        self.inner.link()?.write_with_fds(body, descriptors)
813    }
814
815    /// One receive that also takes descriptors sent with the bytes, into `into`
816    /// (descriptors it still holds are closed first). `Ok(0)` means the peer
817    /// closed its side.
818    ///
819    /// # Errors
820    ///
821    /// `Handoff(Truncated)` when the control message was cut short: every
822    /// descriptor of it was closed and the connection fails, since the bytes
823    /// can no longer be framed; as [`UnixConnection::recv`].
824    pub fn recv_descriptors(
825        &mut self,
826        body: &mut [u8],
827        into: &mut Descriptors,
828    ) -> Result<usize, NetError> {
829        into.clear();
830        let link = self.inner.link()?;
831        loop {
832            match link.read_with_fds(body, &mut into.slots)? {
833                (Got::Data(n), fds) => {
834                    into.len = fds;
835                    return Ok(n);
836                }
837                (Got::Eof, _) => return Ok(0),
838                (Got::Woken, _) => continue,
839            }
840        }
841    }
842}
843
844#[cfg(unix)]
845impl Read for UnixConnection {
846    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
847        self.inner.read(buf)
848    }
849}
850
851#[cfg(unix)]
852impl Write for UnixConnection {
853    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
854        self.inner.write(buf)
855    }
856
857    fn flush(&mut self) -> io::Result<()> {
858        Ok(())
859    }
860}
861
862#[cfg(unix)]
863impl AsFd for UnixConnection {
864    fn as_fd(&self) -> BorrowedFd<'_> {
865        AsFd::as_fd(&self.inner.link_ref().stream)
866    }
867}
868
869#[cfg(unix)]
870impl std::fmt::Debug for UnixConnection {
871    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
872        f.debug_struct("UnixConnection")
873            .field("info", self.inner.info())
874            .finish()
875    }
876}
877
878/// Runs `$body` with the value inside a `Local*` enum. Before named pipes
879/// (`0.5.0`) the enums have no variant on Windows, and nothing can run.
880#[cfg(unix)]
881macro_rules! inside {
882    ($kind:ident, $this:expr, $v:ident => $body:expr) => {
883        match $this {
884            $kind::Unix($v) => $body,
885        }
886    };
887}
888
889#[cfg(not(unix))]
890macro_rules! inside {
891    ($kind:ident, $this:expr, $v:ident => $body:expr) => {
892        match *$this {}
893    };
894}
895
896/// A local IPC listener: a Unix domain socket (a Windows named pipe from `0.5.0`).
897#[non_exhaustive]
898#[derive(Debug)]
899pub enum LocalListener {
900    /// A Unix domain socket.
901    #[cfg(unix)]
902    Unix(UnixListener),
903}
904
905impl LocalListener {
906    /// Blocks until a connection arrives.
907    ///
908    /// # Errors
909    ///
910    /// As `UnixListener::accept`.
911    pub fn accept(&self) -> Result<(LocalConnection, ConnectionInfo), NetError> {
912        inside!(LocalListener, self, l => l
913            .accept()
914            .map(|(c, info)| (LocalConnection::Unix(c), info)))
915    }
916}
917
918/// A local IPC connection: a Unix domain socket (a Windows named pipe from `0.5.0`).
919#[non_exhaustive]
920#[derive(Debug)]
921pub enum LocalConnection {
922    /// A Unix domain socket.
923    #[cfg(unix)]
924    Unix(UnixConnection),
925}
926
927#[cfg_attr(not(unix), allow(unused_variables))]
928impl LocalConnection {
929    /// One receive; `Ok(0)` means the peer closed its side.
930    ///
931    /// # Errors
932    ///
933    /// As [`TcpConnection::recv`](crate::TcpConnection::recv).
934    pub fn recv(&mut self, buf: &mut [u8]) -> Result<usize, NetError> {
935        inside!(LocalConnection, self, c => c.recv(buf))
936    }
937
938    /// Receives until `buf` is full.
939    ///
940    /// # Errors
941    ///
942    /// As [`TcpConnection::recv_exact`](crate::TcpConnection::recv_exact).
943    pub fn recv_exact(&mut self, buf: &mut [u8]) -> Result<(), NetError> {
944        inside!(LocalConnection, self, c => c.recv_exact(buf))
945    }
946
947    /// One send; may be partial.
948    ///
949    /// # Errors
950    ///
951    /// As [`TcpConnection::send`](crate::TcpConnection::send).
952    pub fn send(&mut self, buf: &[u8]) -> Result<usize, NetError> {
953        inside!(LocalConnection, self, c => c.send(buf))
954    }
955
956    /// Sends all of `buf`.
957    ///
958    /// # Errors
959    ///
960    /// As [`TcpConnection::send_all`](crate::TcpConnection::send_all).
961    pub fn send_all(&mut self, buf: &[u8]) -> Result<(), NetError> {
962        inside!(LocalConnection, self, c => c.send_all(buf))
963    }
964
965    /// Half-close.
966    ///
967    /// # Errors
968    ///
969    /// `Closed`, `Os`.
970    pub fn shutdown(&mut self, how: Shutdown) -> Result<(), NetError> {
971        inside!(LocalConnection, self, c => c.shutdown(how))
972    }
973
974    /// Graceful close.
975    ///
976    /// # Errors
977    ///
978    /// None in this version.
979    pub fn close(mut self) -> Result<(), NetError> {
980        let this = &mut self;
981        inside!(LocalConnection, this, c => c.close_in_place())
982    }
983
984    /// Closes at once.
985    pub fn abort(mut self) {
986        let this = &mut self;
987        inside!(LocalConnection, this, c => c.abort_in_place());
988    }
989
990    /// What net-io knows about this connection.
991    #[must_use]
992    pub fn info(&self) -> &ConnectionInfo {
993        inside!(LocalConnection, self, c => c.info())
994    }
995
996    /// A handle other threads can use to abort this connection.
997    #[must_use]
998    pub fn kill_handle(&self) -> KillHandle {
999        inside!(LocalConnection, self, c => c.kill_handle())
1000    }
1001
1002    /// Replaces the limits from the next operation on.
1003    pub fn set_limits(&mut self, limits: Limits) {
1004        inside!(LocalConnection, self, c => c.set_limits(limits));
1005    }
1006}
1007
1008impl Read for LocalConnection {
1009    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
1010        self.recv(buf).map_err(io::Error::from)
1011    }
1012}
1013
1014impl Write for LocalConnection {
1015    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
1016        self.send(buf).map_err(io::Error::from)
1017    }
1018
1019    fn flush(&mut self) -> io::Result<()> {
1020        Ok(())
1021    }
1022}
1023
1024/// Unsupported until named pipes arrive (`0.5.0`).
1025#[cfg(not(unix))]
1026fn unsupported() -> NetError {
1027    NetError::Unsupported {
1028        feature: "local sockets (Windows named pipes arrive in 0.5.0)",
1029        platform: std::env::consts::OS,
1030    }
1031}
1032
1033/// Opens a local IPC listener (see [`crate::listen_local`]).
1034pub(crate) fn listen(name: &str) -> Result<LocalListener, NetError> {
1035    #[cfg(unix)]
1036    {
1037        UnixListener::bind(name).map(LocalListener::Unix)
1038    }
1039    #[cfg(not(unix))]
1040    {
1041        let _ = name;
1042        Err(unsupported())
1043    }
1044}
1045
1046/// Connects to a local IPC listener (see [`crate::connect_local`]).
1047pub(crate) fn connect(name: &str) -> Result<LocalConnection, NetError> {
1048    #[cfg(unix)]
1049    {
1050        UnixConnection::connect(name).map(LocalConnection::Unix)
1051    }
1052    #[cfg(not(unix))]
1053    {
1054        let _ = name;
1055        Err(unsupported())
1056    }
1057}
1058
1059#[cfg(all(test, unix))]
1060mod tests {
1061    use super::*;
1062    use std::os::unix::fs::PermissionsExt;
1063
1064    /// A fresh private directory under the temp dir.
1065    fn dir(tag: &str) -> PathBuf {
1066        let d = std::env::temp_dir().join(format!("nio-{}-{tag}", std::process::id()));
1067        let _ = std::fs::remove_dir_all(&d);
1068        std::fs::create_dir(&d).unwrap();
1069        d
1070    }
1071
1072    #[test]
1073    fn test_bind_accept_echo_and_credentials() {
1074        let d = dir("echo");
1075        let path = d.join("s.sock");
1076        let l = UnixListener::bind(&path).unwrap();
1077        let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
1078        assert_eq!(mode, 0o600);
1079        assert_eq!(l.path(), Some(path.as_path()));
1080        let p2 = path.clone();
1081        let t = std::thread::spawn(move || {
1082            let mut c = UnixConnection::connect(&p2).unwrap();
1083            assert_eq!(c.info().transport, Transport::Unix);
1084            assert_eq!(c.info().origin, Origin::Dialed);
1085            c.send_all(b"ping").unwrap();
1086            let mut b = [0u8; 4];
1087            c.recv_exact(&mut b).unwrap();
1088            assert_eq!(&b, b"ping");
1089            c.peer_credentials().unwrap()
1090        });
1091        let (mut c, info) = l.accept().unwrap();
1092        assert_eq!(info.transport, Transport::Unix);
1093        assert_eq!(info.peer, Endpoint::Unnamed);
1094        let creds = info.peer_credentials.unwrap();
1095        assert_eq!(creds.pid, Some(std::process::id()));
1096        let mut b = [0u8; 4];
1097        c.recv_exact(&mut b).unwrap();
1098        c.send_all(&b).unwrap();
1099        let theirs = t.join().unwrap();
1100        assert_eq!(theirs.uid, creds.uid);
1101        assert_eq!(c.recv(&mut b).unwrap(), 0);
1102        drop(l);
1103        assert!(!path.exists(), "the listener removed its file");
1104        let _ = std::fs::remove_dir_all(&d);
1105    }
1106
1107    #[test]
1108    fn test_stale_socket_removed_live_one_refused() {
1109        let d = dir("stale");
1110        let path = d.join("s.sock");
1111        // A stale socket file: bound, never listening, closed.
1112        {
1113            let s = sys::unix_socket().unwrap();
1114            sys::unix_bind(s.as_fd(), path.as_os_str().as_encoded_bytes()).unwrap();
1115        }
1116        assert!(path.exists());
1117        let refused = UnixListener::bind_with(&path, LocalOptions::new().remove_stale(false));
1118        assert!(matches!(refused, Err(NetError::StaleSocketInUse { .. })));
1119        let l = UnixListener::bind(&path).unwrap();
1120        // A live listener owns the path.
1121        assert!(matches!(
1122            UnixListener::bind(&path),
1123            Err(NetError::StaleSocketInUse { .. })
1124        ));
1125        drop(l);
1126        // Not a socket: never removed.
1127        std::fs::write(&path, b"data").unwrap();
1128        assert!(matches!(
1129            UnixListener::bind(&path),
1130            Err(NetError::StaleSocketInUse { .. })
1131        ));
1132        assert_eq!(std::fs::read(&path).unwrap(), b"data");
1133        let _ = std::fs::remove_dir_all(&d);
1134    }
1135
1136    /// Edge cases 76 and 80: a path exactly at the maximum binds; a directory
1137    /// or a symbolic link at the path is never removed.
1138    #[test]
1139    fn test_longest_path_binds_and_non_sockets_stay() {
1140        let d = dir("max");
1141        let base = d.as_os_str().len() + 1;
1142        let name = "a".repeat(sys::UNIX_PATH_MAX - 1 - base);
1143        let longest = d.join(&name);
1144        assert_eq!(longest.as_os_str().len(), sys::UNIX_PATH_MAX - 1);
1145        let l = UnixListener::bind(&longest).unwrap();
1146        let _c = UnixConnection::connect(&longest).unwrap();
1147        drop(l);
1148        let too_long = d.join(format!("{name}a"));
1149        assert!(matches!(
1150            UnixListener::bind(&too_long),
1151            Err(NetError::PathTooLong { .. })
1152        ));
1153        let sub = d.join("sub");
1154        std::fs::create_dir(&sub).unwrap();
1155        assert!(matches!(
1156            UnixListener::bind(&sub),
1157            Err(NetError::StaleSocketInUse { .. })
1158        ));
1159        assert!(sub.is_dir());
1160        let target = d.join("t.sock");
1161        let live = UnixListener::bind(&target).unwrap();
1162        let link = d.join("link.sock");
1163        std::os::unix::fs::symlink(&target, &link).unwrap();
1164        assert!(matches!(
1165            UnixListener::bind(&link),
1166            Err(NetError::StaleSocketInUse { .. })
1167        ));
1168        assert!(
1169            std::fs::symlink_metadata(&link)
1170                .unwrap()
1171                .file_type()
1172                .is_symlink()
1173        );
1174        drop(live);
1175        let _ = std::fs::remove_dir_all(&d);
1176    }
1177
1178    /// Edge case 84: credentials the OS did not report never satisfy `require_uid`.
1179    #[test]
1180    fn test_unknown_credentials_fail_require_uid() {
1181        let d = dir("nocreds");
1182        let path = d.join("s.sock");
1183        let l = UnixListener::bind_with(&path, LocalOptions::new().require_uid(0)).unwrap();
1184        assert!(!l.permits(None));
1185        let no_uid = PeerCredentials {
1186            pid: Some(1),
1187            uid: None,
1188            gid: None,
1189        };
1190        assert!(!l.permits(Some(no_uid)));
1191        let root = PeerCredentials {
1192            pid: None,
1193            uid: Some(0),
1194            gid: None,
1195        };
1196        assert!(l.permits(Some(root)));
1197        let open = UnixListener::bind(d.join("o.sock")).unwrap();
1198        assert!(open.permits(None));
1199        let _ = std::fs::remove_dir_all(&d);
1200    }
1201
1202    #[test]
1203    fn test_drop_leaves_a_replacement_alone() {
1204        let d = dir("replace");
1205        let path = d.join("s.sock");
1206        let l = UnixListener::bind(&path).unwrap();
1207        std::fs::remove_file(&path).unwrap();
1208        let other = UnixListener::bind(&path).unwrap();
1209        drop(l);
1210        assert!(
1211            path.exists(),
1212            "the first listener did not remove the second's file"
1213        );
1214        drop(other);
1215        assert!(!path.exists());
1216        let _ = std::fs::remove_dir_all(&d);
1217    }
1218
1219    #[test]
1220    fn test_bind_atomic_sets_mode_and_leaves_no_temporary() {
1221        let d = dir("atomic");
1222        let path = d.join("s.sock");
1223        let l = UnixListener::bind_atomic(&path, LocalOptions::new().mode(0o640)).unwrap();
1224        let mode = std::fs::metadata(&path).unwrap().permissions().mode() & 0o777;
1225        assert_eq!(mode, 0o640);
1226        let names: Vec<_> = std::fs::read_dir(&d)
1227            .unwrap()
1228            .map(|e| e.unwrap().file_name())
1229            .collect();
1230        assert_eq!(names.len(), 1, "{names:?}");
1231        let c = UnixConnection::connect(&path).unwrap();
1232        let (_s, _) = l.accept().unwrap();
1233        drop(c);
1234        let _ = std::fs::remove_dir_all(&d);
1235    }
1236
1237    #[test]
1238    fn test_path_checks() {
1239        let long = format!("/tmp/{}", "a".repeat(sys::UNIX_PATH_MAX));
1240        assert!(matches!(
1241            UnixListener::bind(&long),
1242            Err(NetError::PathTooLong { max }) if max == sys::UNIX_PATH_MAX
1243        ));
1244        assert!(matches!(
1245            UnixConnection::connect(&long),
1246            Err(NetError::PathTooLong { .. })
1247        ));
1248        let nul = <std::ffi::OsStr as std::os::unix::ffi::OsStrExt>::from_bytes(b"/tmp/a\0b");
1249        assert!(matches!(
1250            UnixListener::bind(nul),
1251            Err(NetError::InvalidConfig {
1252                field: "path",
1253                reason: ConfigReason::Malformed
1254            })
1255        ));
1256        let d = dir("missing");
1257        assert!(matches!(
1258            UnixConnection::connect(d.join("none.sock")),
1259            Err(NetError::NotFound)
1260        ));
1261        let _ = std::fs::remove_dir_all(&d);
1262    }
1263
1264    #[test]
1265    fn test_require_uid_closes_other_users() {
1266        let d = dir("uid");
1267        let path = d.join("s.sock");
1268        let me = std::os::unix::fs::MetadataExt::uid(&std::fs::metadata(&d).unwrap());
1269        let l = UnixListener::bind_with(&path, LocalOptions::new().require_uid(me.wrapping_add(1)))
1270            .unwrap();
1271        let mut c = UnixConnection::connect(&path).unwrap();
1272        assert!(
1273            l.accept_timeout(Duration::from_millis(200))
1274                .unwrap()
1275                .is_none()
1276        );
1277        // The connection was closed before any read.
1278        c.set_limits(
1279            Limits::builder()
1280                .read_timeout(Duration::from_secs(2))
1281                .build()
1282                .unwrap(),
1283        );
1284        let mut b = [0u8; 1];
1285        assert!(matches!(
1286            c.recv(&mut b),
1287            Ok(0) | Err(NetError::ConnectionReset)
1288        ));
1289        drop(l);
1290        let l = UnixListener::bind_with(&path, LocalOptions::new().require_uid(me)).unwrap();
1291        let _c = UnixConnection::connect(&path).unwrap();
1292        assert!(l.accept_timeout(Duration::from_secs(2)).unwrap().is_some());
1293        let _ = std::fs::remove_dir_all(&d);
1294    }
1295
1296    #[test]
1297    fn test_descriptors_travel_with_bytes() {
1298        let d = dir("fds");
1299        let path = d.join("s.sock");
1300        let l = UnixListener::bind(&path).unwrap();
1301        let p2 = path.clone();
1302        let t = std::thread::spawn(move || {
1303            let mut c = UnixConnection::connect(&p2).unwrap();
1304            let (mut mine, theirs) = std::os::unix::net::UnixStream::pair().unwrap();
1305            c.send_descriptors(b"take this", &[theirs.as_fd()]).unwrap();
1306            drop(theirs);
1307            mine.write_all(b"hello").unwrap();
1308            let mut b = [0u8; 1];
1309            let _ = c.recv(&mut b);
1310        });
1311        let (mut c, _) = l.accept().unwrap();
1312        let mut fds = Descriptors::new();
1313        let mut body = [0u8; 32];
1314        // One sendmsg of 9 bytes with its descriptor arrives as one message.
1315        let n = c.recv_descriptors(&mut body, &mut fds).unwrap();
1316        assert_eq!(&body[..n], b"take this");
1317        assert_eq!(fds.len(), 1);
1318        let fd = fds.take().next().unwrap();
1319        assert!(fds.is_empty());
1320        let mut s = std::os::unix::net::UnixStream::from(fd);
1321        let mut b = [0u8; 5];
1322        s.read_exact(&mut b).unwrap();
1323        assert_eq!(&b, b"hello");
1324        assert!(matches!(
1325            c.send_descriptors(b"", &[]),
1326            Err(NetError::InvalidConfig { field: "body", .. })
1327        ));
1328        c.close().unwrap();
1329        t.join().unwrap();
1330        let _ = std::fs::remove_dir_all(&d);
1331    }
1332
1333    #[test]
1334    fn test_local_enums_round_trip() {
1335        let d = dir("enums");
1336        let path = d.join("s.sock");
1337        let name = path.to_str().unwrap().to_owned();
1338        let l = listen(&name).unwrap();
1339        let n2 = name.clone();
1340        let t = std::thread::spawn(move || {
1341            let mut c = connect(&n2).unwrap();
1342            c.send_all(b"hi").unwrap();
1343            c.close().unwrap();
1344        });
1345        let (mut c, info) = l.accept().unwrap();
1346        assert_eq!(info.transport, Transport::Unix);
1347        let mut b = [0u8; 2];
1348        c.recv_exact(&mut b).unwrap();
1349        assert_eq!(&b, b"hi");
1350        t.join().unwrap();
1351        let _ = std::fs::remove_dir_all(&d);
1352    }
1353}