Skip to main content

clawft_kernel/
mesh_limits.rs

1//! Connection limits for the mesh listener (P3-K1): caps and timeouts. The
2//! per-IP cap and first-frame timeout apply in every admission mode; the idle
3//! timeout only under a strict gate.
4
5use std::sync::Arc;
6
7/// Most concurrent inbound connections served at once.
8pub const MAX_CONNECTIONS: usize = 1024;
9/// Time a peer gets to finish the Noise handshake.
10pub const HANDSHAKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
11/// How often a connection checks that its route still exists and its peer is
12/// not revoked (review S1: a revoked or disconnected peer is closed within this).
13pub const ROUTE_CHECK: std::time::Duration = std::time::Duration::from_millis(250);
14
15/// Default per-IP concurrent connection cap (every admission mode;
16/// `kernel.mesh.max_connections_per_ip`).
17pub const MAX_CONNECTIONS_PER_IP: usize = 64;
18/// Default time a peer gets to send its first frame (every admission mode;
19/// `kernel.mesh.first_frame_timeout_secs`).
20pub const FIRST_FRAME_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
21/// Close an admitted connection idle this long (strict gates only).
22pub const IDLE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(900);
23
24/// Releases a per-IP slot on drop.
25pub(crate) struct IpSlot {
26    ip: std::net::IpAddr,
27    map: Arc<std::sync::Mutex<std::collections::HashMap<std::net::IpAddr, usize>>>,
28}
29
30impl IpSlot {
31    /// Slot key for a source address: IPv4-mapped IPv6 is folded onto its
32    /// IPv4 form and IPv6 is bucketed by /64, so one host cannot multiply
33    /// its quota by rotating addresses.
34    fn key(ip: std::net::IpAddr) -> std::net::IpAddr {
35        match ip.to_canonical() {
36            std::net::IpAddr::V6(v6) => {
37                let mut o = v6.octets();
38                o[8..].fill(0);
39                std::net::IpAddr::V6(o.into())
40            }
41            v4 => v4,
42        }
43    }
44
45    pub(crate) fn acquire(
46        map: &Arc<std::sync::Mutex<std::collections::HashMap<std::net::IpAddr, usize>>>,
47        ip: std::net::IpAddr,
48        max: usize,
49    ) -> Option<Self> {
50        let ip = Self::key(ip);
51        let mut m = map.lock().unwrap();
52        let n = m.entry(ip).or_insert(0);
53        if *n >= max {
54            return None;
55        }
56        *n += 1;
57        Some(Self { ip, map: Arc::clone(map) })
58    }
59}
60
61impl Drop for IpSlot {
62    fn drop(&mut self) {
63        let mut m = self.map.lock().unwrap();
64        if let Some(n) = m.get_mut(&self.ip) {
65            *n -= 1;
66            if *n == 0 {
67                m.remove(&self.ip);
68            }
69        }
70    }
71}
72
73
74/// Connection limits. `first_frame` and `per_ip` apply under every gate;
75/// `idle` only under a strict one (see [`AdmissionGate::strict`]) and on
76/// dialled connections (inbound silence).
77#[derive(Debug, Clone, Copy)]
78pub struct Limits {
79    /// Silence allowed before the first frame.
80    pub first_frame: std::time::Duration,
81    /// Silence allowed on an admitted connection (strict gates; dialled
82    /// connections count inbound silence only).
83    pub idle: std::time::Duration,
84    /// Concurrent connections per source IP.
85    pub per_ip: usize,
86}
87
88impl Default for Limits {
89    fn default() -> Self {
90        Self {
91            first_frame: FIRST_FRAME_TIMEOUT,
92            idle: IDLE_TIMEOUT,
93            per_ip: MAX_CONNECTIONS_PER_IP,
94        }
95    }
96}
97
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use std::net::IpAddr;
103
104    #[test]
105    fn mapped_and_prefix_addresses_share_a_slot() {
106        let map = Arc::new(std::sync::Mutex::new(Default::default()));
107        let v4: IpAddr = "192.0.2.1".parse().unwrap();
108        let mapped: IpAddr = "::ffff:192.0.2.1".parse().unwrap();
109        let _a = IpSlot::acquire(&map, v4, 1).unwrap();
110        assert!(IpSlot::acquire(&map, mapped, 1).is_none());
111
112        let p1: IpAddr = "2001:db8:1:2::1".parse().unwrap();
113        let p2: IpAddr = "2001:db8:1:2:ffff::9".parse().unwrap();
114        let other: IpAddr = "2001:db8:1:3::1".parse().unwrap();
115        let _b = IpSlot::acquire(&map, p1, 1).unwrap();
116        assert!(IpSlot::acquire(&map, p2, 1).is_none(), "same /64");
117        assert!(IpSlot::acquire(&map, other, 1).is_some(), "different /64");
118    }
119}