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