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}