tokio_listener/
listener_address.rs

1use std::{fmt::Display, net::SocketAddr, path::PathBuf, str::FromStr};
2
3/// Abstraction over socket address that instructs in which way and at what address (if any) [`Listener`]
4/// should listen for incoming stream connections.
5///
6/// All address variants are available on all platforms, regardness of actual support in the Listener or enabled crate features.
7///
8/// If serde is enabled, it is serialized/deserialized the same as string, same as as in the CLI, using `FromStr`/`Display`.
9///
10/// See variants documentation for `FromStr` string patterns that are accepted by `ListenerAddress` parser
11///
12/// If you are not using clap helper types then remember to copy or link those documentation snippets into your app's documentation.
13///
14/// ```
15/// # use tokio_listener::*;
16/// let addr : ListenerAddress = "127.0.0.1:8087".parse().unwrap();
17/// let addr : ListenerAddress = "[::]:80".parse().unwrap();
18/// let addr : ListenerAddress = "/path/to/socket".parse().unwrap();
19/// let addr : ListenerAddress = "@abstract_linux_address".parse().unwrap();
20/// let addr : ListenerAddress = "inetd".parse().unwrap();
21/// let addr : ListenerAddress = "sd-listen".parse().unwrap();
22/// let addr : ListenerAddress = "SD_LISTEN".parse().unwrap();
23/// let addr : ListenerAddress = "sd-listen:named_socket".parse().unwrap();
24/// let addr : ListenerAddress = "sd-listen:*".parse().unwrap();
25/// ```
26#[non_exhaustive]
27#[cfg_attr(
28    feature = "serde",
29    derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
30)]
31#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub enum ListenerAddress {
33    /// Usual server TCP socket. Triggered by specifying IPv4 or IPv6 address and port pair.
34    /// Example: `127.0.0.1:8080`.
35    ///
36    /// Hostnames are not supported.
37    Tcp(SocketAddr),
38    /// Path-based UNIX socket. Path must begin with `/` or `.`.  
39    /// Examples: `/tmp/mysock`, `./mysock`
40    Path(PathBuf),
41    /// Linux abstract-namespaced UNIX socket. Indicated by using `@` as a first character.
42    /// Example: `@server`
43    Abstract(String),
44    /// "inetd" or "Accept=yes" mode where stdin and stdout (file descriptors 0 and 1) are used together as a socket
45    /// and only one connection is served. Triggered by using `inetd` or `stdio` or `-` as the address.
46    Inetd,
47    /// SystemD's "Accept=no" mode - using manually specified file descriptor as a pre-created server socket ready to accept TCP or UNIX connections.
48    /// Triggered by specifying `sd-listen` as address, which sets `3` as file descriptor number.
49    FromFd(i32),
50    /// SystemD's "Accept=no" mode - relying on `LISTEN_FDNAMES` environment variable instead of using the hard coded number
51    /// Triggered by using appending a colon and a name after `sd-listen`. Example: `sd-listen:mynamedsock`
52    ///
53    /// Special name `*` means to bind all passed addresses simultaneously, if `multi-listener` crate feature is enabled.
54    FromFdNamed(String),
55}
56
57pub(crate) const SD_LISTEN_FDS_START: i32 = 3;
58
59impl FromStr for ListenerAddress {
60    type Err = &'static str;
61
62    fn from_str(s: &str) -> Result<Self, Self::Err> {
63        if s.starts_with('/') || s.starts_with("./") {
64            Ok(ListenerAddress::Path(s.into()))
65        } else if let Some(x) = s.strip_prefix('@') {
66            Ok(ListenerAddress::Abstract(x.to_owned()))
67        } else if s.eq_ignore_ascii_case("inetd") || s.eq_ignore_ascii_case("stdio") || s == "-" {
68            Ok(ListenerAddress::Inetd)
69        } else if s.eq_ignore_ascii_case("sd-listen")
70            || s.eq_ignore_ascii_case("sd_listen")
71            || s.eq_ignore_ascii_case("sd-listen-unix")
72            || s.eq_ignore_ascii_case("sd_listen_unix")
73        {
74            Ok(ListenerAddress::FromFd(SD_LISTEN_FDS_START))
75        // No easy `strip_prefix_ignore_ascii_case` in Rust stdlib,
76        // so this specific variant is not reachable for upper case end users as well.
77        } else if let Some(x) = s
78            .strip_prefix("sd-listen:")
79            .or(s.strip_prefix("sd_listen:"))
80        {
81            if x.contains(':') {
82                return Err("Invalid tokio-listener sd-listen: name");
83            }
84            Ok(ListenerAddress::FromFdNamed(x.to_owned()))
85        } else if let Ok(a) = s.parse() {
86            Ok(ListenerAddress::Tcp(a))
87        } else {
88            Err("Invalid tokio-listener address type")
89        }
90    }
91}
92
93impl Display for ListenerAddress {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        match self {
96            ListenerAddress::Tcp(a) => a.fmt(f),
97            ListenerAddress::Path(p) => {
98                if let Some(s) = p.to_str() {
99                    if p.is_absolute() || s.starts_with("./") {
100                        s.fmt(f)
101                    } else {
102                        write!(f, "./{s}")
103                    }
104                } else if p.is_absolute() {
105                    "/???".fmt(f)
106                } else {
107                    "./???".fmt(f)
108                }
109            }
110            ListenerAddress::Abstract(p) => {
111                write!(f, "@{p}")
112            }
113            ListenerAddress::Inetd => "inetd".fmt(f),
114            ListenerAddress::FromFd(fd) => {
115                if *fd == SD_LISTEN_FDS_START {
116                    "sd-listen".fmt(f)
117                } else {
118                    write!(f, "accept-from-fd:{fd}")
119                }
120            }
121            ListenerAddress::FromFdNamed(name) => {
122                write!(f, "sd-listen:{name}")
123            }
124        }
125    }
126}
127
128#[cfg(feature = "sd_listen")]
129// based on https://docs.rs/sd-notify/0.4.1/src/sd_notify/lib.rs.html#164, but simplified
130#[allow(unused)]
131pub(crate) fn check_env_for_fd(fdnum: i32) -> Option<()> {
132    use tracing::{debug, error};
133
134    let listen_pid = std::env::var("LISTEN_PID").ok()?;
135    let listen_pid: u32 = listen_pid.parse().ok()?;
136
137    let listen_fds = std::env::var("LISTEN_FDS").ok()?;
138    let listen_fds: i32 = listen_fds.parse().ok()?;
139
140    debug!("Parsed LISTEN_PID and LISTEN_FDS");
141
142    if listen_pid != std::process::id() {
143        error!(expected = %std::process::id(), actual=listen_pid, "Failed LISTEN_PID check");
144        return None;
145    }
146
147    if fdnum < SD_LISTEN_FDS_START || fdnum >= SD_LISTEN_FDS_START.checked_add(listen_fds)? {
148        error!(fdnum, listen_fds, "Failed LISTEN_FDS check");
149        return None;
150    }
151
152    Some(())
153}