Skip to main content

sozu_lib/
udp.rs

1//! UDP load-balancing I/O shell + mio event-loop wiring (issue #1273).
2//!
3//! This module is the **impure** half of the UDP datapath: it owns every
4//! syscall, the buffer copies, the per-flow connected upstream sockets, the
5//! timer arming, the `BackendMap`/health/metrics edges, and the slab/token
6//! bookkeeping. It drives the pure sans-io core in
7//! [`crate::protocol::udp`] (the `UdpManager` /
8//! `UdpFlow` two-level split) through `ManagerInput` / `Output`.
9//!
10//! Architecture (mirrors `tcp.rs`, but UDP is **one-listener-many-flows**):
11//! - [`UdpListener`] wraps the single `mio::net::UdpSocket` a listener binds.
12//!   There is **no accept loop** — a readable event means "datagrams waiting",
13//!   not "a new connection".
14//! - [`UdpProxy`] holds the listeners, the per-listener [`UdpManager`]s, the
15//!   shared `BackendMap`, the session slab and the registry. It does **not**
16//!   implement `ProxyConfiguration` / `L7Proxy` (their signatures are
17//!   `TcpStream`-bound); the server calls its inherent
18//!   [`notify`](UdpProxy::notify) directly.
19//! - [`UdpListenerSession`] is the `ProxySession` the server's generic
20//!   readiness path drives. One session backs one listener; its
21//!   `update_readiness` demuxes by token (listener-token = client recv;
22//!   upstream-token = backend recv). It owns the per-flow connected sockets and
23//!   the `upstream_token -> FlowId` map.
24//!
25//! UDP never goes through `accept()` / `create_session()`: a `Protocol::UDP`
26//! listener readable event falls through `Server::ready`'s generic arm into
27//! `ProxySession::ready`.
28//!
29//! Long-form lifecycle (datapath, NAT return, teardown, control plane,
30//! hardening): `lib/src/protocol/udp/LIFECYCLE.md`.
31
32use std::{
33    cell::RefCell,
34    collections::{BTreeMap, HashMap, VecDeque, hash_map::Entry},
35    io::ErrorKind,
36    net::SocketAddr,
37    os::unix::io::AsRawFd,
38    rc::Rc,
39    time::{Duration, Instant},
40};
41
42use mio::{Interest, Registry, Token, net::UdpSocket, unix::SourceFd};
43use sozu_command::{
44    listener_key::ListenerKey,
45    logging::ansi_palette,
46    proto::command::{
47        Cluster, LoadBalancingAlgorithms, LoadMetric, RequestUdpFrontend, UdpAffinityKey,
48        UdpListenerConfig, UpdateUdpListenerConfig, WorkerRequest, WorkerResponse,
49        request::RequestType,
50    },
51};
52
53use crate::metrics::names;
54use crate::{
55    CachedTags, ListenerError, ListenerHandler, Protocol, ProxyError, ProxySession,
56    SessionIsToBeClosed,
57    backends::BackendMap,
58    pool::Pool,
59    protocol::udp::{
60        CloseReason, ClusterConfig, ConfigEvent, DropReason, FlowId, ManagerInput, MetricEvent,
61        Output, UdpManager,
62    },
63    server::{SessionManager, TIMER},
64    socket::{udp_bind, udp_connect},
65    sozu_command::{ready::Ready, state::ClusterId},
66};
67
68mod health;
69pub use health::UdpHealthChecker;
70
71/// Per-session log envelope (tag `UDP`). The colored output uses the unified
72/// scheme: bold bright-white protocol label, light-grey keyword, gray keys and
73/// bright-white values. Honours the `colored` flag via [`ansi_palette`].
74macro_rules! log_context {
75    ($self:expr) => {{
76        let (open, reset, grey, gray, white) = ansi_palette();
77        format!(
78            "[- - - -]\t{open}UDP{reset}\t{grey}Listener{reset}({gray}token{reset}={white}{token}{reset}, {gray}address{reset}={white}{address}{reset})\t >>>",
79            open = open,
80            reset = reset,
81            grey = grey,
82            gray = gray,
83            white = white,
84            token = $self.listener_token.0,
85            address = $self.address,
86        )
87    }};
88}
89
90/// Module-level prefix for [`UdpProxy`] callbacks (notify, listener add/remove,
91/// soft/hard stop) which own a listener/token map but have no per-session
92/// token. Produces a bold bright-white `UDP` label in colored mode.
93macro_rules! log_module_context {
94    () => {{
95        let (open, reset, _, _, _) = sozu_command::logging::ansi_palette();
96        format!("{open}UDP{reset}\t >>>", open = open, reset = reset)
97    }};
98}
99
100/// Per-flow log envelope (tag `UDP-FLOW`). Renders the flow's stable id plus
101/// client/backend addresses so flow lines stay filterable.
102macro_rules! log_flow_context {
103    ($flow:expr, $client:expr, $backend:expr) => {{
104        let (open, reset, grey, gray, white) = sozu_command::logging::ansi_palette();
105        format!(
106            "[- - - -]\t{open}UDP-FLOW{reset}\t{grey}Flow{reset}({gray}id{reset}={white}{id}{reset}, {gray}client{reset}={white}{client}{reset}, {gray}backend{reset}={white}{backend:?}{reset})\t >>>",
107            open = open,
108            reset = reset,
109            grey = grey,
110            gray = gray,
111            white = white,
112            id = $flow,
113            client = $client,
114            backend = $backend,
115        )
116    }};
117}
118
119/// Bound on the per-flow upstream write queue (return path to one backend). A
120/// connected upstream socket only buffers one flow's traffic, so a small cap is
121/// enough to ride out a transient `EWOULDBLOCK` (kernel send buffer momentarily
122/// full) without letting a slow/stalled backend balloon memory. Past the cap we
123/// drop the datagram (`udp.datagrams.dropped.wq_full`) — UDP is best-effort and
124/// the client retries.
125const UPSTREAM_WRITE_QUEUE_CAP: usize = 64;
126/// Bound on the per-listener client-return write queue (replies fanned back to
127/// many clients through the single listener socket). Sized larger than the
128/// per-flow cap because it is shared across every flow's return traffic.
129const CLIENT_WRITE_QUEUE_CAP: usize = 256;
130
131/// What `WriteQueue::drain` should do after a single send attempt: keep
132/// draining, stop because the socket went `WouldBlock` (rearm WRITABLE), or stop
133/// because the send hit a hard error (drop the datagram, keep draining the rest).
134enum SendOutcome {
135    /// The datagram was written; pop it and continue.
136    Sent,
137    /// `WouldBlock`: leave the datagram at the front and stop draining.
138    WouldBlock,
139    /// A hard error (e.g. ECONNREFUSED): drop this datagram + count it, continue.
140    Dropped,
141}
142
143/// A bounded FIFO of datagrams awaiting a writable socket. The egress fast path
144/// (a `send`/`send_to` that succeeds immediately) never touches this; it is only
145/// engaged when the kernel send buffer is full (`WouldBlock`). On overflow the
146/// oldest-still-pending order is preserved and the *new* datagram is dropped
147/// (the queued ones are closer to leaving the socket).
148///
149/// The queue stores `(SocketAddr, Vec<u8>)`: the upstream variant ignores the
150/// address (a connected socket's `send` has an implicit destination) while the
151/// client-return variant uses it as the `send_to` destination. Keeping one type
152/// for both lets the drain loop and the unit tests stay shared.
153struct WriteQueue {
154    queue: VecDeque<(SocketAddr, Vec<u8>)>,
155    cap: usize,
156}
157
158impl WriteQueue {
159    fn new(cap: usize) -> Self {
160        WriteQueue {
161            queue: VecDeque::new(),
162            cap,
163        }
164    }
165
166    fn is_empty(&self) -> bool {
167        self.queue.is_empty()
168    }
169
170    /// Current depth. Only used by the unit tests; the runtime drains by the
171    /// `is_empty` / `drain` return value, not a length read.
172    #[cfg(test)]
173    fn len(&self) -> usize {
174        self.queue.len()
175    }
176
177    /// Enqueue a datagram for later draining. Returns `true` if it was accepted,
178    /// `false` if the queue was at capacity (caller drops + counts `wq_full`).
179    #[must_use]
180    fn push(&mut self, dst: SocketAddr, payload: Vec<u8>) -> bool {
181        if self.queue.len() >= self.cap {
182            return false;
183        }
184        self.queue.push_back((dst, payload));
185        // Invariant: the bounded FIFO never grows past its cap. The guard above
186        // is the only growth site, so depth <= cap holds after every push.
187        debug_assert!(
188            self.queue.len() <= self.cap,
189            "WriteQueue overran its cap: len {} > cap {}",
190            self.queue.len(),
191            self.cap,
192        );
193        true
194    }
195
196    /// Drain in FIFO order, calling `send` for each datagram. Stops on the first
197    /// `WouldBlock` (leaving that datagram queued for the next writable event) or
198    /// when empty. Hard-errored datagrams are popped and counted via
199    /// `SendOutcome::Dropped`. Returns `true` if the queue is now empty (the
200    /// caller can drop WRITABLE interest back to READABLE-only).
201    fn drain<F: FnMut(&SocketAddr, &[u8]) -> SendOutcome>(&mut self, mut send: F) -> bool {
202        while let Some((dst, payload)) = self.queue.front() {
203            match send(dst, payload) {
204                SendOutcome::Sent | SendOutcome::Dropped => {
205                    self.queue.pop_front();
206                }
207                SendOutcome::WouldBlock => break,
208            }
209        }
210        self.queue.is_empty()
211    }
212}
213
214/// One UDP listener: a single `mio::net::UdpSocket` plus its routing config.
215/// Unlike a TCP listener there is no accept loop — a readable event is a batch
216/// of datagrams the session drains to `WouldBlock`.
217pub struct UdpListener {
218    active: SessionIsToBeClosed,
219    address: SocketAddr,
220    cluster_id: Option<ClusterId>,
221    config: UdpListenerConfig,
222    socket: Option<UdpSocket>,
223    /// A socket this listener holds but has NOT registered.
224    ///
225    /// `activate()` parks an inherited socket here when `Registry::register`
226    /// fails, instead of letting the `?` drop and close a descriptor
227    /// `Listeners::get_*` has already removed from the SCM table (sozu#1342).
228    /// The next activation reuses it rather than binding a second one.
229    ///
230    /// Deliberately NOT `socket`: every other consumer reads that field's `Some` as
231    /// "registered and live" — `soft_stop` and `hard_stop` deregister it and
232    /// report the failure, `give_back_listener` hands it back as an activated
233    /// socket, `accept` accepts on it. A parked socket is none of those things,
234    /// and putting it there made a failed registration answer `ENOENT` from
235    /// `deregister` and fail the whole soft stop.
236    parked_socket: Option<UdpSocket>,
237    tags: BTreeMap<String, CachedTags>,
238    token: Token,
239}
240
241impl ListenerHandler for UdpListener {
242    fn get_addr(&self) -> &SocketAddr {
243        &self.address
244    }
245
246    fn get_tags(&self, key: &str) -> Option<&CachedTags> {
247        self.tags.get(key)
248    }
249
250    fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
251        match tags {
252            Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
253            None => self.tags.remove(&key),
254        };
255    }
256
257    fn protocol(&self) -> Protocol {
258        Protocol::UDP
259    }
260
261    fn public_address(&self) -> SocketAddr {
262        self.config
263            .public_address
264            .map(|addr| addr.into())
265            .unwrap_or(self.address)
266    }
267}
268
269impl UdpListener {
270    /// This listener's identity: its address, plus the network interface its
271    /// socket is bound to when it has one.
272    pub fn key(&self) -> ListenerKey {
273        ListenerKey::new(self.address, self.config.interface.as_deref())
274    }
275
276    /// Whether this listener is the one `key` names.
277    pub fn is(&self, key: &ListenerKey) -> bool {
278        key.matches(&self.address, self.config.interface.as_deref())
279    }
280
281    fn new(config: UdpListenerConfig, token: Token) -> Result<UdpListener, ListenerError> {
282        Ok(UdpListener {
283            cluster_id: None,
284            socket: None,
285            parked_socket: None,
286            token,
287            address: config.address.into(),
288            config,
289            active: false,
290            tags: BTreeMap::new(),
291        })
292    }
293
294    /// Validate that a worker can build this UDP listener configuration WITHOUT
295    /// constructing the full listener or binding a socket. UDP listener
296    /// construction has no fallible config today (no rustls context, no answer
297    /// templates; a bad bind surfaces later as an `ActivateListener` failure),
298    /// so this currently always succeeds. It exists for surface parity with the
299    /// HTTP/HTTPS validators the main process calls before committing an
300    /// `Add*Listener` to `ConfigState` and fanning it out (sozu#1301), and is
301    /// the hook for any future UDP-config validation.
302    pub fn validate_config(_config: &UdpListenerConfig) -> Result<(), ListenerError> {
303        Ok(())
304    }
305
306    /// Bind (or adopt an SCM-passed) socket and register it `READABLE`. The
307    /// READABLE registration is what drives `Server::ready` for this listener —
308    /// there is no accept path.
309    pub fn activate(
310        &mut self,
311        registry: &Registry,
312        udp_socket: Option<UdpSocket>,
313    ) -> Result<Token, ProxyError> {
314        if self.active {
315            return Ok(self.token);
316        }
317        let address: SocketAddr = self.config.address.into();
318
319        // Reuse a socket a previous attempt parked here after a failed
320        // registration, rather than binding a second one. Taking it
321        // unconditionally is what keeps the two from coexisting: if the caller
322        // ALSO handed one over, the parked socket is disposed of deliberately
323        // below instead of being silently overwritten.
324        let parked = self.parked_socket.take();
325        let mut socket = match (udp_socket, parked) {
326            (Some(inherited), Some(parked)) => {
327                // Not reachable while `Listeners::get_*` removes the entry it
328                // hands over and `scm_listeners` is written once: a parked
329                // socket means this address's entry was already consumed, so
330                // the caller cannot have another. Neither of those is enforced
331                // here, so dispose of the loser explicitly and loudly rather
332                // than letting an assignment close it — that silent close is
333                // sozu#1342 itself.
334                warn!(
335                    "{} closing the socket parked on the UDP listener for {}: an \
336                     inherited socket arrived for the same address and is the one being \
337                     activated",
338                    log_module_context!(),
339                    address
340                );
341                drop(parked);
342                inherited
343            }
344            (Some(inherited), None) => inherited,
345            (None, Some(parked)) => parked,
346            (None, None) => udp_bind(address, self.config.interface.as_deref())
347                .map_err(|e| ProxyError::BindToSocket(address, e))?,
348        };
349
350        let registration = registry
351            .register(&mut socket, self.token, Interest::READABLE)
352            .map_err(ProxyError::RegisterListener);
353        if let Err(error) = registration {
354            // Park the socket instead of letting the `?` drop it. `register`
355            // is fallible (EEXIST for a descriptor still registered because
356            // `notify_deactivate_listener` only logs a failed deregister,
357            // EBADF, ENOMEM), and a socket still held in a local is dropped —
358            // and closed — on the way out. For an SCM-inherited socket that is
359            // the sozu#1342 close in a second place: `Listeners::get_*` has
360            // already removed the entry, so nothing else owns the descriptor.
361            // It goes to `parked_socket`, never to `socket`, because every other
362            // consumer of that field reads `Some` as "registered and live".
363            self.parked_socket = Some(socket);
364            return Err(error);
365        }
366
367        self.socket = Some(socket);
368        self.active = true;
369        Ok(self.token)
370    }
371
372    /// Apply a partial-update patch to this UDP listener's live config. Fields
373    /// absent in the patch (`None`) are preserved.
374    pub fn update_config(&mut self, patch: &UpdateUdpListenerConfig) {
375        if let Some(v) = patch.public_address {
376            self.config.public_address = Some(v);
377        }
378        if let Some(v) = patch.front_timeout {
379            self.config.front_timeout = v;
380        }
381        if let Some(v) = patch.back_timeout {
382            self.config.back_timeout = v;
383        }
384        if let Some(v) = patch.max_rx_datagram_size {
385            self.config.max_rx_datagram_size = v;
386        }
387        if let Some(v) = patch.max_flows {
388            self.config.max_flows = v;
389        }
390    }
391}
392
393/// The UDP proxy. Holds the listeners, one `UdpManager` per listener token, the
394/// shared `BackendMap`, the session slab and a cloned registry. Does NOT
395/// implement `ProxyConfiguration` / `L7Proxy`; the server drives it through the
396/// inherent [`notify`](Self::notify) plus the activate/give-back helpers.
397pub struct UdpProxy {
398    fronts: HashMap<ClusterId, Token>,
399    backends: Rc<RefCell<BackendMap>>,
400    listeners: HashMap<Token, Rc<RefCell<UdpListener>>>,
401    /// The built listener session per listener token. The server inserts the
402    /// same `Rc` into the slab at the listener token; the proxy keeps a clone so
403    /// it can drive per-flow teardown (soft/hard stop) without downcasting the
404    /// slab's `dyn ProxySession`. Cleared on listener removal/stop.
405    listener_sessions: HashMap<Token, Rc<RefCell<UdpListenerSession>>>,
406    /// One sans-io manager per listener token, sharing the listener's lifecycle.
407    managers: HashMap<Token, Rc<RefCell<UdpManager>>>,
408    /// Cluster routing for each listener token (set by the UDP frontend).
409    cluster_for_listener: HashMap<Token, ClusterId>,
410    /// Last `AddCluster`-supplied UDP knobs per cluster id. Cached so a frontend
411    /// added AFTER its cluster still picks up `responses` / `requests` / PPv2 /
412    /// affinity — `AddCluster` and `AddUdpFrontend` arrive in either order, and
413    /// neither must clobber the other's contribution to a manager's
414    /// `ClusterConfig`.
415    cluster_udp_config: HashMap<ClusterId, sozu_command::proto::command::UdpClusterConfig>,
416    /// Last `AddCluster`-supplied `max_connections_per_ip` /
417    /// `max_connections_per_subnet` overrides per cluster, cached for the same
418    /// order-independence as `cluster_udp_config`. They live on the `Cluster`
419    /// message itself, not in its `udp` block. Only these cluster-level values
420    /// apply to UDP: the global defaults are deliberately NOT inherited, so a
421    /// global cap set for HTTP/TCP never starts dropping UDP flows.
422    cluster_flow_limits: HashMap<ClusterId, (Option<u64>, Option<u64>)>,
423    /// The worker's boot-time `subnet_ipv4_prefix` / `subnet_ipv6_prefix`,
424    /// copied from the `SessionManager` at construction. A copy rather than a
425    /// borrow: the server calls into this proxy (`add_listener`) while it
426    /// holds the `SessionManager` mutably.
427    subnet_prefixes: (u32, u32),
428    registry: Registry,
429    sessions: Rc<RefCell<SessionManager>>,
430    #[allow(dead_code)]
431    pool: Rc<RefCell<Pool>>,
432    /// Fixed hash seed injected once into every manager. It is deliberately the
433    /// same constant on every worker and across restarts so HRW/Maglev affinity
434    /// is reproducible cluster-wide — a per-worker random seed would scatter the
435    /// same flow key onto a different backend on each worker and reshuffle it on
436    /// every restart, defeating the documented affinity-stability contract (see
437    /// [`crate::load_balancing::DEFAULT_HASH_SEED`]).
438    hash_seed: u64,
439    /// Global `max_connections` (from `ServerConfig`). Used to clamp a UDP
440    /// listener's auto `max_flows` so a single listener cannot inflate the
441    /// shared `SessionManager` slab and starve HTTP/TCP.
442    max_connections: usize,
443    /// Global `buffer_size` (from `ServerConfig`). Used to clamp a listener's
444    /// `max_rx_datagram_size` so a hostile `AddUdpListener` cannot allocate a
445    /// multi-GB recv buffer.
446    buffer_size: usize,
447    /// Endpoint-bound active health prober (TCP probe + hysteresis +
448    /// fail-open). Driven from the server event loop via
449    /// [`UdpProxy::health_poll`] / [`UdpProxy::health_ready`].
450    health: UdpHealthChecker,
451}
452
453impl UdpProxy {
454    pub fn new(
455        registry: Registry,
456        sessions: Rc<RefCell<SessionManager>>,
457        pool: Rc<RefCell<Pool>>,
458        backends: Rc<RefCell<BackendMap>>,
459        max_connections: usize,
460        buffer_size: usize,
461    ) -> UdpProxy {
462        // Fixed seed, identical on every worker and across restarts: HRW/Maglev
463        // affinity must be reproducible cluster-wide. A per-worker/per-restart
464        // random seed (as a `process::id()` + `Instant::now()` mix would be)
465        // would route the same flow key to a different backend on each worker
466        // and reshuffle it on every restart. Reuse the LB module's canonical
467        // affinity seed so UDP and the HTTP/TCP affinity hashers agree.
468        let hash_seed = crate::load_balancing::DEFAULT_HASH_SEED;
469        let subnet_prefixes = {
470            let s = sessions.borrow();
471            (s.subnet_ipv4_prefix, s.subnet_ipv6_prefix)
472        };
473        UdpProxy {
474            backends,
475            listeners: HashMap::new(),
476            listener_sessions: HashMap::new(),
477            managers: HashMap::new(),
478            cluster_for_listener: HashMap::new(),
479            cluster_udp_config: HashMap::new(),
480            cluster_flow_limits: HashMap::new(),
481            subnet_prefixes,
482            fronts: HashMap::new(),
483            registry,
484            sessions,
485            pool,
486            hash_seed,
487            max_connections,
488            buffer_size,
489            health: UdpHealthChecker::new(),
490        }
491    }
492
493    /// Drive the UDP health prober one event-loop step (server calls this once
494    /// per iteration, mirroring `HealthChecker::poll`). Non-blocking.
495    ///
496    /// The prober borrows `self.registry` directly. It must not clone it:
497    /// `Registry::try_clone` is an `fcntl(F_DUPFD_CLOEXEC)` of the epoll
498    /// descriptor and dropping the clone is a `close`, so a clone here cost
499    /// two syscalls on **every** event-loop turn, with or without a UDP
500    /// cluster, and silently skipped health probing whenever the worker was
501    /// out of descriptors (`EMFILE`).
502    pub fn health_poll(&mut self) {
503        self.health.poll(&self.backends, &self.registry);
504    }
505
506    /// Record mio readiness for a UDP health-probe socket.
507    pub fn health_ready(&mut self, token: Token) {
508        self.health.ready(token);
509    }
510
511    /// Whether `token` is a UDP health-probe socket this proxy owns.
512    pub fn health_owns_token(&self, token: Token) -> bool {
513        self.health.owns_token(token)
514    }
515
516    pub fn add_listener(
517        &mut self,
518        config: UdpListenerConfig,
519        token: Token,
520    ) -> Result<Token, ProxyError> {
521        match self.listeners.entry(token) {
522            Entry::Vacant(entry) => {
523                let mut config = config;
524                let max_flows = effective_max_flows(config.max_flows, self.max_connections);
525                // Defense in depth: cap the recv-buffer sizing to `buffer_size`
526                // so a hostile `AddUdpListener` (`max_rx_datagram_size =
527                // u32::MAX`) can't allocate a multi-GB buffer. Write the clamped
528                // value back into the stored config so the manager AND the
529                // session's `recv_buf` (sized from `config` in `new()`) agree on
530                // the same bound.
531                let max_rx = clamp_max_rx(config.max_rx_datagram_size as usize, self.buffer_size);
532                config.max_rx_datagram_size = max_rx as u32;
533                let front = Duration::from_secs(u64::from(config.front_timeout));
534                let back = Duration::from_secs(u64::from(config.back_timeout));
535                let listener = UdpListener::new(config, token).map_err(ProxyError::AddListener)?;
536                entry.insert(Rc::new(RefCell::new(listener)));
537                let cluster_cfg = ClusterConfig {
538                    front_timeout: front,
539                    back_timeout: back,
540                    subnet_ipv4_prefix: self.subnet_prefixes.0,
541                    subnet_ipv6_prefix: self.subnet_prefixes.1,
542                    ..Default::default()
543                };
544                self.managers.insert(
545                    token,
546                    Rc::new(RefCell::new(UdpManager::new(
547                        cluster_cfg,
548                        max_flows,
549                        max_rx,
550                        self.hash_seed,
551                    ))),
552                );
553                self.adopt_sibling_route(token);
554                Ok(token)
555            }
556            _ => Err(ProxyError::ListenerAlreadyPresent),
557        }
558    }
559
560    /// A UDP frontend names its listener by address alone, so a listener
561    /// added next to one already on its address (on another interface) takes
562    /// over the cluster that one routes to, in its own state and in its flow
563    /// manager: every listener on an address routes the same way, whenever it
564    /// was added (sozu-proxy/sozu#719).
565    fn adopt_sibling_route(&mut self, token: Token) {
566        let address = self.listeners[&token].borrow().address;
567        let Some((cluster_id, tags)) = self
568            .listeners
569            .iter()
570            .filter(|(sibling, listener)| {
571                **sibling != token && listener.borrow().address == address
572            })
573            .min_by_key(|(sibling, _)| **sibling)
574            .map(|(_, listener)| {
575                let listener = listener.borrow();
576                (listener.cluster_id.clone(), listener.tags.clone())
577            })
578        else {
579            return;
580        };
581        {
582            let mut listener = self.listeners[&token].borrow_mut();
583            listener.tags = tags;
584            listener.cluster_id.clone_from(&cluster_id);
585        }
586        if let Some(cluster_id) = cluster_id {
587            self.cluster_for_listener.insert(token, cluster_id);
588            if let Some(mgr) = self.managers.get(&token) {
589                let cfg = {
590                    let l = self.listeners[&token].borrow();
591                    self.cluster_config_for(&l, token)
592                };
593                mgr.borrow_mut().handle_input(
594                    ManagerInput::Config(ConfigEvent::SetCluster(cfg)),
595                    Instant::now(),
596                );
597            }
598        }
599    }
600
601    pub fn remove_listener(&mut self, address: &ListenerKey) -> SessionIsToBeClosed {
602        let len = self.listeners.len();
603        let mut removed_tokens = Vec::new();
604        self.listeners.retain(|token, l| {
605            if l.borrow().is(address) {
606                removed_tokens.push(*token);
607                false
608            } else {
609                true
610            }
611        });
612        let now = Instant::now();
613        for token in removed_tokens {
614            self.cluster_for_listener.remove(&token);
615            // Drive per-flow teardown THROUGH the manager (emits FlowEvicted +
616            // CloseFlow per flow) BEFORE dropping the manager, so the
617            // active-flows gauge is decremented once per flow and the per-flow
618            // upstream sockets + slab slots are freed. Removing the manager first
619            // would silently leak the gauge by N.
620            if let Some(session) = self.listener_sessions.remove(&token) {
621                session.borrow_mut().close_all_flows(now);
622            }
623            self.managers.remove(&token);
624        }
625        self.listeners.len() < len
626    }
627
628    /// What this proxy would do with a socket handed to
629    /// [`activate_listener`](Self::activate_listener) for `addr`. See
630    /// [`crate::InheritedSocketFate`].
631    ///
632    /// The event loop is single-threaded, so nothing can change the listener's
633    /// `active` flag between this answer and the `activate_listener` call that
634    /// acts on it.
635    pub fn inherited_socket_fate(&self, addr: &ListenerKey) -> crate::InheritedSocketFate {
636        use crate::InheritedSocketFate;
637        match self
638            .listeners
639            .values()
640            .find(|listener| listener.borrow().is(addr))
641        {
642            None => InheritedSocketFate::Unclaimed,
643            Some(listener) if listener.borrow().active => InheritedSocketFate::Refused,
644            Some(_) => InheritedSocketFate::Adopted,
645        }
646    }
647
648    pub fn activate_listener(
649        &self,
650        addr: &ListenerKey,
651        udp_socket: Option<UdpSocket>,
652    ) -> Result<Token, ProxyError> {
653        let listener = self
654            .listeners
655            .values()
656            .find(|listener| listener.borrow().is(addr))
657            .ok_or(ProxyError::NoListenerFound(addr.clone()))?;
658
659        listener.borrow_mut().activate(&self.registry, udp_socket)
660    }
661
662    /// Whether the real [`UdpListenerSession`] for `token` is already
663    /// installed, i.e. [`build_session`](Self::build_session) has run for it
664    /// and no teardown has removed it since.
665    ///
666    /// `listener_sessions` is populated by `build_session` alone and emptied by
667    /// `give_back_listener` (deactivate), `remove_listener` and soft/hard stop,
668    /// so membership tracks the installed session exactly. `Server` reads it to
669    /// tell a first `ActivateListener` from a repeat: `UdpListener::activate`
670    /// answers `Ok(token)` for an already-active listener without doing any
671    /// work, and rebuilding on that answer would destroy the live session.
672    pub fn has_listener_session(&self, token: Token) -> bool {
673        self.listener_sessions.contains_key(&token)
674    }
675
676    /// The slab token reserved for the listener at `address`, if this proxy
677    /// holds one.
678    ///
679    /// A listener owns exactly one slab slot for its whole `AddListener` ->
680    /// `RemoveListener` lifetime, and no `remove_listener` implementation
681    /// touches the session slab. `Server` therefore reads the token here
682    /// BEFORE dropping the listener, so that reserved slot is released exactly
683    /// once, at the end of the lifetime.
684    pub fn listener_token(&self, address: &ListenerKey) -> Option<Token> {
685        self.listeners
686            .iter()
687            .find(|(_, listener)| listener.borrow().is(address))
688            .map(|(token, _)| *token)
689    }
690
691    /// The cluster the listener at `address` routes to, for tests that drive
692    /// the proxy through `Server::notify_proxys` (`lib/src/server.rs`).
693    #[cfg(test)]
694    pub(crate) fn listener_cluster(&self, address: &ListenerKey) -> Option<ClusterId> {
695        let token = self.listener_token(address)?;
696        self.cluster_for_listener.get(&token).cloned()
697    }
698
699    /// Build the [`UdpListenerSession`] that drives this listener's datagrams.
700    /// The server inserts the returned session into the slab **at the listener
701    /// token**, replacing the `ListenSession` placeholder, so the generic
702    /// readiness path reaches `UdpListenerSession::update_readiness`. Returns
703    /// `None` if the listener token is unknown.
704    pub fn build_session(&mut self, token: Token) -> Option<Rc<RefCell<UdpListenerSession>>> {
705        let listener = self.listeners.get(&token)?.clone();
706        let manager = self.managers.get(&token)?.clone();
707        let registry = self.registry.try_clone().ok()?;
708        let session = Rc::new(RefCell::new(UdpListenerSession::new(
709            listener,
710            manager,
711            self.backends.clone(),
712            registry,
713            self.sessions.clone(),
714            token,
715        )));
716        // Keep a clone so soft/hard stop can drive per-flow teardown.
717        self.listener_sessions.insert(token, session.clone());
718        Some(session)
719    }
720
721    pub fn give_back_listeners(&mut self) -> Vec<(ListenerKey, UdpSocket)> {
722        self.listeners
723            .values()
724            .filter_map(|listener| {
725                let mut owned = listener.borrow_mut();
726                if let Some(socket) = owned.socket.take() {
727                    owned.active = false;
728                    return Some((owned.key(), socket));
729                }
730                None
731            })
732            .collect()
733    }
734
735    pub fn give_back_listener(
736        &mut self,
737        address: &ListenerKey,
738    ) -> Result<(Token, UdpSocket), ProxyError> {
739        let listener = self
740            .listeners
741            .values()
742            .find(|listener| listener.borrow().is(address))
743            .ok_or(ProxyError::NoListenerFound(address.clone()))?;
744
745        let (token, taken) = {
746            let mut owned = listener.borrow_mut();
747            let taken = owned.socket.take().ok_or(ProxyError::UnactivatedListener)?;
748            owned.active = false;
749            (owned.token, taken)
750        };
751        // Deactivating removes the listener from service: tear down its active
752        // flows THROUGH the manager so the per-flow upstream slab slots + fds
753        // don't dangle and the active-flows gauge is decremented once per flow
754        // (the manager is RETAINED here — not removed from `self.managers` — so
755        // `close_all` also resets its flow table, keeping manager and shell
756        // consistent for a later reactivate).
757        if let Some(session) = self.listener_sessions.remove(&token) {
758            session.borrow_mut().close_all_flows(Instant::now());
759        }
760        Ok((token, taken))
761    }
762
763    pub fn update_listener(&mut self, patch: UpdateUdpListenerConfig) -> Result<(), ProxyError> {
764        let address: SocketAddr = patch.address.into();
765        let listener = self
766            .listeners
767            .values()
768            .find(|l| l.borrow().address == address)
769            .ok_or(ProxyError::NoListenerFound(address.into()))?;
770        {
771            let mut l = listener.borrow_mut();
772            l.update_config(&patch);
773            // Clamp the stored rx size in place (defense in depth): keep the
774            // listener config, the manager, and the session's `recv_buf` agreeing
775            // on the same `buffer_size`-bounded value.
776            l.config.max_rx_datagram_size =
777                clamp_max_rx(l.config.max_rx_datagram_size as usize, self.buffer_size) as u32;
778        }
779
780        // Reflect the timeout / cap / rx-size changes into the manager for new
781        // flows. Existing flows keep their captured config (stable contract).
782        if let Some(token) = self
783            .listeners
784            .iter()
785            .find(|(_, l)| l.borrow().address == address)
786            .map(|(t, _)| *t)
787            && let Some(mgr) = self.managers.get(&token)
788        {
789            let now = Instant::now();
790            let (cfg, max_flows, max_rx) = {
791                let l = listener.borrow();
792                (
793                    self.cluster_config_for(&l, token),
794                    effective_max_flows(l.config.max_flows, self.max_connections),
795                    // Defense in depth: clamp the raised rx size to `buffer_size`
796                    // so a hostile `UpdateUdpListener` can't grow the recv buffer
797                    // past the global cap.
798                    clamp_max_rx(l.config.max_rx_datagram_size as usize, self.buffer_size),
799                )
800            };
801            {
802                let mut m = mgr.borrow_mut();
803                m.handle_input(ManagerInput::Config(ConfigEvent::SetCluster(cfg)), now);
804                m.handle_input(
805                    ManagerInput::Config(ConfigEvent::SetMaxFlows(max_flows)),
806                    now,
807                );
808                m.handle_input(
809                    ManagerInput::Config(ConfigEvent::SetMaxRxDatagramSize(max_rx)),
810                    now,
811                );
812            }
813            // Resize the live session's recv scratch to match the (clamped) new
814            // rx size. Without this, a config that RAISES `max_rx_datagram_size`
815            // would leave `recv_buf` at its old (smaller) length: `recv_from`
816            // would kernel-truncate datagrams between the old and new size while
817            // the manager's `len > max_rx` check (now the larger value) passes
818            // them, forwarding them truncated. The `+ 1` mirrors `new()`: it lets
819            // the manager observe `len == max_rx + 1 > max_rx` and drop an
820            // oversized datagram (`DropReason::Truncated`) instead of silently
821            // truncating it. Existing flows keep their captured config; only the
822            // shared recv buffer tracks the live rx size.
823            if let Some(session) = self.listener_sessions.get(&token) {
824                session.borrow_mut().resize_recv_buf(max_rx);
825            }
826        }
827        Ok(())
828    }
829
830    /// The tokens of every listener on `address`, whatever its interface. A
831    /// UDP frontend names its listener by address alone, so it applies to all
832    /// the listeners sharing that address on different interfaces.
833    fn listener_tokens_at(&self, address: SocketAddr) -> Result<Vec<Token>, ProxyError> {
834        let mut tokens: Vec<Token> = self
835            .listeners
836            .iter()
837            .filter(|(_, listener)| listener.borrow().address == address)
838            .map(|(token, _)| *token)
839            .collect();
840        tokens.sort();
841        if tokens.is_empty() {
842            return Err(ProxyError::NoListenerFound(address.into()));
843        }
844        Ok(tokens)
845    }
846
847    pub fn add_udp_front(&mut self, front: RequestUdpFrontend) -> Result<(), ProxyError> {
848        let address = front.address.into();
849        let cluster_id = ClusterId::from(front.cluster_id);
850        for token in self.listener_tokens_at(address)? {
851            {
852                let mut listener = self.listeners[&token].borrow_mut();
853                self.fronts.insert(cluster_id.clone(), listener.token);
854                listener.set_tags(address.to_string(), Some(front.tags.clone()));
855                listener.cluster_id = Some(cluster_id.clone());
856            }
857            self.cluster_for_listener.insert(token, cluster_id.clone());
858
859            // Commit the cluster routing into the manager so admitted flows know
860            // which cluster to select a backend from (`BackendSource::select`).
861            if let Some(mgr) = self.managers.get(&token) {
862                let cfg = {
863                    let l = self.listeners[&token].borrow();
864                    self.cluster_config_for(&l, token)
865                };
866                mgr.borrow_mut().handle_input(
867                    ManagerInput::Config(ConfigEvent::SetCluster(cfg)),
868                    Instant::now(),
869                );
870            }
871        }
872        Ok(())
873    }
874
875    pub fn remove_udp_front(&mut self, front: RequestUdpFrontend) -> Result<(), ProxyError> {
876        let address = front.address.into();
877        for token in self.listener_tokens_at(address)? {
878            {
879                let mut listener = self.listeners[&token].borrow_mut();
880                listener.set_tags(address.to_string(), None);
881                if let Some(cluster_id) = listener.cluster_id.take() {
882                    self.fronts.remove(&cluster_id);
883                }
884            }
885            self.cluster_for_listener.remove(&token);
886            // Drop the routing in the manager — new datagrams now have no backend.
887            if let Some(mgr) = self.managers.get(&token) {
888                mgr.borrow_mut().handle_input(
889                    ManagerInput::Config(ConfigEvent::SetCluster(ClusterConfig::default())),
890                    Instant::now(),
891                );
892            }
893        }
894        Ok(())
895    }
896
897    /// Build a [`ClusterConfig`] for a listener from its current frontend
898    /// cluster routing + timeouts. The per-cluster knobs (responses/requests/
899    /// PPv2/affinity) are populated by [`apply_cluster`](Self::apply_cluster)
900    /// when an `AddCluster` carries a `udp` block; absent that they stay at the
901    /// proto defaults.
902    fn cluster_config_for(&self, listener: &UdpListener, _token: Token) -> ClusterConfig {
903        let cluster = listener.cluster_id.clone().unwrap_or_default();
904        let mut cfg = ClusterConfig {
905            cluster: cluster.clone(),
906            front_timeout: Duration::from_secs(u64::from(listener.config.front_timeout)),
907            back_timeout: Duration::from_secs(u64::from(listener.config.back_timeout)),
908            ..Default::default()
909        };
910        // Fold in the cluster's cached UDP knobs (set by a prior or later
911        // `AddCluster`) so the order of `AddCluster` vs `AddUdpFrontend` does not
912        // matter — both rebuilds of a manager's `ClusterConfig` converge on the
913        // same per-cluster contract.
914        if let Some(udp) = self.cluster_udp_config.get(&cluster) {
915            apply_udp_knobs(&mut cfg, udp);
916        }
917        // Per-source flow limits: opt-in, from the cluster's own
918        // `max_connections_per_ip` / `max_connections_per_subnet` only. An
919        // absent value is unlimited; the global defaults are NOT inherited.
920        // The subnet prefixes are the worker's boot-time ones.
921        let (per_ip, per_subnet) = self
922            .cluster_flow_limits
923            .get(&cluster)
924            .copied()
925            .unwrap_or_default();
926        cfg.max_flows_per_ip = per_ip.unwrap_or(0);
927        cfg.max_flows_per_subnet = per_subnet.unwrap_or(0);
928        (cfg.subnet_ipv4_prefix, cfg.subnet_ipv6_prefix) = self.subnet_prefixes;
929        cfg
930    }
931
932    /// The flow manager of the listener at `token`.
933    #[cfg(test)]
934    pub(crate) fn manager(&self, token: Token) -> Option<Rc<RefCell<UdpManager>>> {
935        self.managers.get(&token).cloned()
936    }
937
938    /// Apply an `AddCluster` to every listener routing to it: fold the UDP
939    /// cluster knobs (affinity / responses / requests / PPv2) into the
940    /// manager's `ClusterConfig`, and rebuild the LB policy (HRW/Maglev table)
941    /// via the shared `BackendMap`.
942    fn apply_cluster(&mut self, cluster: &Cluster) {
943        // 1. LB policy (HRW/Maglev rebuild happens inside the BackendMap).
944        self.backends
945            .borrow_mut()
946            .set_load_balancing_policy_for_cluster(
947                &cluster.cluster_id,
948                LoadBalancingAlgorithms::try_from(cluster.load_balancing).unwrap_or_default(),
949                cluster
950                    .load_metric
951                    .and_then(|n| LoadMetric::try_from(n).ok()),
952            );
953
954        // 1b. Health settings from the cluster's `udp.health` block, if any.
955        // Mode HEALTH_OFF / no health block disables probing for this cluster.
956        let health_settings = cluster.udp.as_ref().and_then(|udp| {
957            udp.health.as_ref().and_then(|h| {
958                let mode = h
959                    .mode
960                    .and_then(|m| sozu_command::proto::command::UdpHealthMode::try_from(m).ok());
961                match mode {
962                    Some(sozu_command::proto::command::UdpHealthMode::HealthOff) => None,
963                    // TCP_PROBE (default when a health block is present) and
964                    // UDP_PROBE both schedule the TCP-probe state machine; the
965                    // app UDP-probe payload rides along for the secondary check.
966                    _ => Some(health::UdpHealthSettings::from_proto(h)),
967                }
968            })
969        });
970        self.health
971            .set_cluster(&cluster.cluster_id, health_settings, &self.registry);
972
973        // 1c. Cache the UDP knobs so a frontend added in EITHER order picks them
974        // up via `cluster_config_for`. An `AddCluster` without a `udp` block
975        // clears the cache (back to proto defaults).
976        match &cluster.udp {
977            Some(udp) => {
978                self.cluster_udp_config
979                    .insert(cluster.cluster_id.as_str().into(), udp.clone());
980            }
981            None => {
982                self.cluster_udp_config.remove(cluster.cluster_id.as_str());
983            }
984        }
985        self.cluster_flow_limits.insert(
986            cluster.cluster_id.as_str().into(),
987            (
988                cluster.max_connections_per_ip,
989                cluster.max_connections_per_subnet,
990            ),
991        );
992
993        // 2. Per-cluster UDP knobs into the managers routing to this cluster.
994        // `cluster_config_for` now folds the cached knobs in, so the rebuild is
995        // identical regardless of whether the frontend or the cluster came
996        // first.
997        let now = Instant::now();
998        let tokens: Vec<Token> = self
999            .cluster_for_listener
1000            .iter()
1001            .filter(|(_, c)| ***c == *cluster.cluster_id)
1002            .map(|(t, _)| *t)
1003            .collect();
1004        for token in tokens {
1005            let Some(listener) = self.listeners.get(&token) else {
1006                continue;
1007            };
1008            let cfg = {
1009                let l = listener.borrow();
1010                self.cluster_config_for(&l, token)
1011            };
1012            if let Some(mgr) = self.managers.get(&token) {
1013                mgr.borrow_mut()
1014                    .handle_input(ManagerInput::Config(ConfigEvent::SetCluster(cfg)), now);
1015            }
1016        }
1017    }
1018
1019    /// Inherent dispatch entry point — the server calls this directly (UDP does
1020    /// not implement `ProxyConfiguration`). Handles UDP frontends, cluster
1021    /// config, listener removal, and stop. No accept / create_session.
1022    pub fn notify(&mut self, message: WorkerRequest) -> WorkerResponse {
1023        let request_type = match message.content.request_type {
1024            Some(t) => t,
1025            None => return WorkerResponse::error(message.id, "Empty request"),
1026        };
1027        match request_type {
1028            RequestType::AddUdpFrontend(front) => match self.add_udp_front(front) {
1029                Ok(()) => WorkerResponse::ok(message.id),
1030                Err(err) => WorkerResponse::error(message.id, err),
1031            },
1032            RequestType::RemoveUdpFrontend(front) => match self.remove_udp_front(front) {
1033                Ok(()) => WorkerResponse::ok(message.id),
1034                Err(err) => WorkerResponse::error(message.id, err),
1035            },
1036            RequestType::AddCluster(cluster) => {
1037                self.apply_cluster(&cluster);
1038                WorkerResponse::ok(message.id)
1039            }
1040            RequestType::RemoveCluster(cluster_id) => {
1041                let tokens: Vec<Token> = self
1042                    .cluster_for_listener
1043                    .iter()
1044                    .filter(|(_, c)| ***c == *cluster_id)
1045                    .map(|(t, _)| *t)
1046                    .collect();
1047                for token in tokens {
1048                    if let Some(mgr) = self.managers.get(&token) {
1049                        mgr.borrow_mut().handle_input(
1050                            ManagerInput::Config(ConfigEvent::SetCluster(ClusterConfig::default())),
1051                            Instant::now(),
1052                        );
1053                    }
1054                }
1055                self.cluster_udp_config.remove(cluster_id.as_str());
1056                self.cluster_flow_limits.remove(cluster_id.as_str());
1057                self.health.remove_cluster(&cluster_id, &self.registry);
1058                WorkerResponse::ok(message.id)
1059            }
1060            RequestType::SoftStop(_) => {
1061                info!(
1062                    "{} {} processing soft shutdown",
1063                    log_module_context!(),
1064                    message.id
1065                );
1066                // Drain: admit no new flows. Then actively tear down existing
1067                // flows so the worker reaches `base_sessions_count` and exits
1068                // promptly instead of waiting out each flow's idle timeout. UDP
1069                // has no half-sent response to preserve, so an immediate flow
1070                // teardown on soft-stop is the right graceful behavior (a stray
1071                // in-flight reply may be lost, which is acceptable for a
1072                // best-effort datagram proxy).
1073                let now = Instant::now();
1074                for mgr in self.managers.values() {
1075                    mgr.borrow_mut()
1076                        .handle_input(ManagerInput::Config(ConfigEvent::Drain), now);
1077                }
1078                // Drive teardown through each manager (FlowEvicted + CloseFlow
1079                // per flow) so the active-flows gauge balances to zero.
1080                for session in self.listener_sessions.values() {
1081                    session.borrow_mut().close_all_flows(now);
1082                }
1083                self.listener_sessions.clear();
1084                let listeners = std::mem::take(&mut self.listeners);
1085                for l in listeners.values() {
1086                    l.borrow_mut()
1087                        .socket
1088                        .take()
1089                        .map(|mut sock| self.registry.deregister(&mut sock));
1090                }
1091                WorkerResponse::processing(message.id)
1092            }
1093            RequestType::HardStop(_) => {
1094                info!("{} {} hard shutdown", log_module_context!(), message.id);
1095                let now = Instant::now();
1096                // Drive teardown through each manager (FlowEvicted + CloseFlow
1097                // per flow) so the active-flows gauge balances to zero before the
1098                // managers are dropped below.
1099                for session in self.listener_sessions.values() {
1100                    session.borrow_mut().close_all_flows(now);
1101                }
1102                self.listener_sessions.clear();
1103                for (_, l) in std::mem::take(&mut self.listeners) {
1104                    l.borrow_mut()
1105                        .socket
1106                        .take()
1107                        .map(|mut sock| self.registry.deregister(&mut sock));
1108                }
1109                self.managers.clear();
1110                WorkerResponse::ok(message.id)
1111            }
1112            RequestType::Status(_) => {
1113                info!("{} {} status", log_module_context!(), message.id);
1114                WorkerResponse::ok(message.id)
1115            }
1116            RequestType::RemoveListener(remove) => {
1117                if !self.remove_listener(&remove.listener_key()) {
1118                    WorkerResponse::error(
1119                        message.id,
1120                        format!("no UDP listener to remove at address {:?}", remove.address),
1121                    )
1122                } else {
1123                    WorkerResponse::ok(message.id)
1124                }
1125            }
1126            command => {
1127                debug!(
1128                    "{} {} unsupported message for UDP proxy, ignoring {:?}",
1129                    log_module_context!(),
1130                    message.id,
1131                    command
1132                );
1133                WorkerResponse::error(message.id, "unsupported message")
1134            }
1135        }
1136    }
1137}
1138
1139/// Fold a proto [`UdpClusterConfig`](sozu_command::proto::command::UdpClusterConfig)
1140/// into a [`ClusterConfig`], applying the proto defaults for absent fields.
1141/// Single source of truth so `apply_cluster` (live push) and
1142/// `cluster_config_for` (frontend add / rebuild) never diverge.
1143fn apply_udp_knobs(cfg: &mut ClusterConfig, udp: &sozu_command::proto::command::UdpClusterConfig) {
1144    cfg.affinity_with_port = matches!(
1145        udp.affinity_key
1146            .and_then(|k| UdpAffinityKey::try_from(k).ok()),
1147        Some(UdpAffinityKey::SourceIpPort)
1148    );
1149    cfg.responses = udp.responses.unwrap_or(0);
1150    cfg.requests = udp.requests.unwrap_or(0);
1151    cfg.send_proxy_protocol = udp.send_proxy_protocol.unwrap_or(false);
1152    cfg.proxy_protocol_every_datagram = udp.proxy_protocol_every_datagram.unwrap_or(false);
1153}
1154
1155/// Fallback auto `max_flows` when `RLIMIT_NOFILE` can't be read.
1156const DEFAULT_AUTO_MAX_FLOWS: usize = 1024;
1157
1158/// `max_flows == 0` means "auto": ~70% of the soft `RLIMIT_NOFILE`, so the fd
1159/// budget adapts to the host without hand-tuning (one fd + one slab slot per
1160/// flow). Falls back to a conservative constant when the limit can't be read.
1161///
1162/// The auto derivation is clamped to `slab_headroom` (the global
1163/// `max_connections`): every admitted flow consumes one shared `SessionManager`
1164/// slab slot, so an unclamped ~70%-of-RLIMIT value on a host with a very large
1165/// fd limit could try to inflate the slab to hundreds of thousands of entries
1166/// and starve HTTP/TCP. An *explicitly configured* `max_flows` is honoured as-is
1167/// (the operator opted in); only the auto value is capped. A `slab_headroom` of
1168/// 0 (no connection budget configured) disables the clamp.
1169fn effective_max_flows(configured: u32, slab_headroom: usize) -> usize {
1170    if configured != 0 {
1171        return configured as usize;
1172    }
1173    let auto = {
1174        #[cfg(unix)]
1175        {
1176            let mut limit = libc::rlimit {
1177                rlim_cur: 0,
1178                rlim_max: 0,
1179            };
1180            // SAFETY: `getrlimit` writes a fully-initialised `rlimit` into
1181            // `limit`; we read the result only on success (`== 0`).
1182            let ret = unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limit) };
1183            if ret == 0 && limit.rlim_cur > 0 {
1184                let soft = limit.rlim_cur;
1185                ((soft.saturating_mul(7)) / 10).max(1) as usize
1186            } else {
1187                DEFAULT_AUTO_MAX_FLOWS
1188            }
1189        }
1190        #[cfg(not(unix))]
1191        {
1192            DEFAULT_AUTO_MAX_FLOWS
1193        }
1194    };
1195    if slab_headroom == 0 {
1196        auto
1197    } else {
1198        auto.min(slab_headroom).max(1)
1199    }
1200}
1201
1202/// Clamp a configured `max_rx_datagram_size` to the global `buffer_size`
1203/// (defense in depth): the per-session `recv_buf` is sized `max_rx + 1`, so a
1204/// raw worker `AddUdpListener`/`UpdateUdpListener` carrying e.g.
1205/// `max_rx_datagram_size = u32::MAX` must NOT be able to allocate a ~4 GB
1206/// buffer. A `buffer_size` of 0 (unset) leaves the value untouched.
1207fn clamp_max_rx(configured: usize, buffer_size: usize) -> usize {
1208    if buffer_size == 0 {
1209        configured
1210    } else {
1211        configured.min(buffer_size)
1212    }
1213}
1214
1215/// The `ProxySession` backing one UDP listener. The server's generic readiness
1216/// path drives this (UDP is not in the listen-accept arm). It owns the per-flow
1217/// connected upstream sockets and demuxes events by token.
1218pub struct UdpListenerSession {
1219    /// The listener this session serves.
1220    listener: Rc<RefCell<UdpListener>>,
1221    /// The sans-io manager for this listener.
1222    manager: Rc<RefCell<UdpManager>>,
1223    /// Shared backend map for LB selection.
1224    backends: Rc<RefCell<BackendMap>>,
1225    /// Cloned mio registry for per-flow socket (de)registration.
1226    registry: Registry,
1227    /// Slab the per-flow upstream tokens are inserted into (the same `Rc` the
1228    /// listener-session is registered under: multi-token pattern).
1229    sessions: Rc<RefCell<SessionManager>>,
1230    /// The listener's own token (client recv + timer key).
1231    listener_token: Token,
1232    /// Listener address (for logging).
1233    address: SocketAddr,
1234    /// Per-flow connected upstream sockets, keyed by their slab token.
1235    upstream_sockets: HashMap<Token, UdpSocket>,
1236    /// Per-flow bounded egress queues for the forward path, keyed by the
1237    /// upstream token. A datagram lands here only when the connected upstream
1238    /// socket returns `WouldBlock`; the socket is then reregistered
1239    /// `READABLE | WRITABLE` and drained on the next writable event. Dropped
1240    /// together with the socket on flow close (no leak, gauge stays correct).
1241    upstream_write_queues: HashMap<Token, WriteQueue>,
1242    /// Bounded egress queue for the client-return path (replies fanned back
1243    /// through the single listener socket via `send_to`). Engaged only when the
1244    /// listener socket returns `WouldBlock`; the listener is then reregistered
1245    /// `READABLE | WRITABLE` and drained on its next writable event.
1246    client_write_queue: WriteQueue,
1247    /// `upstream_token -> FlowId` for NAT-return demux.
1248    upstream_to_flow: HashMap<Token, FlowId>,
1249    /// `FlowId -> upstream_token` to tear down on close.
1250    flow_to_upstream: HashMap<FlowId, Token>,
1251    /// `FlowId -> admission Instant` for `udp.flow.duration` on close.
1252    flow_started: HashMap<FlowId, Instant>,
1253    /// `FlowId -> (client, backend)` for the close access log.
1254    flow_endpoints: HashMap<FlowId, (SocketAddr, Option<SocketAddr>)>,
1255    /// The client source whose datagram is currently being drained. Lets
1256    /// `on_send_to_backend` resolve the right per-flow upstream socket for an
1257    /// already-established flow (where no `OpenUpstream` precedes the send).
1258    /// `None` while draining backend-side or timer-driven outputs.
1259    in_flight_client: Option<SocketAddr>,
1260    /// The flow whose upstream was just opened in this drain pass — covers the
1261    /// new-flow path where `OpenUpstream{flow}` immediately precedes the first
1262    /// `SendToBackend`.
1263    in_flight_flow: Option<FlowId>,
1264    /// Shadow of the manager's flow table: client source address → `FlowId`.
1265    /// Lets the shell resolve the owning flow for a `SendToBackend` on an
1266    /// established flow from the in-flight client source. Kept in lockstep with
1267    /// `OpenUpstream` / `CloseFlow`.
1268    client_key_to_flow: HashMap<SocketAddr, FlowId>,
1269    /// Reusable recv scratch buffer, sized to `max_rx_datagram_size`.
1270    recv_buf: Vec<u8>,
1271    /// The currently-armed `TIMER` handle, so a re-arm cancels the previous
1272    /// deadline instead of leaking timer-slab entries.
1273    ///
1274    /// `None` whenever nothing is armed, which includes the moment an expiry is
1275    /// delivered: `Timer::poll` removes the entry from its slab. `timeout`
1276    /// clears it there so the field means what it says locally, rather than
1277    /// holding a handle that is merely harmless. Harmless it is —
1278    /// `cancel_timeout` rejects a delivered handle outright, because
1279    /// `set_timeout_at` clamps every new entry past `self.tick` while a
1280    /// delivered one sat at or below it, so no successor in that slab slot can
1281    /// ever match its tick
1282    /// (`test_a_delivered_timeout_handle_cannot_cancel_its_slot_successor`,
1283    /// `lib/src/timer.rs`). Keeping the invariant local beats resting on that
1284    /// two-hop argument from another module.
1285    timer_handle: Option<crate::timer::Timeout>,
1286}
1287
1288impl UdpListenerSession {
1289    #[allow(clippy::too_many_arguments)]
1290    pub fn new(
1291        listener: Rc<RefCell<UdpListener>>,
1292        manager: Rc<RefCell<UdpManager>>,
1293        backends: Rc<RefCell<BackendMap>>,
1294        registry: Registry,
1295        sessions: Rc<RefCell<SessionManager>>,
1296        listener_token: Token,
1297    ) -> UdpListenerSession {
1298        let (address, max_rx) = {
1299            let l = listener.borrow();
1300            (l.address, l.config.max_rx_datagram_size as usize)
1301        };
1302        UdpListenerSession {
1303            listener,
1304            manager,
1305            backends,
1306            registry,
1307            sessions,
1308            listener_token,
1309            address,
1310            upstream_sockets: HashMap::new(),
1311            upstream_write_queues: HashMap::new(),
1312            client_write_queue: WriteQueue::new(CLIENT_WRITE_QUEUE_CAP),
1313            upstream_to_flow: HashMap::new(),
1314            flow_to_upstream: HashMap::new(),
1315            flow_started: HashMap::new(),
1316            flow_endpoints: HashMap::new(),
1317            in_flight_client: None,
1318            in_flight_flow: None,
1319            client_key_to_flow: HashMap::new(),
1320            // Size the recv scratch to `max_rx + 1`, NOT `max_rx`: a UDP
1321            // `recv_from` truncates the datagram to the buffer length and
1322            // silently discards the tail. If the buffer were exactly `max_rx`,
1323            // an oversized datagram would arrive as a `max_rx`-byte payload —
1324            // indistinguishable from a legal one — and be forwarded truncated.
1325            // The extra byte lets the manager observe `len == max_rx + 1 >
1326            // max_rx` and drop it (`DropReason::Truncated`) instead.
1327            recv_buf: vec![0u8; max_rx.saturating_add(1).max(1)],
1328            timer_handle: None,
1329        }
1330    }
1331
1332    /// Resize the recv scratch buffer to `max_rx + 1` (the same `+ 1` sizing as
1333    /// [`new`](Self::new): the extra byte lets the manager observe an oversized
1334    /// datagram as `len == max_rx + 1 > max_rx` and drop it as `Truncated`
1335    /// rather than silently forwarding a kernel-truncated payload). Called from
1336    /// `UdpProxy::update_listener` when a config push changes the listener's
1337    /// `max_rx_datagram_size`. Resizing larger zero-fills the new tail; resizing
1338    /// smaller truncates the (idle, between-datagram) scratch — both are safe
1339    /// because the buffer holds no live datagram across calls.
1340    fn resize_recv_buf(&mut self, max_rx: usize) {
1341        self.recv_buf.resize(max_rx.saturating_add(1).max(1), 0u8);
1342    }
1343
1344    /// Drain every datagram waiting on the listener socket into the manager.
1345    /// Edge-triggered epoll: loop `recv_from` to `WouldBlock`.
1346    fn ingest_client(&mut self, now: Instant) {
1347        // Pull the socket out behind a short borrow then operate on it.
1348        loop {
1349            let result = {
1350                let listener = self.listener.borrow();
1351                let Some(socket) = listener.socket.as_ref() else {
1352                    return;
1353                };
1354                socket.recv_from(&mut self.recv_buf)
1355            };
1356            match result {
1357                Ok((len, src)) => {
1358                    // Mark the in-flight client so `on_send_to_backend` can
1359                    // resolve the owning flow's upstream socket for an
1360                    // already-established flow (no `OpenUpstream` precedes it).
1361                    self.in_flight_client = Some(src);
1362                    self.in_flight_flow = None;
1363                    let len = len.min(self.recv_buf.len());
1364                    // Borrow the payload via a split so the mutable borrow of
1365                    // `self.manager` does not alias `self.recv_buf`.
1366                    let payload: &[u8] = &self.recv_buf[..len];
1367                    // SAFETY-free: `handle_input` only reads the slice; the
1368                    // borrow checker is satisfied because `recv_buf` and
1369                    // `manager` are disjoint fields.
1370                    let mgr = self.manager.clone();
1371                    // The backend set, borrowed for this admission only. The
1372                    // manager selects from it; the shell no longer does
1373                    // (#1340, Question 6).
1374                    let backends = self.backends.clone();
1375                    let mut backends = backends.borrow_mut();
1376                    mgr.borrow_mut().handle_input(
1377                        ManagerInput::ClientDatagram {
1378                            src,
1379                            payload,
1380                            backends: &mut *backends,
1381                        },
1382                        now,
1383                    );
1384                    drop(backends);
1385                    // Drain outputs after each datagram to bound queue growth.
1386                    self.drain_outputs(now);
1387                    self.in_flight_client = None;
1388                }
1389                Err(ref e) if e.kind() == ErrorKind::WouldBlock => break,
1390                Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
1391                Err(e) => {
1392                    debug!(
1393                        "{} recv_from error on UDP listener: {}",
1394                        log_context!(self),
1395                        e
1396                    );
1397                    break;
1398                }
1399            }
1400        }
1401    }
1402
1403    /// Drain datagrams waiting on one flow's connected upstream socket into the
1404    /// manager as `BackendDatagram`s.
1405    fn ingest_upstream(&mut self, upstream_token: Token, now: Instant) {
1406        let Some(&flow) = self.upstream_to_flow.get(&upstream_token) else {
1407            return;
1408        };
1409        loop {
1410            let result = {
1411                let Some(socket) = self.upstream_sockets.get(&upstream_token) else {
1412                    return;
1413                };
1414                socket.recv(&mut self.recv_buf)
1415            };
1416            match result {
1417                Ok(len) => {
1418                    let len = len.min(self.recv_buf.len());
1419                    let payload: &[u8] = &self.recv_buf[..len];
1420                    let mgr = self.manager.clone();
1421                    mgr.borrow_mut()
1422                        .handle_input(ManagerInput::BackendDatagram { flow, payload }, now);
1423                    self.drain_outputs(now);
1424                }
1425                Err(ref e) if e.kind() == ErrorKind::WouldBlock => break,
1426                Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
1427                Err(e) => {
1428                    debug!(
1429                        "{} recv error on upstream socket: {}",
1430                        log_context!(self),
1431                        e
1432                    );
1433                    break;
1434                }
1435            }
1436        }
1437    }
1438
1439    /// Drain the manager's output queue, acting on each `Output`.
1440    fn drain_outputs(&mut self, now: Instant) {
1441        let mgr = self.manager.clone();
1442        loop {
1443            let out = mgr.borrow_mut().poll_output();
1444            let Some(out) = out else { break };
1445            match out {
1446                Output::OpenUpstream { flow, backend } => self.on_open_upstream(flow, backend, now),
1447                Output::SendToBackend(transmit) => self.on_send_to_backend(transmit),
1448                Output::SendToClient(transmit) => self.on_send_to_client(transmit),
1449                Output::ArmTimer(deadline) => self.arm_timer(deadline, now),
1450                Output::Metric(ev) => Self::record_metric(ev),
1451                Output::CloseFlow(flow) => self.on_close_flow(flow),
1452                Output::Drop(reason) => Self::record_drop(reason),
1453            }
1454        }
1455    }
1456
1457    fn on_open_upstream(&mut self, flow: FlowId, backend: SocketAddr, now: Instant) {
1458        let mut socket = match udp_connect(backend) {
1459            Ok(socket) => socket,
1460            Err(e) => {
1461                // EMFILE/ENFILE/connect refusal → shed this flow, never panic.
1462                warn!(
1463                    "{} could not open upstream socket to {}: {}; shedding flow {}",
1464                    log_context!(self),
1465                    backend,
1466                    e,
1467                    flow
1468                );
1469                incr!(names::udp::FLOWS_SHED);
1470                // The manager already moved this flow toward Established and
1471                // counted it; abort it so the `max_flows` slot frees immediately
1472                // (FlowEvicted balances the gauge) instead of squatting until the
1473                // idle timeout. Keep the FLOWS_SHED metric above for the
1474                // EMFILE/refused case. `abort_flow` enqueues FlowEvicted +
1475                // CloseFlow for the surrounding `drain_outputs` loop.
1476                self.manager
1477                    .borrow_mut()
1478                    .abort_flow(flow, now, CloseReason::Aborted);
1479                return;
1480            }
1481        };
1482        // Multi-token pattern, templated on the back-token allocation in
1483        // `TcpSession::connect_to_backend` (`lib/src/tcp.rs`): a fresh slab
1484        // slot under the SAME listener-session Rc, registered READABLE so its
1485        // readiness reaches `Server::ready` → demuxed back to this session by
1486        // `update_readiness`. The flow-table cap (`max_flows`) already bounds
1487        // how many upstream sockets/slots can exist, so the slab cannot grow
1488        // unbounded here.
1489        let upstream_token = {
1490            let mut s = self.sessions.borrow_mut();
1491            // The listener token indexes a slab slot this session does not
1492            // own: `RemoveListener` frees it (`server.rs`'s `slab.try_remove`)
1493            // without this session ever being told. A deactivate does NOT —
1494            // it re-arms the slot with the inert `ListenSession` placeholder
1495            // (`Server::reserve_listen_token`) — so `RemoveListener` is the
1496            // only path that empties the key under us. Indexing a freed key
1497            // panics the whole worker, so read it fallibly and shed the flow
1498            // instead — an upstream socket registered under a slot nothing
1499            // can demux back is unusable anyway.
1500            let Some(listener_session) = s.slab.get(self.listener_token.0).cloned() else {
1501                drop(s);
1502                error!(
1503                    "{} listener token {:?} is no longer in the slab; shedding flow {}",
1504                    log_context!(self),
1505                    self.listener_token,
1506                    flow
1507                );
1508                // No `udp.flows.shed`: that counter documents new flows
1509                // dropped at the `max_flows` cap or under fd pressure
1510                // (doc/configure.md), which this is not. Same choice as the
1511                // registration-failure path just below, which also aborts
1512                // without touching it. Like it, abort the flow the manager
1513                // already counted so its `max_flows` slot frees now
1514                // (FlowEvicted balances the gauge).
1515                self.manager
1516                    .borrow_mut()
1517                    .abort_flow(flow, now, CloseReason::Aborted);
1518                return;
1519            };
1520            let entry = s.slab.vacant_entry();
1521            let token = Token(entry.key());
1522            entry.insert(listener_session);
1523            token
1524        };
1525        if let Err(e) = self
1526            .registry
1527            .register(&mut socket, upstream_token, Interest::READABLE)
1528        {
1529            error!(
1530                "{} could not register upstream socket: {}",
1531                log_context!(self),
1532                e
1533            );
1534            self.sessions.borrow_mut().slab.try_remove(upstream_token.0);
1535            // The flow is Established in the manager but has no usable upstream
1536            // socket: abort it so its `max_flows` slot frees now (FlowEvicted
1537            // balances the gauge) rather than squatting until idle timeout.
1538            self.manager
1539                .borrow_mut()
1540                .abort_flow(flow, now, CloseReason::Aborted);
1541            return;
1542        }
1543        self.upstream_sockets.insert(upstream_token, socket);
1544        self.upstream_to_flow.insert(upstream_token, flow);
1545        self.flow_to_upstream.insert(flow, upstream_token);
1546        // `flow_to_upstream` and `upstream_to_flow` are inverse maps: the token
1547        // in one points back to the flow in the other. A broken inverse would let
1548        // a backend reply demux to the wrong client (a NAT-return mismatch).
1549        debug_assert_eq!(
1550            self.upstream_to_flow.get(&upstream_token),
1551            Some(&flow),
1552            "upstream_to_flow must map the new token back to its flow"
1553        );
1554        debug_assert_eq!(
1555            self.flow_to_upstream.get(&flow),
1556            Some(&upstream_token),
1557            "flow_to_upstream must map the flow back to its upstream token"
1558        );
1559        self.flow_started.insert(flow, Instant::now());
1560        // The client source for this flow is the one currently in flight.
1561        let client = self.in_flight_client.unwrap_or(self.address);
1562        self.flow_endpoints.insert(flow, (client, Some(backend)));
1563        if let Some(src) = self.in_flight_client {
1564            self.client_key_to_flow.insert(src, flow);
1565            // The shadow flow-table only ever holds live flows: the flow we just
1566            // mapped must have a live upstream token (it is the one we just
1567            // opened). Pairs the on-close drop in `on_close_flow`.
1568            debug_assert!(
1569                self.flow_to_upstream.contains_key(&flow),
1570                "client_key_to_flow points at flow {flow} with no live upstream token"
1571            );
1572        }
1573        // This flow's first SendToBackend (if any) follows immediately.
1574        self.in_flight_flow = Some(flow);
1575    }
1576
1577    fn on_send_to_backend(&mut self, transmit: crate::protocol::udp::Transmit) {
1578        // Resolve the owning flow precisely (NOT by `transmit.dst`, which two
1579        // flows to the same backend would alias — sending on the wrong
1580        // connected socket misroutes that backend's reply to the wrong client).
1581        //   * new flow:  `OpenUpstream{flow}` set `in_flight_flow` just before.
1582        //   * established flow: resolve via the in-flight client source through
1583        //     the shell-side `client source -> flow` shadow of the flow table.
1584        let flow = self.in_flight_flow.or_else(|| {
1585            self.in_flight_client
1586                .and_then(|src| self.client_key_to_flow.get(&src).copied())
1587        });
1588        let token = flow.and_then(|f| self.flow_to_upstream.get(&f).copied());
1589        let Some(token) = token else {
1590            // No resolved flow / upstream socket: this is not a queue-full drop.
1591            incr!(names::udp::DROPPED_UNKNOWN_FLOW);
1592            return;
1593        };
1594        let Some(socket) = self.upstream_sockets.get(&token) else {
1595            // Socket already gone (flow closed mid-drain): unknown-flow, not
1596            // queue-full.
1597            incr!(names::udp::DROPPED_UNKNOWN_FLOW);
1598            return;
1599        };
1600        // If a queue is already backed up for this flow, preserve FIFO order —
1601        // do NOT jump the line with the fast-path `send`. Append (or drop on
1602        // overflow) and let the WRITABLE drain catch up.
1603        if let Some(q) = self.upstream_write_queues.get_mut(&token)
1604            && !q.is_empty()
1605        {
1606            if !q.push(transmit.dst, transmit.payload) {
1607                debug!("{} upstream write queue full, dropping", log_context!(self));
1608                incr!(names::udp::DROPPED_WQ_FULL);
1609            }
1610            return;
1611        }
1612        match socket.send(&transmit.payload) {
1613            Ok(_) => {}
1614            Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
1615                // Kernel send buffer full: enqueue (bounded) and arm WRITABLE so
1616                // the next writable event drains it. Drop + metric only at cap.
1617                let q = self
1618                    .upstream_write_queues
1619                    .entry(token)
1620                    .or_insert_with(|| WriteQueue::new(UPSTREAM_WRITE_QUEUE_CAP));
1621                if q.push(transmit.dst, transmit.payload) {
1622                    self.arm_upstream_writable(token);
1623                } else {
1624                    debug!("{} upstream write queue full, dropping", log_context!(self));
1625                    incr!(names::udp::DROPPED_WQ_FULL);
1626                }
1627            }
1628            Err(e) => {
1629                debug!("{} upstream send error: {}", log_context!(self), e);
1630                // Hard send error (e.g. ECONNREFUSED), not a queue-full drop.
1631                incr!(names::udp::DROPPED_SEND_ERROR);
1632            }
1633        }
1634    }
1635
1636    /// Reregister a flow's connected upstream socket for `READABLE | WRITABLE`
1637    /// so a queued forward datagram gets a writable wake (the edge-triggered
1638    /// analog of `signal_pending_write`). Idempotent enough — mio coalesces a
1639    /// repeated interest set.
1640    fn arm_upstream_writable(&mut self, token: Token) {
1641        if let Some(socket) = self.upstream_sockets.get_mut(&token)
1642            && let Err(e) =
1643                self.registry
1644                    .reregister(socket, token, Interest::READABLE | Interest::WRITABLE)
1645        {
1646            debug!(
1647                "{} could not arm WRITABLE on upstream socket: {}",
1648                log_context!(self),
1649                e
1650            );
1651        }
1652    }
1653
1654    /// Drop a flow's upstream socket back to `READABLE`-only once its write queue
1655    /// has fully drained, so an empty socket no longer wakes the loop on every
1656    /// writable edge (a permanently-WRITABLE UDP socket would busy-loop).
1657    fn disarm_upstream_writable(&mut self, token: Token) {
1658        if let Some(socket) = self.upstream_sockets.get_mut(&token)
1659            && let Err(e) = self.registry.reregister(socket, token, Interest::READABLE)
1660        {
1661            debug!(
1662                "{} could not disarm WRITABLE on upstream socket: {}",
1663                log_context!(self),
1664                e
1665            );
1666        }
1667    }
1668
1669    /// Drain a flow's upstream write queue on a writable event. Re-sends in FIFO
1670    /// order until `WouldBlock` or empty; on empty, drops WRITABLE interest.
1671    fn drain_upstream_queue(&mut self, token: Token) {
1672        let Some(mut queue) = self.upstream_write_queues.remove(&token) else {
1673            return;
1674        };
1675        let socket = self.upstream_sockets.get(&token);
1676        let Some(socket) = socket else {
1677            // Socket gone (flow closed mid-drain): discard the queue.
1678            return;
1679        };
1680        let emptied = queue.drain(|_dst, payload| match socket.send(payload) {
1681            Ok(_) => SendOutcome::Sent,
1682            Err(ref e) if e.kind() == ErrorKind::WouldBlock => SendOutcome::WouldBlock,
1683            Err(_) => SendOutcome::Dropped,
1684        });
1685        if emptied {
1686            self.disarm_upstream_writable(token);
1687        } else {
1688            // Still backed up: put the queue back and keep WRITABLE armed.
1689            self.upstream_write_queues.insert(token, queue);
1690        }
1691    }
1692
1693    fn on_send_to_client(&mut self, transmit: crate::protocol::udp::Transmit) {
1694        // Preserve FIFO: if the client-return queue is already backed up, append
1695        // (or drop on overflow) rather than jumping the line via the fast path.
1696        if !self.client_write_queue.is_empty() {
1697            if !self.client_write_queue.push(transmit.dst, transmit.payload) {
1698                debug!("{} client write queue full, dropping", log_context!(self));
1699                incr!(names::udp::DROPPED_WQ_FULL);
1700            }
1701            return;
1702        }
1703        let send_result = {
1704            let listener = self.listener.borrow();
1705            let Some(socket) = listener.socket.as_ref() else {
1706                return;
1707            };
1708            socket.send_to(&transmit.payload, transmit.dst)
1709        };
1710        match send_result {
1711            Ok(_) => {}
1712            Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
1713                // Listener send buffer full: enqueue (bounded) + arm WRITABLE on
1714                // the listener so the next writable event drains it.
1715                if self.client_write_queue.push(transmit.dst, transmit.payload) {
1716                    self.arm_client_writable();
1717                } else {
1718                    debug!("{} client write queue full, dropping", log_context!(self));
1719                    incr!(names::udp::DROPPED_WQ_FULL);
1720                }
1721            }
1722            Err(e) => {
1723                debug!("{} client send_to error: {}", log_context!(self), e);
1724                // Hard send_to error, not a queue-full drop.
1725                incr!(names::udp::DROPPED_SEND_ERROR);
1726            }
1727        }
1728    }
1729
1730    /// Reregister the listener socket for `READABLE | WRITABLE` so a queued
1731    /// client-return datagram gets a writable wake. The listener token routes
1732    /// the writable event back into `update_readiness`.
1733    fn arm_client_writable(&mut self) {
1734        let listener = self.listener.borrow();
1735        let fd = match listener.socket.as_ref() {
1736            Some(socket) => socket.as_raw_fd(),
1737            None => return,
1738        };
1739        if let Err(e) = self.registry.reregister(
1740            &mut SourceFd(&fd),
1741            self.listener_token,
1742            Interest::READABLE | Interest::WRITABLE,
1743        ) {
1744            debug!(
1745                "{} could not arm WRITABLE on listener socket: {}",
1746                log_context!(self),
1747                e
1748            );
1749        }
1750    }
1751
1752    /// Drop the listener socket back to `READABLE`-only once the client-return
1753    /// queue is empty (a permanently-WRITABLE listener would busy-loop).
1754    fn disarm_client_writable(&mut self) {
1755        let listener = self.listener.borrow();
1756        let fd = match listener.socket.as_ref() {
1757            Some(socket) => socket.as_raw_fd(),
1758            None => return,
1759        };
1760        if let Err(e) =
1761            self.registry
1762                .reregister(&mut SourceFd(&fd), self.listener_token, Interest::READABLE)
1763        {
1764            debug!(
1765                "{} could not disarm WRITABLE on listener socket: {}",
1766                log_context!(self),
1767                e
1768            );
1769        }
1770    }
1771
1772    /// Drain the client-return write queue on a listener writable event. Re-sends
1773    /// in FIFO order until `WouldBlock` or empty; on empty, drops WRITABLE.
1774    fn drain_client_queue(&mut self) {
1775        let mut queue = std::mem::replace(&mut self.client_write_queue, WriteQueue::new(0));
1776        let emptied = {
1777            let listener = self.listener.borrow();
1778            let Some(socket) = listener.socket.as_ref() else {
1779                // Listener socket gone: discard the queue and restore an empty one.
1780                self.client_write_queue = WriteQueue::new(CLIENT_WRITE_QUEUE_CAP);
1781                return;
1782            };
1783            queue.drain(|dst, payload| match socket.send_to(payload, *dst) {
1784                Ok(_) => SendOutcome::Sent,
1785                Err(ref e) if e.kind() == ErrorKind::WouldBlock => SendOutcome::WouldBlock,
1786                Err(_) => SendOutcome::Dropped,
1787            })
1788        };
1789        // Restore the (possibly still-backed-up) queue; preserve its capacity.
1790        queue.cap = CLIENT_WRITE_QUEUE_CAP;
1791        self.client_write_queue = queue;
1792        if emptied {
1793            self.disarm_client_writable();
1794        }
1795    }
1796
1797    /// The body of [`ProxySession::timeout`], evaluated at `now` instead of
1798    /// the current instant, so a test can deliver a wheel expiry at an exact
1799    /// point of a simulated clock.
1800    fn timeout_at(&mut self, token: Token, now: Instant) -> SessionIsToBeClosed {
1801        if token == self.listener_token {
1802            // The wheel entry that fired is CONSUMED: `Timer::poll` removed it
1803            // from the timer's slab. Say so, so the field is true on its own
1804            // terms rather than by an argument made in `timer.rs` — a handle
1805            // left here is inert (`cancel_timeout`'s tick guard can never match
1806            // a delivered entry) but claims an arming that does not exist.
1807            self.timer_handle = None;
1808            self.manager.borrow_mut().handle_timeout(now);
1809            // Re-arm: `handle_timeout` cleared the manager's `armed_deadline`
1810            // for the same reason, so `reschedule` emits a fresh ArmTimer
1811            // whenever a flow is still scheduled — including when this expiry
1812            // found nothing due, which is the common case for an entry the
1813            // wheel delivered early (`crate::timer` rounds to the nearest
1814            // tick). `drain_outputs` applies it. Never close the listener on a
1815            // flow timeout.
1816            self.drain_outputs(now);
1817        }
1818        false
1819    }
1820
1821    /// Map the single manager-wide `ArmTimer(deadline)` onto the thread-local
1822    /// `TIMER`, keyed by the **listener token**: when it fires, the run loop
1823    /// calls `Server::timeout(listener_token)` → `session.timeout(token)` →
1824    /// `manager.handle_timeout(now)`.
1825    fn arm_timer(&mut self, deadline: Instant, now: Instant) {
1826        let delay = deadline.saturating_duration_since(now);
1827        TIMER.with(|timer| {
1828            let mut timer = timer.borrow_mut();
1829            // Cancel the previous deadline so re-arming does not leak timer-slab
1830            // entries. This is the re-arm-while-still-armed path (a datagram
1831            // pushed the deadline back); on the expiry path `timeout` has
1832            // already cleared the handle, because the entry it named is gone.
1833            if let Some(old) = self.timer_handle.take() {
1834                let _ = timer.cancel_timeout(&old);
1835            }
1836            // Counted from `now`, the instant `deadline` was computed against,
1837            // not from a fresh clock read: the entry then lands on the grid
1838            // point `deadline` itself maps to, however late this drain runs.
1839            self.timer_handle = Some(timer.set_timeout_from(now, delay, self.listener_token));
1840        });
1841    }
1842
1843    fn on_close_flow(&mut self, flow: FlowId) {
1844        if let Some(token) = self.flow_to_upstream.remove(&flow) {
1845            if let Some(mut socket) = self.upstream_sockets.remove(&token)
1846                && let Err(e) = self.registry.deregister(&mut socket)
1847            {
1848                debug!("{} deregister upstream on close: {}", log_context!(self), e);
1849            }
1850            // Drop any queued (un-drained) forward datagrams with the socket so
1851            // the per-flow queue cannot leak; gauge correctness is preserved
1852            // because the queue holds bytes, not flow-count state.
1853            self.upstream_write_queues.remove(&token);
1854            self.upstream_to_flow.remove(&token);
1855            self.sessions.borrow_mut().slab.try_remove(token.0);
1856            // On close, all per-flow maps drop the flow together: neither
1857            // direction of the upstream-token map may still reference it.
1858            debug_assert!(
1859                !self.upstream_to_flow.values().any(|&f| f == flow),
1860                "on_close_flow left upstream_to_flow referencing closed flow {flow}"
1861            );
1862            debug_assert!(
1863                !self.flow_to_upstream.contains_key(&flow),
1864                "on_close_flow left flow_to_upstream entry for closed flow {flow}"
1865            );
1866        }
1867        if let Some(started) = self.flow_started.remove(&flow) {
1868            let duration = started.elapsed();
1869            time!(names::udp::FLOW_DURATION, duration.as_millis());
1870        }
1871        let (client, backend) = self
1872            .flow_endpoints
1873            .remove(&flow)
1874            .unwrap_or((self.address, None));
1875        // Drop the shadow flow-table entry if it still points at this flow.
1876        if self.client_key_to_flow.get(&client) == Some(&flow) {
1877            self.client_key_to_flow.remove(&client);
1878        }
1879        // The shadow flow-table must no longer map THIS flow id. A surviving
1880        // entry would misroute a later established-flow `SendToBackend` onto a
1881        // freed upstream token.
1882        debug_assert!(
1883            !self.client_key_to_flow.values().any(|&f| f == flow),
1884            "on_close_flow left client_key_to_flow referencing closed flow {flow}"
1885        );
1886        info!("{} flow closed", log_flow_context!(flow, client, backend));
1887    }
1888
1889    fn record_metric(ev: MetricEvent) {
1890        match ev {
1891            MetricEvent::FlowCreated => {
1892                incr!(names::udp::FLOWS_CREATED);
1893                gauge_add!(names::udp::ACTIVE_FLOWS, 1);
1894            }
1895            MetricEvent::FlowEvicted => {
1896                incr!(names::udp::FLOWS_EVICTED);
1897                gauge_add!(names::udp::ACTIVE_FLOWS, -1);
1898            }
1899            MetricEvent::FlowShed => {
1900                incr!(names::udp::FLOWS_SHED);
1901            }
1902            MetricEvent::FlowShedSourceLimit => {
1903                incr!(names::udp::FLOWS_SHED);
1904                incr!(names::udp::FLOWS_SHED_SOURCE_LIMIT);
1905            }
1906            MetricEvent::DatagramIn(bytes) => {
1907                incr!(names::udp::DATAGRAMS_IN);
1908                count!(names::udp::BYTES_IN, bytes as i64);
1909            }
1910            MetricEvent::DatagramOut(bytes) => {
1911                incr!(names::udp::DATAGRAMS_OUT);
1912                count!(names::udp::BYTES_OUT, bytes as i64);
1913            }
1914            MetricEvent::DatagramDropped(reason) => Self::record_drop(reason),
1915        }
1916    }
1917
1918    fn record_drop(reason: DropReason) {
1919        incr!(names::udp::DATAGRAMS_DROPPED);
1920        match reason {
1921            DropReason::Invalid => incr!(names::udp::DROPPED_INVALID),
1922            DropReason::Truncated => incr!(names::udp::DROPPED_TRUNCATED),
1923            DropReason::NoBackend => incr!(names::udp::DROPPED_NO_BACKEND),
1924            DropReason::Shed => incr!(names::udp::DROPPED_SHED),
1925            DropReason::UnknownFlow => incr!(names::udp::DROPPED_UNKNOWN_FLOW),
1926        }
1927    }
1928
1929    /// Tear down every active flow on this listener **through the manager**, so
1930    /// each close emits `FlowEvicted` + `CloseFlow` exactly once and the shell's
1931    /// normal `on_close_flow` handler frees the upstream
1932    /// socket + slab slot and decrements `udp.active_flows`. Used on soft/hard
1933    /// stop, listener remove, and listener deactivate so the worker reaches its
1934    /// `base_sessions_count` and exits promptly instead of waiting out every
1935    /// flow's idle timeout — and so the active-flows gauge does not leak by N
1936    /// (the bug the old direct-teardown path had: it cleared the shell maps
1937    /// without telling the manager, so `FlowEvicted` never fired).
1938    ///
1939    /// On a deactivate where the manager is *retained* (not dropped), this also
1940    /// resets the manager's flow table to empty, keeping manager and shell
1941    /// consistent. The listener socket and the listener-session slab slot are
1942    /// left intact (the listener slot is part of `base_sessions_count` and
1943    /// reclaimed when the worker exits); only the connectionless per-flow slots
1944    /// — which are NOT counted in `nb_connections` and so removed without
1945    /// `decr` — are freed.
1946    pub fn close_all_flows(&mut self, now: Instant) {
1947        // Drive teardown through the manager: it emits one `FlowEvicted` +
1948        // `CloseFlow` per live flow into its output queue. Draining those runs
1949        // `record_metric(FlowEvicted)` (the single gauge decrement) and
1950        // `on_close_flow` (frees socket + slab slot + shell maps) per flow.
1951        self.manager.borrow_mut().close_all(now);
1952        self.drain_outputs(now);
1953        // After `close_all`, the manager's flow table is empty and it armed no
1954        // new timer (an empty table has no deadline, so `reschedule` emits
1955        // nothing). Cancel any residual shell timer so it can't fire against a
1956        // now-flowless listener.
1957        if let Some(handle) = self.timer_handle.take() {
1958            TIMER.with(|timer| {
1959                let _ = timer.borrow_mut().cancel_timeout(&handle);
1960            });
1961        }
1962    }
1963}
1964
1965impl ProxySession for UdpListenerSession {
1966    fn protocol(&self) -> Protocol {
1967        Protocol::UDPListen
1968    }
1969
1970    fn update_readiness(&mut self, token: Token, events: Ready) {
1971        // WRITABLE first: a previously-`WouldBlock` socket can now accept queued
1972        // egress. Drain before reading so the kernel send buffer has room before
1973        // this pass enqueues more (and so a writable-only event still drains).
1974        if events.is_writable() {
1975            if token == self.listener_token {
1976                self.drain_client_queue();
1977            } else if self.upstream_to_flow.contains_key(&token) {
1978                self.drain_upstream_queue(token);
1979            }
1980        }
1981        if !events.is_readable() {
1982            return;
1983        }
1984        let now = Instant::now();
1985        if token == self.listener_token {
1986            self.ingest_client(now);
1987        } else if self.upstream_to_flow.contains_key(&token) {
1988            self.ingest_upstream(token, now);
1989        }
1990    }
1991
1992    fn ready(&mut self, _session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
1993        // All work happens in `update_readiness` (it has the firing token; the
1994        // generic `ready()` does not). Never close the listener session here —
1995        // it lives for the listener's lifetime.
1996        false
1997    }
1998
1999    fn timeout(&mut self, token: Token) -> SessionIsToBeClosed {
2000        self.timeout_at(token, Instant::now())
2001    }
2002
2003    fn close(&mut self) {
2004        // Tear down any still-live flows THROUGH the manager (FlowEvicted +
2005        // CloseFlow per flow) so `udp.active_flows` balances to zero even if the
2006        // server reaps this listener session directly without a prior
2007        // proxy-driven `close_all_flows`. In the common path
2008        // (`close_all_flows` already ran) the manager has no live flows, so this
2009        // is a cheap no-op. Also cancels the residual idle timer.
2010        self.close_all_flows(Instant::now());
2011        // Any leftover egress queues / shell maps are dropped here (the per-flow
2012        // ones were already freed by the manager-driven close above; this only
2013        // resets the client-return queue, which is not flow-state).
2014        self.upstream_write_queues.clear();
2015        self.client_write_queue = WriteQueue::new(CLIENT_WRITE_QUEUE_CAP);
2016        self.upstream_to_flow.clear();
2017        self.flow_to_upstream.clear();
2018        // Deregister + drop the listener socket. Never shutdown(Both) — UDP has
2019        // no connection to shut down; just deregister + drop the fds.
2020        let mut listener = self.listener.borrow_mut();
2021        if let Some(socket) = listener.socket.as_ref() {
2022            let fd = socket.as_raw_fd();
2023            let _ = self.registry.deregister(&mut SourceFd(&fd));
2024        }
2025        listener.active = false;
2026    }
2027
2028    fn last_event(&self) -> Instant {
2029        // The listener session lives for the listener's lifetime — it is never a
2030        // zombie even when idle. Mirror `ListenSession::last_event` (which
2031        // returns `now`) so the `zombie_check` (which has no listener-protocol
2032        // exclusion) never reaps a quiet UDP listener. Per-flow idle reaping is
2033        // handled by the manager's timer wheel, not the zombie sweep.
2034        Instant::now()
2035    }
2036
2037    fn print_session(&self) {
2038        error!(
2039            "{} UDP listener session: {} active flows, {} upstream sockets",
2040            log_context!(self),
2041            self.manager.borrow().flow_count(),
2042            self.upstream_sockets.len(),
2043        );
2044    }
2045
2046    fn frontend_token(&self) -> Token {
2047        self.listener_token
2048    }
2049
2050    fn shutting_down(&mut self) -> SessionIsToBeClosed {
2051        // The listener session is a *listener*, not a connection: it was never
2052        // counted by `SessionManager::incr` (only accepted sessions are), so it
2053        // MUST NOT be routed through the connection-counted
2054        // `shut_down_sessions_by_frontend_tokens` path — that calls `decr`,
2055        // which underflows `nb_connections` and panics (`assert!(nb != 0)` at
2056        // `server.rs`). This mirrors `ListenSession::shutting_down` (which also
2057        // returns `false`). Soft-stop draining is owned by
2058        // `UdpProxy::notify(SoftStop)`: it flips every manager to `Drain` and
2059        // deregisters the listener sockets, so existing flows reach teardown and
2060        // no new flow is admitted. The slot is reclaimed when the worker exits.
2061        false
2062    }
2063
2064    fn cluster_id(&self) -> Option<ClusterId> {
2065        self.listener.borrow().cluster_id.clone()
2066    }
2067}
2068
2069#[allow(unused_imports)]
2070pub(crate) use {log_context, log_flow_context, log_module_context};
2071
2072#[cfg(test)]
2073mod tests {
2074    use super::*;
2075
2076    use std::cell::Cell;
2077
2078    #[test]
2079    fn effective_max_flows_explicit_value_is_used() {
2080        // An explicit value is honoured verbatim, ignoring the slab headroom
2081        // clamp (the operator opted in).
2082        assert_eq!(effective_max_flows(42, 0), 42);
2083        assert_eq!(effective_max_flows(42, 10), 42);
2084    }
2085
2086    #[test]
2087    fn effective_max_flows_auto_is_positive() {
2088        // 0 = auto: derives ~70% of RLIMIT_NOFILE, always >= 1.
2089        assert!(effective_max_flows(0, 0) >= 1);
2090    }
2091
2092    #[test]
2093    fn effective_max_flows_auto_is_clamped_to_slab_headroom() {
2094        // Auto derivation is capped at the slab headroom (max_connections) so a
2095        // UDP listener can't inflate the shared slab. A headroom of 0 disables
2096        // the clamp.
2097        assert!(effective_max_flows(0, 4) <= 4);
2098        assert!(effective_max_flows(0, 4) >= 1);
2099    }
2100
2101    #[test]
2102    fn clamp_max_rx_respects_buffer_size() {
2103        // The configured rx size is clamped to buffer_size; an unset (0)
2104        // buffer_size leaves it untouched.
2105        assert_eq!(clamp_max_rx(u32::MAX as usize, 16_384), 16_384);
2106        assert_eq!(clamp_max_rx(1_024, 16_384), 1_024);
2107        assert_eq!(clamp_max_rx(u32::MAX as usize, 0), u32::MAX as usize);
2108    }
2109
2110    fn addr(port: u16) -> SocketAddr {
2111        SocketAddr::from(([127, 0, 0, 1], port))
2112    }
2113
2114    /// A fake socket whose `send` outcome is scripted per call. Lets the
2115    /// `WriteQueue` state machine be exercised with zero real I/O: we feed a
2116    /// sequence of `SendOutcome`s and record what was sent in order.
2117    struct FakeSocket {
2118        /// Outcomes returned by successive `send` calls (front = next).
2119        script: RefCell<VecDeque<SendOutcome>>,
2120        /// Payloads that `Sent`/`Dropped` actually consumed, in order.
2121        consumed: RefCell<Vec<Vec<u8>>>,
2122        /// Default outcome once the script is exhausted.
2123        default: Cell<bool>, // true = Sent, false = WouldBlock
2124    }
2125
2126    impl FakeSocket {
2127        fn new(script: Vec<SendOutcome>, default_sent: bool) -> Self {
2128            FakeSocket {
2129                script: RefCell::new(script.into()),
2130                consumed: RefCell::new(Vec::new()),
2131                default: Cell::new(default_sent),
2132            }
2133        }
2134
2135        fn send(&self, payload: &[u8]) -> SendOutcome {
2136            let outcome = self.script.borrow_mut().pop_front().unwrap_or({
2137                if self.default.get() {
2138                    SendOutcome::Sent
2139                } else {
2140                    SendOutcome::WouldBlock
2141                }
2142            });
2143            if matches!(outcome, SendOutcome::Sent | SendOutcome::Dropped) {
2144                self.consumed.borrow_mut().push(payload.to_vec());
2145            }
2146            outcome
2147        }
2148    }
2149
2150    #[test]
2151    fn write_queue_push_until_full_then_drops() {
2152        let mut q = WriteQueue::new(2);
2153        assert!(q.is_empty());
2154        assert!(q.push(addr(1), vec![1]));
2155        assert!(q.push(addr(2), vec![2]));
2156        assert_eq!(q.len(), 2);
2157        // At capacity: the third push is rejected (caller drops + counts).
2158        assert!(!q.push(addr(3), vec![3]));
2159        assert_eq!(q.len(), 2);
2160    }
2161
2162    #[test]
2163    fn write_queue_drains_in_fifo_order_on_writable() {
2164        let mut q = WriteQueue::new(8);
2165        for i in 0..4u8 {
2166            assert!(q.push(addr(i as u16), vec![i]));
2167        }
2168        // All sends succeed: drain empties the queue, FIFO preserved.
2169        let sock = FakeSocket::new(vec![], true);
2170        let emptied = q.drain(|_dst, payload| sock.send(payload));
2171        assert!(emptied);
2172        assert!(q.is_empty());
2173        assert_eq!(
2174            *sock.consumed.borrow(),
2175            vec![vec![0u8], vec![1u8], vec![2u8], vec![3u8]]
2176        );
2177    }
2178
2179    #[test]
2180    fn write_queue_stops_on_wouldblock_and_resumes() {
2181        let mut q = WriteQueue::new(8);
2182        for i in 0..3u8 {
2183            assert!(q.push(addr(i as u16), vec![i]));
2184        }
2185        // First send ok, then WouldBlock: drain sends one and stops, leaving two.
2186        let sock = FakeSocket::new(vec![SendOutcome::Sent, SendOutcome::WouldBlock], false);
2187        let emptied = q.drain(|_dst, payload| sock.send(payload));
2188        assert!(!emptied);
2189        assert_eq!(q.len(), 2);
2190        assert_eq!(*sock.consumed.borrow(), vec![vec![0u8]]);
2191        // Front is still the second datagram (FIFO preserved across the stall).
2192        assert_eq!(q.queue.front().unwrap().1, vec![1u8]);
2193        // Second writable event: now everything goes through.
2194        let sock2 = FakeSocket::new(vec![], true);
2195        let emptied2 = q.drain(|_dst, payload| sock2.send(payload));
2196        assert!(emptied2);
2197        assert!(q.is_empty());
2198        assert_eq!(*sock2.consumed.borrow(), vec![vec![1u8], vec![2u8]]);
2199    }
2200
2201    #[test]
2202    fn write_queue_hard_error_drops_one_and_continues() {
2203        let mut q = WriteQueue::new(8);
2204        for i in 0..3u8 {
2205            assert!(q.push(addr(i as u16), vec![i]));
2206        }
2207        // Middle datagram hard-errors: it is popped + skipped, the rest proceed.
2208        let sock = FakeSocket::new(
2209            vec![SendOutcome::Sent, SendOutcome::Dropped, SendOutcome::Sent],
2210            true,
2211        );
2212        let emptied = q.drain(|_dst, payload| sock.send(payload));
2213        assert!(emptied);
2214        assert!(q.is_empty());
2215        // The dropped datagram (1) was consumed-for-accounting but the queue is
2216        // empty and the surviving datagrams (0, 2) went out in order.
2217        assert_eq!(
2218            *sock.consumed.borrow(),
2219            vec![vec![0u8], vec![1u8], vec![2u8]]
2220        );
2221    }
2222
2223    #[test]
2224    fn write_queue_empties_cleanly_when_already_empty() {
2225        let mut q = WriteQueue::new(4);
2226        let sock = FakeSocket::new(vec![], true);
2227        // Draining an empty queue is a no-op that reports emptied = true.
2228        let emptied = q.drain(|_dst, payload| sock.send(payload));
2229        assert!(emptied);
2230        assert!(q.is_empty());
2231        assert!(sock.consumed.borrow().is_empty());
2232    }
2233
2234    /// `on_open_upstream` clones the listener session out of the slab to
2235    /// register the new flow's upstream socket under a second token (the
2236    /// multi-token pattern). That slot belongs to the SERVER, not to this
2237    /// session: `RemoveListener` frees it with `slab.try_remove` and tells the
2238    /// session nothing (a deactivate does not — it re-arms the slot with the
2239    /// inert placeholder, see `Server::reserve_listen_token`). Indexing a freed
2240    /// slab key panics, and this one runs on the datagram path of a listener
2241    /// the operator just touched.
2242    ///
2243    /// No datagram sequence reaches the pairing on its own -- `Server::ready`
2244    /// will not dispatch to a session whose slab entry is gone, so the flow
2245    /// that would call this never starts -- which is exactly why the slot is
2246    /// read fallibly rather than trusted: nothing else re-validates
2247    /// `listener_token` for the whole life of the session. The state is
2248    /// synthesized here directly.
2249    ///
2250    /// The lost wakeup end to end: a real `TIMER` wheel, a real
2251    /// `UdpListenerSession`, a real `UdpManager` — and an idle flow that must
2252    /// actually be evicted.
2253    ///
2254    /// `crate::timer` rounds a delay to the NEAREST tick, so the listener's
2255    /// single wheel entry is delivered up to 50 ms BEFORE the flow's idle
2256    /// deadline when the poll lands on the tick grid
2257    /// (`test_timeout_fires_up_to_half_a_tick_early`, `lib/src/timer.rs`) and
2258    /// up to 99 ms off-grid (see `duration_to_tick`), and the delivery
2259    /// CONSUMES the entry. Any earliness at all is enough for what follows.
2260    /// `UdpListenerSession::timeout` then runs `handle_timeout` at a `now`
2261    /// short of the deadline: nothing is due, nothing closes. Unless the
2262    /// manager re-arms on a fire it did not act on, no wheel entry exists any
2263    /// more and this flow is never reaped — it holds its `max_flows` slot, its
2264    /// upstream socket, its slab slot and its `udp.active_flows` count until
2265    /// some *other* flow's deadline happens to move the manager's minimum.
2266    ///
2267    /// The early fire is forced, not hoped for. The wheel sits on a fixed
2268    /// 100 ms grid anchored at the thread-local `TIMER`'s private `start`, so
2269    /// the test first parks a throwaway entry to read one grid point out of the
2270    /// wheel, then places the flow's deadline 30 ms past a later grid point.
2271    /// 30 ms rounds down, so the entry is delivered at that grid point — 30 ms
2272    /// early — and the assertions below check that against the wheel rather
2273    /// than assuming it.
2274    ///
2275    /// The clock is simulated, not the wheel. The real `TIMER`, with its real
2276    /// nearest-tick rounding, is armed with `Timer::set_timeout_from` and
2277    /// polled with `Timer::poll_at`, and the session handles each expiry
2278    /// through `timeout_at` — all at instants the test derives from one
2279    /// `Instant::now()` read. The defect lives entirely in `duration_to_tick`'s
2280    /// arithmetic on those instants, so nothing is lost, and no scheduling
2281    /// delay can move an arming onto the next grid point or push the early
2282    /// fire past the deadline (issue #1619).
2283    ///
2284    /// To SEE THIS RED: remove `self.armed_deadline = None;` from the top of
2285    /// `UdpManager::handle_timeout` (`lib/src/protocol/udp/manager.rs`). The
2286    /// `wheel re-armed after the early fire` assertion fails first, reporting a
2287    /// `next wheel date` of `tv_sec: 18446744073760881` — the `TICK_MAX`
2288    /// sentinel every slot carries when the wheel holds nothing — and with that
2289    /// assertion removed the final one fails with `the idle flow was never
2290    /// evicted, left: 1  right: 0`.
2291    ///
2292    /// The other half of the rule, `self.timer_handle = None;` below, is NOT
2293    /// covered by this test and no test here forces it red — nor could one:
2294    /// a delivered handle is already inert, since `cancel_timeout`'s tick guard
2295    /// can never match the successor in its slab slot
2296    /// (`test_a_delivered_timeout_handle_cannot_cancel_its_slot_successor`,
2297    /// `lib/src/timer.rs`, pins that). Clearing it is hygiene — it keeps the
2298    /// field's meaning local instead of load-bearing on `timer.rs` internals —
2299    /// not a fix for a reachable defect.
2300    #[test]
2301    fn an_early_wheel_fire_still_evicts_the_idle_flow() {
2302        use crate::backends::Backend;
2303        use crate::testing::{ServerParts, prebuild_server, provide_port};
2304
2305        const CLUSTER: &str = "udp-idle-eviction";
2306
2307        let ServerParts {
2308            registry,
2309            sessions,
2310            pool,
2311            backends,
2312            ..
2313        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
2314
2315        // A reachable backend so the flow is admitted and established rather
2316        // than dropped by the core's no-backend path (`DropReason::NoBackend`).
2317        backends.borrow_mut().add_backend(
2318            CLUSTER,
2319            Backend::new(
2320                "backend-1",
2321                SocketAddr::from(([127, 0, 0, 1], provide_port())),
2322                None,
2323                None,
2324                None,
2325            ),
2326        );
2327
2328        let address =
2329            sozu_command::proto::command::SocketAddress::new_v4(127, 0, 0, 1, provide_port());
2330        let config = sozu_command::config::ListenerBuilder::new_udp(address)
2331            .to_udp(None)
2332            .expect("could not build a UdpListenerConfig for the test");
2333
2334        let listener_token = {
2335            let mut session_manager = sessions.borrow_mut();
2336            let entry = session_manager.slab.vacant_entry();
2337            let token = Token(entry.key());
2338            entry.insert(Rc::new(RefCell::new(crate::server::ListenSession {
2339                protocol: Protocol::UDPListen,
2340            })));
2341            token
2342        };
2343
2344        let mut proxy = UdpProxy::new(registry, sessions.clone(), pool, backends, 16, 16384);
2345        proxy
2346            .add_listener(config, listener_token)
2347            .expect("could not add the test UDP listener");
2348        proxy
2349            .activate_listener(&address.into(), None)
2350            .expect("could not activate the test UDP listener");
2351        let session = proxy
2352            .build_session(listener_token)
2353            .expect("could not build the test UDP listener session");
2354
2355        // A ruler for the wheel: park a throwaway entry, read the absolute
2356        // instant the wheel would fire it, and leave it there to be consumed
2357        // below (cancelling it would leave a stale `next_tick` behind).
2358        //
2359        // `now` is the simulated clock's origin, and the only clock read that
2360        // steers this test. It is taken inside `TIMER.with`, after the thread-local
2361        // wheel exists, so its private `start` can never lie after it.
2362        let probe_token = Token(usize::MAX);
2363        let (now, grid) = TIMER.with(|timer| {
2364            let mut timer = timer.borrow_mut();
2365            let now = Instant::now();
2366            timer.set_timeout_from(now, Duration::ZERO, probe_token);
2367            let grid = timer
2368                .next_poll_date()
2369                .expect("the probe must put a date on the wheel");
2370            (now, grid)
2371        });
2372
2373        // The grid point the flow's entry will be delivered at: strictly after
2374        // the probe's (so they land in different wheel slots) and far enough
2375        // ahead that the flow is armed before it.
2376        let mut fires_at = grid + Duration::from_millis(100);
2377        while fires_at < now + Duration::from_millis(60) {
2378            fires_at += Duration::from_millis(100);
2379        }
2380        // 30 ms past a grid point rounds DOWN to it: delivered 30 ms early.
2381        let idle_timeout = (fires_at - now) + Duration::from_millis(30);
2382
2383        {
2384            let session = session.borrow_mut();
2385            let mut manager = session.manager.borrow_mut();
2386            manager.handle_input(
2387                ManagerInput::Config(ConfigEvent::SetCluster(ClusterConfig {
2388                    cluster: CLUSTER.into(),
2389                    front_timeout: idle_timeout,
2390                    back_timeout: idle_timeout,
2391                    ..Default::default()
2392                })),
2393                now,
2394            );
2395            let mut backends = session.backends.borrow_mut();
2396            manager.handle_input(
2397                ManagerInput::ClientDatagram {
2398                    src: SocketAddr::from(([127, 0, 0, 1], 41000)),
2399                    payload: b"query",
2400                    backends: &mut *backends,
2401                },
2402                now,
2403            );
2404        }
2405        // The real shell: this is what arms the wheel, via `Output::ArmTimer`.
2406        session.borrow_mut().drain_outputs(now);
2407        assert_eq!(
2408            session.borrow().manager.borrow().flow_count(),
2409            1,
2410            "the datagram must admit exactly one flow"
2411        );
2412
2413        // Consume the ruler. Its slot is reset by the poll, so the wheel is
2414        // clean and `next_poll_date` now reports the flow's entry alone.
2415        assert_eq!(
2416            TIMER.with(|timer| timer.borrow_mut().poll_at(grid)),
2417            Some(probe_token),
2418            "the probe entry must be delivered first"
2419        );
2420
2421        // The early fire, measured against the real wheel rather than assumed.
2422        let wheel_at = TIMER
2423            .with(|timer| timer.borrow().next_poll_date())
2424            .expect("the listener's wheel entry must be armed");
2425        let deadline = session
2426            .borrow()
2427            .manager
2428            .borrow()
2429            .poll_timeout()
2430            .expect("the manager must have armed an idle deadline");
2431        assert!(
2432            wheel_at < deadline,
2433            "precondition: the wheel must fire EARLY — wheel {wheel_at:?} < deadline \
2434             {deadline:?} (grid {grid:?}, expected fire {fires_at:?})"
2435        );
2436
2437        // The early fire itself, exactly as `Server`'s run loop drives it, at
2438        // the wheel's own date.
2439        let fire = wheel_at;
2440        assert_eq!(
2441            TIMER.with(|timer| timer.borrow_mut().poll_at(fire)),
2442            Some(listener_token),
2443            "the listener's wheel entry must be delivered at the wheel's date"
2444        );
2445        assert!(
2446            fire < deadline,
2447            "the fire must land before the flow's deadline for this test to mean anything"
2448        );
2449        session.borrow_mut().timeout_at(listener_token, fire);
2450
2451        // Nothing was due, so nothing closed — and the wheel entry is gone.
2452        assert_eq!(
2453            session.borrow().manager.borrow().flow_count(),
2454            1,
2455            "nothing was due at the early fire: the flow must survive it"
2456        );
2457        // The wheel must hold a real, imminent entry again. `next_poll_date`
2458        // mins over every slot and an empty wheel's slots all read `TICK_MAX`,
2459        // so it is `Some` even with nothing armed — the bound is what makes this
2460        // an assertion. The re-armed entry lands one tick past the deadline: the
2461        // manager asks for `deadline`, which is 30 ms past a grid point the wheel
2462        // has already passed, so `set_timeout_at` pushes it to the next one.
2463        let rearmed = TIMER
2464            .with(|timer| timer.borrow().next_poll_date())
2465            .expect("next_poll_date always reports a date");
2466        assert!(
2467            rearmed <= deadline + Duration::from_millis(100),
2468            "wheel re-armed after the early fire: an expiry the manager did not \
2469             act on still consumed the entry, so it owes a fresh ArmTimer \
2470             (next wheel date {rearmed:?}, deadline {deadline:?})"
2471        );
2472
2473        // Past the deadline AND past the grid point the re-arm rounded up to,
2474        // the run loop drains the wheel again.
2475        let later = deadline + Duration::from_millis(150);
2476        while let Some(token) = TIMER.with(|timer| timer.borrow_mut().poll_at(later)) {
2477            session.borrow_mut().timeout_at(token, later);
2478        }
2479        assert_eq!(
2480            session.borrow().manager.borrow().flow_count(),
2481            0,
2482            "an early fire must not lose the wakeup: the idle flow was never evicted"
2483        );
2484    }
2485
2486    /// To SEE THIS RED: restore `s.slab[self.listener_token.0].clone()` in
2487    /// `on_open_upstream` -- this test then panics with "invalid key".
2488    #[test]
2489    fn opening_an_upstream_without_a_listener_slab_entry_sheds_the_flow() {
2490        use crate::testing::{ServerParts, prebuild_server, provide_port};
2491
2492        let ServerParts {
2493            registry,
2494            sessions,
2495            pool,
2496            backends,
2497            ..
2498        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
2499
2500        let address =
2501            sozu_command::proto::command::SocketAddress::new_v4(127, 0, 0, 1, provide_port());
2502        let config = sozu_command::config::ListenerBuilder::new_udp(address)
2503            .to_udp(None)
2504            .expect("could not build a UdpListenerConfig for the test");
2505
2506        let listener_token = {
2507            let mut session_manager = sessions.borrow_mut();
2508            let entry = session_manager.slab.vacant_entry();
2509            let token = Token(entry.key());
2510            entry.insert(Rc::new(RefCell::new(crate::server::ListenSession {
2511                protocol: Protocol::UDPListen,
2512            })));
2513            token
2514        };
2515
2516        let mut proxy = UdpProxy::new(registry, sessions.clone(), pool, backends, 16, 16384);
2517        proxy
2518            .add_listener(config, listener_token)
2519            .expect("could not add the test UDP listener");
2520        proxy
2521            .activate_listener(&address.into(), None)
2522            .expect("could not activate the test UDP listener");
2523        let session = proxy
2524            .build_session(listener_token)
2525            .expect("could not build the test UDP listener session");
2526
2527        // The `RemoveListener` that frees the slot behind the session's back.
2528        sessions.borrow_mut().slab.remove(listener_token.0);
2529
2530        let slab_len = sessions.borrow().slab.len();
2531        session.borrow_mut().on_open_upstream(
2532            0,
2533            SocketAddr::from(([127, 0, 0, 1], provide_port())),
2534            Instant::now(),
2535        );
2536        assert_eq!(
2537            sessions.borrow().slab.len(),
2538            slab_len,
2539            "a shed flow must not leave an upstream slab slot behind"
2540        );
2541    }
2542}
2543
2544#[cfg(test)]
2545mod listener_sibling_tests {
2546    use sozu_command::{config::ListenerBuilder, proto::command::SocketAddress};
2547
2548    use super::*;
2549    use crate::testing::{ServerParts, prebuild_server, provide_port};
2550
2551    fn add(proxy: &mut UdpProxy, address: SocketAddress, interface: Option<&str>, token: usize) {
2552        let config = ListenerBuilder::new_udp(address)
2553            .with_interface(interface)
2554            .to_udp(None)
2555            .expect("could not build the listener config");
2556        proxy
2557            .add_listener(config, Token(token))
2558            .expect("could not add the listener");
2559    }
2560
2561    /// A UDP listener added on an address that already routes to a cluster
2562    /// takes that route over from the listener already there, in its own
2563    /// state and in its flow manager, so it forwards from its first datagram
2564    /// (sozu-proxy/sozu#719).
2565    #[test]
2566    fn a_listener_added_next_to_a_sibling_takes_over_its_cluster() {
2567        let ServerParts {
2568            registry,
2569            sessions,
2570            pool,
2571            backends,
2572            ..
2573        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
2574        let mut proxy = UdpProxy::new(registry, sessions, pool, backends, 16, 16384);
2575        let address = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
2576        add(&mut proxy, address, None, 0);
2577        proxy
2578            .add_udp_front(RequestUdpFrontend {
2579                cluster_id: "cluster-a".to_owned(),
2580                address,
2581                ..Default::default()
2582            })
2583            .expect("add the frontend");
2584        add(&mut proxy, address, Some("lo"), 1);
2585
2586        assert_eq!(
2587            proxy.listeners[&Token(1)].borrow().cluster_id.as_deref(),
2588            Some("cluster-a"),
2589            "the listener added after the frontend must route to its cluster"
2590        );
2591        assert_eq!(
2592            proxy
2593                .cluster_for_listener
2594                .get(&Token(1))
2595                .map(|c| c.as_ref()),
2596            Some("cluster-a"),
2597            "the flow manager side must know the cluster too"
2598        );
2599    }
2600}