Skip to main content

sozu_lib/
server.rs

1//! event loop management
2use std::{
3    cell::RefCell,
4    collections::{HashMap, HashSet, VecDeque, hash_map::Entry},
5    hash::{DefaultHasher, Hash, Hasher},
6    io::Error as IoError,
7    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
8    os::unix::io::{AsRawFd, FromRawFd, RawFd},
9    rc::Rc,
10    str::FromStr,
11    sync::LazyLock,
12    time::{Duration, Instant},
13};
14
15use mio::{
16    Events, Interest, Poll, Token,
17    net::{TcpListener as MioTcpListener, TcpStream, UdpSocket as MioUdpSocket},
18};
19use slab::Slab;
20use sozu_command::{
21    channel::Channel,
22    config::MetricDetailLevel,
23    listener_key::ListenerKey,
24    logging,
25    proto::command::{
26        ActivateListener, AddBackend, CertificatesWithFingerprints, Cluster, ClusterHashes,
27        ClusterInformations, DeactivateListener, Event, EventKind, HttpListenerConfig,
28        HttpsListenerConfig, InitialState, ListenerType, LoadBalancingAlgorithms, LoadMetric,
29        MetricDetail, MetricsConfiguration, RemoveBackend, Request, ResponseContent,
30        ResponseStatus, ServerConfig, TcpListenerConfig as CommandTcpListener,
31        UdpListenerConfig as CommandUdpListener, UpdateHttpListenerConfig,
32        UpdateHttpsListenerConfig, UpdateTcpListenerConfig, UpdateUdpListenerConfig, WorkerRequest,
33        WorkerResponse, request::RequestType, response_content::ContentType,
34    },
35    ready::Ready,
36    scm_socket::{Listeners, ScmSocket, ScmSocketError},
37    state::{ClusterId, ConfigState, StateError},
38};
39
40use crate::metrics::names;
41use crate::{
42    AcceptError, Protocol, ProxyConfiguration, ProxySession, SessionIsToBeClosed,
43    backends::{Backend, BackendMap},
44    features::FEATURES,
45    health_check::HealthChecker,
46    http, https,
47    load_balancing::ShuffleSharding,
48    metrics::METRICS,
49    pool::Pool,
50    tcp,
51    timer::Timer,
52    udp,
53};
54
55/// Number of bounded buckets for the per-source connect-rate counter.
56///
57/// `incr!` requires a `&'static str`, so per-IP labelling would either need
58/// runtime `Box::leak` per unique source (unbounded under SYN flood — direct
59/// OWASP A05 / NIST SP 800-92 cardinality-blow-up risk) or a fixed bucket
60/// table. We pick the bucket table: 256 static labels precomputed at startup,
61/// each masked subnet hashes into one of them.
62///
63/// Bucket-noise vs per-IP fidelity is a deliberate trade. Operators wanting
64/// per-IP attribution should pair these counters with structured access logs
65/// or a downstream rate-limiter; the metric here is for "is some /24 spamming
66/// us right now?", not "which IP exactly". 256 buckets keep the memory + UDP
67/// statsd cost flat regardless of attacker effort.
68pub const PER_SOURCE_BUCKETS: usize = 256;
69
70/// Pre-leaked `&'static str` table for per-source bucket counters.
71/// `incr!` requires `&'static str`; we leak once at first access (LazyLock)
72/// for `PER_SOURCE_BUCKETS` keys, totalling ~10 KB heap. The leak is bounded
73/// by `PER_SOURCE_BUCKETS` and never grows with traffic.
74static PER_SOURCE_BUCKET_KEYS: LazyLock<[&'static str; PER_SOURCE_BUCKETS]> = LazyLock::new(|| {
75    let mut keys: [&'static str; PER_SOURCE_BUCKETS] = [""; PER_SOURCE_BUCKETS];
76    for (i, slot) in keys.iter_mut().enumerate() {
77        // e.g. "client.connect.per_source.bucket_042"
78        let owned = format!("client.connect.per_source.bucket_{i:03}");
79        *slot = Box::leak(owned.into_boxed_str());
80    }
81    keys
82});
83
84/// Mask an IP address to its bounded prefix (/24 for IPv4, /48 for IPv6) and
85/// hash it into one of `PER_SOURCE_BUCKETS` slots. The hash is `DefaultHasher`,
86/// which is deterministic within a process but salted across runs — fine for
87/// telemetry, not suitable for cross-host correlation.
88fn per_source_bucket(peer: &SocketAddr) -> &'static str {
89    let mut hasher = DefaultHasher::new();
90    match peer.ip() {
91        IpAddr::V4(v4) => {
92            let octets = v4.octets();
93            // /24 mask: keep first three octets, zero the host portion.
94            let masked = Ipv4Addr::new(octets[0], octets[1], octets[2], 0);
95            masked.hash(&mut hasher);
96        }
97        IpAddr::V6(v6) => {
98            let octets = v6.octets();
99            // /48 mask: keep first 6 bytes, zero the rest.
100            let mut masked_octets = [0u8; 16];
101            masked_octets[..6].copy_from_slice(&octets[..6]);
102            Ipv6Addr::from(masked_octets).hash(&mut hasher);
103        }
104    }
105    let idx = (hasher.finish() as usize) % PER_SOURCE_BUCKETS;
106    PER_SOURCE_BUCKET_KEYS[idx]
107}
108
109/// Period between two `accept_queue.saturated_seconds` ticks. The counter is
110/// incremented once per period while [`SessionManager::can_accept`] is `false`,
111/// distinguishing "queue spent N seconds at max" from "queue briefly hit max"
112/// — the binary `accept_queue.backpressure` gauge collapses that duration.
113const ACCEPT_SATURATION_TICK: Duration = Duration::from_secs(1);
114
115pub type ProxyChannel = Channel<WorkerResponse, WorkerRequest>;
116
117thread_local! {
118  pub static QUEUE: RefCell<VecDeque<WorkerResponse>> = const { RefCell::new(VecDeque::new()) };
119}
120
121thread_local! {
122  pub static TIMER: RefCell<Timer<Token>> = RefCell::new(Timer::default());
123}
124
125pub fn push_queue(message: WorkerResponse) {
126    QUEUE.with(|queue| {
127        (*queue.borrow_mut()).push_back(message);
128    });
129}
130
131pub fn push_event(event: Event) {
132    QUEUE.with(|queue| {
133        (*queue.borrow_mut()).push_back(WorkerResponse {
134            id: "EVENT".to_string(),
135            message: String::new(),
136            status: ResponseStatus::Processing.into(),
137            content: Some(ContentType::Event(event).into()),
138        });
139    });
140}
141
142/// Whether `request` removes a frontend, of any protocol.
143fn is_frontend_removal(request: &Request) -> bool {
144    matches!(
145        request.request_type,
146        Some(
147            RequestType::RemoveHttpFrontend(_)
148                | RequestType::RemoveHttpsFrontend(_)
149                | RequestType::RemoveTcpFrontend(_)
150                | RequestType::RemoveUdpFrontend(_)
151        )
152    )
153}
154
155/// Build the `WorkerMetricDetailStatus` content payload returned in
156/// every successful `SetMetricDetail` worker response. The master
157/// collects these across the fan-out and assembles them into
158/// `MetricDetailStatus.workers[<worker_id>]` so the TUI sees each
159/// worker's actual aggregator state instead of the master's view.
160fn worker_metric_detail_status_content(
161    configured: MetricDetailLevel,
162    effective: MetricDetailLevel,
163    previous_effective: MetricDetailLevel,
164    active_lease_count: u32,
165) -> ResponseContent {
166    use sozu_command::proto::command::WorkerMetricDetailStatus;
167    ContentType::WorkerMetricDetailStatus(WorkerMetricDetailStatus {
168        configured: MetricDetail::from(configured) as i32,
169        effective: MetricDetail::from(effective) as i32,
170        previous_effective: MetricDetail::from(previous_effective) as i32,
171        active_lease_count,
172    })
173    .into()
174}
175
176/// Build a `METRIC_DETAIL_CHANGED` event carrying the worker-local
177/// transition payload (previous/effective levels + transition kind).
178/// `client_id` is `Some(_)` for explicit apply/clear, `None` for the
179/// polled janitor's bulk expiry. The master folds this Event into the
180/// audit log alongside operator-initiated transitions emitted at the
181/// dispatch site in `bin/src/command/requests.rs::worker_request`.
182fn push_metric_detail_transition(
183    previous: MetricDetailLevel,
184    effective: MetricDetailLevel,
185    transition_kind: &'static str,
186    client_id: Option<String>,
187) {
188    use sozu_command::proto::command::MetricDetailTransition;
189    // No-op when nothing actually changed. Defence-in-depth — every
190    // caller already gates on `previous != effective`, but
191    // double-checking here means future call sites can't accidentally
192    // emit a "ghost" transition.
193    if previous == effective {
194        return;
195    }
196    push_event(Event {
197        kind: EventKind::MetricDetailChanged as i32,
198        cluster_id: None,
199        backend_id: None,
200        address: None,
201        metric_detail: Some(MetricDetailTransition {
202            previous_effective: MetricDetail::from(previous) as i32,
203            effective: MetricDetail::from(effective) as i32,
204            transition_kind: transition_kind.to_owned(),
205            client_id,
206        }),
207    });
208}
209
210/// Polled lease-expiry janitor: `SetMetricDetail` leases self-expire after
211/// their TTL so a crashed `sozu top` cannot permanently elevate metrics
212/// cardinality. Called once per event-loop iteration from [`Server::run`],
213/// which wakes at least once per `poll_timeout` (1 s) even with no traffic
214/// and no command, and from [`Server::notify`]. `lease_tick_due` gates the
215/// HashMap walk to once per `LEASE_TICK_INTERVAL` (5 s), so an abandoned
216/// lease is retired within TTL + 5 s + one poll timeout (#1831).
217fn tick_metric_detail_leases(now: Instant) {
218    // Capture (previous, effective) before releasing the borrow so we can
219    // emit an Event afterwards. Holding `METRICS.borrow_mut` across
220    // `push_event` would re-enter the same thread-local from inside
221    // `QUEUE.with` (safe but conceptually noisy); the two-step split keeps
222    // the borrow scopes minimal. Single-threaded worker, so `borrow_mut` is
223    // safe here.
224    let lease_tick_transition = METRICS.with(|metrics| {
225        let mut m = metrics.borrow_mut();
226        if !m.lease_tick_due(now) {
227            return None;
228        }
229        let previous = m.lease_tick(now)?;
230        let effective = m.detail_effective();
231        Some((previous, effective))
232    });
233    if let Some((previous, effective)) = lease_tick_transition {
234        // The janitor retired one or more leases AND the effective level
235        // moved. Surface the worker-local transition as an Event so the
236        // master folds it into the audit log. `client_id` is `None` because
237        // the janitor may have retired multiple leases at once.
238        push_metric_detail_transition(previous, effective, "lease_tick_expired", None);
239    }
240}
241
242#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
243pub struct ListenToken(pub usize);
244#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
245pub struct SessionToken(pub usize);
246
247impl From<usize> for ListenToken {
248    fn from(val: usize) -> ListenToken {
249        ListenToken(val)
250    }
251}
252
253impl From<ListenToken> for usize {
254    fn from(val: ListenToken) -> usize {
255        val.0
256    }
257}
258
259impl From<usize> for SessionToken {
260    fn from(val: usize) -> SessionToken {
261        SessionToken(val)
262    }
263}
264
265impl From<SessionToken> for usize {
266    fn from(val: SessionToken) -> usize {
267        val.0
268    }
269}
270
271/// Mask `address` down to the network named by `prefix` bits.
272///
273/// `prefix` is validated at config-load time
274/// (`ConfigError::InvalidSubnetPrefix` in `command/src/config.rs`), but
275/// this runs on the per-request hot path and does not assume that
276/// happened: a prefix at or above the address width returns the address
277/// unmasked.
278///
279/// Both ends are handled by an explicit branch rather than by the shift.
280/// `u32::MAX << 32` is an overflowing shift — it panics under
281/// `debug_assertions` and silently wraps to a no-op shift in release —
282/// so a full-width prefix must never reach the shift. A zero prefix is
283/// branched from the other end for the same reason.
284fn mask_ipv4(address: Ipv4Addr, prefix: u32) -> Ipv4Addr {
285    if prefix >= u32::BITS {
286        return address;
287    }
288    if prefix == 0 {
289        return Ipv4Addr::UNSPECIFIED;
290    }
291    // Only a strictly-interior prefix reaches the shift, which is what
292    // makes `u32::BITS - prefix` a legal shift amount in 1..32.
293    debug_assert!(
294        prefix > 0 && prefix < u32::BITS,
295        "both shift-overflowing ends must have returned before the shift"
296    );
297    let mask = u32::MAX << (u32::BITS - prefix);
298    let masked = Ipv4Addr::from(u32::from(address) & mask);
299    // Pair assertion, positive then negative space: the result keeps
300    // every masked-in bit of the input, and holds none of the host bits
301    // the prefix is supposed to erase.
302    debug_assert_eq!(
303        u32::from(masked),
304        u32::from(address) & mask,
305        "masking must keep exactly the network bits of the source address"
306    );
307    debug_assert_eq!(
308        u32::from(masked) & !mask,
309        0,
310        "masking must clear every host bit below the prefix"
311    );
312    masked
313}
314
315/// IPv6 twin of [`mask_ipv4`]. The same two shift-overflowing ends are
316/// branched explicitly: `u128::MAX << 128` is as ill-defined as its
317/// 32-bit counterpart.
318fn mask_ipv6(address: Ipv6Addr, prefix: u32) -> Ipv6Addr {
319    if prefix >= u128::BITS {
320        return address;
321    }
322    if prefix == 0 {
323        return Ipv6Addr::UNSPECIFIED;
324    }
325    debug_assert!(
326        prefix > 0 && prefix < u128::BITS,
327        "both shift-overflowing ends must have returned before the shift"
328    );
329    let mask = u128::MAX << (u128::BITS - prefix);
330    let masked = Ipv6Addr::from(u128::from(address) & mask);
331    debug_assert_eq!(
332        u128::from(masked),
333        u128::from(address) & mask,
334        "masking must keep exactly the network bits of the source address"
335    );
336    debug_assert_eq!(
337        u128::from(masked) & !mask,
338        0,
339        "masking must clear every host bit below the prefix"
340    );
341    masked
342}
343
344/// Derive the per-(cluster, source-SUBNET) counter key for `address`.
345///
346/// An IPv4-mapped IPv6 address is canonicalised to its IPv4 form and
347/// charged to `ipv4_prefix`. This matters: a dual-stack `[::]` listener
348/// hands every IPv4 client back as `::ffff:a.b.c.d`, and masking those
349/// with an IPv6 prefix would collapse the entire IPv4 internet into the
350/// single `::/56` bucket — the subnet cap would then fire on unrelated
351/// clients the moment it was switched on. The per-IP counter is NOT
352/// canonicalised: it keeps charging the address exactly as it arrived,
353/// which is part of what leaves its behaviour untouched by this feature.
354pub fn subnet_key(address: &IpAddr, ipv4_prefix: u32, ipv6_prefix: u32) -> IpAddr {
355    let canonical_v4 = match address {
356        IpAddr::V4(v4) => Some(*v4),
357        IpAddr::V6(v6) => v6.to_ipv4_mapped(),
358    };
359    let key = match (canonical_v4, address) {
360        (Some(v4), _) => IpAddr::V4(mask_ipv4(v4, ipv4_prefix)),
361        (None, IpAddr::V6(v6)) => IpAddr::V6(mask_ipv6(*v6, ipv6_prefix)),
362        // Unreachable: `canonical_v4` is `Some` for every `IpAddr::V4`.
363        (None, IpAddr::V4(v4)) => IpAddr::V4(mask_ipv4(*v4, ipv4_prefix)),
364    };
365    // Pair assertion: the key stays in the family it will be compared
366    // against — a mapped source yields a V4 key (positive space), and a
367    // genuine IPv6 source never yields one (negative space).
368    debug_assert_eq!(
369        key.is_ipv4(),
370        canonical_v4.is_some(),
371        "subnet key family must follow the canonicalised source family"
372    );
373    debug_assert_eq!(
374        key.is_ipv6(),
375        canonical_v4.is_none(),
376        "a genuine IPv6 source must never produce an IPv4 subnet key"
377    );
378    key
379}
380
381pub struct SessionManager {
382    pub max_connections: usize,
383    pub nb_connections: usize,
384    pub can_accept: bool,
385    pub slab: Slab<Rc<RefCell<dyn ProxySession>>>,
386    /// Default per-(cluster, source-IP) connection limit. `0` disables
387    /// the feature; cluster-level overrides take precedence at check
388    /// time.
389    pub max_connections_per_ip: u64,
390    /// Default `Retry-After` header value (seconds) for HTTP 429
391    /// responses emitted on per-(cluster, source-IP) limit hit. `0`
392    /// omits the header.
393    pub retry_after: u32,
394    /// Active **frontend connections** per `(cluster_id, source_ip)`.
395    /// Each frontend session contributes AT MOST 1 to the count for any
396    /// given `(cluster, ip)` pair, regardless of how many streams it
397    /// multiplexes to that cluster (an H2 connection serving 100
398    /// streams to cluster X from IP 1.2.3.4 still counts as 1). The
399    /// counter is incremented the first time a session's
400    /// `Router::plan_connect` resolves to a fresh `(cluster, ip)` pair, and
401    /// decremented when the session closes. Empty when the feature is
402    /// unused.
403    ///
404    /// ── Why nested maps instead of `HashMap<(String, IpAddr), usize>` ──
405    ///
406    /// The per-request hot path (`cluster_ip_at_limit`, called from
407    /// `mux/router::plan_connect` for every cluster-resolving request) used
408    /// to allocate a `String` to build the compound key on every
409    /// lookup. Splitting the storage so the outer key is a [`ClusterId`]
410    /// lets the lookup take `&str` — `HashMap::get(cluster_id)` on a
411    /// `HashMap<ClusterId, _>` accepts a borrow via the `Borrow<str>`
412    /// impl. The hot path no longer allocates, and `track_cluster_ip`
413    /// keys the maps with the caller's reference-counted handle, so no
414    /// key is ever a copy of the id. Memory footprint is unchanged
415    /// in steady state — entries are still reaped to zero on session
416    /// close.
417    connections_per_cluster_ip: HashMap<ClusterId, HashMap<IpAddr, usize>>,
418    /// Reverse index: per-token map of `cluster_id` → set of source IPs
419    /// already counted against `connections_per_cluster_ip`. Used to
420    /// make `track_cluster_ip` idempotent within a session (so H2
421    /// streams to the same cluster from the same client only consume
422    /// one slot in the limit) and to drain a session's contributions on
423    /// close. Same nesting rationale as above.
424    cluster_ip_tracks: HashMap<Token, HashMap<ClusterId, HashSet<IpAddr>>>,
425    /// Default per-(cluster, source-SUBNET) connection limit. `0`
426    /// disables the subnet limiter, which is the default; cluster-level
427    /// overrides take precedence at check time.
428    ///
429    /// This is a SECOND, INDEPENDENT counter standing beside
430    /// `max_connections_per_ip`, not a replacement for it. The per-IP
431    /// cap serves the API-gateway case — one abusive client, one
432    /// address. The per-subnet cap serves the denial-of-service case,
433    /// where an attacker holding a routed prefix (an ordinary
434    /// residential IPv6 `/64`) defeats a per-address counter outright
435    /// by taking a fresh address for every connection
436    /// (sozu-proxy/sozu#1270). Both gates are consulted and BOTH must
437    /// admit, so "10 per IP AND 100 per /64" is expressible.
438    pub max_connections_per_subnet: u64,
439    /// IPv4 prefix length, in bits, defining a "subnet" for the counter
440    /// above. Boot-time only: changing a mask would re-key every live
441    /// counter, so there is no runtime setter for it. 32 masks nothing.
442    pub subnet_ipv4_prefix: u32,
443    /// IPv6 prefix length, in bits. Boot-time only, as above. 128 masks
444    /// nothing.
445    pub subnet_ipv6_prefix: u32,
446    /// Active frontend connections per `(cluster_id, masked_source_ip)`.
447    /// Exact mirror of `connections_per_cluster_ip`, keyed on the subnet
448    /// key from `subnet_key` instead of the raw address; the same
449    /// nesting rationale applies verbatim.
450    ///
451    /// Stays EMPTY while the subnet limiter is disabled: unlike the
452    /// per-IP index, which is populated unconditionally, this one is
453    /// only written when a positive limit resolves for the cluster (see
454    /// `SessionManager::track_cluster_subnet`). That is what makes the
455    /// default genuinely free rather than merely inert — no second
456    /// allocation, no second hash, no second entry per session for an
457    /// operator who never turns the feature on.
458    connections_per_cluster_subnet: HashMap<ClusterId, HashMap<IpAddr, usize>>,
459    /// Reverse index for `connections_per_cluster_subnet`, mirroring
460    /// `cluster_ip_tracks`. Also empty while the limiter is disabled.
461    cluster_subnet_tracks: HashMap<Token, HashMap<ClusterId, HashSet<IpAddr>>>,
462    /// Default backend connection attempt budget of a request, the first
463    /// attempt included: `ServerConfig::max_connection_attempts`, changed at
464    /// runtime by `SetMaxConnectionAttempts`. A cluster's own
465    /// `max_connection_attempts` takes precedence
466    /// ([`Self::effective_max_connection_attempts`]). Validated, so between
467    /// 1 and 255.
468    pub max_connection_attempts: u32,
469}
470
471impl SessionManager {
472    pub fn new(
473        slab: Slab<Rc<RefCell<dyn ProxySession>>>,
474        max_connections: usize,
475        max_connections_per_ip: u64,
476        retry_after: u32,
477        max_connections_per_subnet: u64,
478        subnet_ipv4_prefix: u32,
479        subnet_ipv6_prefix: u32,
480    ) -> Rc<RefCell<Self>> {
481        Rc::new(RefCell::new(SessionManager {
482            max_connections,
483            nb_connections: 0,
484            can_accept: true,
485            slab,
486            max_connections_per_ip,
487            retry_after,
488            connections_per_cluster_ip: HashMap::new(),
489            cluster_ip_tracks: HashMap::new(),
490            max_connections_per_subnet,
491            subnet_ipv4_prefix,
492            subnet_ipv6_prefix,
493            connections_per_cluster_subnet: HashMap::new(),
494            cluster_subnet_tracks: HashMap::new(),
495            max_connection_attempts: sozu_command::config::DEFAULT_MAX_CONNECTION_ATTEMPTS,
496        }))
497    }
498
499    /// Resolve the backend connection attempt budget of a request routed to
500    /// a cluster whose own setting is `override_value`: the cluster's value
501    /// when it sets one, the global default otherwise (sozu-proxy/sozu#1800).
502    pub fn effective_max_connection_attempts(&self, override_value: Option<u32>) -> u32 {
503        override_value.unwrap_or(self.max_connection_attempts)
504    }
505
506    /// Resolve the effective per-(cluster, source-IP) limit. `override_value`
507    /// is the cluster-level setting from the proto `Cluster` message:
508    /// `None` inherits the global default, `Some(0)` is explicit
509    /// "unlimited", `Some(n > 0)` overrides.
510    pub fn effective_max_connections_per_ip(&self, override_value: Option<u64>) -> u64 {
511        override_value.unwrap_or(self.max_connections_per_ip)
512    }
513
514    /// Resolve the effective `Retry-After` header value. `Some(0)` (or
515    /// the global default of 0) signals "omit the header" — caller
516    /// must skip emission rather than render `Retry-After: 0`.
517    pub fn effective_retry_after(&self, override_value: Option<u32>) -> u32 {
518        override_value.unwrap_or(self.retry_after)
519    }
520
521    /// Resolve the effective per-(cluster, source-SUBNET) limit. Same
522    /// three-state contract as `effective_max_connections_per_ip`:
523    /// `None` inherits the global default, `Some(0)` is explicit
524    /// "unlimited", `Some(n > 0)` overrides.
525    pub fn effective_max_connections_per_subnet(&self, override_value: Option<u64>) -> u64 {
526        override_value.unwrap_or(self.max_connections_per_subnet)
527    }
528
529    /// Derive the subnet counter key for `ip` under this manager's
530    /// configured prefixes. Allocation-free — `IpAddr` is `Copy`.
531    pub fn subnet_key(&self, ip: &IpAddr) -> IpAddr {
532        subnet_key(ip, self.subnet_ipv4_prefix, self.subnet_ipv6_prefix)
533    }
534
535    /// Returns `true` when admitting `token` to one more connection for
536    /// `(cluster, subnet_of(ip))` would exceed the resolved subnet
537    /// limit. Exact mirror of `cluster_ip_at_limit` on the subnet
538    /// counter: `0` is unlimited, and a token already holding a slot
539    /// for this `(cluster, subnet)` is never at the limit.
540    ///
541    /// The subnet key is derived only AFTER the `limit == 0` early
542    /// return, so a deployment that never enables this feature pays no
543    /// masking work on the hot path at all.
544    pub fn cluster_subnet_at_limit(
545        &self,
546        token: Token,
547        cluster_id: &str,
548        ip: &IpAddr,
549        override_value: Option<u64>,
550    ) -> bool {
551        let limit = self.effective_max_connections_per_subnet(override_value);
552        if limit == 0 {
553            return false;
554        }
555        debug_assert!(
556            limit > 0,
557            "limit==0 (unlimited) must have returned before reaching the bounded check"
558        );
559        let subnet = self.subnet_key(ip);
560        let already_tracked = self
561            .cluster_subnet_tracks
562            .get(&token)
563            .and_then(|by_cluster| by_cluster.get(cluster_id))
564            .is_some_and(|subnets| subnets.contains(&subnet));
565        if already_tracked {
566            debug_assert!(
567                self.connections_per_cluster_subnet
568                    .get(cluster_id)
569                    .and_then(|by_subnet| by_subnet.get(&subnet))
570                    .is_some_and(|c| *c > 0),
571                "a tracked (token, cluster, subnet) slot must have a positive forward count"
572            );
573            return false;
574        }
575        self.connections_per_cluster_subnet
576            .get(cluster_id)
577            .and_then(|by_subnet| by_subnet.get(&subnet))
578            .is_some_and(|c| (*c as u64) >= limit)
579    }
580
581    /// THE gate. Returns `true` when EITHER the per-IP cap or the
582    /// per-subnet cap would be exceeded by admitting `token` to one more
583    /// connection for `cluster_id` from `ip`.
584    ///
585    /// Both enforcement points — the H1+H2 mux router and raw TCP — call
586    /// this rather than the two halves, so the conjunction "admit only
587    /// if BOTH caps allow it" is stated once and cannot drift between
588    /// protocols. The per-IP half is evaluated first and is byte-for-byte
589    /// the pre-existing check; the subnet half is the single line this
590    /// feature adds to the admission path.
591    pub fn cluster_connection_at_limit(
592        &self,
593        token: Token,
594        cluster_id: &str,
595        ip: &IpAddr,
596        ip_override: Option<u64>,
597        subnet_override: Option<u64>,
598    ) -> bool {
599        self.cluster_ip_at_limit(token, cluster_id, ip, ip_override)
600            || self.cluster_subnet_at_limit(token, cluster_id, ip, subnet_override)
601    }
602
603    /// Returns `true` when admitting `token` to one more connection for
604    /// `(cluster, ip)` would exceed the resolved limit. `0` is treated
605    /// as unlimited. A token that already holds a slot for this
606    /// `(cluster, ip)` is NEVER at the limit — H2 sessions multiplex
607    /// many streams to the same cluster on a single connection, and
608    /// the limit governs distinct frontend connections, not streams.
609    ///
610    /// Hot-path: called for every cluster-resolving request from
611    /// `mux/router::plan_connect`. The nested-map storage lets both lookups
612    /// borrow `cluster_id` and `ip`; no per-call allocation runs here
613    /// in steady state.
614    pub fn cluster_ip_at_limit(
615        &self,
616        token: Token,
617        cluster_id: &str,
618        ip: &IpAddr,
619        override_value: Option<u64>,
620    ) -> bool {
621        let limit = self.effective_max_connections_per_ip(override_value);
622        if limit == 0 {
623            return false;
624        }
625        // Pure query: the limit==0 branch already returned, so any work below
626        // runs only with a positive cap.
627        debug_assert!(
628            limit > 0,
629            "limit==0 (unlimited) must have returned before reaching the bounded check"
630        );
631        let already_tracked = self
632            .cluster_ip_tracks
633            .get(&token)
634            .and_then(|by_cluster| by_cluster.get(cluster_id))
635            .is_some_and(|ips| ips.contains(ip));
636        if already_tracked {
637            // Reverse-index/forward-count coherence: if this token already
638            // holds a slot for (cluster, ip), the forward count must be > 0
639            // (decrement-to-zero reaps the inner entry in untrack_all).
640            debug_assert!(
641                self.connections_per_cluster_ip
642                    .get(cluster_id)
643                    .and_then(|by_ip| by_ip.get(ip))
644                    .is_some_and(|c| *c > 0),
645                "a tracked (token, cluster, ip) slot must have a positive forward count"
646            );
647            return false;
648        }
649        self.connections_per_cluster_ip
650            .get(cluster_id)
651            .and_then(|by_ip| by_ip.get(ip))
652            .is_some_and(|c| (*c as u64) >= limit)
653    }
654
655    /// Account `token`'s active connection against `(cluster, ip)`.
656    /// Idempotent within a token: a second call for the same
657    /// `(cluster, ip)` is a no-op so H2 retries / multi-stream opens
658    /// to the same cluster do not double-count.
659    ///
660    /// Copies no cluster id: `entry(cluster_id.clone())` only bumps the
661    /// [`ClusterId`] count. A first `(token, cluster)` observation
662    /// allocates the new slot's IP set; later IPs under it reuse the
663    /// set, and a repeat call allocates nothing.
664    ///
665    /// ── A FAILED backend dial keeps the slot, and that is deliberate ──
666    ///
667    /// `Router::plan_connect` (`lib/src/protocol/mux/router.rs`) claims
668    /// here, after the `Self::cluster_ip_at_limit` gate but BEFORE
669    /// backend selection and the dial. Six fallible exits follow the
670    /// claim and none of them releases. Since sozu-proxy/sozu#1519 they
671    /// sit in two functions rather than one: `Router::plan_connect`
672    /// keeps the two pool-reuse refusals, and `Mux::dial_backend` with the
673    /// `Mux::attach_dialed` it calls (`lib/src/protocol/mux/mod.rs`) own the
674    /// four on the fresh-dial
675    /// path — backend selection, buffer-pool exhaustion, the new
676    /// connection refusing the stream, and the `L7Proxy::register_socket`
677    /// rollback, which restores its gauges and slab entry but not the
678    /// slot. The split moved no ordering: the claim still precedes all
679    /// six. `Self::untrack_all_cluster_ip` is the only release, and
680    /// session teardown is its only caller — the
681    /// `L7Proxy::remove_session` impls on `HttpProxy` / `HttpsProxy` for
682    /// the mux, `TcpSession::close` for raw TCP.
683    /// An unpaired claim reads like an oversight; it is not one, and
684    /// sozu-proxy/sozu#1521 is where the reasoning was measured rather
685    /// than assumed. Do not pair a release with those exits as part of a
686    /// refactor — it would LOOSEN a connection limit under exactly the
687    /// conditions a client can induce.
688    ///
689    /// What makes the unpaired claim cheap on H1 is that a failed dial
690    /// ENDS the frontend connection, so the slot returns with it:
691    ///
692    /// - `Mux::ready_inner` joins the plan and the dial into a single
693    ///   `Result` with `and_then`, so every post-claim exit from either
694    ///   function reaches the same `BackendConnectionError` arm of its
695    ///   pending-link loop, which answers the stream from the template
696    ///   registry instead of retrying. No arm there returns
697    ///   `SessionResult::Close` of its own.
698    /// - Every built-in template in
699    ///   `lib/src/protocol/kawa_h1/answers.rs` carries
700    ///   `Connection: close`, so `set_default_answer_with_retry_after`
701    ///   (`lib/src/protocol/mux/answers.rs`) clears
702    ///   `HttpContext::keep_alive_frontend`. On H1 that one bit is what
703    ///   closes the connection; H2 does not read it.
704    /// - H1: once the answer flushes, the response-complete branch of
705    ///   `ConnectionH1::writable` (`lib/src/protocol/mux/h1.rs`) takes
706    ///   `ConnectionH1::defer_close_for_tls_flush` instead of the
707    ///   keep-alive reset, so the session closes after that one flush.
708    /// - H2: the answer ends its own stream only (RFC 9113 §8.2.2:
709    ///   `Connection` is not an H2 field, sozu-proxy/sozu#1740). The
710    ///   connection stays open, so the slot is held until the client
711    ///   closes it or it times out.
712    /// - TCP never reaches an answer at all: any error out of
713    ///   `TcpSession::connect_to_backend` (`lib/src/tcp.rs`) becomes
714    ///   `SessionResult::Close` in the same event-loop pass.
715    /// - A registered, `Normal` backend that is down may not fail on the
716    ///   dial at all. The dial — `Mux::dial_backend`
717    ///   (`lib/src/protocol/mux/mod.rs`) for HTTP, `Backend::try_connect`
718    ///   (`lib/src/backends.rs`) for TCP — is non-blocking: a refusal the
719    ///   kernel reports synchronously surfaces as
720    ///   `BackendError::ConnectionFailures`, which the mux re-queues for
721    ///   another backend, while an `EINPROGRESS` connect returns `Ok` and
722    ///   its refusal (or its `connect_timeout`) arrives later as a backend
723    ///   failure, so the stream retries through `EndStreamAction::Reconnect`.
724    ///   The re-track is the no-op above, so one slot — not one per
725    ///   attempt — is held for at most `max_connection_attempts` attempts
726    ///   (sozu-proxy/sozu#1800).
727    ///
728    /// The window IS the connection's whole lifetime in two cases: any
729    /// H2 connection, and an H1 connection answered by an operator
730    /// template that omits `Connection: close` (the opt-out
731    /// `doc/rate-limit-design.md` records for the 429 template). Either
732    /// holds its one slot per (connection, cluster) until the client goes
733    /// away, as a connection whose dial succeeded does.
734    pub fn track_cluster_ip(&mut self, token: Token, cluster_id: ClusterId, ip: IpAddr) {
735        // Snapshot the forward count for this (cluster, ip) before the insert
736        // so we can pair-assert the delta. Ungated `let`: read only inside the
737        // debug_assert! below → optimised out in release (no E0425).
738        let count_before = self
739            .connections_per_cluster_ip
740            .get(&cluster_id)
741            .and_then(|by_ip| by_ip.get(&ip))
742            .copied()
743            .unwrap_or(0);
744        let inserted = self
745            .cluster_ip_tracks
746            .entry(token)
747            .or_default()
748            .entry(cluster_id.clone())
749            .or_default()
750            .insert(ip);
751        if inserted {
752            *self
753                .connections_per_cluster_ip
754                .entry(cluster_id.clone())
755                .or_default()
756                .entry(ip)
757                .or_insert(0) += 1;
758        }
759        // Postconditions: the reverse index now records this (token, cluster,
760        // ip), and the forward count advanced by exactly `inserted as usize`
761        // (idempotent: a repeat call for the same triple is a no-op on both).
762        debug_assert!(
763            self.cluster_ip_tracks
764                .get(&token)
765                .and_then(|by_cluster| by_cluster.get(&cluster_id))
766                .is_some_and(|ips| ips.contains(&ip)),
767            "track must leave the (token, cluster, ip) recorded in the reverse index"
768        );
769        debug_assert_eq!(
770            self.connections_per_cluster_ip
771                .get(&cluster_id)
772                .and_then(|by_ip| by_ip.get(&ip))
773                .copied()
774                .unwrap_or(0),
775            count_before + inserted as usize,
776            "forward count must advance by exactly 1 on first track, 0 on a repeat"
777        );
778        #[cfg(debug_assertions)]
779        self.check_invariants();
780    }
781
782    /// Account `token`'s active connection against
783    /// `(cluster, subnet_of(ip))`. Mirror of `track_cluster_ip` on the
784    /// subnet counter, with one deliberate difference: it is a NO-OP
785    /// while the resolved subnet limit is `0`.
786    ///
787    /// That conditional is what keeps the default genuinely free. The
788    /// per-IP index is populated unconditionally, so an operator who
789    /// never enables the subnet cap would otherwise pay a second hash,
790    /// a second allocation and a second per-session entry forever for a
791    /// feature they do not use. The cost is that enabling the cap at
792    /// runtime does not retroactively count sessions already in flight;
793    /// the counter converges as those sessions close. The per-IP path
794    /// has the same shape of gap after `clear_cluster_ip_tracking`.
795    pub fn track_cluster_subnet(
796        &mut self,
797        token: Token,
798        cluster_id: ClusterId,
799        ip: IpAddr,
800        override_value: Option<u64>,
801    ) {
802        if self.effective_max_connections_per_subnet(override_value) == 0 {
803            // No assertion that nothing is recorded for this token: that
804            // is NOT an invariant. A cluster override can drop to
805            // `Some(0)` after a session was tracked under a positive
806            // limit — `AddCluster` is an upsert and clears no counters,
807            // and only the global `SetMaxConnectionsPerSubnet(0)` drains
808            // the bookkeeping. The stale slot is harmless (a `0` limit
809            // never refuses) and is released normally on session close.
810            return;
811        }
812        let subnet = self.subnet_key(&ip);
813        // Snapshot before the insert so the delta can be pair-asserted.
814        // Ungated `let`: read only inside the debug_assert! below, so it
815        // is optimised out in release (no E0425).
816        let count_before = self
817            .connections_per_cluster_subnet
818            .get(&cluster_id)
819            .and_then(|by_subnet| by_subnet.get(&subnet))
820            .copied()
821            .unwrap_or(0);
822        let inserted = self
823            .cluster_subnet_tracks
824            .entry(token)
825            .or_default()
826            .entry(cluster_id.clone())
827            .or_default()
828            .insert(subnet);
829        if inserted {
830            *self
831                .connections_per_cluster_subnet
832                .entry(cluster_id.clone())
833                .or_default()
834                .entry(subnet)
835                .or_insert(0) += 1;
836        }
837        debug_assert!(
838            self.cluster_subnet_tracks
839                .get(&token)
840                .and_then(|by_cluster| by_cluster.get(&cluster_id))
841                .is_some_and(|subnets| subnets.contains(&subnet)),
842            "track must leave the (token, cluster, subnet) recorded in the reverse index"
843        );
844        debug_assert_eq!(
845            self.connections_per_cluster_subnet
846                .get(&cluster_id)
847                .and_then(|by_subnet| by_subnet.get(&subnet))
848                .copied()
849                .unwrap_or(0),
850            count_before + inserted as usize,
851            "forward count must advance by exactly 1 on first track, 0 on a repeat"
852        );
853        #[cfg(debug_assertions)]
854        self.check_invariants();
855    }
856
857    /// Account `token`'s active connection against BOTH counters. The
858    /// companion of `cluster_connection_at_limit`: the two enforcement
859    /// points take the gate and the slot through one call each, so the
860    /// per-IP and per-subnet bookkeeping can never fall out of step
861    /// between the mux and raw TCP.
862    pub fn track_cluster_connection(
863        &mut self,
864        token: Token,
865        cluster_id: ClusterId,
866        ip: IpAddr,
867        subnet_override: Option<u64>,
868    ) {
869        self.track_cluster_ip(token, cluster_id.clone(), ip);
870        self.track_cluster_subnet(token, cluster_id, ip, subnet_override);
871    }
872
873    /// Drain every slot held by `token` — per-IP AND per-subnet — and
874    /// apply the matching decrements. Called on session teardown only —
875    /// there is no per-stream untrack because the limit is
876    /// per-connection, not per-stream. Removes empty inner maps so the
877    /// outer `connections_per_cluster_ip` does not retain
878    /// `(cluster_id, empty_map)` orphans across cluster lifetimes.
879    ///
880    /// The name is historical: this is the SINGLE release point every
881    /// protocol already calls on close (`HttpSession`, `HttpsSession`,
882    /// `TcpSession`), so the subnet counter is drained from here rather
883    /// than from a second call each of those three sites would have to
884    /// remember. A missed release leaks a slot until worker restart,
885    /// which is exactly the failure a second entry point would invite.
886    pub fn untrack_all_cluster_ip(&mut self, token: Token) {
887        self.untrack_all_cluster_subnet(token);
888        let Some(by_cluster) = self.cluster_ip_tracks.remove(&token) else {
889            return;
890        };
891        // The reverse index for this token was just drained by `remove`; no
892        // other code path re-inserts it within this call.
893        debug_assert!(
894            !self.cluster_ip_tracks.contains_key(&token),
895            "untrack_all must evict the token from the reverse index"
896        );
897        for (cluster_id, ips) in by_cluster {
898            let Entry::Occupied(mut outer) = self.connections_per_cluster_ip.entry(cluster_id)
899            else {
900                continue;
901            };
902            for ip in ips {
903                if let Entry::Occupied(mut inner) = outer.get_mut().entry(ip) {
904                    let count = inner.get_mut();
905                    *count = count.saturating_sub(1);
906                    if *count == 0 {
907                        inner.remove();
908                    }
909                }
910            }
911            if outer.get().is_empty() {
912                outer.remove();
913            }
914        }
915        // No orphan bookkeeping survives: the forward map must not retain a
916        // cluster with an empty inner ip-map, nor an ip whose count is zero.
917        debug_assert!(
918            self.connections_per_cluster_ip
919                .values()
920                .all(|by_ip| !by_ip.is_empty() && by_ip.values().all(|&c| c > 0)),
921            "untrack_all must not leave empty inner maps or zero-count ips behind"
922        );
923        #[cfg(debug_assertions)]
924        self.check_invariants();
925    }
926
927    /// Drain every `(cluster, subnet)` slot held by `token`. Mirror of
928    /// the per-IP drain in `untrack_all_cluster_ip`, which is its only
929    /// caller. A no-op when the subnet limiter was never enabled, since
930    /// nothing was tracked in the first place.
931    fn untrack_all_cluster_subnet(&mut self, token: Token) {
932        let Some(by_cluster) = self.cluster_subnet_tracks.remove(&token) else {
933            return;
934        };
935        debug_assert!(
936            !self.cluster_subnet_tracks.contains_key(&token),
937            "untrack_all must evict the token from the subnet reverse index"
938        );
939        for (cluster_id, subnets) in by_cluster {
940            let Entry::Occupied(mut outer) = self.connections_per_cluster_subnet.entry(cluster_id)
941            else {
942                continue;
943            };
944            for subnet in subnets {
945                if let Entry::Occupied(mut inner) = outer.get_mut().entry(subnet) {
946                    let count = inner.get_mut();
947                    *count = count.saturating_sub(1);
948                    if *count == 0 {
949                        inner.remove();
950                    }
951                }
952            }
953            if outer.get().is_empty() {
954                outer.remove();
955            }
956        }
957        debug_assert!(
958            self.connections_per_cluster_subnet
959                .values()
960                .all(|by_subnet| !by_subnet.is_empty() && by_subnet.values().all(|&c| c > 0)),
961            "untrack_all must not leave empty inner maps or zero-count subnets behind"
962        );
963    }
964
965    /// Whether both halves of the subnet accounting are empty.
966    ///
967    /// Exists so a test can assert that a DISABLED subnet limiter leaves
968    /// no bookkeeping behind at all — "inert" and "free" are different
969    /// claims, and only the second one is checkable from outside.
970    pub fn subnet_tracking_is_empty(&self) -> bool {
971        self.cluster_subnet_tracks.is_empty() && self.connections_per_cluster_subnet.is_empty()
972    }
973
974    /// Wipe every per-(cluster, source-SUBNET) accounting bucket.
975    /// Called by the runtime `SetMaxConnectionsPerSubnet(0)` path.
976    /// Deliberately does NOT touch the per-IP buckets: the two limiters
977    /// are independent, and disabling one must not silently reset the
978    /// other's live counters.
979    pub fn clear_cluster_subnet_tracking(&mut self) {
980        self.cluster_subnet_tracks.clear();
981        self.connections_per_cluster_subnet.clear();
982        debug_assert!(
983            self.cluster_subnet_tracks.is_empty() && self.connections_per_cluster_subnet.is_empty(),
984            "clear must wipe both the subnet reverse index and the subnet forward count map"
985        );
986        #[cfg(debug_assertions)]
987        self.check_invariants();
988    }
989
990    /// Wipe every per-(cluster, source-IP) accounting bucket. Called by
991    /// the runtime `SetMaxConnectionsPerIp(0)` path so disabling the
992    /// feature does not leave dead bookkeeping behind that a future
993    /// re-enable would consult.
994    pub fn clear_cluster_ip_tracking(&mut self) {
995        self.cluster_ip_tracks.clear();
996        self.connections_per_cluster_ip.clear();
997        // Both halves of the per-(cluster, ip) accounting are now empty; a
998        // future re-enable starts from a clean slate.
999        debug_assert!(
1000            self.cluster_ip_tracks.is_empty() && self.connections_per_cluster_ip.is_empty(),
1001            "clear must wipe both the reverse index and the forward count map"
1002        );
1003        #[cfg(debug_assertions)]
1004        self.check_invariants();
1005    }
1006
1007    /// The slab is considered at capacity if it contains more sessions than twice max_connections
1008    pub fn at_capacity(&self) -> bool {
1009        self.slab.len() >= self.accept_slab_threshold()
1010    }
1011
1012    /// The slab fill level at which `at_capacity` flips to true and the
1013    /// accept queue is flushed. Reported as `slab.accept_threshold` so the
1014    /// per-iteration `slab.accept_threshold_percent` gauge in the run loop
1015    /// can chart proximity to this gate, distinct from raw slab usage.
1016    ///
1017    /// The constant `10 + 2 * max_connections` is the historical pre-knob
1018    /// budget; configured slab capacity is
1019    /// `10 + slab_entries_per_connection * max_connections` (see
1020    /// `command/src/config.rs`) and can be larger, so `slab.usage_percent`
1021    /// (against `slab.capacity()`) and `slab.accept_threshold_percent`
1022    /// (against this gate) are emitted as independent gauges.
1023    pub fn accept_slab_threshold(&self) -> usize {
1024        let threshold = 10 + 2 * self.max_connections;
1025        // The gate must leave headroom above the connection cap so listener /
1026        // system slots (Channel, Metrics, Timer, listeners) are never starved
1027        // by frontend connections alone.
1028        debug_assert!(
1029            threshold > self.max_connections,
1030            "accept gate must sit strictly above max_connections to reserve system slots"
1031        );
1032        threshold
1033    }
1034
1035    /// Check the number of connections against max_connections, and the slab capacity.
1036    /// Returns false if limits are reached.
1037    pub fn check_limits(&mut self) -> bool {
1038        // Live-count invariant: the accounted connection count never exceeds
1039        // the configured cap (incr() enforces this, decr() never underflows).
1040        debug_assert!(
1041            self.nb_connections <= self.max_connections,
1042            "nb_connections must never exceed max_connections"
1043        );
1044        if self.nb_connections >= self.max_connections {
1045            error!("max number of session connection reached, flushing the accept queue");
1046            gauge!(names::accept_queue::BACKPRESSURE, 1);
1047            self.can_accept = false;
1048            // A negative result must have closed the accept gate.
1049            debug_assert!(
1050                !self.can_accept,
1051                "refusing at the cap must clear can_accept"
1052            );
1053            return false;
1054        }
1055
1056        if self.at_capacity() {
1057            error!("not enough memory to accept another session, flushing the accept queue");
1058            error!(
1059                "nb_connections: {}, max_connections: {}",
1060                self.nb_connections, self.max_connections
1061            );
1062            gauge!(names::accept_queue::BACKPRESSURE, 1);
1063            self.can_accept = false;
1064
1065            debug_assert!(
1066                !self.can_accept,
1067                "refusing at slab capacity must clear can_accept"
1068            );
1069            return false;
1070        }
1071
1072        // A positive result means there is room under both gates.
1073        debug_assert!(
1074            self.nb_connections < self.max_connections && !self.at_capacity(),
1075            "check_limits returned room while a gate was actually saturated"
1076        );
1077        true
1078    }
1079
1080    pub fn to_session(token: Token) -> SessionToken {
1081        SessionToken(token.0)
1082    }
1083
1084    pub fn incr(&mut self) {
1085        let before = self.nb_connections;
1086        self.nb_connections += 1;
1087        assert!(self.nb_connections <= self.max_connections);
1088        // The counter advances by exactly one per accepted session.
1089        debug_assert_eq!(
1090            self.nb_connections,
1091            before + 1,
1092            "incr must raise nb_connections by exactly one"
1093        );
1094        // `client.connections_max` and `client.connections_percent` are
1095        // emitted from the run loop alongside `process.uptime_seconds` /
1096        // `server.live` so all proxy gauges advance in lock-step. Keeping
1097        // `client.connections` per-event preserves the high-resolution
1098        // signal scrapers expect.
1099        gauge!(names::client::CONNECTIONS, self.nb_connections);
1100    }
1101
1102    /// Decrements the number of sessions, start accepting new connections
1103    /// if the capacity limit of 90% has not been reached.
1104    pub fn decr(&mut self) {
1105        assert!(self.nb_connections != 0);
1106        let before = self.nb_connections;
1107        self.nb_connections -= 1;
1108        // Mirror of incr: exactly one connection is released, no underflow.
1109        debug_assert_eq!(
1110            self.nb_connections,
1111            before - 1,
1112            "decr must lower nb_connections by exactly one"
1113        );
1114        gauge!(names::client::CONNECTIONS, self.nb_connections);
1115
1116        // do not be ready to accept right away, wait until we get back to 10% capacity
1117        if !self.can_accept && self.nb_connections < self.max_connections * 90 / 100 {
1118            debug!(
1119                "nb_connections = {}, max_connections = {}, starting to accept again",
1120                self.nb_connections, self.max_connections
1121            );
1122            gauge!(names::accept_queue::BACKPRESSURE, 0);
1123            self.can_accept = true;
1124        }
1125    }
1126
1127    /// Full cross-field invariant sweep for the session manager. Called as a
1128    /// `debug_assert!`-guarded postcondition from the mutating cluster-IP
1129    /// tracking methods. Asserts logic-bug conditions only — every clause
1130    /// holds on any well-formed manager regardless of traffic.
1131    #[cfg(debug_assertions)]
1132    fn check_invariants(&self) {
1133        // 1. Live connection count never exceeds the configured cap.
1134        debug_assert!(
1135            self.nb_connections <= self.max_connections,
1136            "nb_connections {} exceeds max_connections {}",
1137            self.nb_connections,
1138            self.max_connections
1139        );
1140        // 2. The forward count map never retains an empty inner ip-map or a
1141        //    zero count (both are reaped on the last untrack).
1142        debug_assert!(
1143            self.connections_per_cluster_ip
1144                .values()
1145                .all(|by_ip| !by_ip.is_empty() && by_ip.values().all(|&c| c > 0)),
1146            "connections_per_cluster_ip holds an empty inner map or a zero count"
1147        );
1148        // 3. Reverse-index → forward-count coherence: every (token, cluster,
1149        //    ip) recorded in the reverse index must have a positive forward
1150        //    count. The forward count is the sum of contributing tokens, so a
1151        //    tracked slot can never point at a missing/zero count.
1152        debug_assert!(
1153            self.cluster_ip_tracks.values().all(|by_cluster| {
1154                by_cluster.iter().all(|(cluster_id, ips)| {
1155                    ips.iter().all(|ip| {
1156                        self.connections_per_cluster_ip
1157                            .get(cluster_id)
1158                            .and_then(|by_ip| by_ip.get(ip))
1159                            .is_some_and(|&c| c > 0)
1160                    })
1161                })
1162            }),
1163            "a tracked (token, cluster, ip) slot has no positive forward count"
1164        );
1165        // 4. The reverse index never retains an empty inner structure.
1166        debug_assert!(
1167            self.cluster_ip_tracks.values().all(|by_cluster| {
1168                !by_cluster.is_empty() && by_cluster.values().all(|ips| !ips.is_empty())
1169            }),
1170            "cluster_ip_tracks retains an empty per-token or per-cluster entry"
1171        );
1172        // 5-7. The same three structural clauses, on the subnet mirror.
1173        debug_assert!(
1174            self.connections_per_cluster_subnet
1175                .values()
1176                .all(|by_subnet| !by_subnet.is_empty() && by_subnet.values().all(|&c| c > 0)),
1177            "connections_per_cluster_subnet holds an empty inner map or a zero count"
1178        );
1179        debug_assert!(
1180            self.cluster_subnet_tracks.values().all(|by_cluster| {
1181                by_cluster.iter().all(|(cluster_id, subnets)| {
1182                    subnets.iter().all(|subnet| {
1183                        self.connections_per_cluster_subnet
1184                            .get(cluster_id)
1185                            .and_then(|by_subnet| by_subnet.get(subnet))
1186                            .is_some_and(|&c| c > 0)
1187                    })
1188                })
1189            }),
1190            "a tracked (token, cluster, subnet) slot has no positive forward count"
1191        );
1192        debug_assert!(
1193            self.cluster_subnet_tracks.values().all(|by_cluster| {
1194                !by_cluster.is_empty() && by_cluster.values().all(|subnets| !subnets.is_empty())
1195            }),
1196            "cluster_subnet_tracks retains an empty per-token or per-cluster entry"
1197        );
1198    }
1199}
1200
1201#[derive(thiserror::Error, Debug)]
1202pub enum ServerError {
1203    #[error("could not create event loop with MIO poll: {0}")]
1204    CreatePoll(IoError),
1205    #[error("could not clone the MIO registry: {0}")]
1206    CloneRegistry(IoError),
1207    #[error("could not register the channel: {0}")]
1208    RegisterChannel(IoError),
1209    #[error("{msg}:{scm_err}")]
1210    ScmSocket {
1211        msg: String,
1212        scm_err: ScmSocketError,
1213    },
1214}
1215
1216/// `Server` handles the event loop, the listeners, the sessions and
1217/// communication with the configuration channel.
1218///
1219/// A listener wraps a listen socket, the associated proxying protocols
1220/// (HTTP, HTTPS and TCP) and the routing configuration for clusters.
1221/// Listeners handle creating sessions from accepted sockets.
1222///
1223/// A session manages a "front" socket for a connected client, and all
1224/// of the associated data (back socket, protocol state machine, buffers,
1225/// metrics...).
1226///
1227/// `Server` gets configuration updates from the channel (domIN/path routes,
1228/// backend server address...).
1229///
1230/// Listeners and sessions are all stored in a slab structure to index them
1231/// by a [Token], they all have to implement the [ProxySession] trait.
1232pub struct Server {
1233    accept_queue_timeout: Duration,
1234    /// Tuple layout: `(socket, listen token, protocol, accept time, peer
1235    /// address)`. The peer is the address `accept(2)` returned with the
1236    /// socket: it attributes the `client.connect.per_source.*` counter at
1237    /// accept time and is handed to `create_session`, so no one has to read it
1238    /// back with `getpeername(2)` (sozu-proxy/sozu#1586). Unlike that lookup,
1239    /// which fails once the peer has reset, `accept(2)` always reports it.
1240    accept_queue: VecDeque<(TcpStream, ListenToken, Protocol, Instant, SocketAddr)>,
1241    /// When the accept queue saturates and `check_limits` refuses, evict the
1242    /// oldest non-listener sessions to make room. Default off — see
1243    /// `command::config::DEFAULT_EVICT_ON_QUEUE_FULL` for the rationale.
1244    evict_on_queue_full: bool,
1245    accept_ready: HashSet<ListenToken>,
1246    backends: Rc<RefCell<BackendMap>>,
1247    base_sessions_count: usize,
1248    channel: ProxyChannel,
1249    config_state: ConfigState,
1250    current_poll_errors: i32,
1251    health_checker: HealthChecker,
1252    http: Rc<RefCell<http::HttpProxy>>,
1253    https: Rc<RefCell<https::HttpsProxy>>,
1254    last_sessions_len: usize,
1255    last_shutting_down_message: Option<Instant>,
1256    last_zombie_check: Instant,
1257    loop_start: Instant,
1258    /// Wall-clock anchor for the `process.uptime_seconds` gauge. Captured once
1259    /// in [`Server::new`]; never reset on hot upgrades (the new worker that
1260    /// inherits FDs is a fresh process and starts its own counter).
1261    started_at: Instant,
1262    /// Last time the 1Hz `accept_queue.saturated_seconds` ticker fired. The
1263    /// counter is incremented once per [`ACCEPT_SATURATION_TICK`] while
1264    /// `SessionManager::can_accept` is `false`, so dashboards can plot the
1265    /// time spent saturated rather than just whether saturation occurred.
1266    last_saturation_tick: Instant,
1267    max_poll_errors: i32, // TODO: make this configurable? this defaults to 10000 for now
1268    /// Shared reference to the buffer pool the protocol stacks check buffers
1269    /// in and out of. Held here so the run loop can sample
1270    /// `buffer.in_use` / `buffer.capacity` / `buffer.usage_percent` once per
1271    /// iteration without requiring each protocol module to expose its own
1272    /// snapshot.
1273    pool: Rc<RefCell<Pool>>,
1274    pub poll: Poll,
1275    poll_timeout: Option<Duration>, // TODO: make this configurable? this defaults to 1000 milliseconds for now
1276    scm_listeners: Option<Listeners>,
1277    scm: ScmSocket,
1278    sessions: Rc<RefCell<SessionManager>>,
1279    should_poll_at: Option<Instant>,
1280    shutting_down: Option<String>,
1281    tcp: Rc<RefCell<tcp::TcpProxy>>,
1282    udp: Rc<RefCell<udp::UdpProxy>>,
1283    zombie_check_interval: Duration,
1284}
1285
1286impl Server {
1287    pub fn try_new_from_config(
1288        worker_to_main_channel: ProxyChannel,
1289        worker_to_main_scm: ScmSocket,
1290        config: ServerConfig,
1291        initial_state: InitialState,
1292        expects_initial_status: bool,
1293    ) -> Result<Self, ServerError> {
1294        let event_loop = Poll::new().map_err(ServerError::CreatePoll)?;
1295        // Commit the operator-configured Basic-auth credential cap (or
1296        // keep the built-in default) once per worker process. The
1297        // `OnceLock` rejects any later attempt to change the value, so
1298        // calling here — before any L7 listener has accepted a request
1299        // — guarantees the cap is in force the first time `mux::auth`
1300        // runs.
1301        if let Some(cap) = config.basic_auth_max_credential_bytes {
1302            crate::protocol::mux::auth::set_max_decoded_credential_bytes(cap as usize);
1303        }
1304        // Same set-once-per-worker-boot pattern for the splice kernel-pipe
1305        // capacity. The setter no-ops on `0` so an explicit zero in config
1306        // does not collapse the pipe to PAGE_SIZE; the kernel still applies
1307        // page-rounding and `/proc/sys/fs/pipe-max-size` clamping at
1308        // SplicePipe::new time. Cfg-gated because the splice module only
1309        // exists on Linux + `splice` feature.
1310        #[cfg(all(target_os = "linux", feature = "splice"))]
1311        if let Some(cap) = config.splice_pipe_capacity_bytes {
1312            crate::splice::set_pipe_capacity(cap as usize);
1313        }
1314        let pool = Rc::new(RefCell::new(Pool::with_capacity(
1315            config.min_buffers as usize,
1316            config.max_buffers as usize,
1317            config.buffer_size as usize,
1318        )));
1319        let backends = Rc::new(RefCell::new(BackendMap::new()));
1320
1321        // Note: slab_capacity uses 4x multiplier (up from 2x) to account for H2
1322        // multiplexing where each session can have multiple backend connections.
1323        // Newer `optional` proto fields fall through to the
1324        // command-lib defaults when an older worker manager omits them.
1325        let sessions: Rc<RefCell<SessionManager>> = SessionManager::new(
1326            Slab::with_capacity(config.slab_capacity() as usize),
1327            config.max_connections as usize,
1328            config
1329                .max_connections_per_ip
1330                .unwrap_or(sozu_command::config::DEFAULT_MAX_CONNECTIONS_PER_IP),
1331            config
1332                .retry_after
1333                .unwrap_or(sozu_command::config::DEFAULT_RETRY_AFTER),
1334            config
1335                .max_connections_per_subnet
1336                .unwrap_or(sozu_command::config::DEFAULT_MAX_CONNECTIONS_PER_SUBNET),
1337            config
1338                .subnet_ipv4_prefix
1339                .unwrap_or(sozu_command::config::DEFAULT_SUBNET_IPV4_PREFIX),
1340            config
1341                .subnet_ipv6_prefix
1342                .unwrap_or(sozu_command::config::DEFAULT_SUBNET_IPV6_PREFIX),
1343        );
1344        // Newer field, so an older main may omit it. The main validates it at
1345        // config load; a value that still breaks the bounds keeps the default
1346        // rather than disabling retries or overflowing the attempt counters.
1347        sessions.borrow_mut().max_connection_attempts = match config.max_connection_attempts {
1348            Some(attempts)
1349                if sozu_command::config::validate_max_connection_attempts(attempts).is_ok() =>
1350            {
1351                attempts
1352            }
1353            Some(attempts) => {
1354                error!(
1355                    "max_connection_attempts = {} is out of 1..=255, keeping {}",
1356                    attempts,
1357                    sozu_command::config::DEFAULT_MAX_CONNECTION_ATTEMPTS
1358                );
1359                sozu_command::config::DEFAULT_MAX_CONNECTION_ATTEMPTS
1360            }
1361            None => sozu_command::config::DEFAULT_MAX_CONNECTION_ATTEMPTS,
1362        };
1363        {
1364            let mut s = sessions.borrow_mut();
1365            let entry = s.slab.vacant_entry();
1366            trace!("taking token {:?} for channel", SessionToken(entry.key()));
1367            entry.insert(Rc::new(RefCell::new(ListenSession {
1368                protocol: Protocol::Channel,
1369            })));
1370        }
1371        {
1372            let mut s = sessions.borrow_mut();
1373            let entry = s.slab.vacant_entry();
1374            trace!("taking token {:?} for metrics", SessionToken(entry.key()));
1375            entry.insert(Rc::new(RefCell::new(ListenSession {
1376                protocol: Protocol::Timer,
1377            })));
1378        }
1379        {
1380            let mut s = sessions.borrow_mut();
1381            let entry = s.slab.vacant_entry();
1382            trace!("taking token {:?} for metrics", SessionToken(entry.key()));
1383            entry.insert(Rc::new(RefCell::new(ListenSession {
1384                protocol: Protocol::Metrics,
1385            })));
1386        }
1387
1388        Server::new(
1389            event_loop,
1390            worker_to_main_channel,
1391            worker_to_main_scm,
1392            sessions,
1393            pool,
1394            backends,
1395            None,
1396            None,
1397            None,
1398            config,
1399            Some(initial_state),
1400            expects_initial_status,
1401        )
1402    }
1403
1404    #[allow(clippy::too_many_arguments)]
1405    pub fn new(
1406        poll: Poll,
1407        mut channel: ProxyChannel,
1408        scm: ScmSocket,
1409        sessions: Rc<RefCell<SessionManager>>,
1410        pool: Rc<RefCell<Pool>>,
1411        backends: Rc<RefCell<BackendMap>>,
1412        http: Option<http::HttpProxy>,
1413        https: Option<https::HttpsProxy>,
1414        tcp: Option<tcp::TcpProxy>,
1415        server_config: ServerConfig,
1416        initial_state: Option<InitialState>,
1417        expects_initial_status: bool,
1418    ) -> Result<Self, ServerError> {
1419        FEATURES.with(|_features| {
1420            // initializing feature flags
1421        });
1422
1423        poll.registry()
1424            .register(
1425                &mut channel,
1426                Token(0),
1427                Interest::READABLE | Interest::WRITABLE,
1428            )
1429            .map_err(ServerError::RegisterChannel)?;
1430
1431        METRICS.with(|metrics| {
1432            if let Some(sock) = (*metrics.borrow_mut()).socket_mut() {
1433                poll.registry()
1434                    .register(sock, Token(2), Interest::WRITABLE)
1435                    .expect("should register the metrics socket");
1436            }
1437        });
1438
1439        let base_sessions_count = sessions.borrow().slab.len();
1440
1441        let http = Rc::new(RefCell::new(match http {
1442            Some(http) => http,
1443            None => {
1444                let registry = poll
1445                    .registry()
1446                    .try_clone()
1447                    .map_err(ServerError::CloneRegistry)?;
1448
1449                http::HttpProxy::new(registry, sessions.clone(), pool.clone(), backends.clone())
1450            }
1451        }));
1452
1453        let https = Rc::new(RefCell::new(match https {
1454            Some(https) => https,
1455            None => {
1456                let registry = poll
1457                    .registry()
1458                    .try_clone()
1459                    .map_err(ServerError::CloneRegistry)?;
1460
1461                https::HttpsProxy::new(registry, sessions.clone(), pool.clone(), backends.clone())
1462            }
1463        }));
1464
1465        let tcp = Rc::new(RefCell::new(match tcp {
1466            Some(tcp) => tcp,
1467            None => {
1468                let registry = poll
1469                    .registry()
1470                    .try_clone()
1471                    .map_err(ServerError::CloneRegistry)?;
1472
1473                tcp::TcpProxy::new(registry, sessions.clone(), pool.clone(), backends.clone())
1474            }
1475        }));
1476
1477        // UDP proxy is constructed internally (no constructor parameter) so the
1478        // public `Server::new` / `try_new_from_config` signatures stay
1479        // unchanged — bin/ is not forced to change for construction.
1480        let udp = Rc::new(RefCell::new({
1481            let registry = poll
1482                .registry()
1483                .try_clone()
1484                .map_err(ServerError::CloneRegistry)?;
1485
1486            udp::UdpProxy::new(
1487                registry,
1488                sessions.clone(),
1489                pool.clone(),
1490                backends.clone(),
1491                server_config.max_connections as usize,
1492                server_config.buffer_size as usize,
1493            )
1494        }));
1495
1496        let mut server = Server {
1497            accept_queue_timeout: Duration::from_secs(u64::from(
1498                server_config.accept_queue_timeout,
1499            )),
1500            accept_queue: VecDeque::new(),
1501            evict_on_queue_full: server_config.evict_on_queue_full.unwrap_or(false),
1502            accept_ready: HashSet::new(),
1503            backends,
1504            base_sessions_count,
1505            channel,
1506            config_state: ConfigState::new(),
1507            current_poll_errors: 0,
1508            health_checker: HealthChecker::new(),
1509            http,
1510            https,
1511            last_sessions_len: 0, // to be reset on server run
1512            last_shutting_down_message: None,
1513            last_zombie_check: Instant::now(), // to be reset on server run
1514            loop_start: Instant::now(),        // to be reset on server run
1515            started_at: Instant::now(),        // captured once, never reset
1516            last_saturation_tick: Instant::now(), // 1Hz saturation ticker anchor
1517            max_poll_errors: 10000,            // TODO: make it configurable?
1518            pool,
1519            poll_timeout: Some(Duration::from_millis(1000)), // TODO: make it configurable?
1520            poll,
1521            scm_listeners: None,
1522            scm,
1523            sessions,
1524            should_poll_at: None,
1525            shutting_down: None,
1526            tcp,
1527            udp,
1528            zombie_check_interval: Duration::from_secs(u64::from(
1529                server_config.zombie_check_interval,
1530            )),
1531        };
1532
1533        // Take the retiring worker's listening sockets BEFORE applying the
1534        // initial state, because that state is what adopts them.
1535        //
1536        // `fork_main_into_worker` (`bin/src/worker.rs`) sends the descriptors
1537        // over the SCM socket right after the fork — before the main process
1538        // sends the initial `Status` request — and
1539        // `ConfigState::produce_initial_state` carries one `ActivateListener`
1540        // per active listener. Receiving them afterwards left `scm_listeners`
1541        // empty for the whole initial state, so every `ActivateListener` took
1542        // the `server_bind` / `udp_bind` branch (which succeeds only because
1543        // of `SO_REUSEPORT`), the listener came up `active`, and the inherited
1544        // descriptor was then dropped unused by the next, short-circuiting
1545        // `activate()` — discarding the accept backlog (TCP/HTTP/HTTPS) and
1546        // the receive buffer (UDP) the hand-off exists to preserve (sozu#1342).
1547        //
1548        // Blocking here is the same blocking read as before, only earlier: the
1549        // sole production caller of `fork_main_into_worker`,
1550        // `CommandServer::launch_new_worker`, always passes `Some(listeners)`,
1551        // so a fresh start receives an empty manifest immediately and every
1552        // listener binds its own socket exactly as it always did.
1553        info!("will try to receive listeners");
1554        server
1555            .scm
1556            .set_blocking(true)
1557            .map_err(|scm_err| ServerError::ScmSocket {
1558                msg: "Could not set the scm socket to blocking".to_string(),
1559                scm_err,
1560            })?;
1561        let listeners =
1562            server
1563                .scm
1564                .receive_listeners()
1565                .map_err(|scm_err| ServerError::ScmSocket {
1566                    msg: "could not receive listeners from the scm socket".to_string(),
1567                    scm_err,
1568                })?;
1569        server
1570            .scm
1571            .set_blocking(false)
1572            .map_err(|scm_err| ServerError::ScmSocket {
1573                msg: "Could not set the scm socket to unblocking".to_string(),
1574                scm_err,
1575            })?;
1576        info!("received listeners: {:?}", listeners);
1577        server.scm_listeners = Some(listeners);
1578
1579        // initialize the worker with the state we got from a file
1580        if let Some(state) = initial_state {
1581            for request in state.requests {
1582                trace!("generating initial config request: {:#?}", request);
1583                server.notify_proxys(request);
1584            }
1585
1586            // do not send back answers to the initialization messages
1587            QUEUE.with(|queue| {
1588                (*queue.borrow_mut()).clear();
1589            });
1590        }
1591
1592        server.report_unclaimed_inherited_sockets();
1593
1594        if expects_initial_status {
1595            // the main process sends a Status message, so we can notify it
1596            // when the initial state is loaded
1597            server.block_channel();
1598            let msg = server.channel.read_message();
1599            debug!("got message: {:?}", msg);
1600
1601            if let Ok(WorkerRequest {
1602                id,
1603                content:
1604                    Request {
1605                        request_type: Some(RequestType::Status(_)),
1606                    },
1607            }) = msg
1608            {
1609                if let Err(e) = server.channel.write_message(&WorkerResponse::ok(id)) {
1610                    error!("Could not send an ok to the main process: {}", e);
1611                }
1612            } else {
1613                panic!(
1614                    "plz give me a status request first when I start, you sent me this instead: {msg:?}"
1615                );
1616            }
1617            server.unblock_channel();
1618        }
1619
1620        Ok(server)
1621    }
1622
1623    /// The server runs in a loop until a shutdown is ordered
1624    pub fn run(&mut self) {
1625        let mut events = Events::with_capacity(1024); // TODO: make event capacity configurable?
1626        // The session owning each event's token when `epoll_wait` returned,
1627        // by event position; see `Server::ready_if_still_owned`. Reused
1628        // across turns so the loop allocates it once.
1629        let mut event_owners: Vec<Option<Rc<RefCell<dyn ProxySession>>>> =
1630            Vec::with_capacity(events.capacity());
1631        self.last_sessions_len = self.sessions.borrow().slab.len();
1632
1633        self.last_zombie_check = Instant::now();
1634        self.loop_start = Instant::now();
1635
1636        loop {
1637            self.check_for_poll_errors();
1638
1639            let timeout = self.reset_loop_time_and_get_timeout();
1640
1641            match self.poll.poll(&mut events, timeout) {
1642                Ok(_) => self.current_poll_errors = 0,
1643                Err(error) => {
1644                    error!("Error while polling events: {:?}", error);
1645                    self.current_poll_errors += 1;
1646                    continue;
1647                }
1648            }
1649
1650            let after_epoll = Instant::now();
1651            time!(
1652                names::event_loop::EPOLL_TIME,
1653                (after_epoll - self.loop_start).as_millis()
1654            );
1655            self.loop_start = after_epoll;
1656
1657            // Before `send_queue`, so an expiry event leaves on this turn.
1658            tick_metric_detail_leases(after_epoll);
1659
1660            self.send_queue();
1661
1662            self.snapshot_event_owners(events.iter().map(|event| event.token()), &mut event_owners);
1663            for (event, owner) in events.iter().zip(event_owners.drain(..)) {
1664                match event.token() {
1665                    // this is the command channel
1666                    Token(0) => {
1667                        if event.is_error() {
1668                            error!("error reading from command channel");
1669                            continue;
1670                        }
1671                        if event.is_read_closed() || event.is_write_closed() {
1672                            error!("command channel was closed");
1673                            return;
1674                        }
1675                        let ready = Ready::from(event);
1676                        self.channel.handle_events(ready);
1677
1678                        // loop here because iterations has borrow issues
1679                        loop {
1680                            QUEUE.with(|queue| {
1681                                if !(*queue.borrow()).is_empty() {
1682                                    self.channel.interest.insert(Ready::WRITABLE);
1683                                }
1684                            });
1685
1686                            //trace!("WORKER[{}] channel readiness={:?}, interest={:?}, queue={} elements",
1687                            //  line!(), self.channel.readiness, self.channel.interest, self.queue.len());
1688                            if self.channel.readiness() == Ready::EMPTY {
1689                                break;
1690                            }
1691
1692                            // exit the big loop if the message is HardStop
1693                            if self.read_channel_messages_and_notify() {
1694                                return;
1695                            }
1696
1697                            QUEUE.with(|queue| {
1698                                if !(*queue.borrow()).is_empty() {
1699                                    self.channel.interest.insert(Ready::WRITABLE);
1700                                }
1701                            });
1702
1703                            self.send_queue();
1704                        }
1705                    }
1706                    // timer tick
1707                    Token(1) => {
1708                        while let Some(t) = TIMER.with(|timer| timer.borrow_mut().poll()) {
1709                            self.timeout(t);
1710                        }
1711                    }
1712                    // metrics socket is writable
1713                    Token(2) => METRICS.with(|metrics| {
1714                        (*metrics.borrow_mut()).writable();
1715                    }),
1716                    // ListenToken: 1 listener <=> 1 token
1717                    // ProtocolToken (HTTP/HTTPS/TCP): 1 connection <=> 1 token
1718                    token if self.health_checker.owns_token(token) => {
1719                        self.health_checker.ready(token);
1720                    }
1721                    token if self.udp.borrow().health_owns_token(token) => {
1722                        self.udp.borrow_mut().health_ready(token);
1723                    }
1724                    token => self.ready_if_still_owned(token, Ready::from(event), owner),
1725                }
1726            }
1727
1728            if let Some(t) = self.should_poll_at.as_ref()
1729                && *t <= Instant::now()
1730            {
1731                while let Some(t) = TIMER.with(|timer| timer.borrow_mut().poll()) {
1732                    //info!("polled for timeout: {:?}", t);
1733                    self.timeout(t);
1734                }
1735            }
1736            self.handle_remaining_readiness();
1737            self.create_sessions();
1738
1739            self.should_poll_at = TIMER.with(|timer| timer.borrow().next_poll_date());
1740
1741            self.zombie_check();
1742            self.health_checker
1743                .poll(&self.backends, self.poll.registry());
1744            // Drive the UDP endpoint health prober (TCP-probe + hysteresis +
1745            // fail-open). Non-blocking; no-op when no UDP cluster has health
1746            // configured.
1747            self.udp.borrow_mut().health_poll();
1748
1749            // Frontend session gauges. `client.connections` keeps the
1750            // per-event signal from `SessionManager::incr/decr`; the rest
1751            // follow the once-per-iteration batching contract this run loop
1752            // uses for `process.uptime_seconds` / `server.live`.
1753            //
1754            // `slab.usage_percent` charts pure slab utilisation against
1755            // `slab.capacity()`. `slab.accept_threshold_percent` charts how
1756            // close the slab is to the `at_capacity()` accept gate
1757            // (`10 + 2 * max_connections`, see
1758            // `SessionManager::accept_slab_threshold`). Configured slab
1759            // capacity can be larger than that gate (`slab_entries_per_connection`
1760            // > 2), so the two gauges are kept independent on purpose.
1761            {
1762                let sessions = self.sessions.borrow();
1763                let nb_connections = sessions.nb_connections;
1764                let max_connections = sessions.max_connections;
1765                let slab_len = sessions.slab.len();
1766                let slab_capacity = sessions.slab.capacity();
1767                let accept_threshold = sessions.accept_slab_threshold();
1768
1769                gauge!(names::client::CONNECTIONS, nb_connections);
1770                gauge!(names::client::CONNECTIONS_MAX, max_connections);
1771                if let Some(percent) = (nb_connections * 100).checked_div(max_connections) {
1772                    gauge!(names::client::CONNECTIONS_PERCENT, percent);
1773                }
1774
1775                gauge!(names::slab::ENTRIES, slab_len);
1776                gauge!(names::slab::CAPACITY, slab_capacity);
1777                if let Some(percent) = (slab_len * 100).checked_div(slab_capacity) {
1778                    gauge!(names::slab::USAGE_PERCENT, percent);
1779                }
1780                if let Some(percent) = (slab_len * 100).checked_div(accept_threshold) {
1781                    gauge!(names::slab::ACCEPT_THRESHOLD_PERCENT, percent);
1782                }
1783            }
1784            // Buffer pool gauges. `buffer.in_use` replaces the older
1785            // `buffer.number` (renamed in `lib/src/pool.rs` for naming
1786            // consistency with the surrounding `buffer.*` keys).
1787            // `buffer.usage_percent` is computed against the configured
1788            // `buffer.capacity` so dashboards can chart pool pressure.
1789            {
1790                let pool = self.pool.borrow();
1791                let used = pool.inner.used();
1792                let capacity = pool.inner.capacity();
1793                gauge!(names::buffer::IN_USE, used);
1794                gauge!(names::buffer::CAPACITY, capacity);
1795                if let Some(percent) = (used * 100).checked_div(capacity) {
1796                    gauge!(names::buffer::USAGE_PERCENT, percent);
1797                }
1798            }
1799            // 1Hz tick for `accept_queue.saturated_seconds`. Increments once
1800            // per `ACCEPT_SATURATION_TICK` while `SessionManager::can_accept`
1801            // is `false`. Distinguishes "queue spent N seconds at max" from
1802            // "queue briefly hit max" — the binary `accept_queue.backpressure`
1803            // gauge collapses that duration. Sampled here rather than via a
1804            // dedicated mio timer because the run loop ticks at least once
1805            // per `poll_timeout` (1s by default), which is granular enough.
1806            let now = Instant::now();
1807            if now.duration_since(self.last_saturation_tick) >= ACCEPT_SATURATION_TICK {
1808                if !self.sessions.borrow().can_accept {
1809                    incr!(names::accept_queue::SATURATED_SECONDS);
1810                }
1811                self.last_saturation_tick = now;
1812            }
1813            // Process / runtime gauges sampled once per loop iteration. Same
1814            // batch as `client.connections` so dashboards see them update in
1815            // lock-step.
1816            gauge!(
1817                names::process::UPTIME_SECONDS,
1818                self.started_at.elapsed().as_secs() as usize
1819            );
1820            // `server.live` flips to 0 once a graceful shutdown is requested,
1821            // matching Envoy's `server.live` semantics. L4 health checks
1822            // (HAProxy / cloud LBs) can poll this gauge to drain a worker
1823            // before the OS-level termination signal lands.
1824            gauge!(
1825                names::server::LIVE,
1826                if self.shutting_down.is_some() { 0 } else { 1 }
1827            );
1828            METRICS.with(|metrics| {
1829                (*metrics.borrow_mut()).send_data();
1830            });
1831
1832            if self.shutting_down.is_some() && self.shut_down_sessions() {
1833                return;
1834            }
1835        }
1836    }
1837
1838    fn check_for_poll_errors(&mut self) {
1839        if self.current_poll_errors >= self.max_poll_errors {
1840            error!(
1841                "Something is going very wrong. Last {} poll() calls failed, crashing..",
1842                self.current_poll_errors
1843            );
1844            panic!(
1845                "poll() calls failed {} times in a row",
1846                self.current_poll_errors
1847            );
1848        }
1849    }
1850
1851    fn reset_loop_time_and_get_timeout(&mut self) -> Option<Duration> {
1852        let now = Instant::now();
1853        time!(
1854            names::event_loop::EVENT_LOOP_TIME,
1855            (now - self.loop_start).as_millis()
1856        );
1857
1858        let mut timeout = match self.should_poll_at.as_ref() {
1859            None => self.poll_timeout,
1860            Some(i) => {
1861                if *i <= now {
1862                    self.poll_timeout
1863                } else {
1864                    let dur = *i - now;
1865                    match self.poll_timeout {
1866                        None => Some(dur),
1867                        Some(t) => {
1868                            if t < dur {
1869                                Some(t)
1870                            } else {
1871                                Some(dur)
1872                            }
1873                        }
1874                    }
1875                }
1876            }
1877        };
1878
1879        if self.shutting_down.is_some() {
1880            let shutdown_tick = Duration::from_millis(100);
1881            timeout = match timeout {
1882                None => Some(shutdown_tick),
1883                Some(current) => Some(current.min(shutdown_tick)),
1884            };
1885        }
1886
1887        self.loop_start = now;
1888        timeout
1889    }
1890
1891    /// Returns true if hardstop
1892    fn read_channel_messages_and_notify(&mut self) -> bool {
1893        if !self.channel.readiness().is_readable() {
1894            return false;
1895        }
1896
1897        if let Err(e) = self.channel.readable() {
1898            error!("error reading from channel: {:?}", e);
1899        }
1900
1901        loop {
1902            let request = self.channel.read_message();
1903            debug!("Received request {:?}", request);
1904            match request {
1905                Ok(request) => match request.content.request_type {
1906                    Some(RequestType::HardStop(_)) => {
1907                        let req_id = request.id.clone();
1908                        self.notify(request);
1909                        if let Err(e) = self.channel.write_message(&WorkerResponse::ok(req_id)) {
1910                            error!("Could not send ok response to the main process: {}", e);
1911                        }
1912                        if let Err(e) = self.channel.run() {
1913                            error!("Error while running the server channel: {}", e);
1914                        }
1915                        return true;
1916                    }
1917                    Some(RequestType::SoftStop(_)) => {
1918                        self.shutting_down = Some(request.id.clone());
1919                        self.last_sessions_len = self.sessions.borrow().slab.len();
1920                        self.notify(request);
1921                    }
1922                    Some(RequestType::ReturnListenSockets(_)) => {
1923                        info!("received ReturnListenSockets order");
1924                        match self.return_listen_sockets() {
1925                            Ok(_) => push_queue(WorkerResponse::ok(request.id)),
1926                            Err(error) => push_queue(worker_response_error(
1927                                request.id,
1928                                format!("Could not send listeners on scm socket: {error:?}"),
1929                            )),
1930                        }
1931                    }
1932                    _ => self.notify(request),
1933                },
1934                // Not an error per se, occurs when there is nothing to read
1935                Err(_) => {
1936                    // if the message was too large, we grow the buffer and retry to read if possible
1937                    if (self.channel.interest & self.channel.readiness).is_readable() {
1938                        if let Err(e) = self.channel.readable() {
1939                            error!("error reading from channel: {:?}", e);
1940                        }
1941                        continue;
1942                    }
1943                    break;
1944                }
1945            }
1946        }
1947        false
1948    }
1949
1950    /// Scans all sessions that have been inactive for longer than the configured interval
1951    fn zombie_check(&mut self) {
1952        let now = Instant::now();
1953        if now - self.last_zombie_check < self.zombie_check_interval {
1954            return;
1955        }
1956        info!("zombie check");
1957        // `now` is sampled this iteration and we only get here past the
1958        // interval gate, so the check timestamp advances monotonically.
1959        debug_assert!(
1960            now >= self.last_zombie_check,
1961            "zombie-check timestamp must never move backwards"
1962        );
1963        self.last_zombie_check = now;
1964
1965        let mut zombie_tokens = HashSet::new();
1966
1967        // find the zombie sessions
1968        for (_index, session) in self
1969            .sessions
1970            .borrow_mut()
1971            .slab
1972            .iter_mut()
1973            .filter(|(_, c)| now - c.borrow().last_event() > self.zombie_check_interval)
1974        {
1975            let session_token = session.borrow().frontend_token();
1976            if !zombie_tokens.contains(&session_token) {
1977                session.borrow().print_session();
1978                zombie_tokens.insert(session_token);
1979            }
1980        }
1981
1982        // Listen/system sessions report `Instant::now()` as their last event,
1983        // so `now - last_event` is ~0 and never exceeds the interval: a
1984        // listener can never be collected as a zombie. Assert the set is free
1985        // of listen protocols before we reap it.
1986        debug_assert!(
1987            !self.sessions.borrow().slab.iter().any(|(_, session)| {
1988                let s = session.borrow();
1989                zombie_tokens.contains(&s.frontend_token())
1990                    && matches!(
1991                        s.protocol(),
1992                        Protocol::HTTPListen | Protocol::HTTPSListen | Protocol::TCPListen
1993                    )
1994            }),
1995            "zombie reaping must never target a listener session"
1996        );
1997
1998        let zombie_count = zombie_tokens.len() as i64;
1999        count!(names::misc::ZOMBIES, zombie_count);
2000
2001        let remaining_count = self.shut_down_sessions_by_frontend_tokens(zombie_tokens);
2002        info!(
2003            "removing {} zombies ({} remaining entries after close)",
2004            zombie_count, remaining_count
2005        );
2006    }
2007
2008    /// Calls close on targeted sessions, yields the number of entries in the slab
2009    /// that were not properly removed
2010    fn shut_down_sessions_by_frontend_tokens(&self, tokens: HashSet<Token>) -> usize {
2011        if tokens.is_empty() {
2012            return 0;
2013        }
2014
2015        // close the sessions associated with the tokens
2016        for token in &tokens {
2017            if self.sessions.borrow().slab.contains(token.0) {
2018                let slab_before = self.sessions.borrow().slab.len();
2019                let session = { self.sessions.borrow_mut().slab.remove(token.0) };
2020                session.borrow_mut().close();
2021                self.sessions.borrow_mut().decr();
2022                // The removed token is truly gone afterwards. The slab may shrink
2023                // by MORE than one: `close()` also frees the session's backend
2024                // slab slot(s) (the multi-token pattern), so assert it shrank by
2025                // at least the frontend slot we just removed, not exactly one.
2026                debug_assert!(
2027                    !self.sessions.borrow().slab.contains(token.0),
2028                    "removed token must be absent from the slab"
2029                );
2030                debug_assert!(
2031                    self.sessions.borrow().slab.len() < slab_before,
2032                    "removing a present session must free at least its own slab slot"
2033                );
2034            }
2035        }
2036
2037        // find the entries of closed sessions in the session manager (they should not be there)
2038        let mut dangling_entries = HashSet::new();
2039        for (entry_key, session) in &self.sessions.borrow().slab {
2040            if tokens.contains(&session.borrow().frontend_token()) {
2041                dangling_entries.insert(entry_key);
2042            }
2043        }
2044
2045        // remove these from the session manager
2046        let mut dangling_entries_count = 0;
2047        for entry_key in dangling_entries {
2048            let mut sessions = self.sessions.borrow_mut();
2049            if sessions.slab.contains(entry_key) {
2050                sessions.slab.remove(entry_key);
2051                dangling_entries_count += 1;
2052            }
2053        }
2054        // Postcondition: no surviving slab entry still references any of the
2055        // closed frontend tokens — both the direct remove and the dangling
2056        // sweep together leave the slab clean of these sessions.
2057        debug_assert!(
2058            !self
2059                .sessions
2060                .borrow()
2061                .slab
2062                .iter()
2063                .any(|(_, session)| tokens.contains(&session.borrow().frontend_token())),
2064            "no slab entry may reference a closed frontend token after teardown"
2065        );
2066        dangling_entries_count
2067    }
2068
2069    /// Order sessions to shut down, check that they are all down
2070    fn shut_down_sessions(&mut self) -> bool {
2071        let sessions_count = self.sessions.borrow().slab.len();
2072        let mut sessions_to_shut_down = HashSet::new();
2073
2074        for (_key, session) in &self.sessions.borrow().slab {
2075            let mut session = session.borrow_mut();
2076            if session.shutting_down() {
2077                debug!(
2078                    "Server killing session from shutting_down: token={:?}, protocol={:?}",
2079                    session.frontend_token(),
2080                    session.protocol()
2081                );
2082                sessions_to_shut_down.insert(Token(session.frontend_token().0));
2083            }
2084        }
2085        let _ = self.shut_down_sessions_by_frontend_tokens(sessions_to_shut_down);
2086
2087        let new_sessions_count = self.sessions.borrow().slab.len();
2088
2089        if new_sessions_count < sessions_count {
2090            let now = Instant::now();
2091            if let Some(last) = self.last_shutting_down_message
2092                && (now - last) > Duration::from_secs(5)
2093            {
2094                info!(
2095                    "closed {} sessions, {} sessions left, base_sessions_count = {}",
2096                    sessions_count - new_sessions_count,
2097                    new_sessions_count,
2098                    self.base_sessions_count
2099                );
2100            }
2101            self.last_shutting_down_message = Some(now);
2102        }
2103
2104        if new_sessions_count <= self.base_sessions_count {
2105            info!("last session stopped, shutting down!");
2106            if let Err(e) = self.channel.run() {
2107                error!("Error while running the server channel: {}", e);
2108            }
2109            // self.block_channel();
2110            let id = self
2111                .shutting_down
2112                .take()
2113                .expect("should have shut down correctly"); // panicking here makes sense actually
2114
2115            debug!("Responding OK to main process for request {}", id);
2116
2117            let proxy_response = WorkerResponse::ok(id);
2118            if let Err(e) = self.channel.write_message(&proxy_response) {
2119                error!("Could not write response to the main process: {}", e);
2120            }
2121            if let Err(e) = self.channel.run() {
2122                error!("Error while running the server channel: {}", e);
2123            }
2124            return true;
2125        }
2126
2127        if new_sessions_count < self.last_sessions_len {
2128            info!(
2129                "shutting down, {} slab elements remaining (base: {})",
2130                new_sessions_count - self.base_sessions_count,
2131                self.base_sessions_count
2132            );
2133            self.last_sessions_len = new_sessions_count;
2134        }
2135
2136        false
2137    }
2138
2139    fn kill_session(&self, session: Rc<RefCell<dyn ProxySession>>) {
2140        let token = session.borrow().frontend_token();
2141        let _ = self.shut_down_sessions_by_frontend_tokens(HashSet::from([token]));
2142    }
2143
2144    fn send_queue(&mut self) {
2145        if self.channel.readiness.is_writable() {
2146            QUEUE.with(|q| {
2147                let mut queue = q.borrow_mut();
2148                loop {
2149                    if let Some(resp) = queue.pop_front() {
2150                        debug!("Sending response {:?}", resp);
2151                        if let Err(e) = self.channel.write_message(&resp) {
2152                            error!("Could not write message {} on the channel: {}", resp, e);
2153                            queue.push_front(resp);
2154                        }
2155                    }
2156
2157                    if self.channel.back_buf.available_data() > 0
2158                        && let Err(e) = self.channel.writable()
2159                    {
2160                        error!("error writing to channel: {:?}", e);
2161                    }
2162
2163                    if !self.channel.readiness.is_writable() {
2164                        break;
2165                    }
2166
2167                    if self.channel.back_buf.available_data() == 0 && queue.is_empty() {
2168                        break;
2169                    }
2170                }
2171            });
2172        }
2173    }
2174
2175    fn notify(&mut self, message: WorkerRequest) {
2176        // Run the lease janitor here too, not only from the run loop: the
2177        // same `now` reading is threaded into `lease_apply` below
2178        // (`SetMetricDetail` arm) so the whole lease lifecycle for this
2179        // `notify` call is anchored to one clock read, and a query never
2180        // reports a lease the janitor is already due to retire.
2181        let now = std::time::Instant::now();
2182        tick_metric_detail_leases(now);
2183        match &message.content.request_type {
2184            Some(RequestType::ConfigureMetrics(configuration)) => {
2185                match MetricsConfiguration::try_from(*configuration) {
2186                    Ok(metrics_config) => {
2187                        METRICS.with(|metrics| {
2188                            (*metrics.borrow_mut()).configure(&metrics_config);
2189                            push_queue(WorkerResponse::ok(message.id));
2190                        });
2191                    }
2192                    Err(e) => {
2193                        error!("Error configuring metrics: {}", e);
2194                        push_queue(WorkerResponse::error(message.id, e));
2195                    }
2196                }
2197                return;
2198            }
2199            Some(RequestType::QueryMetrics(query_metrics_options)) => {
2200                METRICS.with(|metrics| {
2201                    match (*metrics.borrow_mut()).query(query_metrics_options) {
2202                        Ok(c) => push_queue(WorkerResponse::ok_with_content(message.id, c)),
2203                        Err(e) => {
2204                            error!("Error querying metrics: {}", e);
2205                            push_queue(WorkerResponse::error(message.id, e))
2206                        }
2207                    }
2208                });
2209                return;
2210            }
2211            // Runtime cardinality lease verb — apply, renew, or clear a lease
2212            // on this worker's `Aggregator`. The lease bumps `effective` to
2213            // `max(configured, max(active leases))`; expiry runs on the polled
2214            // janitor, `tick_metric_detail_leases`. Master-side aggregation
2215            // into `MetricDetailStatus` lands in a follow-up; for now the
2216            // worker acks with a bare OK so the existing `worker_request`
2217            // fan-out path can collect.
2218            Some(RequestType::SetMetricDetail(req)) => {
2219                // Master populates the peer binding from the connecting
2220                // `ClientSession` before fan-out (`bin/src/command/
2221                // requests.rs::worker_request`). A pre-binding caller or a
2222                // platform without `SO_PEERCRED` yields `PeerBinding::default()`
2223                // — clears against that lease are accepted from anyone, per
2224                // the proto contract on `SetMetricDetail.peer_pid`.
2225                let presented_binding = crate::metrics::PeerBinding {
2226                    pid: req.peer_pid,
2227                    // Master sends Crockford-base32 ULIDs (`Ulid::to_string`);
2228                    // accept those, with a fallback hex parse for callers that
2229                    // happen to send `0x…` form. A failed parse degrades to
2230                    // `None` — the lease store treats that as "binding
2231                    // unknown" per the proto contract.
2232                    session_ulid: req.peer_session_ulid.as_deref().and_then(|s| {
2233                        rusty_ulid::Ulid::from_str(s)
2234                            .map(u128::from)
2235                            .ok()
2236                            .or_else(|| u128::from_str_radix(s.trim_start_matches("0x"), 16).ok())
2237                    }),
2238                };
2239                if req.clear.unwrap_or(false) {
2240                    // Defense-in-depth: the master pre-validates `client_id`
2241                    // length at the dispatch site, but worker IPC is not
2242                    // master-only — fuzz harnesses, serial_test-flagged
2243                    // integration tests, and future internal callers can
2244                    // issue an oversized clear directly. Mirror the apply
2245                    // path's `ClientIdTooLong` arm so an unbounded HashMap
2246                    // lookup is never driven by an operator-supplied string
2247                    // here either. The reason string echoes the byte length
2248                    // but not the operator bytes themselves (symmetric with
2249                    // the audit-column-smuggling guard on the apply path).
2250                    if req.client_id.len() > crate::metrics::LEASE_CLIENT_ID_MAX_BYTES {
2251                        let msg = format!(
2252                            "SetMetricDetail: clear client_id length {} exceeds {} bytes",
2253                            req.client_id.len(),
2254                            crate::metrics::LEASE_CLIENT_ID_MAX_BYTES,
2255                        );
2256                        error!("{}", msg);
2257                        push_queue(WorkerResponse::error(message.id.clone(), msg));
2258                        return;
2259                    }
2260                    // Capture transition fields + post-clear snapshot
2261                    // before releasing the borrow so we can emit an
2262                    // Event after AND build the WorkerMetricDetailStatus
2263                    // payload that the master folds into
2264                    // `MetricDetailStatus.workers[<worker_id>]`. Without
2265                    // this payload the master used its own view as a
2266                    // stand-in for the worker's per-aggregator state.
2267                    let (outcome, effective_after, configured_after, lease_count_after) = METRICS
2268                        .with(|metrics| {
2269                            let mut m = metrics.borrow_mut();
2270                            let outcome = m.lease_clear(&req.client_id, presented_binding);
2271                            (
2272                                outcome,
2273                                m.detail_effective(),
2274                                m.detail_configured(),
2275                                m.lease_count(),
2276                            )
2277                        });
2278                    match outcome {
2279                        crate::metrics::LeaseClearOutcome::Cleared { previous_effective } => {
2280                            push_metric_detail_transition(
2281                                previous_effective,
2282                                effective_after,
2283                                "lease_clear",
2284                                Some(req.client_id.clone()),
2285                            );
2286                            push_queue(WorkerResponse::ok_with_content(
2287                                message.id.clone(),
2288                                worker_metric_detail_status_content(
2289                                    configured_after,
2290                                    effective_after,
2291                                    previous_effective,
2292                                    lease_count_after,
2293                                ),
2294                            ));
2295                        }
2296                        crate::metrics::LeaseClearOutcome::NotFound => {
2297                            // Silent no-op: no lease existed for that
2298                            // id. The worker's state is unchanged so
2299                            // previous_effective == effective.
2300                            push_queue(WorkerResponse::ok_with_content(
2301                                message.id.clone(),
2302                                worker_metric_detail_status_content(
2303                                    configured_after,
2304                                    effective_after,
2305                                    effective_after,
2306                                    lease_count_after,
2307                                ),
2308                            ));
2309                        }
2310                        crate::metrics::LeaseClearOutcome::Unauthorized => {
2311                            // Do NOT echo `req.client_id` here: the operator-
2312                            // supplied bytes flow back through the master's
2313                            // worker→reason aggregation into the audit line's
2314                            // `reason=` column, which is sanitised for control
2315                            // bytes only. The dedicated `lease_id=` audit
2316                            // column already carries the operator string
2317                            // through `sanitize_for_audit_kv`, so re-embedding
2318                            // it here would let a value containing `,` or `=`
2319                            // forge a sibling KV pair against SIEM consumers
2320                            // that split on `, key=value`.
2321                            let msg = "SetMetricDetail: clear refused (peer \
2322                                 binding does not match the apply-time owner)"
2323                                .to_owned();
2324                            error!("{}", msg);
2325                            push_queue(WorkerResponse::error(message.id.clone(), msg));
2326                        }
2327                    }
2328                    return;
2329                }
2330                let detail_proto = match req.detail {
2331                    Some(d) => d,
2332                    None => {
2333                        // Operator-supplied `client_id` is intentionally
2334                        // omitted from the reason string: the dedicated
2335                        // `lease_id=` audit column carries it through the
2336                        // strict KV sanitiser. See the matching comment on
2337                        // the `Unauthorized` arm above for the column-
2338                        // smuggling rationale.
2339                        let msg = "SetMetricDetail without `detail` and without `clear`".to_owned();
2340                        error!("{}", msg);
2341                        push_queue(WorkerResponse::error(message.id.clone(), msg));
2342                        return;
2343                    }
2344                };
2345                let detail_enum = match MetricDetail::try_from(detail_proto) {
2346                    Ok(d) => d,
2347                    Err(e) => {
2348                        let msg =
2349                            format!("SetMetricDetail: invalid MetricDetail variant {detail_proto}");
2350                        error!("{}: {}", msg, e);
2351                        push_queue(WorkerResponse::error(message.id.clone(), msg));
2352                        return;
2353                    }
2354                };
2355                let level = MetricDetailLevel::from(detail_enum);
2356                // Bound the worst case BEFORE we touch the aggregator: the
2357                // proto contract on `SetMetricDetail.ttl_seconds` says the
2358                // worker rejects values larger than `LEASE_TTL_MAX` so a
2359                // stuck operator-side renewer (or a buggy third-party client)
2360                // cannot lock the worker into elevated cardinality. The
2361                // `Aggregator::lease_apply` clamp is still in place as a
2362                // defence-in-depth net for code paths that bypass this
2363                // dispatch (proto fuzzing, future internal callers).
2364                if let Some(t) = req.ttl_seconds
2365                    && u64::from(t) > crate::metrics::LEASE_TTL_MAX.as_secs()
2366                {
2367                    let msg = format!(
2368                        "SetMetricDetail: ttl_seconds={t} exceeds LEASE_TTL_MAX={}",
2369                        crate::metrics::LEASE_TTL_MAX.as_secs()
2370                    );
2371                    error!("{}", msg);
2372                    push_queue(WorkerResponse::error(message.id.clone(), msg));
2373                    return;
2374                }
2375                let ttl_seconds = req.ttl_seconds.filter(|&t| t > 0).unwrap_or_else(|| {
2376                    // The default fits in a u32 by construction
2377                    // (LEASE_TTL_DEFAULT = 60 s); the lossy `as u32` cast
2378                    // is replaced with a checked conversion so any
2379                    // future tweak past `u32::MAX` seconds (≈ 136 years)
2380                    // can't silently truncate. Falls through to 60 s on
2381                    // the theoretical overflow path.
2382                    u32::try_from(crate::metrics::LEASE_TTL_DEFAULT.as_secs()).unwrap_or(60)
2383                });
2384                let ttl = std::time::Duration::from_secs(ttl_seconds.into());
2385                let (outcome, configured_after, lease_count_after) = METRICS.with(|metrics| {
2386                    let mut m = metrics.borrow_mut();
2387                    let outcome =
2388                        m.lease_apply(req.client_id.clone(), level, ttl, presented_binding, now);
2389                    (outcome, m.detail_configured(), m.lease_count())
2390                });
2391                match outcome {
2392                    crate::metrics::LeaseApplyOutcome::Applied {
2393                        previous_effective,
2394                        new_effective,
2395                    } => {
2396                        push_metric_detail_transition(
2397                            previous_effective,
2398                            new_effective,
2399                            "lease_apply",
2400                            Some(req.client_id.clone()),
2401                        );
2402                        push_queue(WorkerResponse::ok_with_content(
2403                            message.id.clone(),
2404                            worker_metric_detail_status_content(
2405                                configured_after,
2406                                new_effective,
2407                                previous_effective,
2408                                lease_count_after,
2409                            ),
2410                        ));
2411                    }
2412                    crate::metrics::LeaseApplyOutcome::ClientIdTooLong => {
2413                        let msg = format!(
2414                            "SetMetricDetail: client_id length {} exceeds {} bytes",
2415                            req.client_id.len(),
2416                            crate::metrics::LEASE_CLIENT_ID_MAX_BYTES,
2417                        );
2418                        error!("{}", msg);
2419                        push_queue(WorkerResponse::error(message.id.clone(), msg));
2420                    }
2421                    crate::metrics::LeaseApplyOutcome::TableFull => {
2422                        // Same audit-column-smuggling guard as the
2423                        // `Unauthorized` and missing-detail arms: the
2424                        // operator-supplied `client_id` is already rendered
2425                        // safely through the strict KV sanitiser in the
2426                        // audit envelope's `lease_id=` column, so we keep
2427                        // it out of the reason string.
2428                        let msg = format!(
2429                            "SetMetricDetail: lease table at capacity ({} entries); reject new \
2430                             apply — operators must retry after an active lease expires or is \
2431                             cleared",
2432                            crate::metrics::LEASE_TABLE_CAP,
2433                        );
2434                        error!("{}", msg);
2435                        push_queue(WorkerResponse::error(message.id.clone(), msg));
2436                    }
2437                    crate::metrics::LeaseApplyOutcome::TtlOutOfRange => {
2438                        // Unreachable in the normal flow: the dispatch-time
2439                        // gate above already rejected ttl > LEASE_TTL_MAX.
2440                        // Surface explicitly so any future bypass (proto
2441                        // fuzzing, internal callers) fails loud rather
2442                        // than silently capping the lessor's intent.
2443                        let msg = format!(
2444                            "SetMetricDetail: ttl exceeds LEASE_TTL_MAX={} (internal contract \
2445                             violation: dispatch gate should have rejected)",
2446                            crate::metrics::LEASE_TTL_MAX.as_secs(),
2447                        );
2448                        error!("{}", msg);
2449                        push_queue(WorkerResponse::error(message.id.clone(), msg));
2450                    }
2451                    crate::metrics::LeaseApplyOutcome::Unauthorized => {
2452                        // A renewal arrived against an existing lease whose
2453                        // apply-time peer binding does not match the
2454                        // presented one. The `client_id` is intentionally
2455                        // omitted from the error string — the audit-log
2456                        // row already carries it in the dedicated
2457                        // `lease_id` column, and echoing it here would
2458                        // route operator-controlled bytes through the
2459                        // freeform reason field.
2460                        let msg = "SetMetricDetail: renewal refused (peer binding does not \
2461                                   match the apply-time owner)"
2462                            .to_owned();
2463                        error!("{}", msg);
2464                        push_queue(WorkerResponse::error(message.id.clone(), msg));
2465                    }
2466                }
2467                return;
2468            }
2469            Some(RequestType::Logging(logging_filter)) => {
2470                info!(
2471                    "{} changing logging filter to {}",
2472                    message.id, logging_filter
2473                );
2474                // there should not be any errors as it was already parsed by the main process
2475                let (directives, _errors) = logging::parse_logging_spec(logging_filter);
2476                logging::LOGGER.with(|logger| {
2477                    logger.borrow_mut().set_directives(directives);
2478                });
2479                push_queue(WorkerResponse::ok(message.id));
2480                return;
2481            }
2482            Some(RequestType::QueryClustersHashes(_)) => {
2483                push_queue(WorkerResponse::ok_with_content(
2484                    message.id.clone(),
2485                    ContentType::ClusterHashes(ClusterHashes {
2486                        map: self
2487                            .config_state
2488                            .hash_state()
2489                            .into_iter()
2490                            .map(|(cluster_id, hash)| (cluster_id.to_string(), hash))
2491                            .collect(),
2492                    })
2493                    .into(),
2494                ));
2495                return;
2496            }
2497            Some(RequestType::QueryClusterById(cluster_id)) => {
2498                push_queue(WorkerResponse::ok_with_content(
2499                    message.id.clone(),
2500                    ContentType::Clusters(ClusterInformations {
2501                        vec: self
2502                            .config_state
2503                            .cluster_state(cluster_id)
2504                            .map_or(vec![], |ci| vec![ci]),
2505                    })
2506                    .into(),
2507                ));
2508            }
2509            Some(RequestType::SetMaxConnectionsPerIp(limit)) => {
2510                let mut sessions = self.sessions.borrow_mut();
2511                let previous = sessions.max_connections_per_ip;
2512                sessions.max_connections_per_ip = *limit;
2513                // Disabling the feature on the fly should not leave
2514                // stale `(cluster, ip)` entries behind: drain the
2515                // bookkeeping so a re-enable starts from a clean slate
2516                // and `cluster_ip_at_limit` does not consult dead state.
2517                if *limit == 0 {
2518                    sessions.clear_cluster_ip_tracking();
2519                }
2520                info!(
2521                    "{} updated global max_connections_per_ip from {} to {}",
2522                    message.id, previous, limit
2523                );
2524                push_queue(WorkerResponse::ok(message.id));
2525                return;
2526            }
2527            Some(RequestType::QueryMaxConnectionsPerIp(_)) => {
2528                let limit = self.sessions.borrow().max_connections_per_ip;
2529                push_queue(WorkerResponse::ok_with_content(
2530                    message.id,
2531                    ContentType::MaxConnectionsPerIpLimit(
2532                        sozu_command::proto::command::MaxConnectionsPerIpLimit { limit },
2533                    )
2534                    .into(),
2535                ));
2536                return;
2537            }
2538            Some(RequestType::SetMaxConnectionsPerSubnet(limit)) => {
2539                let mut sessions = self.sessions.borrow_mut();
2540                let previous = sessions.max_connections_per_subnet;
2541                sessions.max_connections_per_subnet = *limit;
2542                // Disabling the subnet limiter on the fly should not
2543                // leave stale `(cluster, subnet)` entries behind: drain
2544                // its bookkeeping so a re-enable starts from a clean
2545                // slate. The per-IP buckets are deliberately untouched —
2546                // the two limiters are independent.
2547                if *limit == 0 {
2548                    sessions.clear_cluster_subnet_tracking();
2549                }
2550                info!(
2551                    "{} updated global max_connections_per_subnet from {} to {}",
2552                    message.id, previous, limit
2553                );
2554                push_queue(WorkerResponse::ok(message.id));
2555                return;
2556            }
2557            Some(RequestType::SetMaxConnectionAttempts(attempts)) => {
2558                if let Err(reason) =
2559                    sozu_command::config::validate_max_connection_attempts(*attempts)
2560                {
2561                    push_queue(WorkerResponse::error(message.id, reason));
2562                    return;
2563                }
2564                let mut sessions = self.sessions.borrow_mut();
2565                let previous = sessions.max_connection_attempts;
2566                sessions.max_connection_attempts = *attempts;
2567                info!(
2568                    "{} updated global max_connection_attempts from {} to {}",
2569                    message.id, previous, attempts
2570                );
2571                push_queue(WorkerResponse::ok(message.id));
2572                return;
2573            }
2574            Some(RequestType::QueryMaxConnectionsPerSubnet(_)) => {
2575                let sessions = self.sessions.borrow();
2576                let limit = sessions.max_connections_per_subnet;
2577                // The prefixes ride along because they are boot-time
2578                // only: this reply is the sole runtime surface that can
2579                // tell an operator what the live cap is a cap ON.
2580                let ipv4_prefix = sessions.subnet_ipv4_prefix;
2581                let ipv6_prefix = sessions.subnet_ipv6_prefix;
2582                drop(sessions);
2583                push_queue(WorkerResponse::ok_with_content(
2584                    message.id,
2585                    ContentType::MaxConnectionsPerSubnetLimit(
2586                        sozu_command::proto::command::MaxConnectionsPerSubnetLimit {
2587                            limit,
2588                            ipv4_prefix,
2589                            ipv6_prefix,
2590                        },
2591                    )
2592                    .into(),
2593                ));
2594                return;
2595            }
2596            Some(RequestType::QueryClustersByDomain(domain)) => {
2597                let cluster_ids = self
2598                    .config_state
2599                    .get_cluster_ids_by_domain(domain.hostname.clone(), domain.path.clone());
2600                let vec = cluster_ids
2601                    .iter()
2602                    .filter_map(|cluster_id| self.config_state.cluster_state(cluster_id))
2603                    .collect();
2604
2605                push_queue(WorkerResponse::ok_with_content(
2606                    message.id.clone(),
2607                    ContentType::Clusters(ClusterInformations { vec }).into(),
2608                ));
2609                return;
2610            }
2611            // A filter carrying a fingerprint is answered from this worker's
2612            // own `ConfigState` rather than from its resolver. Note that
2613            // `get_certificates` tests its domain arm FIRST, so a request
2614            // carrying BOTH filters is answered by exact SAN equality on the
2615            // domain and the fingerprint is ignored — the one path where
2616            // `--domain` is not the trie lookup `query_certificate_for_domain`
2617            // and `bin`'s `certificates_serving_domain` both perform
2618            // (sozu#1383). `QueryCertificatesFilters` documents that the two
2619            // filters do not compound; `doc/configure_cli.md` says to pass one
2620            // at a time.
2621            Some(RequestType::QueryCertificatesFromWorkers(filters))
2622                if filters.fingerprint.is_some() =>
2623            {
2624                let certs = self.config_state.get_certificates(filters.clone());
2625                let response = if !certs.is_empty() {
2626                    WorkerResponse::ok_with_content(
2627                        message.id.clone(),
2628                        ContentType::CertificatesWithFingerprints(CertificatesWithFingerprints {
2629                            certs,
2630                        })
2631                        .into(),
2632                    )
2633                } else {
2634                    worker_response_error(
2635                        message.id.clone(),
2636                        "Could not find certificate for this fingerprint",
2637                    )
2638                };
2639                push_queue(response);
2640                return;
2641            }
2642            // if all certificates are queried, or filtered by domain name,
2643            // the request will be handled by the https proxy
2644            _other_request => {}
2645        }
2646        self.notify_proxys(message);
2647    }
2648
2649    pub fn notify_proxys(&mut self, request: WorkerRequest) {
2650        // Read before the dispatch below: `ConfigState::remove_cluster`
2651        // (`command/src/state.rs`) removes these objects from the state, and
2652        // the `RemoveCluster` arm removes them from the proxies.
2653        let cluster_objects = match &request.content.request_type {
2654            Some(RequestType::RemoveCluster(cluster_id)) => {
2655                self.config_state.cluster_removal_requests(cluster_id)
2656            }
2657            _ => Vec::new(),
2658        };
2659
2660        // Snapshot a compensating request before dispatch: removal requests
2661        // carry only an identity selector, while the stored frontend carries
2662        // the metadata an exact re-add needs.
2663        let rollback_candidate = request
2664            .content
2665            .request_type
2666            .as_ref()
2667            .and_then(|request_type| self.config_state.rollback_for(request_type));
2668        let mut rollback = None;
2669        if let Err(e) = self.config_state.dispatch(&request.content) {
2670            if self
2671                .config_state
2672                .removed_with_its_cluster(&request.content, &e)
2673            {
2674                // The object went with its cluster (`RemoveCluster` cascades),
2675                // for instance when a main process that predates the cascade
2676                // follows its `RemoveCluster` with the removal of each object.
2677                // There is nothing left to remove: answer ok rather than fail
2678                // on a missing route.
2679                debug!("{} already removed with its cluster: {}", request.id, e);
2680                push_queue(WorkerResponse::ok(request.id));
2681                return;
2682            }
2683            if is_frontend_removal(&request.content)
2684                && matches!(e, StateError::NotFound { .. } | StateError::NoChange)
2685            {
2686                // The state holds no frontend of the named cluster under
2687                // this identity: `NotFound` when it holds none of that
2688                // cluster at all (or the route key is another cluster's),
2689                // `NoChange` when the TCP or UDP frontends it holds for that
2690                // cluster do not include this one. The proxies look a
2691                // frontend up by address, hostname and path, or by listener,
2692                // whatever cluster owns it: applying the removal to them
2693                // could evict another cluster's route or listener mapping.
2694                // Refuse it without touching them.
2695                push_queue(WorkerResponse::error(
2696                    request.id,
2697                    format!("Could not execute order on config state: {e}"),
2698                ));
2699                return;
2700            }
2701            error!("Could not execute order on config state: {}", e);
2702        } else {
2703            rollback = rollback_candidate;
2704        }
2705
2706        let req_id = request.id.clone();
2707
2708        match request.content.request_type {
2709            Some(RequestType::AddCluster(ref cluster)) => {
2710                // Mirror the master-side ConfigState::add_cluster check so
2711                // off-channel paths (TOML reload, SaveState/LoadState, direct
2712                // API) that smuggle a malformed `cluster.health_check` cannot
2713                // arm the worker's BackendList::set_health_check_config with
2714                // a CRLF/NUL/C0 URI or zero thresholds. The SetHealthCheck
2715                // handler below already runs the same validation; this is the
2716                // AddCluster mirror.
2717                if let Some(hc) = cluster.health_check.as_ref()
2718                    && let Err(reason) = sozu_command::config::validate_health_check_config(hc)
2719                {
2720                    push_queue(worker_response_error(req_id, reason));
2721                    return;
2722                }
2723                // Same mirror for the shuffle-sharding knobs (#524): a worker
2724                // never arms a shard the master would have refused.
2725                if let Err(reason) = sozu_command::config::validate_shuffle_sharding(
2726                    cluster.shard_percent,
2727                    cluster.shard_min_backends,
2728                    cluster.shard_mode,
2729                ) {
2730                    push_queue(worker_response_error(req_id, reason));
2731                    return;
2732                }
2733                if let Some(Err(reason)) = cluster
2734                    .max_connection_attempts
2735                    .map(sozu_command::config::validate_max_connection_attempts)
2736                {
2737                    push_queue(worker_response_error(req_id, reason));
2738                    return;
2739                }
2740                self.add_cluster(cluster);
2741                // An upsert without a health check clears it like
2742                // `RemoveHealthCheck`: `add_cluster` already reset the
2743                // backends, drop the probes still in flight too (#1811).
2744                if cluster.health_check.is_none() {
2745                    self.health_checker.remove_cluster(&cluster.cluster_id);
2746                }
2747                // Re-arm the metric drain tombstone in case this cluster id
2748                // was previously removed — without this the drain would
2749                // continue dropping every emission for the resurrected
2750                // cluster. Idempotent on a fresh id.
2751                METRICS.with(|metrics| {
2752                    (*metrics.borrow_mut()).add_cluster(&cluster.cluster_id);
2753                });
2754                //not returning because the message must still be handled by each proxy
2755            }
2756            Some(RequestType::RemoveCluster(ref cluster_id)) => {
2757                self.remove_cluster_objects(&req_id, cluster_id, cluster_objects);
2758                self.remove_health_check_state(cluster_id);
2759                METRICS.with(|metrics| {
2760                    (*metrics.borrow_mut()).remove_cluster(cluster_id);
2761                });
2762                //not returning because the message must still be handled by each proxy
2763            }
2764            Some(RequestType::SetHealthCheck(ref set)) => {
2765                if let Err(reason) = sozu_command::config::validate_health_check_config(&set.config)
2766                {
2767                    push_queue(worker_response_error(req_id, reason));
2768                    return;
2769                }
2770                self.backends
2771                    .borrow_mut()
2772                    .set_health_check_config(&set.cluster_id, Some(set.config.to_owned()));
2773                push_queue(WorkerResponse::ok(req_id));
2774                return;
2775            }
2776            Some(RequestType::RemoveHealthCheck(ref cluster_id)) => {
2777                self.remove_health_check_state(cluster_id);
2778                push_queue(WorkerResponse::ok(req_id));
2779                return;
2780            }
2781            Some(RequestType::AddBackend(ref backend)) => {
2782                push_queue(self.add_backend(&req_id, backend));
2783                return;
2784            }
2785            Some(RequestType::RemoveBackend(ref remove_backend)) => {
2786                push_queue(self.remove_backend(&req_id, remove_backend));
2787                return;
2788            }
2789            _ => {}
2790        };
2791
2792        let proxy_destinations = request.content.get_destinations();
2793        let mut notify_response = None;
2794        if proxy_destinations.to_http_proxy {
2795            notify_response = Some(self.http.borrow_mut().notify(request.clone()));
2796        }
2797        if proxy_destinations.to_https_proxy {
2798            let http_proxy_response = self.https.borrow_mut().notify(request.clone());
2799            if http_proxy_response.is_failure() || notify_response.is_none() {
2800                notify_response = Some(http_proxy_response);
2801            }
2802        }
2803        if proxy_destinations.to_tcp_proxy {
2804            let tcp_proxy_response = self.tcp.borrow_mut().notify(request.clone());
2805            if tcp_proxy_response.is_failure() || notify_response.is_none() {
2806                notify_response = Some(tcp_proxy_response);
2807            }
2808        }
2809        if proxy_destinations.to_udp_proxy {
2810            let udp_proxy_response = self.udp.borrow_mut().notify(request.clone());
2811            if udp_proxy_response.is_failure() || notify_response.is_none() {
2812                notify_response = Some(udp_proxy_response);
2813            }
2814        }
2815        if let Some(response) = notify_response {
2816            self.finish_proxy_order(&mut rollback, response);
2817        }
2818
2819        match request.content.request_type {
2820            // special case for adding listeners, because we need to register a listener
2821            Some(RequestType::AddHttpListener(listener)) => {
2822                let response = self.notify_add_http_listener(&req_id, listener);
2823                self.finish_proxy_order(&mut rollback, response);
2824            }
2825            Some(
2826                RequestType::AddHttpsListener(listener)
2827                | RequestType::AddHttpsListenerWithClientAuth(listener),
2828            ) => {
2829                let response = self.notify_add_https_listener(&req_id, listener);
2830                self.finish_proxy_order(&mut rollback, response);
2831            }
2832            Some(RequestType::AddTcpListener(listener)) => {
2833                let response = self.notify_add_tcp_listener(&req_id, listener);
2834                self.finish_proxy_order(&mut rollback, response);
2835            }
2836            Some(RequestType::AddUdpListener(listener)) => {
2837                let response = self.notify_add_udp_listener(&req_id, listener);
2838                self.finish_proxy_order(&mut rollback, response);
2839            }
2840            Some(RequestType::UpdateHttpListener(patch)) => {
2841                let response = self.notify_update_http_listener(&req_id, patch);
2842                self.finish_proxy_order(&mut rollback, response);
2843            }
2844            Some(
2845                RequestType::UpdateHttpsListener(patch)
2846                | RequestType::UpdateHttpsListenerWithClientAuth(patch),
2847            ) => {
2848                let response = self.notify_update_https_listener(&req_id, patch);
2849                self.finish_proxy_order(&mut rollback, response);
2850            }
2851            Some(RequestType::UpdateTcpListener(patch)) => {
2852                let response = self.notify_update_tcp_listener(&req_id, patch);
2853                self.finish_proxy_order(&mut rollback, response);
2854            }
2855            Some(RequestType::UpdateUdpListener(patch)) => {
2856                let response = self.notify_update_udp_listener(&req_id, patch);
2857                self.finish_proxy_order(&mut rollback, response);
2858            }
2859            Some(RequestType::RemoveListener(ref remove)) => {
2860                debug!("{} remove {:?} listener {:?}", req_id, remove.proxy, remove);
2861                // We only remove a listener that was previously added, so the
2862                // base count is at least 1 — the subtraction cannot underflow.
2863                debug_assert!(
2864                    self.base_sessions_count > 0,
2865                    "removing a listener with base_sessions_count == 0 would underflow"
2866                );
2867                self.base_sessions_count -= 1;
2868                let listener_type = ListenerType::try_from(remove.proxy);
2869                // `RemoveListener` is the sole release point of the slab slot
2870                // `AddListener` reserved (see `reserve_listen_token`): no
2871                // proxy's `remove_listener` touches the session slab, so
2872                // without this the slot leaks one slab key per add/remove
2873                // cycle — and `base_sessions_count`, decremented just above,
2874                // drifts below the number of reserved slots. Read the token
2875                // BEFORE the proxy drops the listener that holds it.
2876                let address = remove.listener_key();
2877                let listen_token = match listener_type {
2878                    Ok(ListenerType::Http) => self.http.borrow().listener_token(&address),
2879                    Ok(ListenerType::Https) => self.https.borrow().listener_token(&address),
2880                    Ok(ListenerType::Tcp) => self.tcp.borrow().listener_token(&address),
2881                    Ok(ListenerType::Udp) => self.udp.borrow().listener_token(&address),
2882                    Err(_) => None,
2883                };
2884                let response = match listener_type {
2885                    Ok(ListenerType::Http) => self.http.borrow_mut().notify(request),
2886                    Ok(ListenerType::Https) => self.https.borrow_mut().notify(request),
2887                    Ok(ListenerType::Tcp) => self.tcp.borrow_mut().notify(request),
2888                    Ok(ListenerType::Udp) => self.udp.borrow_mut().notify(request),
2889                    Err(_) => WorkerResponse::error(req_id, "Wrong variant ListenerType"),
2890                };
2891                if let Some(token) = listen_token {
2892                    self.sessions.borrow_mut().slab.try_remove(token.0);
2893                    // Same rule as the deactivate arms: a listen token with no
2894                    // slab slot must not stay queued for a deferred accept.
2895                    self.accept_ready.remove(&ListenToken(token.0));
2896                    info!("released listen token {:?}", token);
2897                }
2898                self.finish_proxy_order(&mut rollback, response);
2899            }
2900            Some(RequestType::ActivateListener(ref activate)) => {
2901                let response = self.notify_activate_listener(&req_id, activate);
2902                self.finish_proxy_order(&mut rollback, response);
2903            }
2904            Some(RequestType::DeactivateListener(ref deactivate)) => {
2905                let response = self.notify_deactivate_listener(&req_id, deactivate);
2906                self.finish_proxy_order(&mut rollback, response);
2907            }
2908            _other_request => {}
2909        };
2910    }
2911
2912    /// Queue a proxy response and restore the worker's persisted view when a
2913    /// single-plane runtime order was refused after `ConfigState::dispatch`.
2914    fn finish_proxy_order(&mut self, rollback: &mut Option<Request>, response: WorkerResponse) {
2915        if response.is_failure()
2916            && let Some(rollback) = rollback.take()
2917        {
2918            debug_assert!(
2919                rollback.request_type.is_some(),
2920                "a ConfigState rollback must carry one request"
2921            );
2922            if let Err(error) = self.config_state.dispatch(&rollback) {
2923                error!(
2924                    "Could not roll back worker config state after a proxy refused an order: {}",
2925                    error
2926                );
2927            }
2928            debug_assert!(
2929                rollback.request_type.is_some(),
2930                "dispatching a rollback must not consume its request type"
2931            );
2932        }
2933
2934        push_queue(response);
2935    }
2936
2937    fn add_cluster(&mut self, cluster: &Cluster) {
2938        let mut backends = self.backends.borrow_mut();
2939        backends.set_load_balancing_policy_for_cluster(
2940            &cluster.cluster_id,
2941            LoadBalancingAlgorithms::try_from(cluster.load_balancing).unwrap_or_default(),
2942            cluster
2943                .load_metric
2944                .and_then(|n| LoadMetric::try_from(n).ok()),
2945        );
2946        backends.set_shuffle_sharding_for_cluster(
2947            &cluster.cluster_id,
2948            ShuffleSharding::from_cluster(
2949                cluster.shard_percent,
2950                cluster.shard_min_backends,
2951                cluster.shard_mode,
2952            ),
2953        );
2954        backends.set_health_check_config(&cluster.cluster_id, cluster.health_check.to_owned());
2955        backends.set_cluster_http2(&cluster.cluster_id, cluster.http2.unwrap_or(false));
2956    }
2957
2958    fn add_backend(&mut self, req_id: &str, add_backend: &AddBackend) -> WorkerResponse {
2959        let new_backend = Backend::new(
2960            &add_backend.backend_id,
2961            add_backend.address.into(),
2962            add_backend.sticky_id.clone(),
2963            add_backend.load_balancing_parameters,
2964            add_backend.backup,
2965        );
2966        self.backends
2967            .borrow_mut()
2968            .add_backend(&add_backend.cluster_id, new_backend);
2969
2970        WorkerResponse::ok(req_id)
2971    }
2972
2973    /// Remove from the proxies and the `BackendMap` the frontends and backends
2974    /// of a cluster being removed, `requests` being the removal orders
2975    /// `ConfigState::cluster_removal_requests` (`command/src/state.rs`)
2976    /// returned for it. Each object goes through the removal its own order
2977    /// takes, so the router, the tags, the TCP and UDP listener to cluster
2978    /// mappings and the per-backend metrics are cleaned the same way.
2979    ///
2980    /// A session already established keeps the backend connection it holds
2981    /// and drains on it; only new requests and connections stop routing. A
2982    /// failure is logged and does not stop the others: the `RemoveCluster`
2983    /// answer stays the single response the main process waits for.
2984    fn remove_cluster_objects(&mut self, req_id: &str, cluster_id: &str, requests: Vec<Request>) {
2985        for request in requests {
2986            let result = match request.request_type {
2987                Some(RequestType::RemoveHttpFrontend(front)) => {
2988                    self.http.borrow_mut().remove_http_frontend(front)
2989                }
2990                Some(RequestType::RemoveHttpsFrontend(front)) => self
2991                    .https
2992                    .borrow_mut()
2993                    .remove_https_frontend(front)
2994                    .map(|_| ()),
2995                Some(RequestType::RemoveTcpFrontend(front)) => {
2996                    self.tcp.borrow_mut().remove_tcp_front(front)
2997                }
2998                Some(RequestType::RemoveUdpFrontend(front)) => {
2999                    self.udp.borrow_mut().remove_udp_front(front)
3000                }
3001                Some(RequestType::RemoveBackend(backend)) => {
3002                    let response = self.remove_backend(req_id, &backend);
3003                    debug_assert_eq!(
3004                        response.status,
3005                        ResponseStatus::Ok as i32,
3006                        "Server::remove_backend always answers ok"
3007                    );
3008                    Ok(())
3009                }
3010                other => {
3011                    debug_assert!(
3012                        false,
3013                        "cluster_removal_requests only returns frontend and backend removals, got {other:?}"
3014                    );
3015                    Ok(())
3016                }
3017            };
3018            if let Err(error) = result {
3019                warn!(
3020                    "{} removing cluster {}: could not remove one of its objects: {}",
3021                    req_id, cluster_id, error
3022                );
3023            }
3024        }
3025        self.backends.borrow_mut().remove_cluster(cluster_id);
3026        debug_assert!(
3027            !self.backends.borrow().backends.contains_key(cluster_id),
3028            "a removed cluster must leave no backend list behind"
3029        );
3030    }
3031
3032    /// Stop health checking `cluster_id`: drop its in-flight probes, then
3033    /// forget its configuration and reset every backend it marked DOWN, since
3034    /// no probe will ever mark them UP again (#1811). Dropping the probes first
3035    /// keeps one already in flight from marking a backend DOWN after the reset.
3036    fn remove_health_check_state(&mut self, cluster_id: &str) {
3037        self.health_checker.remove_cluster(cluster_id);
3038        self.backends
3039            .borrow_mut()
3040            .set_health_check_config(cluster_id, None);
3041    }
3042
3043    fn remove_backend(&mut self, req_id: &str, backend: &RemoveBackend) -> WorkerResponse {
3044        let address = backend.address.into();
3045        // Runtime removal keys on `(backend_id, address)`, as `ConfigState`
3046        // does: another id at the same address stays (#1821). The metrics
3047        // layer is id-keyed, so one row aggregates every entry carrying the
3048        // id: it is dropped only once no entry of that id remains at any
3049        // address, and is still tidied when the backend was already gone,
3050        // clearing any orphan row.
3051        let id_still_live = {
3052            let mut backends = self.backends.borrow_mut();
3053            backends.remove_backend(&backend.cluster_id, &backend.backend_id, &address);
3054            backends.has_backend_id(&backend.cluster_id, &backend.backend_id)
3055        };
3056        if !id_still_live {
3057            METRICS.with(|metrics| {
3058                (*metrics.borrow_mut()).remove_backend(&backend.cluster_id, &backend.backend_id);
3059            });
3060        }
3061
3062        WorkerResponse::ok(req_id)
3063    }
3064
3065    fn notify_add_http_listener(
3066        &mut self,
3067        req_id: &str,
3068        listener: HttpListenerConfig,
3069    ) -> WorkerResponse {
3070        debug!("{} add http listener {:?}", req_id, listener);
3071
3072        if self.sessions.borrow().at_capacity() {
3073            return worker_response_error(req_id, "session list is full, cannot add a listener");
3074        }
3075
3076        let mut session_manager = self.sessions.borrow_mut();
3077        // The vacant entry's key is free now and becomes the listener's token.
3078        let slab_before = session_manager.slab.len();
3079        debug_assert!(
3080            !session_manager
3081                .slab
3082                .contains(session_manager.slab.vacant_key()),
3083            "the next vacant slab key must be free before insertion"
3084        );
3085        let entry = session_manager.slab.vacant_entry();
3086        let token = Token(entry.key());
3087
3088        match self.http.borrow_mut().add_listener(listener, token) {
3089            Ok(_token) => {
3090                entry.insert(Rc::new(RefCell::new(ListenSession {
3091                    protocol: Protocol::HTTPListen,
3092                })));
3093                // The listener session occupies exactly the token's slab key,
3094                // and the slab grew by exactly one slot.
3095                debug_assert!(
3096                    session_manager.slab.contains(token.0),
3097                    "listener insert must occupy the token's slab key"
3098                );
3099                debug_assert_eq!(
3100                    session_manager.slab.len(),
3101                    slab_before + 1,
3102                    "adding a listener must occupy exactly one slab slot"
3103                );
3104                self.base_sessions_count += 1;
3105                WorkerResponse::ok(req_id)
3106            }
3107            Err(e) => worker_response_error(req_id, format!("Could not add HTTP listener: {e}")),
3108        }
3109    }
3110
3111    fn notify_add_https_listener(
3112        &mut self,
3113        req_id: &str,
3114        listener: HttpsListenerConfig,
3115    ) -> WorkerResponse {
3116        debug!("{} add https listener {:?}", req_id, listener);
3117
3118        if self.sessions.borrow().at_capacity() {
3119            return worker_response_error(req_id, "session list is full, cannot add a listener");
3120        }
3121
3122        let mut session_manager = self.sessions.borrow_mut();
3123        let slab_before = session_manager.slab.len();
3124        debug_assert!(
3125            !session_manager
3126                .slab
3127                .contains(session_manager.slab.vacant_key()),
3128            "the next vacant slab key must be free before insertion"
3129        );
3130        let entry = session_manager.slab.vacant_entry();
3131        let token = Token(entry.key());
3132
3133        match self
3134            .https
3135            .borrow_mut()
3136            .add_listener(listener.clone(), token)
3137        {
3138            Ok(_token) => {
3139                entry.insert(Rc::new(RefCell::new(ListenSession {
3140                    protocol: Protocol::HTTPSListen,
3141                })));
3142                debug_assert!(
3143                    session_manager.slab.contains(token.0),
3144                    "listener insert must occupy the token's slab key"
3145                );
3146                debug_assert_eq!(
3147                    session_manager.slab.len(),
3148                    slab_before + 1,
3149                    "adding a listener must occupy exactly one slab slot"
3150                );
3151                self.base_sessions_count += 1;
3152                WorkerResponse::ok(req_id)
3153            }
3154            Err(e) => worker_response_error(req_id, format!("Could not add HTTPS listener: {e}")),
3155        }
3156    }
3157
3158    fn notify_add_tcp_listener(
3159        &mut self,
3160        req_id: &str,
3161        listener: CommandTcpListener,
3162    ) -> WorkerResponse {
3163        debug!("{} add tcp listener {:?}", req_id, listener);
3164
3165        if self.sessions.borrow().at_capacity() {
3166            return worker_response_error(req_id, "session list is full, cannot add a listener");
3167        }
3168
3169        let mut session_manager = self.sessions.borrow_mut();
3170        let slab_before = session_manager.slab.len();
3171        debug_assert!(
3172            !session_manager
3173                .slab
3174                .contains(session_manager.slab.vacant_key()),
3175            "the next vacant slab key must be free before insertion"
3176        );
3177        let entry = session_manager.slab.vacant_entry();
3178        let token = Token(entry.key());
3179
3180        match self.tcp.borrow_mut().add_listener(listener, token) {
3181            Ok(_token) => {
3182                entry.insert(Rc::new(RefCell::new(ListenSession {
3183                    protocol: Protocol::TCPListen,
3184                })));
3185                debug_assert!(
3186                    session_manager.slab.contains(token.0),
3187                    "listener insert must occupy the token's slab key"
3188                );
3189                debug_assert_eq!(
3190                    session_manager.slab.len(),
3191                    slab_before + 1,
3192                    "adding a listener must occupy exactly one slab slot"
3193                );
3194                self.base_sessions_count += 1;
3195                WorkerResponse::ok(req_id)
3196            }
3197            Err(e) => worker_response_error(req_id, format!("Could not add TCP listener: {e}")),
3198        }
3199    }
3200
3201    fn notify_add_udp_listener(
3202        &mut self,
3203        req_id: &str,
3204        listener: CommandUdpListener,
3205    ) -> WorkerResponse {
3206        debug!("{} add udp listener {:?}", req_id, listener);
3207
3208        if self.sessions.borrow().at_capacity() {
3209            return worker_response_error(req_id, "session list is full, cannot add a listener");
3210        }
3211
3212        let mut session_manager = self.sessions.borrow_mut();
3213        let entry = session_manager.slab.vacant_entry();
3214        let token = Token(entry.key());
3215
3216        match self.udp.borrow_mut().add_listener(listener, token) {
3217            Ok(_token) => {
3218                entry.insert(Rc::new(RefCell::new(ListenSession {
3219                    protocol: Protocol::UDPListen,
3220                })));
3221                self.base_sessions_count += 1;
3222                WorkerResponse::ok(req_id)
3223            }
3224            Err(e) => worker_response_error(req_id, format!("Could not add UDP listener: {e}")),
3225        }
3226    }
3227
3228    fn notify_update_udp_listener(
3229        &mut self,
3230        req_id: &str,
3231        patch: UpdateUdpListenerConfig,
3232    ) -> WorkerResponse {
3233        debug!("{} update udp listener {:?}", req_id, patch.address);
3234        match self.udp.borrow_mut().update_listener(patch) {
3235            Ok(()) => WorkerResponse::ok(req_id),
3236            Err(e) => worker_response_error(req_id, format!("Could not update UDP listener: {e}")),
3237        }
3238    }
3239
3240    fn notify_update_http_listener(
3241        &mut self,
3242        req_id: &str,
3243        patch: UpdateHttpListenerConfig,
3244    ) -> WorkerResponse {
3245        debug!("{} update http listener {:?}", req_id, patch.address);
3246        match self.http.borrow_mut().update_listener(patch) {
3247            Ok(()) => WorkerResponse::ok(req_id),
3248            Err(e) => worker_response_error(req_id, format!("Could not update HTTP listener: {e}")),
3249        }
3250    }
3251
3252    fn notify_update_https_listener(
3253        &mut self,
3254        req_id: &str,
3255        patch: UpdateHttpsListenerConfig,
3256    ) -> WorkerResponse {
3257        debug!("{} update https listener {:?}", req_id, patch.address);
3258        match self.https.borrow_mut().update_listener(patch) {
3259            Ok(()) => WorkerResponse::ok(req_id),
3260            Err(e) => {
3261                worker_response_error(req_id, format!("Could not update HTTPS listener: {e}"))
3262            }
3263        }
3264    }
3265
3266    fn notify_update_tcp_listener(
3267        &mut self,
3268        req_id: &str,
3269        patch: UpdateTcpListenerConfig,
3270    ) -> WorkerResponse {
3271        debug!("{} update tcp listener {:?}", req_id, patch.address);
3272        match self.tcp.borrow_mut().update_listener(patch) {
3273            Ok(()) => WorkerResponse::ok(req_id),
3274            Err(e) => worker_response_error(req_id, format!("Could not update TCP listener: {e}")),
3275        }
3276    }
3277
3278    /// Re-arm a deactivated listener's slab slot with the inert
3279    /// [`ListenSession`] placeholder that `AddListener` first installed there.
3280    ///
3281    /// DESIGN (2026-09-18) — a listener owns exactly one slab slot for its
3282    /// whole `AddListener` -> `RemoveListener` lifetime, activated or not.
3283    /// The alternative was to stop retaining the token across a deactivate and
3284    /// allocate a fresh slab key on every activate; it was rejected because
3285    /// the token is the *identity* of a listener in all four proxies — their
3286    /// `listeners` maps (plus UDP's `managers`, `cluster_for_listener` and
3287    /// `listener_sessions`) are keyed by it, `give_back_listener` hands the
3288    /// same token back and `activate()` returns it again — so a fresh key
3289    /// would mean rekeying five maps across four proxies on every activate,
3290    /// far more machinery than the defect warrants.
3291    ///
3292    /// Reserving the slot is what makes the retained token honest, and it
3293    /// closes both halves of the defect at once:
3294    ///
3295    /// * the slot is never vacant, so `Server::ready` keeps dispatching the
3296    ///   listen token after a deactivate/activate cycle. Freeing it left the
3297    ///   socket re-registered under a token with no slab entry — `ready()`
3298    ///   silently drops the event, and the reactivated listener was deaf
3299    ///   while `ActivateListener` had reported success;
3300    /// * the slab can never hand that key to another session, so the UDP
3301    ///   activate path can no longer overwrite a live session with its
3302    ///   listener session. Mode 2 is gone by construction, not by check.
3303    ///
3304    /// `RemoveListener` is the sole release point (see `notify_proxys`). No
3305    /// proxy's `remove_listener` touches the session slab, so before this
3306    /// change the deactivate arms were the ONLY code that ever freed a
3307    /// listener's slot — removing a listener that had not been deactivated
3308    /// first already stranded its slab key (and, for UDP, the last
3309    /// `UdpListenerSession` reference, hence the listener's open `UdpSocket`).
3310    /// Reserving the slot here makes that release mandatory rather than
3311    /// incidental.
3312    fn reserve_listen_token(&mut self, token: Token, protocol: Protocol) {
3313        let mut sessions = self.sessions.borrow_mut();
3314        match sessions.slab.get_mut(token.0) {
3315            Some(slot) => {
3316                *slot = Rc::new(RefCell::new(ListenSession { protocol }));
3317                info!("reserved listen token {:?} for {:?}", token, protocol);
3318            }
3319            // Unreachable while the lifetime above holds: `AddListener`
3320            // inserted the slot and only `RemoveListener` frees it. Loud
3321            // rather than silent, because the listener is deaf if it happens.
3322            None => error!(
3323                "listen token {:?} ({:?}) has no slab slot to reserve; the listener would not be reachable if reactivated",
3324                token, protocol
3325            ),
3326        }
3327    }
3328
3329    /// Say what the initial state left unclaimed in the SCM table.
3330    ///
3331    /// A descriptor is kept whenever no listener exists at its address yet,
3332    /// because an `AddListener` + `ActivateListener` pair can still arrive and
3333    /// adopt it — `UpgradeWorkerTask` (`bin/src/command/upgrade.rs`) scatters
3334    /// `generate_activate_requests()` only after `Server::new` has returned, so
3335    /// there is no point during startup at which a leftover is provably
3336    /// unclaimable. Retention is therefore open-ended, and the one thing this
3337    /// worker can do is make it visible.
3338    ///
3339    /// An address that already has a listener is the expected shape (an initial
3340    /// state carrying the listeners inactive, activated straight afterwards) and
3341    /// is reported at `info`. An address with no listener at all is the one an
3342    /// operator needs to see: nothing in this worker's state mentions it, so the
3343    /// descriptor is the retiring worker's listening socket, still bound with
3344    /// `SO_REUSEPORT` and registered with no event loop, which is expected to
3345    /// keep taking a share of new connections that nothing accepts. (That
3346    /// expectation follows from `SO_REUSEPORT` load-balancing semantics; nothing
3347    /// here measures it.) That is reported at `warn`.
3348    fn report_unclaimed_inherited_sockets(&self) {
3349        let Some(scm_listeners) = self.scm_listeners.as_ref() else {
3350            return;
3351        };
3352        let leftovers: [(&str, &Vec<(ListenerKey, RawFd)>); 4] = [
3353            ("HTTP", &scm_listeners.http),
3354            ("HTTPS", &scm_listeners.tls),
3355            ("TCP", &scm_listeners.tcp),
3356            ("UDP", &scm_listeners.udp),
3357        ];
3358        for (label, table) in leftovers {
3359            for (address, fd) in table {
3360                let has_listener = match label {
3361                    "HTTP" => self.http.borrow().listener_token(address).is_some(),
3362                    "HTTPS" => self.https.borrow().listener_token(address).is_some(),
3363                    "TCP" => self.tcp.borrow().listener_token(address).is_some(),
3364                    _ => self.udp.borrow().listener_token(address).is_some(),
3365                };
3366                if has_listener {
3367                    info!(
3368                        "inherited {} listening socket for {} (fd {}) is held for an \
3369                         ActivateListener this worker has not processed yet",
3370                        label, address, fd
3371                    );
3372                } else {
3373                    warn!(
3374                        "inherited {} listening socket for {} (fd {}) is held with no listener \
3375                         at that address: until an AddListener plus ActivateListener for {} \
3376                         adopts it, it is expected to keep taking a share of new connections \
3377                         that nothing accepts (inferred from SO_REUSEPORT semantics, not \
3378                         measured here)",
3379                        label, address, fd, address
3380                    );
3381                }
3382            }
3383        }
3384    }
3385
3386    fn notify_activate_listener(
3387        &mut self,
3388        req_id: &str,
3389        activate: &ActivateListener,
3390    ) -> WorkerResponse {
3391        use crate::InheritedSocketFate;
3392
3393        debug!(
3394            "{} activate {:?} listener {:?}",
3395            req_id, activate.proxy, activate
3396        );
3397
3398        let address = activate.listener_key();
3399
3400        match ListenerType::try_from(activate.proxy) {
3401            Ok(ListenerType::Http) => {
3402                let listener = match self.http.borrow().inherited_socket_fate(&address) {
3403                    InheritedSocketFate::Adopted => self
3404                        .scm_listeners
3405                        .as_mut()
3406                        .and_then(|listeners| listeners.get_http(&address))
3407                        // SAFETY: `fd` was just received from the supervisor via SCM_RIGHTS
3408                        // (see `command/src/scm_socket.rs`) and is not owned elsewhere — the
3409                        // `Listeners` table removes it on `get_http`. Ownership transfers to
3410                        // the mio wrapper, whose `Drop` closes the descriptor.
3411                        .map(|fd| unsafe { MioTcpListener::from_raw_fd(fd) }),
3412                    InheritedSocketFate::Refused => {
3413                        discard_inherited_socket::<MioTcpListener>(
3414                            self.scm_listeners
3415                                .as_mut()
3416                                .and_then(|listeners| listeners.get_http(&address)),
3417                            &address,
3418                            "HTTP",
3419                        );
3420                        None
3421                    }
3422                    // Left in the SCM table on purpose: a later `AddListener` +
3423                    // `ActivateListener` for this address can still adopt it.
3424                    InheritedSocketFate::Unclaimed => None,
3425                };
3426
3427                let activated_token = self.http.borrow_mut().activate_listener(&address, listener);
3428                match activated_token {
3429                    Ok(token) => {
3430                        self.accept(ListenToken(token.0), Protocol::HTTPListen);
3431                        WorkerResponse::ok(req_id)
3432                    }
3433                    Err(activate_error) => worker_response_error(
3434                        req_id,
3435                        format!("Could not activate HTTP listener: {activate_error}"),
3436                    ),
3437                }
3438            }
3439            Ok(ListenerType::Https) => {
3440                let listener = match self.https.borrow().inherited_socket_fate(&address) {
3441                    InheritedSocketFate::Adopted => self
3442                        .scm_listeners
3443                        .as_mut()
3444                        .and_then(|listeners| listeners.get_https(&address))
3445                        // SAFETY: `fd` was just received from the supervisor via SCM_RIGHTS
3446                        // (see `command/src/scm_socket.rs`) and is not owned elsewhere — the
3447                        // `Listeners` table removes it on `get_https`. Ownership transfers to
3448                        // the mio wrapper, whose `Drop` closes the descriptor.
3449                        .map(|fd| unsafe { MioTcpListener::from_raw_fd(fd) }),
3450                    InheritedSocketFate::Refused => {
3451                        discard_inherited_socket::<MioTcpListener>(
3452                            self.scm_listeners
3453                                .as_mut()
3454                                .and_then(|listeners| listeners.get_https(&address)),
3455                            &address,
3456                            "HTTPS",
3457                        );
3458                        None
3459                    }
3460                    // Left in the SCM table on purpose: a later `AddListener` +
3461                    // `ActivateListener` for this address can still adopt it.
3462                    InheritedSocketFate::Unclaimed => None,
3463                };
3464
3465                let activated_token = self
3466                    .https
3467                    .borrow_mut()
3468                    .activate_listener(&address, listener);
3469                match activated_token {
3470                    Ok(token) => {
3471                        self.accept(ListenToken(token.0), Protocol::HTTPSListen);
3472                        WorkerResponse::ok(req_id)
3473                    }
3474                    Err(activate_error) => worker_response_error(
3475                        req_id,
3476                        format!("Could not activate HTTPS listener: {activate_error}"),
3477                    ),
3478                }
3479            }
3480            Ok(ListenerType::Tcp) => {
3481                let listener = match self.tcp.borrow().inherited_socket_fate(&address) {
3482                    InheritedSocketFate::Adopted => self
3483                        .scm_listeners
3484                        .as_mut()
3485                        .and_then(|listeners| listeners.get_tcp(&address))
3486                        // SAFETY: `fd` was just received from the supervisor via SCM_RIGHTS
3487                        // (see `command/src/scm_socket.rs`) and is not owned elsewhere — the
3488                        // `Listeners` table removes it on `get_tcp`. Ownership transfers to
3489                        // the mio wrapper, whose `Drop` closes the descriptor.
3490                        .map(|fd| unsafe { MioTcpListener::from_raw_fd(fd) }),
3491                    InheritedSocketFate::Refused => {
3492                        discard_inherited_socket::<MioTcpListener>(
3493                            self.scm_listeners
3494                                .as_mut()
3495                                .and_then(|listeners| listeners.get_tcp(&address)),
3496                            &address,
3497                            "TCP",
3498                        );
3499                        None
3500                    }
3501                    // Left in the SCM table on purpose: a later `AddListener` +
3502                    // `ActivateListener` for this address can still adopt it.
3503                    InheritedSocketFate::Unclaimed => None,
3504                };
3505
3506                let listener_token = self.tcp.borrow_mut().activate_listener(&address, listener);
3507                match listener_token {
3508                    Ok(token) => {
3509                        self.accept(ListenToken(token.0), Protocol::TCPListen);
3510                        WorkerResponse::ok(req_id)
3511                    }
3512                    Err(activate_error) => worker_response_error(
3513                        req_id,
3514                        format!("Could not activate TCP listener: {activate_error}"),
3515                    ),
3516                }
3517            }
3518            Ok(ListenerType::Udp) => {
3519                let socket = match self.udp.borrow().inherited_socket_fate(&address) {
3520                    InheritedSocketFate::Adopted => self
3521                        .scm_listeners
3522                        .as_mut()
3523                        .and_then(|listeners| listeners.get_udp(&address))
3524                        // SAFETY: `fd` was just received from the supervisor via
3525                        // SCM_RIGHTS (see `command/src/scm_socket.rs`) and is not
3526                        // owned elsewhere — `Listeners::get_udp` removes it from
3527                        // the table. Ownership transfers to the mio `UdpSocket`
3528                        // wrapper, whose `Drop` closes the descriptor. `O_NONBLOCK`
3529                        // + `SO_REUSE*` are file-description flags preserved across
3530                        // SCM + exec.
3531                        .map(|fd| unsafe { MioUdpSocket::from_raw_fd(fd) }),
3532                    InheritedSocketFate::Refused => {
3533                        discard_inherited_socket::<MioUdpSocket>(
3534                            self.scm_listeners
3535                                .as_mut()
3536                                .and_then(|listeners| listeners.get_udp(&address)),
3537                            &address,
3538                            "UDP",
3539                        );
3540                        None
3541                    }
3542                    // Left in the SCM table on purpose: a later `AddListener` +
3543                    // `ActivateListener` for this address can still adopt it.
3544                    InheritedSocketFate::Unclaimed => None,
3545                };
3546
3547                let activated_token = self.udp.borrow_mut().activate_listener(&address, socket);
3548                match activated_token {
3549                    Ok(token) => {
3550                        // UDP never uses accept()/create_session: replace the
3551                        // `ListenSession` placeholder at the listener token with
3552                        // the real `UdpListenerSession` so the READABLE
3553                        // registration drives `Server::ready`'s generic path
3554                        // into `UdpListenerSession::update_readiness`.
3555                        //
3556                        // Install it once per ACTIVATION, not once per request.
3557                        // `Ok(token)` does not mean this call activated
3558                        // anything: `UdpListener::activate` short-circuits on
3559                        // its own `active` flag and answers `Ok(self.token)` for
3560                        // a listener that is already up. Nothing upstream
3561                        // filters that repeat out either —
3562                        // `ConfigState::activate_listener` sets `active = true`
3563                        // and answers `Ok(())` however often it is asked, so the
3564                        // main process forwards a second
3565                        // `sozu listener udp activate`, and
3566                        // `ConfigState::generate_requests` re-emits
3567                        // `ActivateListener` for every active listener on each
3568                        // state replay.
3569                        //
3570                        // Rebuilding on a repeat would therefore overwrite a
3571                        // LIVE session: the proxy keeps the shared `UdpManager`
3572                        // and its flow table, but the replacement session starts
3573                        // with empty `upstream_sockets` / `upstream_to_flow` /
3574                        // `flow_to_upstream`, so every in-flight flow stops
3575                        // forwarding and its upstream slab slot can never be
3576                        // released (`on_close_flow` reaches it only through
3577                        // `flow_to_upstream`). `close()` is a `ProxySession`
3578                        // method and not `Drop`, so dropping the displaced
3579                        // session runs no teardown on the way out.
3580                        //
3581                        // `has_listener_session` is the precise "already
3582                        // installed" test — `build_session` is its only
3583                        // producer, and deactivate / remove / soft+hard stop are
3584                        // its only consumers — paired with the reserved slab
3585                        // slot still being present, so a slot that vanished
3586                        // still falls through to the invariant-break report
3587                        // below. It deliberately tracks the INSTALLED session
3588                        // and not the listener's `active` flag: the SCM
3589                        // hand-off's `give_back_listeners` clears `active` and
3590                        // takes the socket without removing the session, and
3591                        // skipping the rebuild there is right — the retained
3592                        // session reaches the freshly bound socket through the
3593                        // shared `Rc<RefCell<UdpListener>>`, so its flows
3594                        // survive the re-exec instead of being orphaned. A repeat then answers `ok` having touched
3595                        // nothing: the listener is active and its session is in
3596                        // the slab, which is exactly what the caller asked for,
3597                        // and it matches what the HTTP/HTTPS/TCP arms already
3598                        // answer for their own repeats.
3599                        let session_installed = self.udp.borrow().has_listener_session(token)
3600                            && self.sessions.borrow().slab.contains(token.0);
3601                        if !session_installed {
3602                            // The slot is reserved for the listener's whole
3603                            // lifetime (`reserve_listen_token`), so both failures
3604                            // below are invariant breaks; report them instead of
3605                            // returning `ok` for a listener whose socket is
3606                            // registered under a token `Server::ready` would
3607                            // ignore.
3608                            let session = match self.udp.borrow_mut().build_session(token) {
3609                                Some(session) => session,
3610                                None => {
3611                                    return worker_response_error(
3612                                        req_id,
3613                                        format!(
3614                                            "Could not build the UDP listener session for {address}"
3615                                        ),
3616                                    );
3617                                }
3618                            };
3619                            let mut sessions = self.sessions.borrow_mut();
3620                            match sessions.slab.get_mut(token.0) {
3621                                Some(slot) => *slot = session,
3622                                None => {
3623                                    return worker_response_error(
3624                                        req_id,
3625                                        format!(
3626                                            "UDP listener {address} has no session slot at {token:?}"
3627                                        ),
3628                                    );
3629                                }
3630                            }
3631                        }
3632                        WorkerResponse::ok(req_id)
3633                    }
3634                    Err(activate_error) => worker_response_error(
3635                        req_id,
3636                        format!("Could not activate UDP listener: {activate_error}"),
3637                    ),
3638                }
3639            }
3640            Err(_) => worker_response_error(req_id, "Wrong variant for ListenerType on request"),
3641        }
3642    }
3643
3644    fn notify_deactivate_listener(
3645        &mut self,
3646        req_id: &str,
3647        deactivate: &DeactivateListener,
3648    ) -> WorkerResponse {
3649        debug!(
3650            "{} deactivate {:?} listener {:?}",
3651            req_id, deactivate.proxy, deactivate
3652        );
3653
3654        let address = deactivate.listener_key();
3655
3656        match ListenerType::try_from(deactivate.proxy) {
3657            Ok(ListenerType::Http) => {
3658                let (token, mut listener) =
3659                    match self.http.borrow_mut().give_back_listener(&address) {
3660                        Ok((token, listener)) => (token, listener),
3661                        Err(e) => {
3662                            return worker_response_error(
3663                                req_id,
3664                                format!("Couldn't deactivate HTTP listener at {address}: {e}"),
3665                            );
3666                        }
3667                    };
3668
3669                if let Err(e) = self.poll.registry().deregister(&mut listener) {
3670                    error!(
3671                        "error deregistering HTTP listen socket({:?}): {:?}",
3672                        deactivate, e
3673                    );
3674                }
3675
3676                // The slot stays RESERVED for the deactivated listener — see
3677                // `reserve_listen_token`. It carries the same inert placeholder
3678                // `AddListener` installed, so a later `ActivateListener` finds
3679                // its token still valid and no other session can take the key.
3680                self.reserve_listen_token(token, Protocol::HTTPListen);
3681                // The listen token may still be queued for a deferred accept
3682                // (`ready()` enqueues it whenever the listener is readable but
3683                // `can_accept` is false). The listener has just given its
3684                // socket back, so `handle_remaining_readiness` must not call
3685                // `accept()` on it.
3686                self.accept_ready.remove(&ListenToken(token.0));
3687
3688                if deactivate.to_scm {
3689                    self.unblock_scm_socket();
3690                    let listeners = Listeners {
3691                        http: vec![(address.clone(), listener.as_raw_fd())],
3692                        tls: vec![],
3693                        tcp: vec![],
3694                        udp: vec![],
3695                    };
3696                    info!("sending HTTP listener: {:?}", listeners);
3697                    let res = self.scm.send_listeners(&listeners);
3698
3699                    self.block_scm_socket();
3700
3701                    info!("sent HTTP listener: {:?}", res);
3702                }
3703                WorkerResponse::ok(req_id)
3704            }
3705            Ok(ListenerType::Https) => {
3706                let (token, mut listener) =
3707                    match self.https.borrow_mut().give_back_listener(&address) {
3708                        Ok((token, listener)) => (token, listener),
3709                        Err(e) => {
3710                            return worker_response_error(
3711                                req_id,
3712                                format!("Couldn't deactivate HTTPS listener at {address}: {e}",),
3713                            );
3714                        }
3715                    };
3716                if let Err(e) = self.poll.registry().deregister(&mut listener) {
3717                    error!(
3718                        "error deregistering HTTPS listen socket({:?}): {:?}",
3719                        deactivate, e
3720                    );
3721                }
3722                // See the HTTP arm: the slot stays reserved, and a
3723                // socket-less listener must not stay queued for an accept.
3724                self.reserve_listen_token(token, Protocol::HTTPSListen);
3725                self.accept_ready.remove(&ListenToken(token.0));
3726
3727                if deactivate.to_scm {
3728                    self.unblock_scm_socket();
3729                    let listeners = Listeners {
3730                        http: vec![],
3731                        tls: vec![(address.clone(), listener.as_raw_fd())],
3732                        tcp: vec![],
3733                        udp: vec![],
3734                    };
3735                    info!("sending HTTPS listener: {:?}", listeners);
3736                    let res = self.scm.send_listeners(&listeners);
3737
3738                    self.block_scm_socket();
3739
3740                    info!("sent HTTPS listener: {:?}", res);
3741                }
3742                WorkerResponse::ok(req_id)
3743            }
3744            Ok(ListenerType::Tcp) => {
3745                let (token, mut listener) = match self.tcp.borrow_mut().give_back_listener(&address)
3746                {
3747                    Ok((token, listener)) => (token, listener),
3748                    Err(e) => {
3749                        return worker_response_error(
3750                            req_id,
3751                            format!("Could not deactivate TCP listener at {address}: {e}"),
3752                        );
3753                    }
3754                };
3755
3756                if let Err(e) = self.poll.registry().deregister(&mut listener) {
3757                    error!(
3758                        "error deregistering TCP listen socket({:?}): {:?}",
3759                        deactivate, e
3760                    );
3761                }
3762                // See the HTTP arm: the slot stays reserved, and a
3763                // socket-less listener must not stay queued for an accept.
3764                self.reserve_listen_token(token, Protocol::TCPListen);
3765                self.accept_ready.remove(&ListenToken(token.0));
3766
3767                if deactivate.to_scm {
3768                    self.unblock_scm_socket();
3769                    let listeners = Listeners {
3770                        http: vec![],
3771                        tls: vec![],
3772                        tcp: vec![(address.clone(), listener.as_raw_fd())],
3773                        udp: vec![],
3774                    };
3775                    info!("sending TCP listener: {:?}", listeners);
3776                    let res = self.scm.send_listeners(&listeners);
3777
3778                    self.block_scm_socket();
3779
3780                    info!("sent TCP listener: {:?}", res);
3781                }
3782                WorkerResponse::ok(req_id)
3783            }
3784            Ok(ListenerType::Udp) => {
3785                let (token, mut listener) = match self.udp.borrow_mut().give_back_listener(&address)
3786                {
3787                    Ok((token, listener)) => (token, listener),
3788                    Err(e) => {
3789                        return worker_response_error(
3790                            req_id,
3791                            format!("Could not deactivate UDP listener at {address}: {e}"),
3792                        );
3793                    }
3794                };
3795
3796                if let Err(e) = self.poll.registry().deregister(&mut listener) {
3797                    error!(
3798                        "error deregistering UDP listen socket({:?}): {:?}",
3799                        deactivate, e
3800                    );
3801                }
3802                // See the HTTP arm. For UDP this ALSO drops the live
3803                // `UdpListenerSession` the activate path installed here,
3804                // putting the slot back to the placeholder — the flows it owned
3805                // were already torn down by `give_back_listener`.
3806                self.reserve_listen_token(token, Protocol::UDPListen);
3807                // A UDP listen token never reaches `accept_ready` (`ready()`
3808                // only enqueues the three accept-driven listen protocols), but
3809                // the removal keeps the rule uniform across the four arms.
3810                self.accept_ready.remove(&ListenToken(token.0));
3811
3812                if deactivate.to_scm {
3813                    self.unblock_scm_socket();
3814                    let listeners = Listeners {
3815                        http: vec![],
3816                        tls: vec![],
3817                        tcp: vec![],
3818                        udp: vec![(address.clone(), listener.as_raw_fd())],
3819                    };
3820                    info!("sending UDP listener: {:?}", listeners);
3821                    let res = self.scm.send_listeners(&listeners);
3822
3823                    self.block_scm_socket();
3824
3825                    info!("sent UDP listener: {:?}", res);
3826                }
3827                WorkerResponse::ok(req_id)
3828            }
3829            Err(_) => worker_response_error(req_id, "Wrong variant for ListenerType on request"),
3830        }
3831    }
3832
3833    /// Send all socket addresses and file descriptors of all proxies, via the scm socket
3834    pub fn return_listen_sockets(&mut self) -> Result<(), ScmSocketError> {
3835        self.unblock_scm_socket();
3836
3837        let mut http_listeners = self.http.borrow_mut().give_back_listeners();
3838        for &mut (_, ref mut sock) in http_listeners.iter_mut() {
3839            if let Err(e) = self.poll.registry().deregister(sock) {
3840                error!(
3841                    "error deregistering HTTP listen socket({:?}): {:?}",
3842                    sock, e
3843                );
3844            }
3845        }
3846
3847        let mut https_listeners = self.https.borrow_mut().give_back_listeners();
3848        for &mut (_, ref mut sock) in https_listeners.iter_mut() {
3849            if let Err(e) = self.poll.registry().deregister(sock) {
3850                error!(
3851                    "error deregistering HTTPS listen socket({:?}): {:?}",
3852                    sock, e
3853                );
3854            }
3855        }
3856
3857        let mut tcp_listeners = self.tcp.borrow_mut().give_back_listeners();
3858        for &mut (_, ref mut sock) in tcp_listeners.iter_mut() {
3859            if let Err(e) = self.poll.registry().deregister(sock) {
3860                error!("error deregistering TCP listen socket({:?}): {:?}", sock, e);
3861            }
3862        }
3863
3864        let mut udp_listeners = self.udp.borrow_mut().give_back_listeners();
3865        for &mut (_, ref mut sock) in udp_listeners.iter_mut() {
3866            if let Err(e) = self.poll.registry().deregister(sock) {
3867                error!("error deregistering UDP listen socket({:?}): {:?}", sock, e);
3868            }
3869        }
3870
3871        // use as_raw_fd because the listeners should be dropped after sending them
3872        let listeners = Listeners {
3873            http: http_listeners
3874                .iter()
3875                .map(|(key, listener)| (key.clone(), listener.as_raw_fd()))
3876                .collect(),
3877            tls: https_listeners
3878                .iter()
3879                .map(|(key, listener)| (key.clone(), listener.as_raw_fd()))
3880                .collect(),
3881            tcp: tcp_listeners
3882                .iter()
3883                .map(|(key, listener)| (key.clone(), listener.as_raw_fd()))
3884                .collect(),
3885            udp: udp_listeners
3886                .iter()
3887                .map(|(key, listener)| (key.clone(), listener.as_raw_fd()))
3888                .collect(),
3889        };
3890        // Each handed-back listener is collected exactly once: the assembled
3891        // fd lists mirror the give_back lists one-to-one (the maps above are
3892        // straight `.iter().map().collect()` with no filtering or dedup).
3893        debug_assert_eq!(
3894            listeners.http.len(),
3895            http_listeners.len(),
3896            "every HTTP listener must be collected exactly once"
3897        );
3898        debug_assert_eq!(
3899            listeners.tls.len(),
3900            https_listeners.len(),
3901            "every HTTPS listener must be collected exactly once"
3902        );
3903        debug_assert_eq!(
3904            listeners.tcp.len(),
3905            tcp_listeners.len(),
3906            "every TCP listener must be collected exactly once"
3907        );
3908        info!("sending default listeners: {:?}", listeners);
3909        let res = self.scm.send_listeners(&listeners);
3910
3911        self.block_scm_socket();
3912
3913        info!("sent default listeners: {:?}", res);
3914        res
3915    }
3916
3917    fn block_scm_socket(&mut self) {
3918        if let Err(e) = self.scm.set_blocking(true) {
3919            error!("Could not block scm socket: {}", e);
3920        }
3921    }
3922
3923    fn unblock_scm_socket(&mut self) {
3924        if let Err(e) = self.scm.set_blocking(false) {
3925            error!("Could not unblock scm socket: {}", e);
3926        }
3927    }
3928
3929    pub fn to_session(&self, token: Token) -> SessionToken {
3930        SessionToken(token.0)
3931    }
3932
3933    pub fn from_session(&self, token: SessionToken) -> Token {
3934        Token(token.0)
3935    }
3936
3937    pub fn accept(&mut self, token: ListenToken, protocol: Protocol) {
3938        // Per-protocol counter key. Keeping the namespace static (3 keys +
3939        // aggregate) is a deliberate cardinality cap: per-listener-address
3940        // labelling would require runtime `Box::leak` because `incr!` takes
3941        // `&'static str`, and listener addresses can be reconfigured at
3942        // runtime by the control plane. Operators wanting per-listener
3943        // attribution should correlate with the listener-protocol breakdown
3944        // below.
3945        //
3946        // Non-listen protocols reach this code only on an invariant break
3947        // upstream (`ready()` dispatched a non-listen `Protocol` to
3948        // `accept()`). Log and return rather than panicking — defense in
3949        // depth on the accept path, which is process-fatal if it aborts.
3950        let (proto_key, accepted_protocol) = match protocol {
3951            Protocol::TCPListen => ("listener.accepted.tcp", Protocol::TCPListen),
3952            Protocol::HTTPListen => ("listener.accepted.http", Protocol::HTTPListen),
3953            Protocol::HTTPSListen => ("listener.accepted.https", Protocol::HTTPSListen),
3954            other => {
3955                warn!(
3956                    "accept() called with non-listen protocol {:?} on token {:?}; skipping",
3957                    other, token
3958                );
3959                return;
3960            }
3961        };
3962
3963        // Past the guard, `accepted_protocol` is one of the three listen
3964        // variants — the inner dispatch's `unreachable!` arm relies on this.
3965        debug_assert!(
3966            matches!(
3967                accepted_protocol,
3968                Protocol::TCPListen | Protocol::HTTPListen | Protocol::HTTPSListen
3969            ),
3970            "accept dispatch must run with a listen protocol only"
3971        );
3972
3973        loop {
3974            let result = match accepted_protocol {
3975                Protocol::TCPListen => self.tcp.borrow_mut().accept(token),
3976                Protocol::HTTPListen => self.http.borrow_mut().accept(token),
3977                Protocol::HTTPSListen => self.https.borrow_mut().accept(token),
3978                // The outer match populates `accepted_protocol` only with the
3979                // three listen variants and returns early otherwise — this
3980                // arm is structurally unreachable.
3981                other => unreachable!(
3982                    "accept dispatch reached non-listen protocol {:?} after outer guard",
3983                    other
3984                ),
3985            };
3986            match result {
3987                Ok((sock, peer)) => {
3988                    // `peer` is the address `accept(2)` returned: the counter
3989                    // and the session below both use it, and neither costs a
3990                    // `getpeername(2)`.
3991                    incr!(names::listener::ACCEPTED_TOTAL);
3992                    incr!(proto_key);
3993                    incr!(per_source_bucket(&peer));
3994                    let queue_before = self.accept_queue.len();
3995                    self.accept_queue.push_back((
3996                        sock,
3997                        token,
3998                        accepted_protocol,
3999                        Instant::now(),
4000                        peer,
4001                    ));
4002                    // One accepted socket enqueues exactly one entry.
4003                    debug_assert_eq!(
4004                        self.accept_queue.len(),
4005                        queue_before + 1,
4006                        "each accepted socket must enqueue exactly one entry"
4007                    );
4008                }
4009                Err(AcceptError::WouldBlock) => {
4010                    self.accept_ready.remove(&token);
4011                    break;
4012                }
4013                Err(other) => {
4014                    error!(
4015                        "error accepting {:?} sockets: {:?}",
4016                        accepted_protocol, other
4017                    );
4018                    self.accept_ready.remove(&token);
4019                    break;
4020                }
4021            }
4022        }
4023
4024        gauge!(names::accept_queue::CONNECTIONS, self.accept_queue.len());
4025    }
4026
4027    pub fn create_sessions(&mut self) {
4028        while let Some((sock, token, protocol, timestamp, peer)) = self.accept_queue.pop_back() {
4029            let wait_time = Instant::now() - timestamp;
4030            time!(names::accept_queue::WAIT_TIME, wait_time.as_millis());
4031            if wait_time > self.accept_queue_timeout {
4032                incr!(names::accept_queue::TIMEOUT);
4033                continue;
4034            }
4035
4036            if !self.sessions.borrow_mut().check_limits() {
4037                // Every `break` below drops the socket we just popped,
4038                // unserved, while the remaining queued sockets wait for the
4039                // next pass or time out. `listener.connection_capped` counts
4040                // exactly that dropped socket, once, at each `break`: a socket
4041                // served after eviction made room is not a refusal.
4042                if !self.evict_on_queue_full {
4043                    incr!(names::listener::CONNECTION_CAPPED);
4044                    break;
4045                }
4046
4047                // Skip eviction during graceful shutdown — defeats the
4048                // shutting_down semantics and is wasted work since the
4049                // worker is winding down anyway.
4050                if self.shutting_down.is_some() {
4051                    incr!(names::listener::CONNECTION_CAPPED);
4052                    break;
4053                }
4054
4055                // Evict 1% of `max_connections` per iteration. Conservative
4056                // ratio: large enough to make meaningful progress clearing
4057                // the accept queue, small enough to limit collateral damage
4058                // to active sessions. The cap loop re-checks limits after
4059                // each eviction round, so multiple rounds can run if the
4060                // queue has many pending connections. Decoupled from
4061                // `slab_entries_per_connection` (which only sizes the slab,
4062                // not `max_connections`).
4063                let to_evict = (self.sessions.borrow().max_connections / 100).max(1);
4064                let evicted = self.evict_least_active_sessions(to_evict);
4065                if evicted == 0 {
4066                    // Informational, not an invariant break: the worker may
4067                    // be at boot, or every active session is a system
4068                    // protocol (Channel/Metrics/Timer/listeners) and is
4069                    // ineligible. Stay at warn so operators see it in info-
4070                    // level production logs.
4071                    warn!("evict_on_queue_full enabled but no candidate sessions to evict");
4072                    incr!(names::listener::CONNECTION_CAPPED);
4073                    break;
4074                }
4075
4076                count!(names::sessions::EVICTED, evicted as i64);
4077                warn!(
4078                    "evicted {} least recently active sessions to make room",
4079                    evicted
4080                );
4081
4082                if !self.sessions.borrow_mut().check_limits() {
4083                    incr!(names::listener::CONNECTION_CAPPED);
4084                    break;
4085                }
4086            }
4087
4088            //FIXME: check the timestamp
4089            //TODO: create_session should return the session and
4090            // the server should insert it in the the SessionManager
4091            // The accept path only ever enqueues listen protocols, so the
4092            // `_ => panic!` arm below is genuinely unreachable. Assert the set
4093            // here so a future enqueue regression trips in debug, not prod.
4094            debug_assert!(
4095                matches!(
4096                    protocol,
4097                    Protocol::TCPListen | Protocol::HTTPListen | Protocol::HTTPSListen
4098                ),
4099                "accept queue must only hold listen protocols, got {protocol:?}"
4100            );
4101            match protocol {
4102                Protocol::TCPListen => {
4103                    let proxy = self.tcp.clone();
4104                    if self
4105                        .tcp
4106                        .borrow_mut()
4107                        .create_session(sock, peer, token, wait_time, proxy)
4108                        .is_err()
4109                    {
4110                        break;
4111                    }
4112                }
4113                Protocol::HTTPListen => {
4114                    let proxy = self.http.clone();
4115                    if self
4116                        .http
4117                        .borrow_mut()
4118                        .create_session(sock, peer, token, wait_time, proxy)
4119                        .is_err()
4120                    {
4121                        break;
4122                    }
4123                }
4124                Protocol::HTTPSListen => {
4125                    if self
4126                        .https
4127                        .borrow_mut()
4128                        .create_session(sock, peer, token, wait_time, self.https.clone())
4129                        .is_err()
4130                    {
4131                        break;
4132                    }
4133                }
4134                _ => panic!("should not call accept() on a HTTP, HTTPS or TCP session"),
4135            };
4136            let nb_before = self.sessions.borrow().nb_connections;
4137            self.sessions.borrow_mut().incr();
4138            // A successfully created session bumps the live count by one.
4139            debug_assert_eq!(
4140                self.sessions.borrow().nb_connections,
4141                nb_before + 1,
4142                "create_sessions must account exactly one new connection per created session"
4143            );
4144        }
4145
4146        gauge!(names::accept_queue::CONNECTIONS, self.accept_queue.len());
4147    }
4148
4149    /// Record, for each token of one `epoll_wait` result, the session its
4150    /// slab slot holds right now, before any of the result is dispatched.
4151    fn snapshot_event_owners(
4152        &self,
4153        tokens: impl Iterator<Item = Token>,
4154        owners: &mut Vec<Option<Rc<RefCell<dyn ProxySession>>>>,
4155    ) {
4156        debug_assert!(
4157            owners.is_empty(),
4158            "the previous turn must have consumed every event owner"
4159        );
4160        let sessions = self.sessions.borrow();
4161        owners.extend(tokens.map(|token| sessions.slab.get(token.0).cloned()));
4162    }
4163
4164    /// Dispatch `events` through `Server::ready` only if `token` still
4165    /// belongs to `owner`, the session `Server::snapshot_event_owners`
4166    /// recorded for it when `epoll_wait` returned.
4167    ///
4168    /// A session that closes while the loop walks one `epoll_wait` result
4169    /// frees its slab slots at once, and the slab hands the most recently
4170    /// freed key to the next allocation. A session dispatched later in the
4171    /// same result can therefore take that key for its new backend
4172    /// (`TcpSession::connect_to_backend` in `lib/src/tcp.rs`, or the mux's
4173    /// backend dial through `L7Proxy::add_session`, `lib/src/lib.rs`) while the
4174    /// result still holds an event, typically a HUP, from the closed
4175    /// session's own socket. Delivered under the reused token, that event
4176    /// reads as the new backend hanging up, and the new session closes
4177    /// (sozu-proxy/sozu#1877). Comparing with the recorded owner drops it.
4178    /// The recorded `Rc` stays alive until this call, so no new session can
4179    /// sit at its address and pass the comparison by accident.
4180    ///
4181    /// The guard compares sessions, not sockets: a session that closes one of
4182    /// its own backends and dials another in the same result, on a different
4183    /// descriptor's event, gets the freed key back under the same `Rc`, and a
4184    /// queued event of the old socket still reaches it.
4185    fn ready_if_still_owned(
4186        &mut self,
4187        token: Token,
4188        events: Ready,
4189        owner: Option<Rc<RefCell<dyn ProxySession>>>,
4190    ) {
4191        let still_owned = owner.is_some_and(|owner| {
4192            self.sessions
4193                .borrow()
4194                .slab
4195                .get(token.0)
4196                .is_some_and(|current| Rc::ptr_eq(current, &owner))
4197        });
4198        if !still_owned {
4199            trace!(
4200                "PROXY\t{:?} dropping events {:?} from a socket whose session closed earlier in this epoll result",
4201                token, events
4202            );
4203            return;
4204        }
4205        self.ready(token, events);
4206    }
4207
4208    pub fn ready(&mut self, token: Token, events: Ready) {
4209        trace!("PROXY\t{:?} got events: {:?}", token, events);
4210
4211        let session_token = token.0;
4212        if self.sessions.borrow().slab.contains(session_token) {
4213            //info!("sessions contains {:?}", session_token);
4214            let protocol = self.sessions.borrow().slab[session_token]
4215                .borrow()
4216                .protocol();
4217            // NOTE: `token` is NOT necessarily the session's frontend token. A
4218            // session is registered under BOTH its frontend and its backend slab
4219            // slots (the multi-token pattern: `connect_to_backend` inserts the
4220            // same session Rc under a second vacant key), so `ready()` is also
4221            // dispatched with the backend token, where `frontend_token() !=
4222            // session_token`. There is therefore no `token == frontend_token`
4223            // identity to assert here.
4224            //info!("protocol: {:?}", protocol);
4225            match protocol {
4226                Protocol::HTTPListen | Protocol::HTTPSListen | Protocol::TCPListen => {
4227                    //info!("PROTOCOL IS LISTEN");
4228                    if events.is_readable() {
4229                        self.accept_ready.insert(ListenToken(token.0));
4230                        if self.sessions.borrow().can_accept {
4231                            self.accept(ListenToken(token.0), protocol);
4232                        }
4233                        return;
4234                    }
4235
4236                    if events.is_writable() {
4237                        error!(
4238                            "received writable for listener {:?}, this should not happen",
4239                            token
4240                        );
4241                        return;
4242                    }
4243
4244                    if events.is_hup() {
4245                        error!("should not happen: server {:?} closed", token);
4246                        return;
4247                    }
4248
4249                    unreachable!();
4250                }
4251                _ => {}
4252            }
4253
4254            let session = self.sessions.borrow_mut().slab[session_token].clone();
4255            session.borrow_mut().update_readiness(token, events);
4256            if session.borrow_mut().ready(session.clone()) {
4257                debug!(
4258                    "Server killing session from ready: token={:?}, protocol={:?}, events={:?}",
4259                    token, protocol, events
4260                );
4261                self.kill_session(session);
4262            }
4263        }
4264    }
4265
4266    pub fn timeout(&mut self, token: Token) {
4267        trace!("PROXY\t{:?} got timeout", token);
4268
4269        let session_token = token.0;
4270        if self.sessions.borrow().slab.contains(session_token) {
4271            let session = self.sessions.borrow_mut().slab[session_token].clone();
4272            if session.borrow_mut().timeout(token) {
4273                debug!(
4274                    "Server killing session from timeout: token={:?}, protocol={:?}",
4275                    token,
4276                    session.borrow().protocol()
4277                );
4278                self.kill_session(session);
4279            } else if session.borrow().needs_ready_pass() {
4280                // The timeout left work only `ready` can do, and no socket
4281                // event will come to trigger it: a backend whose connect timed
4282                // out must be counted as failed and its requests sent to
4283                // another backend (sozu-proxy/sozu#1800).
4284                if session.borrow_mut().ready(session.clone()) {
4285                    debug!(
4286                        "Server killing session from ready after timeout: token={:?}, protocol={:?}",
4287                        token,
4288                        session.borrow().protocol()
4289                    );
4290                    self.kill_session(session);
4291                }
4292            }
4293        }
4294    }
4295
4296    pub fn handle_remaining_readiness(&mut self) {
4297        // try to accept again after handling all session events,
4298        // since we might have released a few session slots
4299        if self.sessions.borrow().can_accept && !self.accept_ready.is_empty() {
4300            while let Some(token) = self
4301                .accept_ready
4302                .iter()
4303                .next()
4304                .map(|token| ListenToken(token.0))
4305            {
4306                // A listen token queued here while `can_accept` was false can
4307                // outlive its slab entry. `notify_deactivate_listener` no
4308                // longer frees that entry — it RESERVES the slot, re-arming it
4309                // with the inert `ListenSession` placeholder (see
4310                // `reserve_listen_token`) — so after a deactivate the lookup
4311                // below still succeeds and it is the `accept_ready` purge in
4312                // those arms that keeps a socket-less listener out of
4313                // `accept()`. `RemoveListener` is the sole release point of the
4314                // slot (`notify_proxys`), and it purges the token too, so a
4315                // token reaching this loop with no slab entry means those
4316                // purges were bypassed. Indexing the slab with it panicked the
4317                // worker, hence the fallible `get`. Purging it HERE as well as
4318                // at those sites is what keeps the loop finite: `accept()`
4319                // below only removes the token on `WouldBlock`/error, so a
4320                // `continue` without the removal would hand `iter().next()` the
4321                // same stale token forever.
4322                let protocol = self
4323                    .sessions
4324                    .borrow()
4325                    .slab
4326                    .get(token.0)
4327                    .map(|session| session.borrow().protocol());
4328                let Some(protocol) = protocol else {
4329                    error!(
4330                        "accept_ready holds listen token {:?}, which no longer has a session; dropping it",
4331                        token
4332                    );
4333                    self.accept_ready.remove(&token);
4334                    continue;
4335                };
4336                self.accept(token, protocol);
4337                if !self.sessions.borrow().can_accept || self.accept_ready.is_empty() {
4338                    break;
4339                }
4340            }
4341        }
4342    }
4343    fn block_channel(&mut self) {
4344        if let Err(e) = self.channel.blocking() {
4345            error!("Could not block channel: {}", e);
4346        }
4347    }
4348    fn unblock_channel(&mut self) {
4349        if let Err(e) = self.channel.nonblocking() {
4350            error!("Could not block channel: {}", e);
4351        }
4352    }
4353
4354    /// Evict the `count` least-recently-active non-listener sessions and
4355    /// return how many tokens were enqueued for shutdown. Used by
4356    /// `create_sessions` when the accept queue is saturated and the
4357    /// `evict_on_queue_full` knob is set.
4358    ///
4359    /// Uses `select_nth_unstable_by_key` (introselect, O(n) average) to
4360    /// partition the oldest `count` sessions in-place rather than a full
4361    /// O(n log n) sort. The candidate `Vec` is unavoidable because of
4362    /// `RefCell` borrow rules — the immutable borrow on `self.sessions`
4363    /// must drop before `shut_down_sessions_by_frontend_tokens` can take
4364    /// its mutable borrow.
4365    fn evict_least_active_sessions(&self, count: usize) -> usize {
4366        if count == 0 {
4367            return 0;
4368        }
4369
4370        let tokens = {
4371            let sessions = self.sessions.borrow();
4372            let mut candidates: Vec<(Token, Instant)> = sessions
4373                .slab
4374                .iter()
4375                .filter(|(_, session)| {
4376                    !matches!(
4377                        session.borrow().protocol(),
4378                        Protocol::HTTPListen
4379                            | Protocol::HTTPSListen
4380                            | Protocol::TCPListen
4381                            | Protocol::UDPListen
4382                            | Protocol::Channel
4383                            | Protocol::Metrics
4384                            | Protocol::Timer
4385                    )
4386                })
4387                .map(|(_, session)| {
4388                    let s = session.borrow();
4389                    (s.frontend_token(), s.last_event())
4390                })
4391                .collect();
4392
4393            // Early return is load-bearing: the `pivot` computation below
4394            // does `count.min(len) - 1`, which underflows on empty input.
4395            if candidates.is_empty() {
4396                return 0;
4397            }
4398
4399            let pivot = count.min(candidates.len()) - 1;
4400            candidates.select_nth_unstable_by_key(pivot, |&(_, last_event)| last_event);
4401
4402            candidates[..=pivot]
4403                .iter()
4404                .map(|&(token, _)| token)
4405                .collect::<HashSet<Token>>()
4406        };
4407
4408        let evicted = tokens.len();
4409        self.shut_down_sessions_by_frontend_tokens(tokens);
4410        evicted
4411    }
4412}
4413
4414/// Close an inherited listening socket this worker is not going to use, and
4415/// say so.
4416///
4417/// Reached only for [`crate::InheritedSocketFate::Refused`]: a listener is
4418/// already up at `address` on a socket of its own, so `activate()`
4419/// short-circuits and nothing consumes this descriptor. That is a statement
4420/// about now, not about the lifetime — `DeactivateListener` then
4421/// `ActivateListener` would bring the address back to `Adopted`, and because
4422/// `scm_listeners` is filled once and never refilled, closing here means such a
4423/// reactivation binds a fresh socket. See [`crate::InheritedSocketFate::Refused`]
4424/// for why that trade is taken: a queued backlog decays, whereas an unaccepted
4425/// socket in the address's `SO_REUSEPORT` group is expected to go on taking a
4426/// share of new connections for as long as it stays open. That expectation
4427/// follows from `SO_REUSEPORT` load-balancing semantics; nothing here measures
4428/// it.
4429///
4430/// The close is explicit rather than a dropped wrapper falling out of scope: it
4431/// is the same syscall either way, but this one is deliberate, attributable and
4432/// logged, which is precisely what sozu#1342 was missing.
4433fn discard_inherited_socket<T: FromRawFd>(
4434    fd: Option<RawFd>,
4435    address: &ListenerKey,
4436    protocol: &str,
4437) {
4438    let Some(fd) = fd else {
4439        return;
4440    };
4441    warn!(
4442        "closing the inherited {} listening socket for {}: this worker already has an active \
4443         listener there, so nothing will adopt this descriptor unless that listener is \
4444         deactivated and reactivated, and until then it is expected to keep taking a share of \
4445         new connections that nothing accepts (inferred from SO_REUSEPORT semantics, not \
4446         measured here)",
4447        protocol, address
4448    );
4449    // SAFETY: `fd` was just removed from the SCM table by `Listeners::get_*`,
4450    // which hands over ownership, so no other owner survives. The wrapper is
4451    // built only to be dropped: `Drop` is what performs the close.
4452    drop(unsafe { T::from_raw_fd(fd) });
4453}
4454
4455/// log the error together with the request id
4456/// create a WorkerResponse
4457fn worker_response_error<S: ToString, T: ToString>(request_id: S, error: T) -> WorkerResponse {
4458    error!(
4459        "error on request {}, {}",
4460        request_id.to_string(),
4461        error.to_string()
4462    );
4463    WorkerResponse::error(request_id, error)
4464}
4465
4466pub struct ListenSession {
4467    pub protocol: Protocol,
4468}
4469
4470impl ProxySession for ListenSession {
4471    fn last_event(&self) -> Instant {
4472        Instant::now()
4473    }
4474
4475    fn print_session(&self) {}
4476
4477    fn frontend_token(&self) -> Token {
4478        Token(0)
4479    }
4480
4481    fn protocol(&self) -> Protocol {
4482        self.protocol
4483    }
4484
4485    fn ready(&mut self, _session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
4486        false
4487    }
4488
4489    fn shutting_down(&mut self) -> SessionIsToBeClosed {
4490        false
4491    }
4492
4493    fn update_readiness(&mut self, _token: Token, _events: Ready) {}
4494
4495    fn close(&mut self) {}
4496
4497    fn timeout(&mut self, _token: Token) -> SessionIsToBeClosed {
4498        error!(
4499            "called ProxySession::timeout(token={:?}, time) on ListenSession {{ protocol: {:?} }}",
4500            _token, self.protocol
4501        );
4502        false
4503    }
4504}
4505
4506#[cfg(test)]
4507mod accept_telemetry_tests {
4508    use super::*;
4509
4510    /// Two IPv4 addresses sharing the same /24 must hash to the same bucket;
4511    /// the masking logic guarantees this regardless of the host octet.
4512    #[test]
4513    fn per_source_bucket_collapses_ipv4_slash24() {
4514        let a: SocketAddr = "203.0.113.5:1234".parse().unwrap();
4515        let b: SocketAddr = "203.0.113.250:9999".parse().unwrap();
4516        assert_eq!(
4517            per_source_bucket(&a),
4518            per_source_bucket(&b),
4519            "addresses in the same /24 must land in the same bucket"
4520        );
4521    }
4522
4523    /// Two IPv6 addresses sharing the same /48 must hash to the same bucket.
4524    #[test]
4525    fn per_source_bucket_collapses_ipv6_slash48() {
4526        let a: SocketAddr = "[2001:db8:1234::1]:443".parse().unwrap();
4527        let b: SocketAddr = "[2001:db8:1234:abcd::ffff]:8443".parse().unwrap();
4528        assert_eq!(
4529            per_source_bucket(&a),
4530            per_source_bucket(&b),
4531            "addresses in the same /48 must land in the same bucket"
4532        );
4533    }
4534
4535    /// Every bucket label must be one of `PER_SOURCE_BUCKETS` precomputed
4536    /// statics — the cardinality cap is the load-bearing property.
4537    #[test]
4538    fn per_source_bucket_keys_are_bounded() {
4539        assert_eq!(PER_SOURCE_BUCKET_KEYS.len(), PER_SOURCE_BUCKETS);
4540        for (i, key) in PER_SOURCE_BUCKET_KEYS.iter().enumerate() {
4541            let expected = format!("client.connect.per_source.bucket_{i:03}");
4542            assert_eq!(*key, expected.as_str());
4543        }
4544    }
4545
4546    /// A modest sample across distinct /24 prefixes should hit a healthy
4547    /// number of distinct buckets — guards against the hash collapsing.
4548    #[test]
4549    fn per_source_bucket_distributes_distinct_subnets() {
4550        let mut hits = std::collections::HashSet::new();
4551        for i in 0..200u8 {
4552            let addr: SocketAddr = format!("10.0.{i}.42:80").parse().unwrap();
4553            hits.insert(per_source_bucket(&addr));
4554        }
4555        // With 200 distinct /24 prefixes hashed into 256 buckets we expect
4556        // many distinct labels — assert a conservative lower bound that
4557        // tolerates birthday collisions.
4558        assert!(
4559            hits.len() >= 100,
4560            "expected at least 100 distinct buckets across 200 /24s, got {}",
4561            hits.len()
4562        );
4563    }
4564}
4565
4566#[cfg(test)]
4567mod state_error_log_tests {
4568    use sozu_command::state::StateError;
4569
4570    #[test]
4571    fn worker_state_dispatch_error_sink_bounds_the_reason() {
4572        const REASON_SECRET: &str = "WORKER_STATE_ERROR_REASON_SECRET_SENTINEL";
4573
4574        let reason = format!("{REASON_SECRET}{}", "x".repeat(4096));
4575        let reason_len = reason.len();
4576        let output = crate::capture_test_logs(move || {
4577            let error = StateError::InvalidTcpFrontend {
4578                address: "127.0.0.1:443"
4579                    .parse()
4580                    .expect("test TCP frontend address must parse"),
4581                reason,
4582            };
4583            error!("Could not execute order on config state: {}", error);
4584        });
4585
4586        assert!(
4587            !output.contains(REASON_SECRET),
4588            "worker state-error sink leaked the reason: {output}"
4589        );
4590        assert!(
4591            output.contains(&format!("reason_bytes={reason_len}")),
4592            "worker state-error sink omitted bounded reason metadata: {output}"
4593        );
4594        assert!(
4595            output.len() <= 512,
4596            "worker state-error sink is not bounded: {} bytes",
4597            output.len()
4598        );
4599    }
4600}
4601
4602#[cfg(test)]
4603mod eviction_tests {
4604    use std::collections::HashSet;
4605    use std::time::{Duration, Instant};
4606
4607    use mio::Token;
4608
4609    /// `select_nth_unstable_by_key` partitions in O(n) so that the first
4610    /// `pivot + 1` entries are the `pivot + 1` smallest by key. This guards
4611    /// against a future refactor swapping the comparator orientation.
4612    #[test]
4613    fn select_nth_finds_oldest_sessions() {
4614        let now = Instant::now();
4615        let mut candidates = [
4616            (Token(1), now - Duration::from_secs(10)), // 10s old
4617            (Token(2), now - Duration::from_secs(50)), // 50s old (oldest)
4618            (Token(3), now - Duration::from_secs(5)),  // 5s old (newest)
4619            (Token(4), now - Duration::from_secs(30)), // 30s old
4620            (Token(5), now - Duration::from_secs(20)), // 20s old
4621        ];
4622
4623        let count = 2;
4624        let pivot = count.min(candidates.len()) - 1;
4625        candidates.select_nth_unstable_by_key(pivot, |&(_, last_event)| last_event);
4626
4627        let selected: HashSet<Token> = candidates[..=pivot]
4628            .iter()
4629            .map(|&(token, _)| token)
4630            .collect();
4631
4632        assert_eq!(selected.len(), 2);
4633        assert!(
4634            selected.contains(&Token(2)),
4635            "should contain 50s-old session"
4636        );
4637        assert!(
4638            selected.contains(&Token(4)),
4639            "should contain 30s-old session"
4640        );
4641    }
4642
4643    /// When `count` exceeds available candidates, the pivot collapses to
4644    /// `len - 1` so we evict everything; this test pins that behaviour
4645    /// against a future refactor that might silently truncate.
4646    #[test]
4647    fn select_nth_with_count_exceeding_candidates() {
4648        let now = Instant::now();
4649        let mut candidates = [(Token(1), now - Duration::from_secs(10))];
4650
4651        let count = 5;
4652        let pivot = count.min(candidates.len()) - 1;
4653        candidates.select_nth_unstable_by_key(pivot, |&(_, last_event)| last_event);
4654
4655        let selected: HashSet<Token> = candidates[..=pivot]
4656            .iter()
4657            .map(|&(token, _)| token)
4658            .collect();
4659
4660        assert_eq!(selected.len(), 1);
4661        assert!(selected.contains(&Token(1)));
4662    }
4663}
4664
4665#[cfg(test)]
4666mod accept_ready_tests {
4667    use sozu_command::{
4668        config::ListenerBuilder,
4669        proto::command::{DeactivateListener, ListenerType, SocketAddress},
4670    };
4671
4672    use super::*;
4673    use crate::testing::{ServerParts, prebuild_server, provide_port};
4674
4675    /// A worker holding one activated TCP listener, reachable through the very
4676    /// same `Server` surface the event loop drives. The listen token carries a
4677    /// `ListenSession` placeholder in the slab, exactly like
4678    /// `tcp::testing::start_tcp_worker` installs it.
4679    pub(super) fn server_with_tcp_listener() -> (Server, Token, SocketAddress) {
4680        let ServerParts {
4681            event_loop,
4682            registry,
4683            sessions,
4684            pool,
4685            backends,
4686            server_scm_socket,
4687            server_config,
4688            ..
4689        // `send_scm = true`: `Server::new` makes a BLOCKING
4690        // `receive_listeners()` call before it applies the initial state, so
4691        // the client side must have queued its (empty) listener set first --
4692        // exactly what `tcp::testing::start_tcp_worker` does.
4693        } = prebuild_server(16, 16384, true).expect("could not prebuild a test server");
4694        let (_command_channel, proxy_channel) =
4695            Channel::generate(1000, 10000).expect("could not generate a test channel");
4696
4697        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4698        let listener_config = ListenerBuilder::new_tcp(address)
4699            .to_tcp(None)
4700            .expect("could not build a TcpListenerConfig for the test");
4701
4702        let listen_token = {
4703            let mut session_manager = sessions.borrow_mut();
4704            let entry = session_manager.slab.vacant_entry();
4705            let token = Token(entry.key());
4706            entry.insert(Rc::new(RefCell::new(ListenSession {
4707                protocol: Protocol::TCPListen,
4708            })));
4709            token
4710        };
4711
4712        let mut tcp_proxy =
4713            tcp::TcpProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
4714        tcp_proxy
4715            .add_listener(listener_config, listen_token)
4716            .expect("could not add the test TCP listener");
4717        tcp_proxy
4718            .activate_listener(&address.into(), None)
4719            .expect("could not activate the test TCP listener");
4720
4721        let server = Server::new(
4722            event_loop,
4723            proxy_channel,
4724            server_scm_socket,
4725            sessions,
4726            pool,
4727            backends,
4728            None,
4729            None,
4730            Some(tcp_proxy),
4731            server_config,
4732            None,
4733            false,
4734        )
4735        .expect("could not build the test server");
4736
4737        // `_command_channel` is dropped here on purpose: nothing in these
4738        // tests reads the worker's side of the channel.
4739        (server, listen_token, address)
4740    }
4741
4742    /// `ready()` queues a listen token in `accept_ready` whenever its listener
4743    /// is readable but the worker cannot accept (buffer-pool backpressure), and
4744    /// only `accept()` — which never runs while `can_accept` is false — takes
4745    /// it back out. Deactivating that listener hands its socket back, so
4746    /// without this purge the deferred accept pass called `accept()` on a
4747    /// socket-less listener (and, before the slot was reserved for the
4748    /// listener's whole lifetime, indexed a vacant slab key).
4749    ///
4750    /// To SEE THIS RED: drop `self.accept_ready.remove(&ListenToken(token.0))`
4751    /// from `notify_deactivate_listener`'s TCP arm — the last assertion below
4752    /// fails.
4753    #[test]
4754    fn deactivating_a_listener_drops_its_pending_accept_token() {
4755        let (mut server, listen_token, address) = server_with_tcp_listener();
4756
4757        // The backpressure state: readable listener, worker at capacity.
4758        server.accept_ready.insert(ListenToken(listen_token.0));
4759
4760        let response = server.notify_deactivate_listener(
4761            "test-deactivate",
4762            &DeactivateListener {
4763                interface: None,
4764                address,
4765                proxy: ListenerType::Tcp as i32,
4766                to_scm: false,
4767            },
4768        );
4769        assert_eq!(
4770            response.status,
4771            ResponseStatus::Ok as i32,
4772            "deactivating an activated TCP listener must succeed: {response:?}"
4773        );
4774        assert!(
4775            server.sessions.borrow().slab.contains(listen_token.0),
4776            "deactivating a listener must KEEP its slab slot reserved — see \
4777             `reserve_listen_token`; the accept purge below is what stops the \
4778             deferred pass from accepting on a socket-less listener"
4779        );
4780        assert!(
4781            !server.accept_ready.contains(&ListenToken(listen_token.0)),
4782            "a deactivated listener must not stay queued for a deferred accept"
4783        );
4784
4785        // The pass that used to panic.
4786        server.handle_remaining_readiness();
4787    }
4788
4789    /// Structural safety for the deferred-accept pass itself: whatever else
4790    /// ever drops a listen token's slab entry, indexing the slab with a token
4791    /// that is no longer there must not take the worker down. Dropping the
4792    /// stale token is also what keeps THIS branch finite: `accept()` only
4793    /// removes a token on `WouldBlock`/error, so re-entering the loop without
4794    /// the removal would spin on the same vacant slot forever.
4795    ///
4796    /// To SEE THIS RED: restore `self.sessions.borrow().slab[token.0]` in
4797    /// `handle_remaining_readiness` — this test then panics with
4798    /// "invalid key".
4799    #[test]
4800    fn a_stale_accept_token_is_dropped_instead_of_panicking() {
4801        let (mut server, listen_token, _address) = server_with_tcp_listener();
4802
4803        server.sessions.borrow_mut().slab.remove(listen_token.0);
4804        server.accept_ready.insert(ListenToken(listen_token.0));
4805
4806        server.handle_remaining_readiness();
4807
4808        assert!(
4809            server.accept_ready.is_empty(),
4810            "a listen token with no session must be dropped from accept_ready"
4811        );
4812    }
4813}
4814/// One `epoll_wait` result can hold events for a session that closes while
4815/// the loop walks it, after another session took over one of its slab keys.
4816/// `Server::ready_if_still_owned` must deliver such an event to nobody
4817/// (sozu-proxy/sozu#1877).
4818#[cfg(test)]
4819mod event_owner_tests {
4820    use super::*;
4821
4822    /// A session that closes on its first `ready` and frees its backend slot
4823    /// from `close`, as `TcpSession::close_backend` (`lib/src/tcp.rs`) does.
4824    struct ClosingSession {
4825        sessions: Rc<RefCell<SessionManager>>,
4826        frontend: Token,
4827        backend: Token,
4828    }
4829
4830    impl ProxySession for ClosingSession {
4831        fn protocol(&self) -> Protocol {
4832            Protocol::TCP
4833        }
4834        fn ready(&mut self, _session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
4835            true
4836        }
4837        fn shutting_down(&mut self) -> SessionIsToBeClosed {
4838            true
4839        }
4840        fn update_readiness(&mut self, _token: Token, _events: Ready) {}
4841        fn close(&mut self) {
4842            self.sessions.borrow_mut().slab.try_remove(self.backend.0);
4843        }
4844        fn timeout(&mut self, _token: Token) -> SessionIsToBeClosed {
4845            false
4846        }
4847        fn last_event(&self) -> Instant {
4848            Instant::now()
4849        }
4850        fn print_session(&self) {}
4851        fn frontend_token(&self) -> Token {
4852            self.frontend
4853        }
4854    }
4855
4856    /// A session whose first `ready` dials a backend, taking the slab's next
4857    /// vacant key as `TcpSession::connect_to_backend` (`lib/src/tcp.rs`) does,
4858    /// and that records every token it is handed readiness for.
4859    struct DialingSession {
4860        sessions: Rc<RefCell<SessionManager>>,
4861        frontend: Token,
4862        backend: Option<Token>,
4863        delivered: Vec<Token>,
4864    }
4865
4866    impl ProxySession for DialingSession {
4867        fn protocol(&self) -> Protocol {
4868            Protocol::TCP
4869        }
4870        fn ready(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
4871            if self.backend.is_none() {
4872                self.backend = Some(Token(self.sessions.borrow_mut().slab.insert(session)));
4873            }
4874            false
4875        }
4876        fn shutting_down(&mut self) -> SessionIsToBeClosed {
4877            true
4878        }
4879        fn update_readiness(&mut self, token: Token, _events: Ready) {
4880            self.delivered.push(token);
4881        }
4882        fn close(&mut self) {}
4883        fn timeout(&mut self, _token: Token) -> SessionIsToBeClosed {
4884            false
4885        }
4886        fn last_event(&self) -> Instant {
4887            Instant::now()
4888        }
4889        fn print_session(&self) {}
4890        fn frontend_token(&self) -> Token {
4891            self.frontend
4892        }
4893    }
4894
4895    /// The batch of sozu-proxy/sozu#1877: the client's FIN on the first
4896    /// session's frontend, the first readiness of the second session, then
4897    /// the HUP of the first session's backend socket, in that order.
4898    ///
4899    /// To SEE THIS RED: make `Server::ready_if_still_owned` call
4900    /// `self.ready(token, events)` unconditionally — the dialing session then
4901    /// receives the closed backend's HUP under its own backend token.
4902    #[test]
4903    fn an_event_of_a_closed_session_is_not_delivered_to_the_session_reusing_its_token() {
4904        let (mut server, _listen_token, _address) =
4905            super::accept_ready_tests::server_with_tcp_listener();
4906        let sessions = server.sessions.clone();
4907
4908        let closing = Rc::new(RefCell::new(ClosingSession {
4909            sessions: sessions.clone(),
4910            frontend: Token(0),
4911            backend: Token(0),
4912        }));
4913        let dialing = Rc::new(RefCell::new(DialingSession {
4914            sessions: sessions.clone(),
4915            frontend: Token(0),
4916            backend: None,
4917            delivered: Vec::new(),
4918        }));
4919        {
4920            let mut sessions = sessions.borrow_mut();
4921            let closing_session: Rc<RefCell<dyn ProxySession>> = closing.clone();
4922            let frontend = Token(sessions.slab.insert(closing_session.clone()));
4923            let backend = Token(sessions.slab.insert(closing_session));
4924            let mut closing = closing.borrow_mut();
4925            closing.frontend = frontend;
4926            closing.backend = backend;
4927            let dialing_session: Rc<RefCell<dyn ProxySession>> = dialing.clone();
4928            dialing.borrow_mut().frontend = Token(sessions.slab.insert(dialing_session));
4929            sessions.incr();
4930            sessions.incr();
4931        }
4932        let closed_frontend = closing.borrow().frontend;
4933        let closed_backend = closing.borrow().backend;
4934        let dialing_frontend = dialing.borrow().frontend;
4935
4936        let batch = [
4937            (closed_frontend, Ready::READABLE | Ready::HUP),
4938            (dialing_frontend, Ready::READABLE | Ready::WRITABLE),
4939            (
4940                closed_backend,
4941                Ready::READABLE | Ready::WRITABLE | Ready::HUP,
4942            ),
4943        ];
4944        let mut owners = Vec::new();
4945        server.snapshot_event_owners(batch.iter().map(|(token, _)| *token), &mut owners);
4946        for ((token, events), owner) in batch.into_iter().zip(owners.drain(..)) {
4947            server.ready_if_still_owned(token, events, owner);
4948        }
4949
4950        assert_eq!(
4951            dialing.borrow().backend,
4952            Some(closed_backend),
4953            "precondition: the dial must reuse the key the closed session freed in this batch"
4954        );
4955        assert!(
4956            !sessions.borrow().slab.contains(closed_frontend.0),
4957            "the closed session must have left the slab"
4958        );
4959        assert_eq!(
4960            dialing.borrow().delivered,
4961            vec![dialing_frontend],
4962            "the closed backend's HUP must not reach the session that reused its token"
4963        );
4964    }
4965}
4966/// The listener slab slot is reserved for a listener's whole `AddListener` ->
4967/// `RemoveListener` lifetime. These tests pin both ends of that lifetime and
4968/// the deactivate/reactivate cycle in between; see `reserve_listen_token` for
4969/// the design and the alternative it rejects.
4970#[cfg(test)]
4971mod listener_lifecycle_tests {
4972    use std::net::UdpSocket as StdUdpSocket;
4973
4974    use sozu_command::{
4975        config::ListenerBuilder,
4976        proto::command::{
4977            ActivateListener, DeactivateListener, ListenerType, LoadBalancingParams,
4978            RemoveListener, RequestUdpFrontend, SocketAddress, filtered_metrics,
4979        },
4980    };
4981
4982    use super::accept_ready_tests::server_with_tcp_listener;
4983    use super::*;
4984    use crate::{
4985        metrics::{METRICS, names},
4986        testing::{ServerParts, prebuild_server, provide_port},
4987    };
4988
4989    /// A worker with no listener at all, driven through `Server`'s own request
4990    /// surface. Same shape as `accept_ready_tests::server_with_tcp_listener`,
4991    /// minus the pre-installed listener — `Server::new` builds its own UDP
4992    /// proxy, which is the one these tests exercise.
4993    pub(super) fn bare_server() -> Server {
4994        let ServerParts {
4995            event_loop,
4996            sessions,
4997            pool,
4998            backends,
4999            server_scm_socket,
5000            server_config,
5001            ..
5002        } = prebuild_server(16, 16384, true).expect("could not prebuild a test server");
5003        let (_command_channel, proxy_channel) =
5004            Channel::generate(1000, 10000).expect("could not generate a test channel");
5005
5006        Server::new(
5007            event_loop,
5008            proxy_channel,
5009            server_scm_socket,
5010            sessions,
5011            pool,
5012            backends,
5013            None,
5014            None,
5015            None,
5016            server_config,
5017            None,
5018            false,
5019        )
5020        .expect("could not build the test server")
5021    }
5022
5023    fn add_udp_listener(server: &mut Server, address: SocketAddress) -> Token {
5024        let listener_config = ListenerBuilder::new_udp(address)
5025            .to_udp(None)
5026            .expect("could not build a UdpListenerConfig for the test");
5027        let response = server.notify_add_udp_listener("test-add-udp", listener_config);
5028        assert_eq!(
5029            response.status,
5030            ResponseStatus::Ok as i32,
5031            "adding a UDP listener must succeed: {response:?}"
5032        );
5033        server
5034            .udp
5035            .borrow()
5036            .listener_token(&address.into())
5037            .expect("the added UDP listener must own a token")
5038    }
5039
5040    fn activate(server: &mut Server, address: SocketAddress, proxy: ListenerType) {
5041        let response = server.notify_activate_listener(
5042            "test-activate",
5043            &ActivateListener {
5044                interface: None,
5045                address,
5046                proxy: proxy as i32,
5047                from_scm: false,
5048            },
5049        );
5050        assert_eq!(
5051            response.status,
5052            ResponseStatus::Ok as i32,
5053            "activating a {proxy:?} listener must succeed: {response:?}"
5054        );
5055    }
5056
5057    fn deactivate(server: &mut Server, address: SocketAddress, proxy: ListenerType) {
5058        let response = server.notify_deactivate_listener(
5059            "test-deactivate",
5060            &DeactivateListener {
5061                interface: None,
5062                address,
5063                proxy: proxy as i32,
5064                to_scm: false,
5065            },
5066        );
5067        assert_eq!(
5068            response.status,
5069            ResponseStatus::Ok as i32,
5070            "deactivating a {proxy:?} listener must succeed: {response:?}"
5071        );
5072    }
5073
5074    /// The protocol recorded in the slab slot at `token`, and whether that slot
5075    /// holds the inert `ListenSession` placeholder. `ListenSession` reports
5076    /// `Token(0)` as its frontend token while a real `UdpListenerSession`
5077    /// reports its own listen token — the only way to tell the two apart, since
5078    /// both answer `Protocol::UDPListen`.
5079    fn slot_state(server: &Server, token: Token) -> Option<(Protocol, bool)> {
5080        let sessions = server.sessions.borrow();
5081        let session = sessions.slab.get(token.0)?;
5082        let session = session.borrow();
5083        Some((session.protocol(), session.frontend_token() == Token(0)))
5084    }
5085
5086    /// FAILURE MODE 1, the whole point of this change: a deactivated listener
5087    /// that is activated again must be reachable from the event loop.
5088    ///
5089    /// The proxies retain the listener's token across a deactivate
5090    /// (`give_back_listener` and `activate()` both hand back the same one), and
5091    /// `Server::ready` ignores any token the slab does not contain. Freeing the
5092    /// slot on deactivate therefore left `ActivateListener` re-registering the
5093    /// socket under a dead token: the request answered `ok` and the listener
5094    /// never saw another readiness event.
5095    ///
5096    /// `can_accept` is forced to `false` so `ready()` parks the token in
5097    /// `accept_ready` instead of immediately draining it in `accept()` —
5098    /// membership there is the observable proof that the event reached the
5099    /// listener's dispatch path at all.
5100    ///
5101    /// To SEE THIS RED: in `notify_deactivate_listener`'s TCP arm, replace
5102    /// `self.reserve_listen_token(token, Protocol::TCPListen);` with
5103    /// `self.sessions.borrow_mut().slab.remove(token.0);` — the reactivated
5104    /// listener's readiness event is then dropped, and the assertion fails
5105    /// with "a reactivated listener must still be dispatched by `ready()`".
5106    #[test]
5107    fn a_reactivated_tcp_listener_is_reachable_from_the_event_loop() {
5108        let (mut server, listen_token, address) = server_with_tcp_listener();
5109
5110        deactivate(&mut server, address, ListenerType::Tcp);
5111        activate(&mut server, address, ListenerType::Tcp);
5112
5113        server.sessions.borrow_mut().can_accept = false;
5114        server.ready(listen_token, Ready::READABLE);
5115
5116        assert!(
5117            server.accept_ready.contains(&ListenToken(listen_token.0)),
5118            "a reactivated listener must still be dispatched by `ready()`"
5119        );
5120    }
5121
5122    /// The UDP half of failure mode 1. UDP never accepts, so its activate path
5123    /// installs a real `UdpListenerSession` at the listen token instead — which
5124    /// it skipped outright when the slot had been freed, leaving the socket
5125    /// registered against the placeholder-less token and every datagram
5126    /// unread, while the request still answered `ok`.
5127    ///
5128    /// To SEE THIS RED: in `notify_deactivate_listener`'s UDP arm, replace
5129    /// `self.reserve_listen_token(token, Protocol::UDPListen);` with
5130    /// `self.sessions.borrow_mut().slab.remove(token.0);` — the activate arm
5131    /// then returns an error response ("has no session slot"), so `activate()`
5132    /// panics on the status assertion.
5133    #[test]
5134    fn a_reactivated_udp_listener_gets_its_session_back() {
5135        let mut server = bare_server();
5136        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5137        let listen_token = add_udp_listener(&mut server, address);
5138
5139        assert_eq!(
5140            slot_state(&server, listen_token),
5141            Some((Protocol::UDPListen, true)),
5142            "an added, not-yet-activated UDP listener holds the placeholder"
5143        );
5144
5145        activate(&mut server, address, ListenerType::Udp);
5146        assert_eq!(
5147            slot_state(&server, listen_token),
5148            Some((Protocol::UDPListen, false)),
5149            "activating must install the real UdpListenerSession"
5150        );
5151
5152        deactivate(&mut server, address, ListenerType::Udp);
5153        activate(&mut server, address, ListenerType::Udp);
5154
5155        assert_eq!(
5156            slot_state(&server, listen_token),
5157            Some((Protocol::UDPListen, false)),
5158            "reactivating must reinstall the real UdpListenerSession"
5159        );
5160    }
5161
5162    /// FAILURE MODE 2, now impossible by construction: while the slot was
5163    /// freed on deactivate, the slab was free to hand that key to the next
5164    /// session, and the UDP activate path — which found `contains` true and
5165    /// assigned — overwrote that live session with its listener session.
5166    ///
5167    /// The reserved slot is never vacant, so `vacant_entry()` can no longer
5168    /// return it. This test pins that, and ONLY that: the session created while
5169    /// the listener is deactivated must land on a different key and survive the
5170    /// reactivate. It says nothing about what the activate arm writes into the
5171    /// listener's OWN key, because the reactivate here follows a deactivate and
5172    /// so takes the full activation path. The repeat with no deactivate in
5173    /// between — where the arm's answer is `Ok(token)` from
5174    /// `UdpListener::activate`'s `active` short-circuit and the listener's own
5175    /// key holds a LIVE `UdpListenerSession` — is FAILURE MODE 3, pinned by
5176    /// `a_duplicate_udp_activate_keeps_the_live_listener_session` and the three
5177    /// tests beside it. Naming this one for "a live session" hid that gap: it
5178    /// stayed green throughout.
5179    ///
5180    /// To SEE THIS RED: in `notify_deactivate_listener`'s UDP arm, replace
5181    /// `self.reserve_listen_token(token, Protocol::UDPListen);` with
5182    /// `self.sessions.borrow_mut().slab.remove(token.0);` — the interloper
5183    /// then takes the listener's key and the `assert_ne!` below fails with
5184    /// "a deactivated listener's key must not be handed to another session".
5185    #[test]
5186    fn a_deactivated_udp_listeners_key_is_not_handed_to_another_session() {
5187        let mut server = bare_server();
5188        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5189        let listen_token = add_udp_listener(&mut server, address);
5190        activate(&mut server, address, ListenerType::Udp);
5191        deactivate(&mut server, address, ListenerType::Udp);
5192
5193        // Whatever the worker admits next takes the slab's next vacant key.
5194        // `Protocol::Channel` is just a marker no listener ever reports.
5195        let interloper = {
5196            let mut sessions = server.sessions.borrow_mut();
5197            let entry = sessions.slab.vacant_entry();
5198            let key = entry.key();
5199            entry.insert(Rc::new(RefCell::new(ListenSession {
5200                protocol: Protocol::Channel,
5201            })));
5202            Token(key)
5203        };
5204        assert_ne!(
5205            interloper, listen_token,
5206            "a deactivated listener's key must not be handed to another session"
5207        );
5208
5209        activate(&mut server, address, ListenerType::Udp);
5210
5211        assert_eq!(
5212            slot_state(&server, interloper),
5213            Some((Protocol::Channel, true)),
5214            "reactivating a listener must not overwrite another session's slot"
5215        );
5216    }
5217
5218    /// The cluster every UDP data-path test in this module routes through.
5219    const UDP_FLOW_CLUSTER: &str = "udp-duplicate-activate";
5220
5221    /// The session Rc currently occupying `token`. Identity (`Rc::ptr_eq`)
5222    /// across an operation is the direct statement that the operation did not
5223    /// replace the live session — `UdpListenerSession`'s per-flow maps are
5224    /// private to `udp.rs`, so identity plus an end-to-end datagram are what
5225    /// this module can observe.
5226    fn slab_session(server: &Server, token: Token) -> Rc<RefCell<dyn ProxySession>> {
5227        server
5228            .sessions
5229            .borrow()
5230            .slab
5231            .get(token.0)
5232            .expect("the listener must own a slab slot")
5233            .clone()
5234    }
5235
5236    /// The current value of the `udp.active_flows` gauge for this test thread.
5237    /// `METRICS` is a `thread_local!` and libtest gives each test its own
5238    /// thread, so this reads only what the test itself emitted.
5239    fn active_flows_gauge() -> i64 {
5240        METRICS.with(|metrics| {
5241            metrics
5242                .borrow_mut()
5243                .dump_local_proxy_metrics()
5244                .get(names::udp::ACTIVE_FLOWS)
5245                .and_then(|metric| match metric.inner {
5246                    Some(filtered_metrics::Inner::Gauge(value)) => Some(value as i64),
5247                    _ => None,
5248                })
5249                .unwrap_or(0)
5250        })
5251    }
5252
5253    /// A worker holding one activated UDP listener, a frontend and one backend,
5254    /// plus the real client and backend sockets. The UDP data path is driven for
5255    /// real on loopback because the orphaning these tests pin — the shell's
5256    /// `upstream_to_flow` / `flow_to_upstream` / `upstream_sockets` maps going
5257    /// empty under a live `UdpManager` — is only observable end to end.
5258    fn udp_worker_with_backend(
5259        server: &mut Server,
5260        address: SocketAddress,
5261    ) -> (StdUdpSocket, StdUdpSocket) {
5262        server.notify_proxys(WorkerRequest {
5263            id: "test-add-udp-front".to_owned(),
5264            content: RequestType::AddUdpFrontend(RequestUdpFrontend {
5265                cluster_id: UDP_FLOW_CLUSTER.to_owned(),
5266                address,
5267                tags: Default::default(),
5268            })
5269            .into(),
5270        });
5271
5272        let backend_socket =
5273            StdUdpSocket::bind("127.0.0.1:0").expect("could not bind the test UDP backend");
5274        backend_socket
5275            .set_read_timeout(Some(Duration::from_millis(500)))
5276            .expect("could not set the backend read timeout");
5277        let backend_address = backend_socket
5278            .local_addr()
5279            .expect("the test UDP backend must have a local address");
5280
5281        server.notify_proxys(WorkerRequest {
5282            id: "test-add-udp-backend".to_owned(),
5283            content: RequestType::AddBackend(AddBackend {
5284                cluster_id: UDP_FLOW_CLUSTER.to_owned(),
5285                backend_id: format!("{UDP_FLOW_CLUSTER}-0"),
5286                address: backend_address.into(),
5287                load_balancing_parameters: Some(LoadBalancingParams::default()),
5288                sticky_id: None,
5289                backup: None,
5290            })
5291            .into(),
5292        });
5293
5294        let client_socket =
5295            StdUdpSocket::bind("127.0.0.1:0").expect("could not bind the test UDP client");
5296        (client_socket, backend_socket)
5297    }
5298
5299    /// Send one datagram from `client` to the listener and drive a single
5300    /// READABLE event through `Server::ready`, exactly as the event loop does.
5301    /// Returns what reached the backend within its read timeout, or `None` when
5302    /// nothing was forwarded. Loopback `send_to` has already queued the datagram
5303    /// on the listener socket by the time it returns, so one pass is enough.
5304    fn forward_one(
5305        server: &mut Server,
5306        listen_token: Token,
5307        listen_address: SocketAddress,
5308        client: &StdUdpSocket,
5309        backend: &StdUdpSocket,
5310        payload: &[u8],
5311    ) -> Option<Vec<u8>> {
5312        let target: std::net::SocketAddr = listen_address.into();
5313        client
5314            .send_to(payload, target)
5315            .expect("the test client must reach the UDP listener");
5316        server.ready(listen_token, Ready::READABLE);
5317
5318        let mut received = [0u8; 64];
5319        backend
5320            .recv_from(&mut received)
5321            .ok()
5322            .map(|(len, _)| received[..len].to_vec())
5323    }
5324
5325    /// FAILURE MODE 3, the residual of the listener-slot change: a SECOND
5326    /// `ActivateListener` for a listener that is already active must not rebuild
5327    /// its session.
5328    ///
5329    /// `UdpListener::activate` short-circuits on its own `active` flag and
5330    /// answers `Ok(self.token)` without doing any work, so `Ok(token)` does not
5331    /// mean this call activated anything — and the arm used to run
5332    /// `build_session` + `*slot = session` on that answer regardless.
5333    ///
5334    /// To SEE THIS RED: in `notify_activate_listener`'s UDP arm, drop the
5335    /// `if !session_installed` guard so `build_session` and the slab write run
5336    /// unconditionally again — the second activate then installs a fresh
5337    /// session and the `Rc::ptr_eq` assertion fails with "a repeated
5338    /// ActivateListener must not replace the live UDP listener session".
5339    #[test]
5340    fn a_duplicate_udp_activate_keeps_the_live_listener_session() {
5341        let mut server = bare_server();
5342        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5343        let listen_token = add_udp_listener(&mut server, address);
5344        activate(&mut server, address, ListenerType::Udp);
5345
5346        let installed = slab_session(&server, listen_token);
5347
5348        let response = server.notify_activate_listener(
5349            "test-activate-twice",
5350            &ActivateListener {
5351                interface: None,
5352                address,
5353                proxy: ListenerType::Udp as i32,
5354                from_scm: false,
5355            },
5356        );
5357        // The response an already-active listener must get. `ok` and not an
5358        // error: `ConfigState::activate_listener` answers `Ok(())` for the same
5359        // repeat, `generate_activate_requests` re-emits one per active listener
5360        // on every state replay, and the HTTP/HTTPS/TCP arms all answer `ok`
5361        // for their own repeats. Erroring here would fail an ordinary replay and
5362        // make the worker disagree with the state the main process persisted.
5363        assert_eq!(
5364            response.status,
5365            ResponseStatus::Ok as i32,
5366            "a repeated ActivateListener on an active UDP listener must answer ok: {response:?}"
5367        );
5368        assert_eq!(
5369            slot_state(&server, listen_token),
5370            Some((Protocol::UDPListen, false)),
5371            "the real UdpListenerSession must still be the one in the slab"
5372        );
5373        assert!(
5374            Rc::ptr_eq(&installed, &slab_session(&server, listen_token)),
5375            "a repeated ActivateListener must not replace the live UDP listener session"
5376        );
5377    }
5378
5379    /// The consequence of that rebuild, end to end. The displaced session took
5380    /// the whole shell side of every flow with it — `upstream_sockets`,
5381    /// `upstream_to_flow`, `flow_to_upstream` — while the proxy kept the shared
5382    /// `UdpManager` and its flow table. `close()` is a `ProxySession` method and
5383    /// not `Drop`, so nothing tore those flows down; they simply stopped
5384    /// forwarding while `udp.active_flows` went on counting them.
5385    ///
5386    /// To SEE THIS RED: in `notify_activate_listener`'s UDP arm, drop the
5387    /// `if !session_installed` guard — the datagram sent after the duplicate
5388    /// activate then never reaches the backend and the assertion fails with
5389    /// "an in-flight UDP flow must survive a repeated ActivateListener".
5390    #[test]
5391    fn in_flight_udp_flows_survive_a_duplicate_udp_activate() {
5392        let mut server = bare_server();
5393        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5394        let listen_token = add_udp_listener(&mut server, address);
5395        activate(&mut server, address, ListenerType::Udp);
5396        let (client, backend) = udp_worker_with_backend(&mut server, address);
5397
5398        assert_eq!(
5399            forward_one(
5400                &mut server,
5401                listen_token,
5402                address,
5403                &client,
5404                &backend,
5405                b"before"
5406            )
5407            .as_deref(),
5408            Some(&b"before"[..]),
5409            "the flow must be established before the duplicate activate"
5410        );
5411        assert_eq!(
5412            active_flows_gauge(),
5413            1,
5414            "one established flow must be on the gauge"
5415        );
5416
5417        activate(&mut server, address, ListenerType::Udp);
5418
5419        // The gauge counts what the manager holds, and the manager is retained
5420        // across the rebuild — so the gauge alone cannot tell a live flow from
5421        // an orphaned one. Assert both together: the flow the gauge claims must
5422        // still be a flow the data path can serve.
5423        assert_eq!(
5424            active_flows_gauge(),
5425            1,
5426            "a repeated ActivateListener must neither open nor evict a flow"
5427        );
5428        assert_eq!(
5429            forward_one(
5430                &mut server,
5431                listen_token,
5432                address,
5433                &client,
5434                &backend,
5435                b"after"
5436            )
5437            .as_deref(),
5438            Some(&b"after"[..]),
5439            "an in-flight UDP flow must survive a repeated ActivateListener"
5440        );
5441    }
5442
5443    /// The resource the rebuild strands for good. Every flow holds one extra
5444    /// slab slot for its upstream socket, and `on_close_flow` reaches that slot
5445    /// only through `flow_to_upstream`. A replacement session's map is empty, so
5446    /// the eventual manager-driven teardown frees the flow, balances the gauge —
5447    /// and leaves the upstream slot allocated for the worker's whole life.
5448    ///
5449    /// To SEE THIS RED: in `notify_activate_listener`'s UDP arm, drop the
5450    /// `if !session_installed` guard — the deactivate then leaves the upstream
5451    /// slot behind and the assertion fails with "tearing the listener down must
5452    /// release every per-flow slab slot".
5453    #[test]
5454    fn a_duplicate_udp_activate_strands_no_upstream_slab_slot() {
5455        let mut server = bare_server();
5456        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5457        let listen_token = add_udp_listener(&mut server, address);
5458        activate(&mut server, address, ListenerType::Udp);
5459        let (client, backend) = udp_worker_with_backend(&mut server, address);
5460
5461        // Baseline AFTER the listener exists: its own reserved slot is part of
5462        // `base_sessions_count` and is not what this test measures.
5463        let baseline = server.sessions.borrow().slab.len();
5464
5465        assert!(
5466            forward_one(
5467                &mut server,
5468                listen_token,
5469                address,
5470                &client,
5471                &backend,
5472                b"flow"
5473            )
5474            .is_some(),
5475            "the flow must be established before the duplicate activate"
5476        );
5477        assert_eq!(
5478            server.sessions.borrow().slab.len(),
5479            baseline + 1,
5480            "an established flow owns exactly one upstream slab slot"
5481        );
5482
5483        activate(&mut server, address, ListenerType::Udp);
5484        deactivate(&mut server, address, ListenerType::Udp);
5485
5486        assert_eq!(
5487            server.sessions.borrow().slab.len(),
5488            baseline,
5489            "tearing the listener down must release every per-flow slab slot"
5490        );
5491        assert_eq!(
5492            active_flows_gauge(),
5493            0,
5494            "tearing the listener down must return the active-flows gauge to zero"
5495        );
5496    }
5497
5498    /// The path that reaches this arm without anyone typing a command:
5499    /// `load_state` replays the state the main process persisted, and
5500    /// `ConfigState::generate_activate_requests` emits one `ActivateListener`
5501    /// per ACTIVE listener. For a worker whose listener is already up, that
5502    /// replay is a duplicate activate by construction — the ordinary case, not
5503    /// an operator mistake, which is why this arm answers `ok` instead of
5504    /// surfacing the repeat as an error.
5505    ///
5506    /// Driven through the real `ConfigState` rather than a hand-written
5507    /// `ActivateListener`, so the test breaks if that emission ever changes.
5508    ///
5509    /// To SEE THIS RED: in `notify_activate_listener`'s UDP arm, drop the
5510    /// `if !session_installed` guard — the replay then rebuilds the session and
5511    /// the `Rc::ptr_eq` assertion fails with "replaying ActivateListener must
5512    /// leave the live UDP listener session in place".
5513    #[test]
5514    fn replaying_activate_listener_is_a_clean_no_op_for_an_active_udp_listener() {
5515        let mut server = bare_server();
5516        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5517        let listener_config = ListenerBuilder::new_udp(address)
5518            .to_udp(None)
5519            .expect("could not build a UdpListenerConfig for the test");
5520
5521        // Through `notify_proxys` so the worker's own `ConfigState` records the
5522        // listener and its activation, exactly as a live worker's does.
5523        server.notify_proxys(WorkerRequest {
5524            id: "test-add-udp".to_owned(),
5525            content: RequestType::AddUdpListener(listener_config).into(),
5526        });
5527        server.notify_proxys(WorkerRequest {
5528            id: "test-activate".to_owned(),
5529            content: RequestType::ActivateListener(ActivateListener {
5530                interface: None,
5531                address,
5532                proxy: ListenerType::Udp as i32,
5533                from_scm: false,
5534            })
5535            .into(),
5536        });
5537        let listen_token = server
5538            .udp
5539            .borrow()
5540            .listener_token(&address.into())
5541            .expect("the added UDP listener must own a token");
5542        let installed = slab_session(&server, listen_token);
5543
5544        let replayed: Vec<ActivateListener> = server
5545            .config_state
5546            .generate_activate_requests()
5547            .into_iter()
5548            .filter_map(|request| match request.request_type {
5549                Some(RequestType::ActivateListener(activate)) => Some(activate),
5550                _ => None,
5551            })
5552            .collect();
5553        assert_eq!(
5554            replayed.len(),
5555            1,
5556            "an active UDP listener must be replayed exactly once: {replayed:?}"
5557        );
5558
5559        for activate in &replayed {
5560            let response = server.notify_activate_listener("test-replay", activate);
5561            assert_eq!(
5562                response.status,
5563                ResponseStatus::Ok as i32,
5564                "a replayed ActivateListener must answer ok: {response:?}"
5565            );
5566        }
5567
5568        assert_eq!(
5569            slot_state(&server, listen_token),
5570            Some((Protocol::UDPListen, false)),
5571            "the replay must leave the real UdpListenerSession in the slab"
5572        );
5573        assert!(
5574            Rc::ptr_eq(&installed, &slab_session(&server, listen_token)),
5575            "replaying ActivateListener must leave the live UDP listener session in place"
5576        );
5577    }
5578
5579    /// The other end of the lifetime. Reserving the slot across a deactivate is
5580    /// only sound if something releases it: no proxy's `remove_listener`
5581    /// touches the session slab, so `RemoveListener` is the sole release point.
5582    /// Without it each add/remove cycle would strand one slab key — and
5583    /// `base_sessions_count`, decremented on every `RemoveListener`, would
5584    /// drift below the number of occupied slots, so a soft stop would wait for
5585    /// sessions that do not exist.
5586    ///
5587    /// Three full cycles, because a leak is only visible as growth, and every
5588    /// other one removes the listener while it is still ACTIVATED: that path
5589    /// drops a live `UdpListenerSession` out of the slab — the Rc that, left
5590    /// there, also pinned the listener's `UdpSocket` open and kept `ready()`
5591    /// dispatching to a removed listener.
5592    ///
5593    /// To SEE THIS RED: drop the `if let Some(token) = listen_token` block from
5594    /// `notify_proxys`'s `RemoveListener` arm — the slab then grows by one slot
5595    /// per cycle and the assertion fails with "cycle 0: removing a listener
5596    /// must release its reserved slot".
5597    #[test]
5598    fn removing_a_listener_releases_its_reserved_slab_slot() {
5599        let mut server = bare_server();
5600        let baseline = server.sessions.borrow().slab.len();
5601        let base_sessions_count = server.base_sessions_count;
5602
5603        for cycle in 0..3 {
5604            let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5605            let listen_token = add_udp_listener(&mut server, address);
5606            activate(&mut server, address, ListenerType::Udp);
5607            if cycle % 2 == 0 {
5608                deactivate(&mut server, address, ListenerType::Udp);
5609            }
5610
5611            server.notify_proxys(WorkerRequest {
5612                id: "test-remove".to_owned(),
5613                content: RequestType::RemoveListener(RemoveListener {
5614                    interface: None,
5615                    address,
5616                    proxy: ListenerType::Udp as i32,
5617                })
5618                .into(),
5619            });
5620
5621            assert!(
5622                !server.sessions.borrow().slab.contains(listen_token.0),
5623                "cycle {cycle}: removing a listener must release its reserved slot"
5624            );
5625            assert_eq!(
5626                server.sessions.borrow().slab.len(),
5627                baseline,
5628                "cycle {cycle}: the slab must return to its baseline occupancy"
5629            );
5630            assert_eq!(
5631                server.base_sessions_count, base_sessions_count,
5632                "cycle {cycle}: base_sessions_count must track the reserved slots"
5633            );
5634        }
5635    }
5636
5637    /// The UDP per-source flow limit is opt-in per cluster: a global
5638    /// `max_connections_per_ip` / `max_connections_per_subnet` — here set at
5639    /// runtime, as an operator would for HTTP/TCP — never reaches a UDP
5640    /// listener, while the cluster's own `max_connections_per_ip` does, and
5641    /// sheds a new port of an address that holds its limit.
5642    ///
5643    /// To SEE THIS RED: in `UdpProxy::cluster_config_for`, replace
5644    /// `per_ip.unwrap_or(0)` with
5645    /// `per_ip.unwrap_or(self.sessions.borrow().max_connections_per_ip)`, so
5646    /// UDP inherits the global default; the first assertion then reads `1`.
5647    #[test]
5648    fn udp_flow_limits_come_from_the_cluster_not_the_global_defaults() {
5649        use std::{net::SocketAddr, time::Instant};
5650
5651        use crate::protocol::udp::{
5652            BackendId, BackendSource, ManagerInput, MetricEvent, Output, UdpManager,
5653        };
5654
5655        struct OneBackend;
5656        impl BackendSource for OneBackend {
5657            fn select(
5658                &mut self,
5659                _cluster: &str,
5660                _key: Option<u64>,
5661                _now: Instant,
5662            ) -> Option<(BackendId, SocketAddr)> {
5663                Some(("b".to_owned(), SocketAddr::from(([127, 0, 0, 1], 5353))))
5664            }
5665        }
5666        // Feed one datagram from `port` of one address; `true` when it opened
5667        // a flow, `false` when it was shed at the per-source limit.
5668        fn admit(manager: &RefCell<UdpManager>, port: u16) -> bool {
5669            let mut manager = manager.borrow_mut();
5670            manager.handle_input(
5671                ManagerInput::ClientDatagram {
5672                    src: SocketAddr::from(([192, 0, 2, 1], port)),
5673                    payload: b"q",
5674                    backends: &mut OneBackend,
5675                },
5676                Instant::now(),
5677            );
5678            let mut opened = false;
5679            let mut shed = false;
5680            while let Some(out) = manager.poll_output() {
5681                opened |= matches!(out, Output::OpenUpstream { .. });
5682                shed |= matches!(out, Output::Metric(MetricEvent::FlowShedSourceLimit));
5683            }
5684            assert_ne!(opened, shed, "a datagram is either admitted or shed");
5685            opened
5686        }
5687        fn add_cluster(server: &mut Server, max_connections_per_ip: Option<u64>) {
5688            server.notify_proxys(WorkerRequest {
5689                id: "test-add-cluster".to_owned(),
5690                content: RequestType::AddCluster(Cluster {
5691                    cluster_id: "udp-limits".to_owned(),
5692                    max_connections_per_ip,
5693                    ..Default::default()
5694                })
5695                .into(),
5696            });
5697        }
5698
5699        let mut server = bare_server();
5700        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5701        let token = add_udp_listener(&mut server, address);
5702        server.notify(WorkerRequest {
5703            id: "test-set-per-ip".to_owned(),
5704            content: RequestType::SetMaxConnectionsPerIp(1).into(),
5705        });
5706        server.notify(WorkerRequest {
5707            id: "test-set-per-subnet".to_owned(),
5708            content: RequestType::SetMaxConnectionsPerSubnet(1).into(),
5709        });
5710        add_cluster(&mut server, None);
5711        server.notify_proxys(WorkerRequest {
5712            id: "test-add-udp-front".to_owned(),
5713            content: RequestType::AddUdpFrontend(RequestUdpFrontend {
5714                cluster_id: "udp-limits".to_owned(),
5715                address,
5716                tags: Default::default(),
5717            })
5718            .into(),
5719        });
5720        let manager = server
5721            .udp
5722            .borrow()
5723            .manager(token)
5724            .expect("the UDP listener must own a flow manager");
5725
5726        {
5727            let manager = manager.borrow();
5728            let cfg = manager.cluster_config();
5729            assert_eq!(
5730                cfg.max_flows_per_ip, 0,
5731                "the global per-IP default must not apply to UDP"
5732            );
5733            assert_eq!(
5734                cfg.max_flows_per_subnet, 0,
5735                "the global per-subnet default must not apply to UDP"
5736            );
5737        }
5738        assert!(admit(&manager, 1000));
5739        assert!(
5740            admit(&manager, 1001),
5741            "a global limit of 1 must not shed a second UDP flow"
5742        );
5743
5744        add_cluster(&mut server, Some(2));
5745        assert_eq!(manager.borrow().cluster_config().max_flows_per_ip, 2);
5746        assert!(
5747            !admit(&manager, 1002),
5748            "the cluster's own limit of 2 counts the two open flows and sheds a third"
5749        );
5750    }
5751}
5752
5753#[cfg(test)]
5754mod add_cluster_validation_tests {
5755    use sozu_command::proto::command::{LoadBalancingParams, ShardMode};
5756
5757    use super::listener_lifecycle_tests::bare_server;
5758    use super::*;
5759    use crate::{backends::ShardOutcome, load_balancing::affinity_key_from_ip};
5760
5761    const CLUSTER: &str = "sharded";
5762    const ADD_CLUSTER_ID: &str = "test-add-invalid-shard";
5763
5764    /// The worker mirrors `validate_shuffle_sharding` on `AddCluster`
5765    /// (#524): an `AddCluster` that reaches the worker without the master's
5766    /// check, as a TOML reload or a `LoadState` can, with `shard_percent`
5767    /// outside `1..=100` is answered with an error and leaves the cluster
5768    /// unsharded.
5769    ///
5770    /// TO SEE THIS RED: delete the `validate_shuffle_sharding` check from the
5771    /// `AddCluster` arm of `Server::notify_proxys`; the worker then answers
5772    /// `Ok` and arms a shard of two over the four backends.
5773    #[test]
5774    fn an_add_cluster_with_an_invalid_shard_percent_is_refused_and_unsharded() {
5775        let mut server = bare_server();
5776        server.notify_proxys(WorkerRequest {
5777            id: ADD_CLUSTER_ID.to_owned(),
5778            content: RequestType::AddCluster(Cluster {
5779                cluster_id: CLUSTER.to_owned(),
5780                load_balancing: LoadBalancingAlgorithms::RoundRobin as i32,
5781                // The only invalid knob: `shard_min_backends` and
5782                // `shard_mode` are valid on their own.
5783                shard_percent: Some(0),
5784                shard_min_backends: Some(2),
5785                shard_mode: Some(ShardMode::Strict as i32),
5786                ..Default::default()
5787            })
5788            .into(),
5789        });
5790        let response = QUEUE
5791            .with(|queue| {
5792                queue
5793                    .borrow()
5794                    .iter()
5795                    .find(|response| response.id == ADD_CLUSTER_ID)
5796                    .cloned()
5797            })
5798            .expect("the worker must answer the AddCluster");
5799        assert_eq!(
5800            response.status,
5801            ResponseStatus::Failure as i32,
5802            "an invalid shard_percent must be refused: {response:?}"
5803        );
5804        assert_eq!(response.message, "shard_percent must lie in 1..=100");
5805
5806        for index in 0..4u16 {
5807            server.notify_proxys(WorkerRequest {
5808                id: format!("test-add-backend-{index}"),
5809                content: RequestType::AddBackend(AddBackend {
5810                    cluster_id: CLUSTER.to_owned(),
5811                    backend_id: format!("{CLUSTER}-{index}"),
5812                    address: SocketAddr::from(([127, 0, 0, 1], 23_000 + index)).into(),
5813                    load_balancing_parameters: Some(LoadBalancingParams::default()),
5814                    sticky_id: None,
5815                    backup: None,
5816                })
5817                .into(),
5818            });
5819        }
5820        let key = affinity_key_from_ip(IpAddr::V4(Ipv4Addr::LOCALHOST));
5821        let mut backends = server.backends.borrow_mut();
5822        let list = backends
5823            .backends
5824            .get_mut(CLUSTER)
5825            .expect("the backends must have created the cluster's list");
5826        assert_eq!(
5827            list.select_with_key(Some(key), Instant::now()).1,
5828            ShardOutcome::Unsharded,
5829            "a refused AddCluster must not arm a shard"
5830        );
5831    }
5832}
5833
5834/// Clearing a cluster's health check resets the backends a probe marked DOWN
5835/// (#1811): no probe will ever mark them UP again.
5836#[cfg(test)]
5837mod remove_health_check_tests {
5838    use sozu_command::proto::command::{HealthCheckConfig, LoadBalancingParams};
5839
5840    use super::listener_lifecycle_tests::bare_server;
5841    use super::*;
5842
5843    const CLUSTER: &str = "health-checked";
5844
5845    fn health_check() -> HealthCheckConfig {
5846        HealthCheckConfig {
5847            uri: "/livez".to_owned(),
5848            interval: 1,
5849            timeout: 1,
5850            healthy_threshold: 1,
5851            unhealthy_threshold: 1,
5852            ..Default::default()
5853        }
5854    }
5855
5856    fn add_cluster(server: &mut Server, health_check: Option<HealthCheckConfig>) {
5857        server.notify_proxys(WorkerRequest {
5858            id: "test-add-cluster".to_owned(),
5859            content: RequestType::AddCluster(Cluster {
5860                cluster_id: CLUSTER.to_owned(),
5861                health_check,
5862                ..Default::default()
5863            })
5864            .into(),
5865        });
5866    }
5867
5868    /// A server whose cluster carries a health check and one backend a probe
5869    /// marked DOWN.
5870    fn server_with_down_backend() -> Server {
5871        let mut server = bare_server();
5872        add_cluster(&mut server, Some(health_check()));
5873        server.notify_proxys(WorkerRequest {
5874            id: "test-add-backend".to_owned(),
5875            content: RequestType::AddBackend(AddBackend {
5876                cluster_id: CLUSTER.to_owned(),
5877                backend_id: format!("{CLUSTER}-0"),
5878                address: SocketAddr::from(([127, 0, 0, 1], 23_100)).into(),
5879                load_balancing_parameters: Some(LoadBalancingParams::default()),
5880                sticky_id: None,
5881                backup: None,
5882            })
5883            .into(),
5884        });
5885        server.backends.borrow().backends[CLUSTER].backends[0]
5886            .borrow_mut()
5887            .health
5888            .record_failure(1);
5889        assert!(!backend_is_healthy(&server), "test setup: backend DOWN");
5890        server
5891    }
5892
5893    fn backend_is_healthy(server: &Server) -> bool {
5894        server.backends.borrow().backends[CLUSTER].backends[0]
5895            .borrow()
5896            .health
5897            .is_healthy()
5898    }
5899
5900    /// TO SEE THIS RED: make `Server::remove_health_check_state` remove the
5901    /// cluster from `health_check_configs` directly instead of calling
5902    /// `set_health_check_config(cluster_id, None)`; the backend stays DOWN.
5903    #[test]
5904    fn remove_health_check_resets_a_backend_marked_down() {
5905        let mut server = server_with_down_backend();
5906        server.notify_proxys(WorkerRequest {
5907            id: "test-remove-health-check".to_owned(),
5908            content: RequestType::RemoveHealthCheck(CLUSTER.to_owned()).into(),
5909        });
5910        assert!(
5911            !server
5912                .backends
5913                .borrow()
5914                .health_check_configs
5915                .contains_key(CLUSTER),
5916            "RemoveHealthCheck must forget the cluster's configuration"
5917        );
5918        assert!(
5919            backend_is_healthy(&server),
5920            "RemoveHealthCheck must reset a backend a probe marked DOWN"
5921        );
5922    }
5923
5924    /// An `AddCluster` upsert without a health check clears it the same way.
5925    ///
5926    /// TO SEE THIS RED: delete the `set_health_check_config` call from
5927    /// `Server::add_cluster`; the backend stays DOWN. The in-flight probes the
5928    /// `AddCluster` arm drops are not covered: that needs a live probe socket.
5929    #[test]
5930    fn add_cluster_without_health_check_resets_a_backend_marked_down() {
5931        let mut server = server_with_down_backend();
5932        add_cluster(&mut server, None);
5933        assert!(
5934            !server
5935                .backends
5936                .borrow()
5937                .health_check_configs
5938                .contains_key(CLUSTER),
5939            "an AddCluster without a health check must forget the configuration"
5940        );
5941        assert!(
5942            backend_is_healthy(&server),
5943            "an AddCluster without a health check must reset a backend marked DOWN"
5944        );
5945    }
5946}
5947
5948/// The SCM hand-off a worker upgrade performs: the retiring worker's listening
5949/// sockets travel to the new worker over the SCM socket, and the new worker
5950/// must ADOPT them instead of binding fresh ones.
5951/// `RemoveCluster` removes the cluster's frontends and backends from the
5952/// worker's proxies, the way `ConfigState::remove_cluster`
5953/// (`command/src/state.rs`) removes them from the state.
5954#[cfg(test)]
5955mod remove_cluster_cascade_tests {
5956    use sozu_command::{
5957        config::ListenerBuilder,
5958        proto::command::{
5959            LoadBalancingParams, PathRule, RequestHttpFrontend, RequestTcpFrontend,
5960            RequestUdpFrontend, RulePosition, SocketAddress,
5961        },
5962    };
5963
5964    use super::listener_lifecycle_tests::bare_server;
5965    use super::*;
5966    use crate::{L7ListenerHandler, protocol::http::parser::Method, testing::provide_port};
5967
5968    const REMOVED: &str = "cascade_removed";
5969    const KEPT: &str = "cascade_kept";
5970
5971    /// The answers the worker queues for `request`. Events it queues along
5972    /// the way (a removed backend's, for instance) are not answers and are
5973    /// left out.
5974    fn send(server: &mut Server, id: &str, request: RequestType) -> Vec<WorkerResponse> {
5975        server.notify_proxys(WorkerRequest {
5976            id: id.to_owned(),
5977            content: request.into(),
5978        });
5979        QUEUE.with(|queue| {
5980            queue
5981                .borrow_mut()
5982                .drain(..)
5983                .filter(|response| response.id != "EVENT")
5984                .collect()
5985        })
5986    }
5987
5988    fn http_front(cluster_id: Option<&str>, hostname: &str, path: &str) -> RequestHttpFrontend {
5989        RequestHttpFrontend {
5990            cluster_id: cluster_id.map(str::to_owned),
5991            hostname: hostname.to_owned(),
5992            path: PathRule::prefix(path.to_owned()),
5993            address: SocketAddress::new_v4(127, 0, 0, 1, 0),
5994            ..Default::default()
5995        }
5996    }
5997
5998    /// A proxy can reject an order after the worker's `ConfigState` accepted
5999    /// it. The worker must then restore its state, or `QueryClusterById`
6000    /// reports an object the runtime never installed and disagrees with the
6001    /// main process after its own rollback.
6002    #[test]
6003    fn a_proxy_rejection_rolls_back_reversible_worker_state() {
6004        let cluster_id = "worker-rollback";
6005        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6006        let http = http_front(Some(cluster_id), "rollback.test", "/");
6007
6008        let requests = [
6009            RequestType::AddHttpFrontend(RequestHttpFrontend {
6010                address,
6011                ..http.clone()
6012            }),
6013            RequestType::AddHttpsFrontend(RequestHttpFrontend { address, ..http }),
6014            RequestType::AddTcpFrontend(RequestTcpFrontend {
6015                cluster_id: cluster_id.to_owned(),
6016                address,
6017                ..Default::default()
6018            }),
6019            RequestType::AddUdpFrontend(RequestUdpFrontend {
6020                cluster_id: cluster_id.to_owned(),
6021                address,
6022                ..Default::default()
6023            }),
6024        ];
6025
6026        for (index, request) in requests.into_iter().enumerate() {
6027            let mut server = bare_server();
6028            for response in send(
6029                &mut server,
6030                &format!("cluster-{index}"),
6031                RequestType::AddCluster(Cluster {
6032                    cluster_id: cluster_id.to_owned(),
6033                    ..Default::default()
6034                }),
6035            ) {
6036                assert_eq!(response.status, ResponseStatus::Ok as i32);
6037            }
6038
6039            let responses = send(&mut server, &format!("frontend-{index}"), request);
6040            assert_eq!(
6041                responses
6042                    .iter()
6043                    .map(|response| response.status)
6044                    .collect::<Vec<_>>(),
6045                vec![ResponseStatus::Failure as i32],
6046                "a frontend with no listener must be refused: {responses:?}"
6047            );
6048
6049            let cluster = server
6050                .config_state
6051                .cluster_state(cluster_id)
6052                .expect("the setup cluster must stay in worker state");
6053            assert!(
6054                cluster.http_frontends.is_empty()
6055                    && cluster.https_frontends.is_empty()
6056                    && cluster.tcp_frontends.is_empty()
6057                    && cluster.udp_frontends.is_empty(),
6058                "a refused frontend must not remain in worker ConfigState: {cluster:?}"
6059            );
6060        }
6061    }
6062
6063    /// Removing a frontend is keyed by only its routing identity. If the
6064    /// runtime refuses the removal after `ConfigState` accepted it, rollback
6065    /// must restore the complete stored frontend rather than manufacture an
6066    /// `Add` from the removal selector and lose metadata.
6067    #[test]
6068    fn a_refused_frontend_removal_restores_the_exact_stored_frontend() {
6069        let cluster_id = "worker-remove-rollback";
6070        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6071        let tags = std::collections::BTreeMap::from([("owner".to_owned(), "preserved".to_owned())]);
6072        let mut http = http_front(Some(cluster_id), "remove.test", "/api");
6073        http.address = address;
6074        http.method = Some("POST".to_owned());
6075        http.position = RulePosition::Post.into();
6076        http.tags = tags.clone();
6077        http.rewrite_host = Some("internal.test".to_owned());
6078
6079        let mut http_selector = http.clone();
6080        http_selector.position = RulePosition::Tree.into();
6081        http_selector.tags.clear();
6082        http_selector.rewrite_host = None;
6083
6084        let mut https = http.clone();
6085        https.hostname = "secure-remove.test".to_owned();
6086        let mut https_selector = https.clone();
6087        https_selector.position = RulePosition::Tree.into();
6088        https_selector.tags.clear();
6089        https_selector.rewrite_host = None;
6090
6091        let tcp = RequestTcpFrontend {
6092            cluster_id: cluster_id.to_owned(),
6093            address,
6094            tags: tags.clone(),
6095            sni: Some("REMOVE.TEST".to_owned()),
6096            alpn: vec!["http/1.1".to_owned(), "h2".to_owned()],
6097        };
6098        let tcp_selector = RequestTcpFrontend {
6099            tags: Default::default(),
6100            sni: Some("remove.test".to_owned()),
6101            alpn: vec!["h2".to_owned(), "http/1.1".to_owned()],
6102            ..tcp.clone()
6103        };
6104
6105        let udp = RequestUdpFrontend {
6106            cluster_id: cluster_id.to_owned(),
6107            address,
6108            tags,
6109        };
6110
6111        let cases = [
6112            (
6113                "http",
6114                RequestType::AddHttpFrontend(http),
6115                RequestType::RemoveHttpFrontend(http_selector),
6116            ),
6117            (
6118                "https",
6119                RequestType::AddHttpsFrontend(https),
6120                RequestType::RemoveHttpsFrontend(https_selector),
6121            ),
6122            (
6123                "tcp",
6124                RequestType::AddTcpFrontend(tcp),
6125                RequestType::RemoveTcpFrontend(tcp_selector),
6126            ),
6127            (
6128                "udp",
6129                RequestType::AddUdpFrontend(udp.clone()),
6130                RequestType::RemoveUdpFrontend(udp),
6131            ),
6132        ];
6133
6134        for (protocol, add, remove) in cases {
6135            let mut server = bare_server();
6136            server
6137                .config_state
6138                .dispatch(
6139                    &RequestType::AddCluster(Cluster {
6140                        cluster_id: cluster_id.to_owned(),
6141                        ..Default::default()
6142                    })
6143                    .into(),
6144                )
6145                .expect("the setup cluster must enter worker state");
6146            server
6147                .config_state
6148                .dispatch(&add.into())
6149                .unwrap_or_else(|error| {
6150                    panic!("the {protocol} setup frontend must be stored: {error}")
6151                });
6152            let before = server
6153                .config_state
6154                .cluster_state(cluster_id)
6155                .expect("the setup cluster must expose its frontend");
6156
6157            let responses = send(&mut server, &format!("remove-{protocol}"), remove);
6158            assert_eq!(
6159                responses
6160                    .iter()
6161                    .map(|response| response.status)
6162                    .collect::<Vec<_>>(),
6163                vec![ResponseStatus::Failure as i32],
6164                "a frontend removal with no runtime listener must be refused: {responses:?}"
6165            );
6166            assert_eq!(
6167                server.config_state.cluster_state(cluster_id),
6168                Some(before),
6169                "a refused {protocol} removal must restore the complete stored frontend"
6170            );
6171        }
6172    }
6173
6174    /// A request-only inverse is safe only after the corresponding `Add`
6175    /// entered `ConfigState`. A duplicate is rejected before mutation; if the
6176    /// runtime then refuses it too, applying the inverse would delete the
6177    /// frontend that predated the request.
6178    #[test]
6179    fn a_refused_duplicate_frontend_add_preserves_the_preexisting_frontend() {
6180        let cluster_id = "worker-add-rollback";
6181        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6182        let tags =
6183            std::collections::BTreeMap::from([("owner".to_owned(), "preexisting".to_owned())]);
6184        let mut http = http_front(Some(cluster_id), "duplicate.test", "/api");
6185        http.address = address;
6186        http.method = Some("PATCH".to_owned());
6187        http.position = RulePosition::Pre.into();
6188        http.tags = tags.clone();
6189
6190        let mut https = http.clone();
6191        https.hostname = "secure-duplicate.test".to_owned();
6192        let tcp = RequestTcpFrontend {
6193            cluster_id: cluster_id.to_owned(),
6194            address,
6195            tags: tags.clone(),
6196            sni: Some("duplicate.test".to_owned()),
6197            alpn: vec!["h2".to_owned()],
6198        };
6199        let udp = RequestUdpFrontend {
6200            cluster_id: cluster_id.to_owned(),
6201            address,
6202            tags,
6203        };
6204
6205        let cases = [
6206            ("http", RequestType::AddHttpFrontend(http)),
6207            ("https", RequestType::AddHttpsFrontend(https)),
6208            ("tcp", RequestType::AddTcpFrontend(tcp)),
6209            ("udp", RequestType::AddUdpFrontend(udp)),
6210        ];
6211
6212        for (protocol, add) in cases {
6213            let mut server = bare_server();
6214            server
6215                .config_state
6216                .dispatch(
6217                    &RequestType::AddCluster(Cluster {
6218                        cluster_id: cluster_id.to_owned(),
6219                        ..Default::default()
6220                    })
6221                    .into(),
6222                )
6223                .expect("the setup cluster must enter worker state");
6224            server
6225                .config_state
6226                .dispatch(&add.clone().into())
6227                .unwrap_or_else(|error| {
6228                    panic!("the {protocol} setup frontend must be stored: {error}")
6229                });
6230            let before = server
6231                .config_state
6232                .cluster_state(cluster_id)
6233                .expect("the setup cluster must expose its frontend");
6234
6235            let responses = send(&mut server, &format!("duplicate-{protocol}"), add);
6236            assert_eq!(
6237                responses
6238                    .iter()
6239                    .map(|response| response.status)
6240                    .collect::<Vec<_>>(),
6241                vec![ResponseStatus::Failure as i32],
6242                "a duplicate frontend with no runtime listener must be refused: {responses:?}"
6243            );
6244            assert_eq!(
6245                server.config_state.cluster_state(cluster_id),
6246                Some(before),
6247                "a refused duplicate {protocol} add must preserve the preexisting frontend"
6248            );
6249        }
6250    }
6251
6252    /// Listener adds are request-invertible only on the vacant state path. A
6253    /// duplicate state error must leave rollback unarmed when the runtime also
6254    /// refuses the order.
6255    #[test]
6256    fn a_refused_duplicate_listener_add_preserves_the_preexisting_listener() {
6257        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6258        let listener = ListenerBuilder::new_http(address)
6259            .to_http(None)
6260            .expect("the test HTTP listener configuration must build");
6261        let mut server = bare_server();
6262        server
6263            .config_state
6264            .dispatch(&RequestType::AddHttpListener(listener.clone()).into())
6265            .expect("the setup listener must enter worker state");
6266        let before = server.config_state.http_listeners.clone();
6267
6268        // Fill the reserved slab headroom so the worker takes the explicit
6269        // refusal path before attempting to install a runtime listener.
6270        let threshold = server.sessions.borrow().accept_slab_threshold();
6271        while server.sessions.borrow().slab.len() < threshold {
6272            server
6273                .sessions
6274                .borrow_mut()
6275                .slab
6276                .insert(Rc::new(RefCell::new(ListenSession {
6277                    protocol: Protocol::HTTPListen,
6278                })));
6279        }
6280        assert!(server.sessions.borrow().at_capacity());
6281
6282        let responses = send(
6283            &mut server,
6284            "duplicate-listener",
6285            RequestType::AddHttpListener(listener),
6286        );
6287        assert_eq!(
6288            responses
6289                .iter()
6290                .map(|response| response.status)
6291                .collect::<Vec<_>>(),
6292            vec![ResponseStatus::Failure as i32],
6293            "the full session slab must refuse the duplicate listener: {responses:?}"
6294        );
6295        assert_eq!(
6296            server.config_state.http_listeners, before,
6297            "a refused duplicate listener add must preserve the preexisting listener"
6298        );
6299    }
6300
6301    fn backend(cluster_id: &str, port: u16) -> AddBackend {
6302        AddBackend {
6303            cluster_id: cluster_id.to_owned(),
6304            backend_id: format!("{cluster_id}-0"),
6305            address: SocketAddress::new_v4(127, 0, 0, 1, port),
6306            load_balancing_parameters: Some(LoadBalancingParams::default()),
6307            ..Default::default()
6308        }
6309    }
6310
6311    /// Admission leases belong to live frontend-session tokens, not to the
6312    /// current cluster configuration object. Removing a cluster therefore
6313    /// leaves the old session's contribution in both counters; re-adding the
6314    /// same id lets a new session contribute alongside it; closing either
6315    /// session releases exactly that token's contribution.
6316    ///
6317    /// To SEE THIS RED: change `untrack_all_cluster_ip`'s per-IP decrement from
6318    /// `saturating_sub(1)` to `saturating_sub(0)`. The old lease then remains at
6319    /// two and the post-close assertion fails, proving this observes release
6320    /// rather than only reconfiguration.
6321    #[test]
6322    fn remove_then_readd_same_cluster_id_preserves_and_releases_live_admission_leases() {
6323        let mut server = bare_server();
6324        let cluster_id = ClusterId::from(REMOVED);
6325        let source: IpAddr = "203.0.113.9".parse().expect("test address must parse");
6326        let subnet = server.sessions.borrow().subnet_key(&source);
6327        let old_token = Token(60_001);
6328        let new_token = Token(60_002);
6329
6330        for response in send(
6331            &mut server,
6332            "add-old-incarnation",
6333            RequestType::AddCluster(Cluster {
6334                cluster_id: REMOVED.to_owned(),
6335                max_connections_per_ip: Some(2),
6336                max_connections_per_subnet: Some(2),
6337                ..Default::default()
6338            }),
6339        ) {
6340            assert_eq!(response.status, ResponseStatus::Ok as i32);
6341        }
6342        server.sessions.borrow_mut().track_cluster_connection(
6343            old_token,
6344            cluster_id.clone(),
6345            source,
6346            Some(2),
6347        );
6348
6349        for response in send(
6350            &mut server,
6351            "remove-old-incarnation",
6352            RequestType::RemoveCluster(REMOVED.to_owned()),
6353        ) {
6354            assert_eq!(response.status, ResponseStatus::Ok as i32);
6355        }
6356        for response in send(
6357            &mut server,
6358            "add-new-incarnation",
6359            RequestType::AddCluster(Cluster {
6360                cluster_id: REMOVED.to_owned(),
6361                max_connections_per_ip: Some(2),
6362                max_connections_per_subnet: Some(2),
6363                ..Default::default()
6364            }),
6365        ) {
6366            assert_eq!(response.status, ResponseStatus::Ok as i32);
6367        }
6368
6369        {
6370            let mut sessions = server.sessions.borrow_mut();
6371            assert_eq!(sessions.connections_per_cluster_ip[REMOVED][&source], 1);
6372            assert_eq!(sessions.connections_per_cluster_subnet[REMOVED][&subnet], 1);
6373            sessions.track_cluster_connection(new_token, cluster_id, source, Some(2));
6374            assert_eq!(sessions.connections_per_cluster_ip[REMOVED][&source], 2);
6375            assert_eq!(sessions.connections_per_cluster_subnet[REMOVED][&subnet], 2);
6376
6377            sessions.untrack_all_cluster_ip(old_token);
6378            assert_eq!(sessions.connections_per_cluster_ip[REMOVED][&source], 1);
6379            assert_eq!(sessions.connections_per_cluster_subnet[REMOVED][&subnet], 1);
6380            assert!(sessions.cluster_ip_tracks.contains_key(&new_token));
6381            assert!(sessions.cluster_subnet_tracks.contains_key(&new_token));
6382
6383            sessions.untrack_all_cluster_ip(new_token);
6384            assert!(!sessions.connections_per_cluster_ip.contains_key(REMOVED));
6385            assert!(
6386                !sessions
6387                    .connections_per_cluster_subnet
6388                    .contains_key(REMOVED)
6389            );
6390        }
6391    }
6392
6393    /// The route an HTTP request to `hostname` + `path` resolves to on the
6394    /// worker's only HTTP listener, as a cluster id (`Some(None)` for a route
6395    /// that names no cluster), or `None` when nothing routes it.
6396    fn http_route(
6397        server: &Server,
6398        address: SocketAddress,
6399        hostname: &str,
6400        path: &str,
6401    ) -> Option<Option<String>> {
6402        let http = server.http.borrow();
6403        let token = http
6404            .listener_token(&SocketAddr::from(address).into())
6405            .expect("the test HTTP listener must own a token");
6406        let listener = http
6407            .get_listener(&token)
6408            .expect("the test HTTP listener must exist");
6409        listener
6410            .borrow()
6411            .frontend_from_request(hostname, path, &Method::Get)
6412            .ok()
6413            .map(|route| route.cluster_id.map(|id| id.to_string()))
6414    }
6415
6416    /// A removed cluster's HTTP routes and backends are gone from the worker,
6417    /// while another cluster's and a route that names no cluster stay. The
6418    /// worker answers the `RemoveCluster` once, and a removal of one of the
6419    /// cluster's objects sent afterwards (what a main process that predates
6420    /// the cascade still sends) answers ok instead of failing on a route the
6421    /// cascade already removed.
6422    #[test]
6423    fn remove_cluster_removes_its_routes_and_backends_from_the_worker() {
6424        let mut server = bare_server();
6425        let http_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6426        let tcp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6427
6428        let mut removed_front = http_front(Some(REMOVED), "removed.test", "/");
6429        removed_front.address = http_address;
6430        // A pre-rule: unlike a tree rule, removing it twice is an error in
6431        // `Router::remove_pre_rule` (`lib/src/router/mod.rs`).
6432        removed_front.position = RulePosition::Pre.into();
6433        let mut deny_front = http_front(None, "removed.test", "/deny");
6434        deny_front.address = http_address;
6435        let mut kept_front = http_front(Some(KEPT), "kept.test", "/");
6436        kept_front.address = http_address;
6437        let removed_tcp_front = RequestTcpFrontend {
6438            cluster_id: REMOVED.to_owned(),
6439            address: tcp_address,
6440            ..Default::default()
6441        };
6442
6443        let setup = vec![
6444            RequestType::AddHttpListener(
6445                ListenerBuilder::new_http(http_address)
6446                    .to_http(None)
6447                    .expect("could not build the test HTTP listener config"),
6448            ),
6449            RequestType::AddTcpListener(
6450                ListenerBuilder::new_tcp(tcp_address)
6451                    .to_tcp(None)
6452                    .expect("could not build the test TCP listener config"),
6453            ),
6454            RequestType::AddCluster(Cluster {
6455                cluster_id: REMOVED.to_owned(),
6456                ..Default::default()
6457            }),
6458            RequestType::AddCluster(Cluster {
6459                cluster_id: KEPT.to_owned(),
6460                ..Default::default()
6461            }),
6462            RequestType::AddHttpFrontend(removed_front.clone()),
6463            RequestType::AddHttpFrontend(deny_front),
6464            RequestType::AddHttpFrontend(kept_front),
6465            RequestType::AddTcpFrontend(removed_tcp_front.clone()),
6466            RequestType::AddBackend(backend(REMOVED, 1026)),
6467            RequestType::AddBackend(backend(KEPT, 1027)),
6468        ];
6469        for (index, request) in setup.into_iter().enumerate() {
6470            for response in send(&mut server, &format!("setup-{index}"), request) {
6471                assert_eq!(
6472                    response.status,
6473                    ResponseStatus::Ok as i32,
6474                    "setup must succeed: {response:?}"
6475                );
6476            }
6477        }
6478        assert_eq!(
6479            http_route(&server, http_address, "removed.test", "/"),
6480            Some(Some(REMOVED.to_owned())),
6481            "premise: the removed cluster's hostname routes to it"
6482        );
6483
6484        let responses = send(
6485            &mut server,
6486            "remove-cluster",
6487            RequestType::RemoveCluster(REMOVED.to_owned()),
6488        );
6489        assert_eq!(
6490            responses.iter().map(|r| r.id.as_str()).collect::<Vec<_>>(),
6491            vec!["remove-cluster"],
6492            "the worker must answer the RemoveCluster exactly once: {responses:?}"
6493        );
6494        assert_eq!(
6495            responses[0].status,
6496            ResponseStatus::Ok as i32,
6497            "RemoveCluster must succeed: {responses:?}"
6498        );
6499
6500        assert_eq!(
6501            http_route(&server, http_address, "removed.test", "/"),
6502            None,
6503            "the removed cluster's hostname must no longer route"
6504        );
6505        assert_eq!(
6506            http_route(&server, http_address, "removed.test", "/deny"),
6507            Some(None),
6508            "a route that names no cluster must survive the removal of a cluster on its hostname"
6509        );
6510        assert_eq!(
6511            http_route(&server, http_address, "kept.test", "/"),
6512            Some(Some(KEPT.to_owned())),
6513            "another cluster's route must stay"
6514        );
6515        let backends = server.backends.borrow();
6516        assert!(
6517            !backends.backends.contains_key(REMOVED),
6518            "the removed cluster's backends must be gone from the BackendMap"
6519        );
6520        assert!(
6521            backends.backends.contains_key(KEPT),
6522            "another cluster's backends must stay"
6523        );
6524        drop(backends);
6525
6526        for (index, request) in [
6527            RequestType::RemoveHttpFrontend(removed_front),
6528            RequestType::RemoveTcpFrontend(removed_tcp_front),
6529            RequestType::RemoveBackend(RemoveBackend {
6530                cluster_id: REMOVED.to_owned(),
6531                backend_id: format!("{REMOVED}-0"),
6532                address: SocketAddress::new_v4(127, 0, 0, 1, 1026),
6533            }),
6534        ]
6535        .into_iter()
6536        .enumerate()
6537        {
6538            let responses = send(&mut server, &format!("follow-up-{index}"), request);
6539            assert!(
6540                !responses.is_empty()
6541                    && responses
6542                        .iter()
6543                        .all(|r| r.status == ResponseStatus::Ok as i32),
6544                "a removal the cascade already performed must answer ok: {responses:?}"
6545            );
6546        }
6547    }
6548
6549    /// The route key names no cluster, so cluster B can take the key cluster
6550    /// A's frontend used. A removal of A's frontend arriving afterwards must
6551    /// leave B's route in the worker's router, whether A is gone (the removal
6552    /// answers ok: A's frontend went with A) or still there (refused).
6553    #[test]
6554    fn a_stale_frontend_removal_never_removes_another_clusters_route() {
6555        let http_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6556        let route = |cluster_id: &str| {
6557            let mut front = http_front(Some(cluster_id), "reused.test", "/");
6558            front.address = http_address;
6559            front
6560        };
6561        let cluster = |cluster_id: &str| {
6562            RequestType::AddCluster(Cluster {
6563                cluster_id: cluster_id.to_owned(),
6564                ..Default::default()
6565            })
6566        };
6567        let listener = || {
6568            RequestType::AddHttpListener(
6569                ListenerBuilder::new_http(http_address)
6570                    .to_http(None)
6571                    .expect("could not build the test HTTP listener config"),
6572            )
6573        };
6574
6575        for (a_is_gone, setup, expected_status) in [
6576            (
6577                true,
6578                vec![
6579                    listener(),
6580                    cluster("stale_a"),
6581                    RequestType::AddHttpFrontend(route("stale_a")),
6582                    RequestType::RemoveCluster("stale_a".to_owned()),
6583                    cluster("stale_b"),
6584                    RequestType::AddHttpFrontend(route("stale_b")),
6585                ],
6586                ResponseStatus::Ok,
6587            ),
6588            (
6589                false,
6590                vec![
6591                    listener(),
6592                    cluster("stale_a"),
6593                    cluster("stale_b"),
6594                    RequestType::AddHttpFrontend(route("stale_b")),
6595                ],
6596                ResponseStatus::Failure,
6597            ),
6598        ] {
6599            let mut server = bare_server();
6600            for (index, request) in setup.into_iter().enumerate() {
6601                for response in send(&mut server, &format!("setup-{index}"), request) {
6602                    assert_eq!(
6603                        response.status,
6604                        ResponseStatus::Ok as i32,
6605                        "setup must succeed: {response:?}"
6606                    );
6607                }
6608            }
6609            assert_eq!(
6610                http_route(&server, http_address, "reused.test", "/"),
6611                Some(Some("stale_b".to_owned())),
6612                "premise: cluster B owns the route"
6613            );
6614
6615            let responses = send(
6616                &mut server,
6617                "stale-removal",
6618                RequestType::RemoveHttpFrontend(route("stale_a")),
6619            );
6620            assert_eq!(
6621                responses.iter().map(|r| r.status).collect::<Vec<_>>(),
6622                vec![expected_status as i32],
6623                "A gone: {a_is_gone}, answers: {responses:?}"
6624            );
6625            assert_eq!(
6626                http_route(&server, http_address, "reused.test", "/"),
6627                Some(Some("stale_b".to_owned())),
6628                "cluster B's route must survive a stale removal naming cluster A (A gone: {a_is_gone})"
6629            );
6630        }
6631    }
6632
6633    /// TCP and UDP frontends are removed from their listener by address,
6634    /// whatever cluster owns it. Cluster A frees address X, B takes X, and A
6635    /// comes back with a frontend on Y. A stale removal of A's frontend on X
6636    /// then finds A's frontend list but no match in it (`NoChange`, not
6637    /// `NotFound`), and must still leave B's listener on X alone.
6638    #[test]
6639    fn a_stale_tcp_or_udp_frontend_removal_never_unmaps_another_clusters_listener() {
6640        let cluster = |cluster_id: &str| {
6641            RequestType::AddCluster(Cluster {
6642                cluster_id: cluster_id.to_owned(),
6643                ..Default::default()
6644            })
6645        };
6646        for udp in [false, true] {
6647            let x = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6648            let y = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6649            let listener = |address: SocketAddress| {
6650                if udp {
6651                    RequestType::AddUdpListener(
6652                        ListenerBuilder::new_udp(address)
6653                            .to_udp(None)
6654                            .expect("could not build the test UDP listener config"),
6655                    )
6656                } else {
6657                    RequestType::AddTcpListener(
6658                        ListenerBuilder::new_tcp(address)
6659                            .to_tcp(None)
6660                            .expect("could not build the test TCP listener config"),
6661                    )
6662                }
6663            };
6664            let front = |cluster_id: &str, address: SocketAddress| {
6665                if udp {
6666                    (
6667                        RequestType::AddUdpFrontend(RequestUdpFrontend {
6668                            cluster_id: cluster_id.to_owned(),
6669                            address,
6670                            ..Default::default()
6671                        }),
6672                        RequestType::RemoveUdpFrontend(RequestUdpFrontend {
6673                            cluster_id: cluster_id.to_owned(),
6674                            address,
6675                            ..Default::default()
6676                        }),
6677                    )
6678                } else {
6679                    (
6680                        RequestType::AddTcpFrontend(RequestTcpFrontend {
6681                            cluster_id: cluster_id.to_owned(),
6682                            address,
6683                            ..Default::default()
6684                        }),
6685                        RequestType::RemoveTcpFrontend(RequestTcpFrontend {
6686                            cluster_id: cluster_id.to_owned(),
6687                            address,
6688                            ..Default::default()
6689                        }),
6690                    )
6691                }
6692            };
6693            let owner = |server: &Server| {
6694                let key = SocketAddr::from(x).into();
6695                if udp {
6696                    server.udp.borrow().listener_cluster(&key)
6697                } else {
6698                    server.tcp.borrow().listener_cluster(&key)
6699                }
6700                .map(|id| id.to_string())
6701            };
6702
6703            let mut server = bare_server();
6704            for (index, request) in [
6705                listener(x),
6706                listener(y),
6707                cluster("stale_a"),
6708                front("stale_a", x).0,
6709                RequestType::RemoveCluster("stale_a".to_owned()),
6710                cluster("stale_b"),
6711                front("stale_b", x).0,
6712                cluster("stale_a"),
6713                front("stale_a", y).0,
6714            ]
6715            .into_iter()
6716            .enumerate()
6717            {
6718                for response in send(&mut server, &format!("setup-{index}"), request) {
6719                    assert_eq!(
6720                        response.status,
6721                        ResponseStatus::Ok as i32,
6722                        "setup must succeed (udp: {udp}): {response:?}"
6723                    );
6724                }
6725            }
6726            assert_eq!(
6727                owner(&server).as_deref(),
6728                Some("stale_b"),
6729                "premise: cluster B owns the listener on X (udp: {udp})"
6730            );
6731
6732            let responses = send(&mut server, "stale-removal", front("stale_a", x).1);
6733            assert_eq!(
6734                responses.iter().map(|r| r.status).collect::<Vec<_>>(),
6735                vec![ResponseStatus::Failure as i32],
6736                "the stale removal must be refused (udp: {udp}): {responses:?}"
6737            );
6738            assert_eq!(
6739                owner(&server).as_deref(),
6740                Some("stale_b"),
6741                "cluster B must keep the listener on X after a stale removal naming A (udp: {udp})"
6742            );
6743        }
6744    }
6745}
6746
6747#[cfg(test)]
6748mod scm_listener_handoff_tests {
6749    use std::{
6750        net::{TcpStream as StdTcpStream, UdpSocket as StdUdpSocket},
6751        os::fd::{IntoRawFd, RawFd},
6752    };
6753
6754    use sozu_command::{config::ListenerBuilder, proto::command::SocketAddress};
6755
6756    use super::*;
6757    use crate::{
6758        ProxyError,
6759        socket::{server_bind, udp_bind},
6760        testing::{ServerParts, prebuild_server, provide_port},
6761    };
6762
6763    /// A `ConfigState` holding one ACTIVE listener per protocol, i.e. what the
6764    /// main process carries into `produce_initial_state` for a worker upgrade.
6765    fn state_with_four_active_listeners(
6766        http_address: SocketAddress,
6767        https_address: SocketAddress,
6768        tcp_address: SocketAddress,
6769        udp_address: SocketAddress,
6770    ) -> ConfigState {
6771        let mut state = ConfigState::new();
6772        let adds: Vec<(Request, SocketAddress, ListenerType)> = vec![
6773            (
6774                RequestType::AddHttpListener(
6775                    ListenerBuilder::new_http(http_address)
6776                        .to_http(None)
6777                        .expect("could not build the test HTTP listener config"),
6778                )
6779                .into(),
6780                http_address,
6781                ListenerType::Http,
6782            ),
6783            (
6784                RequestType::AddHttpsListener(
6785                    ListenerBuilder::new_https(https_address)
6786                        .to_tls(None)
6787                        .expect("could not build the test HTTPS listener config"),
6788                )
6789                .into(),
6790                https_address,
6791                ListenerType::Https,
6792            ),
6793            (
6794                RequestType::AddTcpListener(
6795                    ListenerBuilder::new_tcp(tcp_address)
6796                        .to_tcp(None)
6797                        .expect("could not build the test TCP listener config"),
6798                )
6799                .into(),
6800                tcp_address,
6801                ListenerType::Tcp,
6802            ),
6803            (
6804                RequestType::AddUdpListener(
6805                    ListenerBuilder::new_udp(udp_address)
6806                        .to_udp(None)
6807                        .expect("could not build the test UDP listener config"),
6808                )
6809                .into(),
6810                udp_address,
6811                ListenerType::Udp,
6812            ),
6813        ];
6814        for (add, address, proxy) in adds {
6815            state.dispatch(&add).expect("could not add the listener");
6816            state
6817                .dispatch(
6818                    &RequestType::ActivateListener(ActivateListener {
6819                        interface: None,
6820                        address,
6821                        proxy: proxy.into(),
6822                        from_scm: false,
6823                    })
6824                    .into(),
6825                )
6826                .expect("could not mark the listener active");
6827        }
6828        state
6829    }
6830
6831    /// The SCM table for `label`, so a test can assert on retention per protocol.
6832    fn inherited_table<'a>(server: &'a Server, label: &str) -> &'a Vec<(ListenerKey, RawFd)> {
6833        let scm_listeners = server
6834            .scm_listeners
6835            .as_ref()
6836            .expect("scm_listeners must be present");
6837        match label {
6838            "HTTP" => &scm_listeners.http,
6839            "HTTPS" => &scm_listeners.tls,
6840            "TCP" => &scm_listeners.tcp,
6841            "UDP" => &scm_listeners.udp,
6842            other => panic!("unknown protocol label {other}"),
6843        }
6844    }
6845
6846    /// Whether `fd` is still the listening socket bound to `port`.
6847    ///
6848    /// Deliberately identity-aware rather than a bare "is it open" check: the
6849    /// test binary runs its tests in parallel, so a descriptor closed here can
6850    /// be handed straight back out to an unrelated file opened by another test
6851    /// and a liveness-only probe would call that "still open". Every port in
6852    /// these tests comes from `provide_port()` and is therefore unique to one
6853    /// test, so `getsockname` answering with that exact port is proof the
6854    /// descriptor is still the same socket.
6855    fn descriptor_is_socket_on_port(fd: RawFd, port: u16) -> bool {
6856        let mut storage: libc::sockaddr_storage = unsafe { std::mem::zeroed() };
6857        let mut length = std::mem::size_of::<libc::sockaddr_storage>() as libc::socklen_t;
6858        // SAFETY: `getsockname` writes at most `length` bytes into `storage`
6859        // and updates `length`; both outlive the call. A closed, reused or
6860        // non-socket descriptor answers -1, which is the answer under test.
6861        let result = unsafe {
6862            libc::getsockname(
6863                fd,
6864                (&raw mut storage).cast::<libc::sockaddr>(),
6865                &raw mut length,
6866            )
6867        };
6868        if result != 0 || storage.ss_family != libc::AF_INET as libc::sa_family_t {
6869            return false;
6870        }
6871        // SAFETY: the family check above proves the kernel filled a
6872        // `sockaddr_in`, which `sockaddr_storage` is aligned and sized for.
6873        let inet = unsafe { &*(&raw const storage).cast::<libc::sockaddr_in>() };
6874        u16::from_be(inet.sin_port) == port
6875    }
6876
6877    /// The datagram queued on the inherited UDP socket before the hand-off.
6878    const QUEUED_DATAGRAM: &[u8] = b"queued-before-the-handoff";
6879
6880    /// A worker that inherits its predecessor's listening sockets must adopt
6881    /// them, not bind its own and leave the inherited descriptors unused.
6882    ///
6883    /// `fork_main_into_worker` (`bin/src/worker.rs`) sends the retiring
6884    /// worker's descriptors over the SCM socket immediately after the fork,
6885    /// before the main process sends anything else, and
6886    /// `ConfigState::produce_initial_state` carries one `ActivateListener` per
6887    /// active listener. `Server::new` must therefore have received those
6888    /// descriptors BEFORE it applies the initial state: otherwise every
6889    /// `ActivateListener` takes the `server_bind` / `udp_bind` branch — which
6890    /// succeeds only because of `SO_REUSEPORT` — and the inherited descriptor
6891    /// is dropped unused, discarding the accept backlog (TCP/HTTP/HTTPS) and
6892    /// the receive buffer (UDP) the hand-off exists to preserve.
6893    ///
6894    /// The three TCP-family assertions read `accept_queue`, which
6895    /// `notify_activate_listener` fills through `Server::accept` right after a
6896    /// successful activation; the UDP assertion reads the listener's own
6897    /// socket, since UDP has no accept path.
6898    ///
6899    /// To SEE THIS RED: in `Server::new`, move the `receive_listeners()` block
6900    /// back below the `if let Some(state) = initial_state` block. All four
6901    /// assertions fail — each listener binds its own socket, and the queued
6902    /// connections and datagram stay on the abandoned descriptors.
6903    ///
6904    /// The `TCP_NODELAY` assertions guard the backlog drain of
6905    /// sozu-proxy/sozu#1586. To SEE THEM RED: delete the
6906    /// `self.nodelay_backlog = true;` line from `HttpListener::activate`. The
6907    /// listener still gets the flag, but the connection queued before it did
6908    /// is accepted without it.
6909    #[test]
6910    fn inherited_listener_sockets_are_adopted_by_the_initial_activation() {
6911        let ServerParts {
6912            event_loop,
6913            sessions,
6914            pool,
6915            backends,
6916            client_scm_socket,
6917            server_scm_socket,
6918            server_config,
6919            ..
6920        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
6921        let (_command_channel, proxy_channel) =
6922            Channel::generate(1000, 10000).expect("could not generate a test channel");
6923
6924        let http_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6925        let https_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6926        let tcp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6927        let udp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
6928
6929        // The retiring worker's sockets, bound exactly the way a live worker
6930        // binds them.
6931        let http_listener = server_bind(http_address.into(), None)
6932            .expect("could not bind the inherited HTTP socket");
6933        let https_listener = server_bind(https_address.into(), None)
6934            .expect("could not bind the inherited HTTPS socket");
6935        let tcp_listener =
6936            server_bind(tcp_address.into(), None).expect("could not bind the inherited TCP socket");
6937        let udp_socket =
6938            udp_bind(udp_address.into(), None).expect("could not bind the inherited UDP socket");
6939
6940        // Queue on each inherited socket exactly what the hand-off exists to
6941        // preserve: a completed connection in the accept backlog, and a
6942        // datagram in the receive buffer. The clients stay alive for the whole
6943        // test so the connections are not closed from under the backlog.
6944        let http_client = StdTcpStream::connect::<SocketAddr>(http_address.into())
6945            .expect("could not queue a connection on the inherited HTTP socket");
6946        let https_client = StdTcpStream::connect::<SocketAddr>(https_address.into())
6947            .expect("could not queue a connection on the inherited HTTPS socket");
6948        let tcp_client = StdTcpStream::connect::<SocketAddr>(tcp_address.into())
6949            .expect("could not queue a connection on the inherited TCP socket");
6950        let datagram_sender =
6951            StdUdpSocket::bind("127.0.0.1:0").expect("could not bind the test datagram sender");
6952        datagram_sender
6953            .send_to(QUEUED_DATAGRAM, SocketAddr::from(udp_address))
6954            .expect("could not queue a datagram on the inherited UDP socket");
6955
6956        // The main process's side of the hand-off: send the descriptors, then
6957        // close its own copies, exactly as `fork_main_into_worker` does.
6958        let listeners = Listeners {
6959            http: vec![(http_address.into(), http_listener.into_raw_fd())],
6960            tls: vec![(https_address.into(), https_listener.into_raw_fd())],
6961            tcp: vec![(tcp_address.into(), tcp_listener.into_raw_fd())],
6962            udp: vec![(udp_address.into(), udp_socket.into_raw_fd())],
6963        };
6964        client_scm_socket
6965            .send_listeners(&listeners)
6966            .expect("could not send the inherited listeners");
6967        listeners.close();
6968
6969        // The state the main process writes for the new worker. Built through
6970        // `ConfigState` and `produce_initial_state` rather than hand-rolled, so
6971        // the emission order under test is the real generator's: this breaks if
6972        // `generate_requests` ever stops pairing each `AddXxxListener` with the
6973        // `ActivateListener` that follows it.
6974        let initial_state =
6975            state_with_four_active_listeners(http_address, https_address, tcp_address, udp_address)
6976                .produce_initial_state();
6977        assert_eq!(
6978            initial_state.requests.len(),
6979            8,
6980            "the generator must emit one Add and one Activate per active listener"
6981        );
6982
6983        let server = Server::new(
6984            event_loop,
6985            proxy_channel,
6986            server_scm_socket,
6987            sessions,
6988            pool,
6989            backends,
6990            None,
6991            None,
6992            None,
6993            server_config,
6994            Some(initial_state),
6995            false,
6996        )
6997        .expect("could not build the test server");
6998
6999        let accepted: Vec<Protocol> = server
7000            .accept_queue
7001            .iter()
7002            .map(|(_, _, protocol, _, _)| *protocol)
7003            .collect();
7004        assert!(
7005            accepted.contains(&Protocol::HTTPListen),
7006            "the connection queued on the inherited HTTP socket must survive the hand-off, got {accepted:?}"
7007        );
7008        assert!(
7009            accepted.contains(&Protocol::HTTPSListen),
7010            "the connection queued on the inherited HTTPS socket must survive the hand-off, got {accepted:?}"
7011        );
7012        assert!(
7013            accepted.contains(&Protocol::TCPListen),
7014            "the connection queued on the inherited TCP socket must survive the hand-off, got {accepted:?}"
7015        );
7016
7017        // What those queued connections are handed to the sessions with. They
7018        // completed their handshake BEFORE the new worker set `TCP_NODELAY` on
7019        // the inherited socket, and a socket inherits the flag when its
7020        // handshake completes, not when it is accepted, so the listener alone
7021        // cannot have given it to them: the first accept drain after an
7022        // activation must. And the peer address queued with each one is the
7023        // one `accept(2)` returned, which is the client's own address.
7024        for (protocol, client) in [
7025            (Protocol::HTTPListen, &http_client),
7026            (Protocol::HTTPSListen, &https_client),
7027            (Protocol::TCPListen, &tcp_client),
7028        ] {
7029            let (sock, _, _, _, peer) = server
7030                .accept_queue
7031                .iter()
7032                .find(|(_, _, queued, _, _)| *queued == protocol)
7033                .expect("the queued connection was asserted above");
7034            assert_eq!(
7035                socket2::SockRef::from(sock).tcp_nodelay().ok(),
7036                Some(true),
7037                "a {protocol:?} connection queued before the hand-off must carry TCP_NODELAY"
7038            );
7039            assert_eq!(
7040                *peer,
7041                client.local_addr().expect("the test client has an address"),
7042                "the {protocol:?} peer address must be the client's"
7043            );
7044        }
7045
7046        let (_, adopted_udp_socket) = server
7047            .udp
7048            .borrow_mut()
7049            .give_back_listeners()
7050            .pop()
7051            .expect("the UDP listener must hold a socket after activation");
7052        let mut buffer = [0u8; 64];
7053        let received = adopted_udp_socket.recv_from(&mut buffer);
7054        match received {
7055            Ok((length, _)) => assert_eq!(
7056                &buffer[..length],
7057                QUEUED_DATAGRAM,
7058                "the datagram queued on the inherited UDP socket must survive the hand-off"
7059            ),
7060            Err(error) => panic!(
7061                "the datagram queued on the inherited UDP socket must survive the hand-off, got {error:?}"
7062            ),
7063        }
7064    }
7065
7066    /// An `ActivateListener` for a listener that is ALREADY active must dispose
7067    /// of the inherited descriptor deliberately, not leave it in the table.
7068    ///
7069    /// `activate()` short-circuits on `if self.active`, so nothing adopts that
7070    /// descriptor while the listener holds the address — a deactivate plus
7071    /// reactivate would, which is why `InheritedSocketFate::Refused` documents
7072    /// the close as a trade rather than an impossibility. Leaving it in the SCM
7073    /// table is not neutral either: it is the retiring worker's listening
7074    /// socket, still bound with `SO_REUSEPORT` and registered with no event
7075    /// loop, which is expected to keep taking a share of new connections that
7076    /// nothing accepts. Nothing here measures that expectation — the assertion
7077    /// below checks only that the descriptor is gone from the table and closed.
7078    ///
7079    /// Receiving the SCM listeners before the initial state means the ordinary
7080    /// upgrade no longer reaches this state, so the test constructs it directly.
7081    ///
7082    /// To SEE THIS RED: make any arm's `InheritedSocketFate::Refused` branch
7083    /// return `None` without discarding — that protocol's descriptor stays in the
7084    /// table and open, and both assertions below fail. This guards the
7085    /// retain-everything regression; the ORIGINAL defect closed the descriptor
7086    /// here too, by dropping the wrapper, and is guarded by
7087    /// `inherited_listener_sockets_are_adopted_by_the_initial_activation`
7088    /// instead.
7089    #[test]
7090    fn a_repeated_activation_closes_the_descriptor_it_cannot_adopt_now() {
7091        let ServerParts {
7092            event_loop,
7093            sessions,
7094            pool,
7095            backends,
7096            server_scm_socket,
7097            server_config,
7098            ..
7099        } = prebuild_server(16, 16384, true).expect("could not prebuild a test server");
7100        let (_command_channel, proxy_channel) =
7101            Channel::generate(1000, 10000).expect("could not generate a test channel");
7102
7103        let http_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7104        let https_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7105        let tcp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7106        let udp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7107
7108        // A worker whose four listeners are up on their OWN sockets: the initial
7109        // state activates them and `scm_listeners` is empty, every ordinary start.
7110        let initial_state =
7111            state_with_four_active_listeners(http_address, https_address, tcp_address, udp_address)
7112                .produce_initial_state();
7113        let mut server = Server::new(
7114            event_loop,
7115            proxy_channel,
7116            server_scm_socket,
7117            sessions,
7118            pool,
7119            backends,
7120            None,
7121            None,
7122            None,
7123            server_config,
7124            Some(initial_state),
7125            false,
7126        )
7127        .expect("could not build the test server");
7128
7129        // Now hand that already-active worker a descriptor per address.
7130        let http_fd = server_bind(http_address.into(), None)
7131            .expect("could not bind the spare HTTP socket")
7132            .into_raw_fd();
7133        let https_fd = server_bind(https_address.into(), None)
7134            .expect("could not bind the spare HTTPS socket")
7135            .into_raw_fd();
7136        let tcp_fd = server_bind(tcp_address.into(), None)
7137            .expect("could not bind the spare TCP socket")
7138            .into_raw_fd();
7139        let udp_fd = udp_bind(udp_address.into(), None)
7140            .expect("could not bind the spare UDP socket")
7141            .into_raw_fd();
7142        {
7143            let scm_listeners = server
7144                .scm_listeners
7145                .as_mut()
7146                .expect("Server::new must have populated scm_listeners");
7147            scm_listeners.http.push((http_address.into(), http_fd));
7148            scm_listeners.tls.push((https_address.into(), https_fd));
7149            scm_listeners.tcp.push((tcp_address.into(), tcp_fd));
7150            scm_listeners.udp.push((udp_address.into(), udp_fd));
7151        }
7152
7153        for (address, proxy) in [
7154            (http_address, ListenerType::Http),
7155            (https_address, ListenerType::Https),
7156            (tcp_address, ListenerType::Tcp),
7157            (udp_address, ListenerType::Udp),
7158        ] {
7159            let response = server.notify_activate_listener(
7160                "test-repeat",
7161                &ActivateListener {
7162                    interface: None,
7163                    address,
7164                    proxy: proxy.into(),
7165                    from_scm: false,
7166                },
7167            );
7168            assert_eq!(
7169                response.status,
7170                ResponseStatus::Ok as i32,
7171                "a repeated {proxy:?} activation must still answer ok: {response:?}"
7172            );
7173        }
7174
7175        let scm_listeners = server
7176            .scm_listeners
7177            .as_ref()
7178            .expect("scm_listeners must still be present");
7179        for (label, table, fd, port) in [
7180            (
7181                "HTTP",
7182                &scm_listeners.http,
7183                http_fd,
7184                http_address.port as u16,
7185            ),
7186            (
7187                "HTTPS",
7188                &scm_listeners.tls,
7189                https_fd,
7190                https_address.port as u16,
7191            ),
7192            ("TCP", &scm_listeners.tcp, tcp_fd, tcp_address.port as u16),
7193            ("UDP", &scm_listeners.udp, udp_fd, udp_address.port as u16),
7194        ] {
7195            assert!(
7196                !table.iter().any(|(_, entry)| *entry == fd),
7197                "the inherited {label} descriptor this activation cannot adopt must leave the SCM table"
7198            );
7199            assert!(
7200                !descriptor_is_socket_on_port(fd, port),
7201                "the inherited {label} descriptor this activation cannot adopt must be closed, \
7202                 not left bound in the address's SO_REUSEPORT group, where it is expected — \
7203                 inferred, not measured here — to go on taking a share of new connections"
7204            );
7205        }
7206    }
7207
7208    /// An inherited descriptor whose address has NO listener yet must stay in
7209    /// the SCM table, and a later `AddListener` + `ActivateListener` must adopt
7210    /// it — backlog and all.
7211    ///
7212    /// This is a real upgrade shape, not a hypothetical: `Worker::upgrade`
7213    /// (`e2e/src/sozu/worker.rs`) hands the new worker a state whose listeners
7214    /// are inactive and sends `generate_activate_requests()` afterwards, and
7215    /// `UpgradeWorkerTask` (`bin/src/command/upgrade.rs`) scatters those
7216    /// requests only once `Server::new` has returned. Closing leftovers at the
7217    /// end of the initial state would destroy exactly this case, which is why
7218    /// `InheritedSocketFate::Unclaimed` keeps them.
7219    ///
7220    /// To SEE THIS RED: make any arm's `InheritedSocketFate::Unclaimed` branch
7221    /// take the descriptor out of the table like the other two — the first
7222    /// activation consumes and closes it, so the listener added afterwards binds
7223    /// its own socket and that protocol's assertions fail.
7224    #[test]
7225    fn an_inherited_descriptor_with_no_listener_yet_is_adopted_by_a_later_activation() {
7226        let ServerParts {
7227            event_loop,
7228            sessions,
7229            pool,
7230            backends,
7231            server_scm_socket,
7232            server_config,
7233            ..
7234        } = prebuild_server(16, 16384, true).expect("could not prebuild a test server");
7235        let (_command_channel, proxy_channel) =
7236            Channel::generate(1000, 10000).expect("could not generate a test channel");
7237
7238        let http_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7239        let https_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7240        let tcp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7241        let udp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7242
7243        let mut server = Server::new(
7244            event_loop,
7245            proxy_channel,
7246            server_scm_socket,
7247            sessions,
7248            pool,
7249            backends,
7250            None,
7251            None,
7252            None,
7253            server_config,
7254            None,
7255            false,
7256        )
7257        .expect("could not build the test server");
7258
7259        // Four inherited descriptors and no listener anywhere. The TCP-family
7260        // ones each carry a completed connection so the adoption can be shown to
7261        // preserve the backlog, not merely the descriptor.
7262        let http_listener = server_bind(http_address.into(), None)
7263            .expect("could not bind the inherited HTTP socket");
7264        let https_listener = server_bind(https_address.into(), None)
7265            .expect("could not bind the inherited HTTPS socket");
7266        let tcp_listener =
7267            server_bind(tcp_address.into(), None).expect("could not bind the inherited TCP socket");
7268        let udp_socket =
7269            udp_bind(udp_address.into(), None).expect("could not bind the inherited UDP socket");
7270        let _http_client = StdTcpStream::connect::<SocketAddr>(http_address.into())
7271            .expect("could not queue a connection on the inherited HTTP socket");
7272        let _https_client = StdTcpStream::connect::<SocketAddr>(https_address.into())
7273            .expect("could not queue a connection on the inherited HTTPS socket");
7274        let _tcp_client = StdTcpStream::connect::<SocketAddr>(tcp_address.into())
7275            .expect("could not queue a connection on the inherited TCP socket");
7276        let (http_fd, https_fd, tcp_fd, udp_fd) = (
7277            http_listener.into_raw_fd(),
7278            https_listener.into_raw_fd(),
7279            tcp_listener.into_raw_fd(),
7280            udp_socket.into_raw_fd(),
7281        );
7282        {
7283            let scm_listeners = server
7284                .scm_listeners
7285                .as_mut()
7286                .expect("Server::new must have populated scm_listeners");
7287            scm_listeners.http.push((http_address.into(), http_fd));
7288            scm_listeners.tls.push((https_address.into(), https_fd));
7289            scm_listeners.tcp.push((tcp_address.into(), tcp_fd));
7290            scm_listeners.udp.push((udp_address.into(), udp_fd));
7291        }
7292
7293        let add_requests =
7294            state_with_four_active_listeners(http_address, https_address, tcp_address, udp_address);
7295        let cases = [
7296            ("HTTP", http_address, ListenerType::Http, http_fd),
7297            ("HTTPS", https_address, ListenerType::Https, https_fd),
7298            ("TCP", tcp_address, ListenerType::Tcp, tcp_fd),
7299            ("UDP", udp_address, ListenerType::Udp, udp_fd),
7300        ];
7301
7302        // No listener at any of the addresses: every activation fails, and every
7303        // descriptor must survive it untouched.
7304        for (label, address, proxy, fd) in cases {
7305            let response = server.notify_activate_listener(
7306                "test-early",
7307                &ActivateListener {
7308                    interface: None,
7309                    address,
7310                    proxy: proxy.into(),
7311                    from_scm: false,
7312                },
7313            );
7314            assert_ne!(
7315                response.status,
7316                ResponseStatus::Ok as i32,
7317                "activating {label} with no listener must fail: {response:?}"
7318            );
7319            assert!(
7320                inherited_table(&server, label)
7321                    .iter()
7322                    .any(|(_, e)| *e == fd),
7323                "the {label} descriptor must stay in the SCM table while no listener owns \
7324                 its address"
7325            );
7326            assert!(
7327                descriptor_is_socket_on_port(fd, address.port as u16),
7328                "the {label} descriptor must stay open while no listener owns its address"
7329            );
7330        }
7331
7332        // The listeners arrive, then their activations: every descriptor is
7333        // adopted, so nothing is left in the table and nothing was closed unused.
7334        for request in add_requests.produce_initial_state().requests {
7335            server.notify_proxys(request);
7336        }
7337        for (label, _address, _proxy, fd) in cases {
7338            assert!(
7339                !inherited_table(&server, label)
7340                    .iter()
7341                    .any(|(_, e)| *e == fd),
7342                "the retained {label} descriptor must be adopted by the later activation"
7343            );
7344        }
7345        let accepted: Vec<Protocol> = server
7346            .accept_queue
7347            .iter()
7348            .map(|(_, _, protocol, _, _)| *protocol)
7349            .collect();
7350        for expected in [
7351            Protocol::HTTPListen,
7352            Protocol::HTTPSListen,
7353            Protocol::TCPListen,
7354        ] {
7355            assert!(
7356                accepted.contains(&expected),
7357                "the connection queued on the retained {expected:?} descriptor must be served \
7358                 by the later activation, got {accepted:?}"
7359            );
7360        }
7361    }
7362
7363    /// A socket parked by a failed registration must not be mistaken for a
7364    /// live, registered one.
7365    ///
7366    /// Every consumer of the listener's socket field reads its `Some` as
7367    /// "registered and live": `give_back_listener` hands it back as an
7368    /// activated socket and answers `Ok`, `soft_stop` / `hard_stop` deregister
7369    /// it and fold a failure into `ProxyError::SoftStop` / `HardStop`, `accept`
7370    /// accepts on it. Parking there made a listener that had simply never come
7371    /// up look activated — and, for a registration failure that leaves the
7372    /// socket unregistered, made `deregister` answer `ENOENT` and report a
7373    /// failed shutdown. The park therefore lives in its own `parked_listener` /
7374    /// `parked_socket` field, which nothing but `activate()` reads.
7375    ///
7376    /// `give_back_listener` is the assertion that falsifies the mutation:
7377    /// `ProxyError::UnactivatedListener` keys on the live field being `None`,
7378    /// so parking into it turns the answer into `Ok`. The stop assertions ride
7379    /// along to pin the shutdown behaviour, but they cannot redden on this
7380    /// repro — the EEXIST it uses means the descriptor IS registered (under
7381    /// another token), so `deregister` succeeds either way.
7382    ///
7383    /// To SEE THIS RED: in any `activate()`, replace
7384    /// `self.parked_listener = Some(listener)` (or `self.parked_socket = Some(socket)`)
7385    /// with `self.listener = Some(listener)` / `self.socket = Some(socket)` —
7386    /// that protocol's `give_back_listener` assertion fails.
7387    #[test]
7388    fn a_socket_parked_by_a_failed_registration_is_not_mistaken_for_a_live_one() {
7389        let ServerParts {
7390            registry,
7391            sessions,
7392            pool,
7393            backends,
7394            ..
7395        } = prebuild_server(16, 16384, true).expect("could not prebuild a test server");
7396
7397        let token_for = |protocol: Protocol| {
7398            let mut manager = sessions.borrow_mut();
7399            let entry = manager.slab.vacant_entry();
7400            let token = Token(entry.key());
7401            entry.insert(Rc::new(RefCell::new(ListenSession { protocol })));
7402            token
7403        };
7404        // A socket whose descriptor is already registered on the same epoll
7405        // instance, so `activate()`'s own `register` answers EEXIST and parks.
7406        let pre_registered = |address: SocketAddress| {
7407            let mut socket = server_bind(address.into(), None)
7408                .expect("could not bind the test listening socket");
7409            registry
7410                .register(&mut socket, Token(usize::MAX - 2), Interest::READABLE)
7411                .expect("could not pre-register the test socket");
7412            socket
7413        };
7414
7415        let http_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7416        let mut http_proxy = http::HttpProxy::new(
7417            registry.try_clone().expect("could not clone the registry"),
7418            sessions.clone(),
7419            pool.clone(),
7420            backends.clone(),
7421        );
7422        http_proxy
7423            .add_listener(
7424                ListenerBuilder::new_http(http_address)
7425                    .to_http(None)
7426                    .expect("could not build the test HTTP listener config"),
7427                token_for(Protocol::HTTPListen),
7428            )
7429            .expect("could not add the test HTTP listener");
7430        assert!(
7431            http_proxy
7432                .activate_listener(&http_address.into(), Some(pre_registered(http_address)))
7433                .is_err(),
7434            "the pre-registered descriptor must make the HTTP registration fail"
7435        );
7436        assert!(
7437            matches!(
7438                http_proxy.give_back_listener(&http_address.into()),
7439                Err(ProxyError::UnactivatedListener)
7440            ),
7441            "an HTTP listener holding only a parked socket must not answer as activated"
7442        );
7443        assert!(
7444            http_proxy.soft_stop().is_ok(),
7445            "a parked socket must not fail an HTTP soft stop"
7446        );
7447
7448        let https_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7449        let mut https_proxy = https::HttpsProxy::new(
7450            registry.try_clone().expect("could not clone the registry"),
7451            sessions.clone(),
7452            pool.clone(),
7453            backends.clone(),
7454        );
7455        https_proxy
7456            .add_listener(
7457                ListenerBuilder::new_https(https_address)
7458                    .to_tls(None)
7459                    .expect("could not build the test HTTPS listener config"),
7460                token_for(Protocol::HTTPSListen),
7461            )
7462            .expect("could not add the test HTTPS listener");
7463        assert!(
7464            https_proxy
7465                .activate_listener(&https_address.into(), Some(pre_registered(https_address)))
7466                .is_err(),
7467            "the pre-registered descriptor must make the HTTPS registration fail"
7468        );
7469        assert!(
7470            matches!(
7471                https_proxy.give_back_listener(&https_address.into()),
7472                Err(ProxyError::UnactivatedListener)
7473            ),
7474            "an HTTPS listener holding only a parked socket must not answer as activated"
7475        );
7476        assert!(
7477            https_proxy.hard_stop().is_ok(),
7478            "a parked socket must not fail an HTTPS hard stop"
7479        );
7480
7481        let tcp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7482        let mut tcp_proxy = tcp::TcpProxy::new(
7483            registry.try_clone().expect("could not clone the registry"),
7484            sessions.clone(),
7485            pool.clone(),
7486            backends.clone(),
7487        );
7488        tcp_proxy
7489            .add_listener(
7490                ListenerBuilder::new_tcp(tcp_address)
7491                    .to_tcp(None)
7492                    .expect("could not build the test TCP listener config"),
7493                token_for(Protocol::TCPListen),
7494            )
7495            .expect("could not add the test TCP listener");
7496        assert!(
7497            tcp_proxy
7498                .activate_listener(&tcp_address.into(), Some(pre_registered(tcp_address)))
7499                .is_err(),
7500            "the pre-registered descriptor must make the TCP registration fail"
7501        );
7502        assert!(
7503            matches!(
7504                tcp_proxy.give_back_listener(&tcp_address.into()),
7505                Err(ProxyError::UnactivatedListener)
7506            ),
7507            "a TCP listener holding only a parked socket must not answer as activated"
7508        );
7509
7510        let udp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7511        let mut udp_proxy = udp::UdpProxy::new(
7512            registry.try_clone().expect("could not clone the registry"),
7513            sessions.clone(),
7514            pool.clone(),
7515            backends.clone(),
7516            16,
7517            16384,
7518        );
7519        udp_proxy
7520            .add_listener(
7521                ListenerBuilder::new_udp(udp_address)
7522                    .to_udp(None)
7523                    .expect("could not build the test UDP listener config"),
7524                token_for(Protocol::UDP),
7525            )
7526            .expect("could not add the test UDP listener");
7527        let mut udp_socket =
7528            udp_bind(udp_address.into(), None).expect("could not bind the test UDP socket");
7529        registry
7530            .register(&mut udp_socket, Token(usize::MAX - 2), Interest::READABLE)
7531            .expect("could not pre-register the test UDP socket");
7532        assert!(
7533            udp_proxy
7534                .activate_listener(&udp_address.into(), Some(udp_socket))
7535                .is_err(),
7536            "the pre-registered descriptor must make the UDP registration fail"
7537        );
7538        assert!(
7539            matches!(
7540                udp_proxy.give_back_listener(&udp_address.into()),
7541                Err(ProxyError::UnactivatedListener)
7542            ),
7543            "a UDP listener holding only a parked socket must not answer as activated"
7544        );
7545    }
7546
7547    /// A failed mio registration must not close the socket it was handed.
7548    ///
7549    /// `activate()` moves the socket into a local to register it. Propagating a
7550    /// registration error straight from that local drops it — and closes a
7551    /// descriptor `Listeners::get_*` has already removed from the SCM table,
7552    /// which is the sozu#1342 close in a second place. `Registry::register`
7553    /// answers `EEXIST` for a descriptor already registered on the same epoll
7554    /// instance, which is what the test arranges and what a `deactivate` whose
7555    /// `deregister` only logged leaves behind.
7556    ///
7557    /// To SEE THIS RED: in any `activate()`, go back to
7558    /// `registry.register(..).map_err(..)?` followed by `self.listener = Some(listener)`
7559    /// — the `?` drops the local and that protocol's assertion fails.
7560    #[test]
7561    fn a_failed_registration_does_not_close_the_socket_it_was_handed() {
7562        let ServerParts {
7563            registry,
7564            sessions,
7565            pool,
7566            backends,
7567            ..
7568        } = prebuild_server(16, 16384, true).expect("could not prebuild a test server");
7569
7570        let token_for = |protocol: Protocol| {
7571            let mut manager = sessions.borrow_mut();
7572            let entry = manager.slab.vacant_entry();
7573            let token = Token(entry.key());
7574            entry.insert(Rc::new(RefCell::new(ListenSession { protocol })));
7575            token
7576        };
7577
7578        // Hand each proxy a socket whose descriptor is ALREADY registered on the
7579        // same epoll instance, so `activate()`'s own `register` answers EEXIST.
7580        let handed_out = |address: SocketAddress| {
7581            let mut socket = server_bind(address.into(), None)
7582                .expect("could not bind the test listening socket");
7583            registry
7584                .register(&mut socket, Token(usize::MAX - 1), Interest::READABLE)
7585                .expect("could not pre-register the test socket");
7586            let fd = socket.as_raw_fd();
7587            (socket, fd)
7588        };
7589
7590        let http_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7591        let token = token_for(Protocol::HTTPListen);
7592        let mut http_proxy = http::HttpProxy::new(
7593            registry.try_clone().expect("could not clone the registry"),
7594            sessions.clone(),
7595            pool.clone(),
7596            backends.clone(),
7597        );
7598        http_proxy
7599            .add_listener(
7600                ListenerBuilder::new_http(http_address)
7601                    .to_http(None)
7602                    .expect("could not build the test HTTP listener config"),
7603                token,
7604            )
7605            .expect("could not add the test HTTP listener");
7606        let (socket, http_fd) = handed_out(http_address);
7607        let result = http_proxy.activate_listener(&http_address.into(), Some(socket));
7608        assert!(
7609            result.is_err(),
7610            "the pre-registered descriptor must make the HTTP registration fail"
7611        );
7612        assert!(
7613            descriptor_is_socket_on_port(http_fd, http_address.port as u16),
7614            "a failed HTTP registration must not close the socket it was handed"
7615        );
7616
7617        let tcp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7618        let token = token_for(Protocol::TCPListen);
7619        let mut tcp_proxy = tcp::TcpProxy::new(
7620            registry.try_clone().expect("could not clone the registry"),
7621            sessions.clone(),
7622            pool.clone(),
7623            backends.clone(),
7624        );
7625        tcp_proxy
7626            .add_listener(
7627                ListenerBuilder::new_tcp(tcp_address)
7628                    .to_tcp(None)
7629                    .expect("could not build the test TCP listener config"),
7630                token,
7631            )
7632            .expect("could not add the test TCP listener");
7633        let (socket, tcp_fd) = handed_out(tcp_address);
7634        let result = tcp_proxy.activate_listener(&tcp_address.into(), Some(socket));
7635        assert!(
7636            result.is_err(),
7637            "the pre-registered descriptor must make the TCP registration fail"
7638        );
7639        assert!(
7640            descriptor_is_socket_on_port(tcp_fd, tcp_address.port as u16),
7641            "a failed TCP registration must not close the socket it was handed"
7642        );
7643
7644        let https_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7645        let token = token_for(Protocol::HTTPSListen);
7646        let mut https_proxy = https::HttpsProxy::new(
7647            registry.try_clone().expect("could not clone the registry"),
7648            sessions.clone(),
7649            pool.clone(),
7650            backends.clone(),
7651        );
7652        https_proxy
7653            .add_listener(
7654                ListenerBuilder::new_https(https_address)
7655                    .to_tls(None)
7656                    .expect("could not build the test HTTPS listener config"),
7657                token,
7658            )
7659            .expect("could not add the test HTTPS listener");
7660        let (socket, https_fd) = handed_out(https_address);
7661        let result = https_proxy.activate_listener(&https_address.into(), Some(socket));
7662        assert!(
7663            result.is_err(),
7664            "the pre-registered descriptor must make the HTTPS registration fail"
7665        );
7666        assert!(
7667            descriptor_is_socket_on_port(https_fd, https_address.port as u16),
7668            "a failed HTTPS registration must not close the socket it was handed"
7669        );
7670
7671        let udp_address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
7672        let token = token_for(Protocol::UDP);
7673        let mut udp_proxy = udp::UdpProxy::new(
7674            registry.try_clone().expect("could not clone the registry"),
7675            sessions.clone(),
7676            pool.clone(),
7677            backends.clone(),
7678            16,
7679            16384,
7680        );
7681        udp_proxy
7682            .add_listener(
7683                ListenerBuilder::new_udp(udp_address)
7684                    .to_udp(None)
7685                    .expect("could not build the test UDP listener config"),
7686                token,
7687            )
7688            .expect("could not add the test UDP listener");
7689        let mut socket =
7690            udp_bind(udp_address.into(), None).expect("could not bind the test UDP socket");
7691        registry
7692            .register(&mut socket, Token(usize::MAX - 1), Interest::READABLE)
7693            .expect("could not pre-register the test UDP socket");
7694        let udp_fd = socket.as_raw_fd();
7695        let result = udp_proxy.activate_listener(&udp_address.into(), Some(socket));
7696        assert!(
7697            result.is_err(),
7698            "the pre-registered descriptor must make the UDP registration fail"
7699        );
7700        assert!(
7701            descriptor_is_socket_on_port(udp_fd, udp_address.port as u16),
7702            "a failed UDP registration must not close the socket it was handed"
7703        );
7704    }
7705}
7706
7707#[cfg(test)]
7708mod tests {
7709    use super::{ClusterId, SessionManager, mask_ipv4, mask_ipv6, subnet_key};
7710    use mio::Token;
7711    use slab::Slab;
7712    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
7713
7714    /// A cluster override can drop to `Some(0)` AFTER a session was
7715    /// tracked under a positive limit: `AddCluster` is an upsert and
7716    /// clears no counters, and only the GLOBAL `SetMaxConnectionsPerSubnet(0)`
7717    /// drains the bookkeeping. The next track for that still-open session
7718    /// then takes the disabled-limiter early return with its slot already
7719    /// recorded, which must be an ordinary no-op rather than a panic.
7720    #[test]
7721    fn a_cluster_override_dropping_to_zero_after_a_track_is_a_no_op() {
7722        let manager = SessionManager::new(Slab::with_capacity(8), 8, 0, 0, 0, 24, 56);
7723        let token = Token(1);
7724        let cluster = ClusterId::from("api");
7725        let ip: IpAddr = "203.0.113.9".parse().expect("test address must parse");
7726
7727        // Tracked under a positive per-cluster override.
7728        manager
7729            .borrow_mut()
7730            .track_cluster_subnet(token, cluster.clone(), ip, Some(5));
7731        assert!(
7732            !manager.borrow().subnet_tracking_is_empty(),
7733            "premise: the positive override must have recorded a slot"
7734        );
7735
7736        // The operator upserts the cluster with the cap disabled. The same
7737        // session opens another stream: the early return must not trip on
7738        // the slot it legitimately left behind.
7739        manager
7740            .borrow_mut()
7741            .track_cluster_subnet(token, cluster.clone(), ip, Some(0));
7742
7743        // And the stale slot is still released normally on close.
7744        manager.borrow_mut().untrack_all_cluster_ip(token);
7745        assert!(
7746            manager.borrow().subnet_tracking_is_empty(),
7747            "teardown must drain the slot even though the cap was disabled in between"
7748        );
7749    }
7750
7751    /// The two ends of the prefix range are the ones that break: a
7752    /// full-width prefix would shift by the whole word, and a zero prefix
7753    /// would shift by it from the other side. Both are handled by an
7754    /// explicit branch, and this pins that neither reaches the shift.
7755    #[test]
7756    fn masking_handles_both_shift_overflowing_prefix_bounds() {
7757        let v4: Ipv4Addr = "203.0.113.45".parse().expect("test address must parse");
7758        assert_eq!(
7759            mask_ipv4(v4, 32),
7760            v4,
7761            "a /32 must be the address itself, not a wrapped-shift artefact"
7762        );
7763        assert_eq!(
7764            mask_ipv4(v4, 0),
7765            Ipv4Addr::UNSPECIFIED,
7766            "a /0 must mask every bit away"
7767        );
7768
7769        let v6: Ipv6Addr = "2001:db8:dead:beef::1"
7770            .parse()
7771            .expect("test address must parse");
7772        assert_eq!(
7773            mask_ipv6(v6, 128),
7774            v6,
7775            "a /128 must be the address itself, not a wrapped-shift artefact"
7776        );
7777        assert_eq!(
7778            mask_ipv6(v6, 0),
7779            Ipv6Addr::UNSPECIFIED,
7780            "a /0 must mask every bit away"
7781        );
7782
7783        // Out-of-range prefixes cannot reach the shift either. Config load
7784        // rejects them, but the hot path does not rely on that having run.
7785        assert_eq!(
7786            mask_ipv4(v4, 33),
7787            v4,
7788            "an over-wide IPv4 prefix must clamp to the address, not panic or wrap"
7789        );
7790        assert_eq!(
7791            mask_ipv6(v6, 129),
7792            v6,
7793            "an over-wide IPv6 prefix must clamp to the address, not panic or wrap"
7794        );
7795    }
7796
7797    /// The recommended prefixes from sozu-proxy/sozu#1270 — /24 and /56 —
7798    /// must group the addresses an operator expects them to.
7799    #[test]
7800    fn masking_groups_addresses_at_the_recommended_prefixes() {
7801        let parse_v4 = |s: &str| -> Ipv4Addr { s.parse().expect("test address must parse") };
7802        assert_eq!(
7803            mask_ipv4(parse_v4("203.0.113.1"), 24),
7804            mask_ipv4(parse_v4("203.0.113.254"), 24),
7805            "two hosts in one /24 must share a subnet key"
7806        );
7807        assert_ne!(
7808            mask_ipv4(parse_v4("203.0.113.1"), 24),
7809            mask_ipv4(parse_v4("203.0.114.1"), 24),
7810            "hosts in different /24s must NOT share a subnet key"
7811        );
7812
7813        let parse_v6 = |s: &str| -> Ipv6Addr { s.parse().expect("test address must parse") };
7814        assert_eq!(
7815            mask_ipv6(parse_v6("2001:db8:abcd:0000::1"), 56),
7816            mask_ipv6(parse_v6("2001:db8:abcd:00ff::9999"), 56),
7817            "two addresses in one /56 must share a subnet key"
7818        );
7819        assert_ne!(
7820            mask_ipv6(parse_v6("2001:db8:abcd:0000::1"), 56),
7821            mask_ipv6(parse_v6("2001:db8:abcd:0100::1"), 56),
7822            "addresses in different /56s must NOT share a subnet key"
7823        );
7824
7825        // The issue's own scenario: a /64 holder taking a fresh address
7826        // per connection lands in one bucket at /56.
7827        assert_eq!(
7828            mask_ipv6(parse_v6("2001:db8:abcd:1234::1"), 56),
7829            mask_ipv6(parse_v6("2001:db8:abcd:1234:ffff:ffff:ffff:ffff"), 56),
7830            "every address a /64 holder can mint must collapse to one /56 key — that \
7831             collapse IS the mitigation for sozu-proxy/sozu#1270"
7832        );
7833    }
7834
7835    /// A dual-stack `[::]` listener hands IPv4 clients back as
7836    /// `::ffff:a.b.c.d`. Masking those with an IPv6 prefix would collapse
7837    /// the entire IPv4 internet into one `::/56` bucket, so the mapped
7838    /// form is canonicalised to IPv4 and charged to the IPv4 prefix.
7839    #[test]
7840    fn an_ipv4_mapped_source_is_charged_to_the_ipv4_prefix() {
7841        let mapped: IpAddr = "::ffff:203.0.113.7".parse().expect("must parse");
7842        let native: IpAddr = "203.0.113.7".parse().expect("must parse");
7843        assert_eq!(
7844            subnet_key(&mapped, 24, 56),
7845            subnet_key(&native, 24, 56),
7846            "a mapped IPv4 source must key identically to the same address arriving natively"
7847        );
7848        assert!(
7849            subnet_key(&mapped, 24, 56).is_ipv4(),
7850            "the key for a mapped source must be an IPv4 key"
7851        );
7852
7853        // Negative space: two mapped addresses from DIFFERENT /24s must
7854        // not collide. Under the bug this guards against, both would mask
7855        // to `::/56` and share one counter.
7856        let other: IpAddr = "::ffff:198.51.100.7".parse().expect("must parse");
7857        assert_ne!(
7858            subnet_key(&mapped, 24, 56),
7859            subnet_key(&other, 24, 56),
7860            "mapped sources from different IPv4 /24s must NOT share a subnet key — if they \
7861             do, every IPv4 client on a dual-stack listener shares one bucket"
7862        );
7863
7864        // A genuine IPv6 source is still keyed as IPv6.
7865        let genuine: IpAddr = "2001:db8::1".parse().expect("must parse");
7866        assert!(
7867            subnet_key(&genuine, 24, 56).is_ipv6(),
7868            "a genuine IPv6 source must keep an IPv6 subnet key"
7869        );
7870    }
7871}