Skip to main content

net_io/
info.rs

1//! What net-io knows about one connection.
2
3use std::fmt;
4use std::net::SocketAddr;
5#[cfg(unix)]
6use std::path::Path;
7use std::sync::Arc;
8use std::time::Instant;
9
10use crate::addr::{PrefixKey, PrefixPolicy};
11use crate::id::{ConnectionId, ListenerId, WorkerId};
12use crate::proxy::ProxyFacts;
13use crate::tls::TlsInfo;
14
15/// An opaque importance class the gate assigns at admission; lower is more important.
16///
17/// net-io uses it only to shed lanes under load and for per-lane reservations.
18///
19/// # Examples
20///
21/// ```
22/// use net_io::Lane;
23/// assert!(Lane::MOST_IMPORTANT < Lane(3));
24/// ```
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
26pub struct Lane(pub u8);
27
28impl Lane {
29    /// Lane 0.
30    pub const MOST_IMPORTANT: Lane = Lane(0);
31}
32
33/// An opaque value the gate returns at admission, stored in [`ConnectionInfo::gate`].
34///
35/// It lets a protocol engine name the connection to the gate without being
36/// generic over the gate's type. net-io never interprets it.
37///
38/// # Examples
39///
40/// ```
41/// let tag = net_io::GateTag(17);
42/// assert_eq!(tag.0, 17);
43/// ```
44#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
45pub struct GateTag(pub u64);
46
47/// The kind of socket a connection uses.
48///
49/// ```
50/// assert_ne!(net_io::Transport::Tcp, net_io::Transport::Unix);
51/// ```
52#[non_exhaustive]
53#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
54pub enum Transport {
55    /// TCP.
56    Tcp,
57    /// UDP.
58    Udp,
59    /// A Unix domain socket.
60    Unix,
61    /// A Windows named pipe.
62    Pipe,
63}
64
65/// How a connection came to exist.
66///
67/// ```
68/// assert_ne!(net_io::Origin::Accepted, net_io::Origin::Dialed);
69/// ```
70#[non_exhaustive]
71#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
72pub enum Origin {
73    /// Accepted by a listener.
74    Accepted,
75    /// Dialed by this process.
76    Dialed,
77    /// Dialed and then adopted into a server.
78    Adopted,
79    /// Inherited from another process.
80    Inherited,
81}
82
83/// A Unix socket path in a fixed array (up to 108 bytes).
84///
85/// ```
86/// let path = net_io::UnixPath::new(b"/run/app.sock").unwrap();
87/// assert_eq!(path.as_bytes(), b"/run/app.sock");
88/// ```
89#[derive(Clone, Copy, PartialEq, Eq, Hash)]
90pub struct UnixPath {
91    bytes: [u8; 108],
92    len: u8,
93}
94
95impl UnixPath {
96    /// The largest path this type holds.
97    pub const MAX: usize = 108;
98
99    /// A path from bytes; `None` when longer than [`UnixPath::MAX`].
100    #[must_use]
101    pub fn new(bytes: &[u8]) -> Option<UnixPath> {
102        if bytes.len() > Self::MAX {
103            return None;
104        }
105        let mut out = [0u8; 108];
106        out[..bytes.len()].copy_from_slice(bytes);
107        Some(UnixPath {
108            bytes: out,
109            len: u8::try_from(bytes.len()).ok()?,
110        })
111    }
112
113    /// The path bytes.
114    #[must_use]
115    pub fn as_bytes(&self) -> &[u8] {
116        &self.bytes[..usize::from(self.len)]
117    }
118
119    /// The path as a `Path`.
120    ///
121    /// ```
122    /// # #[cfg(unix)] {
123    /// let path = net_io::UnixPath::new(b"/tmp/x").unwrap();
124    /// assert_eq!(path.as_path(), std::path::Path::new("/tmp/x"));
125    /// # }
126    /// ```
127    #[cfg(unix)]
128    #[must_use]
129    pub fn as_path(&self) -> &Path {
130        use std::os::unix::ffi::OsStrExt;
131        Path::new(std::ffi::OsStr::from_bytes(self.as_bytes()))
132    }
133}
134
135impl std::fmt::Debug for UnixPath {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        write!(
138            f,
139            "UnixPath({:?})",
140            String::from_utf8_lossy(self.as_bytes())
141        )
142    }
143}
144
145/// A Windows pipe name in a fixed array (up to 256 UTF-16 units).
146///
147/// ```
148/// let name = net_io::PipeName::new("myapp").unwrap();
149/// assert_eq!(name.to_string_lossy(), "myapp");
150/// ```
151#[derive(Clone, Copy, PartialEq, Eq, Hash)]
152pub struct PipeName {
153    units: [u16; 256],
154    len: u16,
155}
156
157impl PipeName {
158    /// The longest name this type holds, in UTF-16 units.
159    pub const MAX: usize = 256;
160
161    /// A name from text; `None` when longer than [`PipeName::MAX`] units.
162    #[must_use]
163    pub fn new(name: &str) -> Option<PipeName> {
164        let mut units = [0u16; 256];
165        let mut len = 0usize;
166        for u in name.encode_utf16() {
167            if len == Self::MAX {
168                return None;
169            }
170            units[len] = u;
171            len += 1;
172        }
173        Some(PipeName {
174            units,
175            len: u16::try_from(len).ok()?,
176        })
177    }
178
179    /// The name as UTF-16 units.
180    #[must_use]
181    pub fn as_wide(&self) -> &[u16] {
182        &self.units[..usize::from(self.len)]
183    }
184
185    /// The name as text, with invalid units replaced.
186    #[must_use]
187    pub fn to_string_lossy(&self) -> String {
188        String::from_utf16_lossy(self.as_wide())
189    }
190}
191
192impl std::fmt::Debug for PipeName {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        write!(f, "PipeName({:?})", self.to_string_lossy())
195    }
196}
197
198/// One end of a connection.
199///
200/// ```
201/// let end = net_io::Endpoint::Ip("127.0.0.1:80".parse().unwrap());
202/// assert_eq!(end.socket_addr().unwrap().port(), 80);
203/// ```
204#[non_exhaustive]
205#[derive(Clone, Debug, PartialEq, Eq, Hash)]
206pub enum Endpoint {
207    /// An IP socket address.
208    Ip(SocketAddr),
209    /// A Unix socket path (shared with the listener that owns it).
210    Unix(Arc<UnixPath>),
211    /// A Windows pipe name (shared with the listener that owns it).
212    Pipe(Arc<PipeName>),
213    /// An unnamed Unix socket peer.
214    Unnamed,
215}
216
217/// The address as text: `127.0.0.1:80`, a Unix path, a pipe name, or `unnamed`.
218///
219/// ```
220/// let e = net_io::Endpoint::Ip("127.0.0.1:80".parse().unwrap());
221/// assert_eq!(e.to_string(), "127.0.0.1:80");
222/// ```
223impl fmt::Display for Endpoint {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        match self {
226            Endpoint::Ip(a) => write!(f, "{a}"),
227            Endpoint::Unix(p) => write!(f, "{}", String::from_utf8_lossy(p.as_bytes())),
228            Endpoint::Pipe(p) => {
229                for c in char::decode_utf16(p.as_wide().iter().copied()) {
230                    write!(f, "{}", c.unwrap_or(char::REPLACEMENT_CHARACTER))?;
231                }
232                Ok(())
233            }
234            Endpoint::Unnamed => f.write_str("unnamed"),
235        }
236    }
237}
238
239impl Endpoint {
240    /// The socket address, for IP endpoints.
241    #[must_use]
242    pub fn socket_addr(&self) -> Option<SocketAddr> {
243        match self {
244            Endpoint::Ip(a) => Some(*a),
245            _ => None,
246        }
247    }
248}
249
250/// Who is on the other end of a local socket. A field the platform cannot report is `None`.
251///
252/// ```
253/// let creds = net_io::PeerCredentials { pid: Some(1), uid: None, gid: None };
254/// assert_eq!(creds.pid, Some(1));
255/// ```
256#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
257pub struct PeerCredentials {
258    /// The peer's process id.
259    pub pid: Option<u32>,
260    /// The peer's user id.
261    pub uid: Option<u32>,
262    /// The peer's group id.
263    pub gid: Option<u32>,
264}
265
266/// What net-io knows about one connection, built at accept and completed as the
267/// handshake reveals more. It holds no heap memory.
268///
269/// # Examples
270///
271/// ```
272/// let info = net_io::ConnectionInfo::for_testing("192.0.2.1:5000".parse().unwrap());
273/// assert_eq!(info.client_addr().unwrap().port(), 5000);
274/// let key = info.client_key(net_io::PrefixPolicy::DEFAULT).unwrap();
275/// assert_eq!(key.to_string(), "192.0.2.1/32");
276/// ```
277#[non_exhaustive]
278#[derive(Clone, Debug)]
279pub struct ConnectionInfo {
280    /// The connection's id.
281    pub id: ConnectionId,
282    /// The listener that accepted it, if any.
283    pub listener: Option<ListenerId>,
284    /// The worker that owns it.
285    pub worker: WorkerId,
286    /// The kind of socket.
287    pub transport: Transport,
288    /// How it came to exist.
289    pub origin: Origin,
290    /// Our end.
291    pub local: Endpoint,
292    /// The socket peer, normalized.
293    pub peer: Endpoint,
294    /// The real client from a trusted PROXY header, normalized.
295    pub real_peer: Option<SocketAddr>,
296    /// Facts copied from a trusted PROXY header (pooled per worker, no allocation after warm-up).
297    pub proxy: Option<Arc<ProxyFacts>>,
298    /// Who is on the other end of a local socket.
299    pub peer_credentials: Option<PeerCredentials>,
300    /// TLS facts after the handshake (pooled per worker, no allocation after warm-up).
301    pub tls: Option<Arc<TlsInfo>>,
302    /// The lane the gate assigned.
303    pub lane: Lane,
304    /// The gate's tag for this connection.
305    pub gate: GateTag,
306    /// When the connection was accepted or dialed.
307    pub accepted_at: Instant,
308}
309
310impl ConnectionInfo {
311    /// The client address: the real peer from a trusted PROXY header when set,
312    /// otherwise the IP peer.
313    #[inline]
314    #[must_use]
315    pub fn client_addr(&self) -> Option<SocketAddr> {
316        self.real_peer.or_else(|| self.peer.socket_addr())
317    }
318
319    /// The client address grouped under `policy`.
320    #[must_use]
321    pub fn client_key(&self, policy: PrefixPolicy) -> Option<PrefixKey> {
322        self.client_addr().map(|a| PrefixKey::of(a.ip(), policy))
323    }
324
325    /// The socket peer, ignoring any PROXY header.
326    #[must_use]
327    pub fn socket_peer(&self) -> Option<SocketAddr> {
328        self.peer.socket_addr()
329    }
330
331    /// A connection fact set attached to no socket, for testing gates and sessions.
332    ///
333    /// The id comes from the process counter, so it never collides with a real
334    /// connection. Fields are public to adjust.
335    #[must_use]
336    pub fn for_testing(peer: SocketAddr) -> ConnectionInfo {
337        let local = if peer.is_ipv4() {
338            SocketAddr::from(([127, 0, 0, 1], 0))
339        } else {
340            SocketAddr::from(([0u16, 0, 0, 0, 0, 0, 0, 1], 0))
341        };
342        ConnectionInfo::new_tcp(
343            crate::id::issue_single(),
344            None,
345            WorkerId(0),
346            Origin::Accepted,
347            local,
348            crate::addr::normalize_socket(peer),
349            Instant::now(),
350        )
351    }
352
353    pub(crate) fn new_tcp(
354        id: ConnectionId,
355        listener: Option<ListenerId>,
356        worker: WorkerId,
357        origin: Origin,
358        local: SocketAddr,
359        peer: SocketAddr,
360        at: Instant,
361    ) -> ConnectionInfo {
362        ConnectionInfo {
363            id,
364            listener,
365            worker,
366            transport: Transport::Tcp,
367            origin,
368            local: Endpoint::Ip(local),
369            peer: Endpoint::Ip(peer),
370            real_peer: None,
371            proxy: None,
372            peer_credentials: None,
373            tls: None,
374            lane: Lane::MOST_IMPORTANT,
375            gate: GateTag(0),
376            accepted_at: at,
377        }
378    }
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384
385    #[test]
386    fn test_client_addr_prefers_real_peer() {
387        let mut info = ConnectionInfo::for_testing("10.0.0.1:1".parse().unwrap());
388        assert_eq!(info.client_addr().unwrap().ip().to_string(), "10.0.0.1");
389        info.real_peer = Some("203.0.113.5:9".parse().unwrap());
390        assert_eq!(info.client_addr().unwrap().ip().to_string(), "203.0.113.5");
391        assert_eq!(info.socket_peer().unwrap().ip().to_string(), "10.0.0.1");
392    }
393
394    #[test]
395    fn test_for_testing_normalizes_mapped_peer() {
396        let info = ConnectionInfo::for_testing("[::ffff:1.2.3.4]:80".parse().unwrap());
397        assert_eq!(info.socket_peer().unwrap().to_string(), "1.2.3.4:80");
398    }
399
400    #[test]
401    fn test_unix_path_bounds() {
402        assert!(UnixPath::new(&[b'a'; 108]).is_some());
403        assert!(UnixPath::new(&[b'a'; 109]).is_none());
404    }
405
406    #[test]
407    fn test_pipe_name_bounds() {
408        assert!(PipeName::new(&"a".repeat(256)).is_some());
409        assert!(PipeName::new(&"a".repeat(257)).is_none());
410    }
411
412    #[test]
413    fn test_connection_info_size_fits_idle_budget() {
414        // Stored once per connection; the idle-connection target is 512 bytes of net-io state.
415        assert!(
416            std::mem::size_of::<ConnectionInfo>() <= 256,
417            "{}",
418            std::mem::size_of::<ConnectionInfo>()
419        );
420    }
421}