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`](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    logging::ansi_palette,
45    proto::command::{
46        Cluster, LoadBalancingAlgorithms, LoadMetric, RequestUdpFrontend, UdpAffinityKey,
47        UdpListenerConfig, UpdateUdpListenerConfig, WorkerRequest, WorkerResponse,
48        request::RequestType,
49    },
50};
51
52use crate::metrics::names;
53use crate::{
54    CachedTags, ListenerError, ListenerHandler, Protocol, ProxyError, ProxySession,
55    SessionIsToBeClosed,
56    backends::BackendMap,
57    pool::Pool,
58    protocol::udp::{
59        CloseReason, ClusterConfig, ConfigEvent, DropReason, FlowId, ManagerInput, MetricEvent,
60        Output, UdpManager,
61    },
62    server::{SessionManager, TIMER},
63    socket::{udp_bind, udp_connect},
64    sozu_command::{ready::Ready, state::ClusterId},
65};
66
67mod health;
68pub use health::UdpHealthChecker;
69
70/// Per-session log envelope (tag `UDP`). The colored output uses the unified
71/// scheme: bold bright-white protocol label, light-grey keyword, gray keys and
72/// bright-white values. Honours the `colored` flag via [`ansi_palette`].
73macro_rules! log_context {
74    ($self:expr) => {{
75        let (open, reset, grey, gray, white) = ansi_palette();
76        format!(
77            "[- - - -]\t{open}UDP{reset}\t{grey}Listener{reset}({gray}token{reset}={white}{token}{reset}, {gray}address{reset}={white}{address}{reset})\t >>>",
78            open = open,
79            reset = reset,
80            grey = grey,
81            gray = gray,
82            white = white,
83            token = $self.listener_token.0,
84            address = $self.address,
85        )
86    }};
87}
88
89/// Module-level prefix for [`UdpProxy`] callbacks (notify, listener add/remove,
90/// soft/hard stop) which own a listener/token map but have no per-session
91/// token. Produces a bold bright-white `UDP` label in colored mode.
92macro_rules! log_module_context {
93    () => {{
94        let (open, reset, _, _, _) = sozu_command::logging::ansi_palette();
95        format!("{open}UDP{reset}\t >>>", open = open, reset = reset)
96    }};
97}
98
99/// Per-flow log envelope (tag `UDP-FLOW`). Renders the flow's stable id plus
100/// client/backend addresses so flow lines stay filterable.
101macro_rules! log_flow_context {
102    ($flow:expr, $client:expr, $backend:expr) => {{
103        let (open, reset, grey, gray, white) = sozu_command::logging::ansi_palette();
104        format!(
105            "[- - - -]\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 >>>",
106            open = open,
107            reset = reset,
108            grey = grey,
109            gray = gray,
110            white = white,
111            id = $flow,
112            client = $client,
113            backend = $backend,
114        )
115    }};
116}
117
118/// Bound on the per-flow upstream write queue (return path to one backend). A
119/// connected upstream socket only buffers one flow's traffic, so a small cap is
120/// enough to ride out a transient `EWOULDBLOCK` (kernel send buffer momentarily
121/// full) without letting a slow/stalled backend balloon memory. Past the cap we
122/// drop the datagram (`udp.datagrams.dropped.wq_full`) — UDP is best-effort and
123/// the client retries.
124const UPSTREAM_WRITE_QUEUE_CAP: usize = 64;
125/// Bound on the per-listener client-return write queue (replies fanned back to
126/// many clients through the single listener socket). Sized larger than the
127/// per-flow cap because it is shared across every flow's return traffic.
128const CLIENT_WRITE_QUEUE_CAP: usize = 256;
129
130/// What `WriteQueue::drain` should do after a single send attempt: keep
131/// draining, stop because the socket went `WouldBlock` (rearm WRITABLE), or stop
132/// because the send hit a hard error (drop the datagram, keep draining the rest).
133enum SendOutcome {
134    /// The datagram was written; pop it and continue.
135    Sent,
136    /// `WouldBlock`: leave the datagram at the front and stop draining.
137    WouldBlock,
138    /// A hard error (e.g. ECONNREFUSED): drop this datagram + count it, continue.
139    Dropped,
140}
141
142/// A bounded FIFO of datagrams awaiting a writable socket. The egress fast path
143/// (a `send`/`send_to` that succeeds immediately) never touches this; it is only
144/// engaged when the kernel send buffer is full (`WouldBlock`). On overflow the
145/// oldest-still-pending order is preserved and the *new* datagram is dropped
146/// (the queued ones are closer to leaving the socket).
147///
148/// The queue stores `(SocketAddr, Vec<u8>)`: the upstream variant ignores the
149/// address (a connected socket's `send` has an implicit destination) while the
150/// client-return variant uses it as the `send_to` destination. Keeping one type
151/// for both lets the drain loop and the unit tests stay shared.
152struct WriteQueue {
153    queue: VecDeque<(SocketAddr, Vec<u8>)>,
154    cap: usize,
155}
156
157impl WriteQueue {
158    fn new(cap: usize) -> Self {
159        WriteQueue {
160            queue: VecDeque::new(),
161            cap,
162        }
163    }
164
165    fn is_empty(&self) -> bool {
166        self.queue.is_empty()
167    }
168
169    /// Current depth. Only used by the unit tests; the runtime drains by the
170    /// `is_empty` / `drain` return value, not a length read.
171    #[cfg(test)]
172    fn len(&self) -> usize {
173        self.queue.len()
174    }
175
176    /// Enqueue a datagram for later draining. Returns `true` if it was accepted,
177    /// `false` if the queue was at capacity (caller drops + counts `wq_full`).
178    #[must_use]
179    fn push(&mut self, dst: SocketAddr, payload: Vec<u8>) -> bool {
180        if self.queue.len() >= self.cap {
181            return false;
182        }
183        self.queue.push_back((dst, payload));
184        // Invariant: the bounded FIFO never grows past its cap. The guard above
185        // is the only growth site, so depth <= cap holds after every push.
186        debug_assert!(
187            self.queue.len() <= self.cap,
188            "WriteQueue overran its cap: len {} > cap {}",
189            self.queue.len(),
190            self.cap,
191        );
192        true
193    }
194
195    /// Drain in FIFO order, calling `send` for each datagram. Stops on the first
196    /// `WouldBlock` (leaving that datagram queued for the next writable event) or
197    /// when empty. Hard-errored datagrams are popped and counted via
198    /// `SendOutcome::Dropped`. Returns `true` if the queue is now empty (the
199    /// caller can drop WRITABLE interest back to READABLE-only).
200    fn drain<F: FnMut(&SocketAddr, &[u8]) -> SendOutcome>(&mut self, mut send: F) -> bool {
201        while let Some((dst, payload)) = self.queue.front() {
202            match send(dst, payload) {
203                SendOutcome::Sent | SendOutcome::Dropped => {
204                    self.queue.pop_front();
205                }
206                SendOutcome::WouldBlock => break,
207            }
208        }
209        self.queue.is_empty()
210    }
211}
212
213/// One UDP listener: a single `mio::net::UdpSocket` plus its routing config.
214/// Unlike a TCP listener there is no accept loop — a readable event is a batch
215/// of datagrams the session drains to `WouldBlock`.
216pub struct UdpListener {
217    active: SessionIsToBeClosed,
218    address: SocketAddr,
219    cluster_id: Option<String>,
220    config: UdpListenerConfig,
221    socket: Option<UdpSocket>,
222    tags: BTreeMap<String, CachedTags>,
223    token: Token,
224}
225
226impl ListenerHandler for UdpListener {
227    fn get_addr(&self) -> &SocketAddr {
228        &self.address
229    }
230
231    fn get_tags(&self, key: &str) -> Option<&CachedTags> {
232        self.tags.get(key)
233    }
234
235    fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
236        match tags {
237            Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
238            None => self.tags.remove(&key),
239        };
240    }
241
242    fn protocol(&self) -> Protocol {
243        Protocol::UDP
244    }
245
246    fn public_address(&self) -> SocketAddr {
247        self.config
248            .public_address
249            .map(|addr| addr.into())
250            .unwrap_or(self.address)
251    }
252}
253
254impl UdpListener {
255    fn new(config: UdpListenerConfig, token: Token) -> Result<UdpListener, ListenerError> {
256        Ok(UdpListener {
257            cluster_id: None,
258            socket: None,
259            token,
260            address: config.address.into(),
261            config,
262            active: false,
263            tags: BTreeMap::new(),
264        })
265    }
266
267    /// Validate that a worker can build this UDP listener configuration WITHOUT
268    /// constructing the full listener or binding a socket. UDP listener
269    /// construction has no fallible config today (no rustls context, no answer
270    /// templates; a bad bind surfaces later as an `ActivateListener` failure),
271    /// so this currently always succeeds. It exists for surface parity with the
272    /// HTTP/HTTPS validators the main process calls before committing an
273    /// `Add*Listener` to `ConfigState` and fanning it out (sozu#1301), and is
274    /// the hook for any future UDP-config validation.
275    pub fn validate_config(_config: &UdpListenerConfig) -> Result<(), ListenerError> {
276        Ok(())
277    }
278
279    /// Bind (or adopt an SCM-passed) socket and register it `READABLE`. The
280    /// READABLE registration is what drives `Server::ready` for this listener —
281    /// there is no accept path.
282    pub fn activate(
283        &mut self,
284        registry: &Registry,
285        udp_socket: Option<UdpSocket>,
286    ) -> Result<Token, ProxyError> {
287        if self.active {
288            return Ok(self.token);
289        }
290
291        let mut socket = match udp_socket {
292            Some(socket) => socket,
293            None => {
294                let address: SocketAddr = self.config.address.into();
295                udp_bind(address).map_err(|e| ProxyError::BindToSocket(address, e))?
296            }
297        };
298
299        registry
300            .register(&mut socket, self.token, Interest::READABLE)
301            .map_err(ProxyError::RegisterListener)?;
302
303        self.socket = Some(socket);
304        self.active = true;
305        Ok(self.token)
306    }
307
308    /// Apply a partial-update patch to this UDP listener's live config. Fields
309    /// absent in the patch (`None`) are preserved.
310    pub fn update_config(&mut self, patch: &UpdateUdpListenerConfig) {
311        if let Some(v) = patch.public_address {
312            self.config.public_address = Some(v);
313        }
314        if let Some(v) = patch.front_timeout {
315            self.config.front_timeout = v;
316        }
317        if let Some(v) = patch.back_timeout {
318            self.config.back_timeout = v;
319        }
320        if let Some(v) = patch.max_rx_datagram_size {
321            self.config.max_rx_datagram_size = v;
322        }
323        if let Some(v) = patch.max_flows {
324            self.config.max_flows = v;
325        }
326    }
327}
328
329/// The UDP proxy. Holds the listeners, one `UdpManager` per listener token, the
330/// shared `BackendMap`, the session slab and a cloned registry. Does NOT
331/// implement `ProxyConfiguration` / `L7Proxy`; the server drives it through the
332/// inherent [`notify`](Self::notify) plus the activate/give-back helpers.
333pub struct UdpProxy {
334    fronts: HashMap<String, Token>,
335    backends: Rc<RefCell<BackendMap>>,
336    listeners: HashMap<Token, Rc<RefCell<UdpListener>>>,
337    /// The built listener session per listener token. The server inserts the
338    /// same `Rc` into the slab at the listener token; the proxy keeps a clone so
339    /// it can drive per-flow teardown (soft/hard stop) without downcasting the
340    /// slab's `dyn ProxySession`. Cleared on listener removal/stop.
341    listener_sessions: HashMap<Token, Rc<RefCell<UdpListenerSession>>>,
342    /// One sans-io manager per listener token, sharing the listener's lifecycle.
343    managers: HashMap<Token, Rc<RefCell<UdpManager>>>,
344    /// Cluster routing for each listener token (set by the UDP frontend).
345    cluster_for_listener: HashMap<Token, ClusterId>,
346    /// Last `AddCluster`-supplied UDP knobs per cluster id. Cached so a frontend
347    /// added AFTER its cluster still picks up `responses` / `requests` / PPv2 /
348    /// affinity — `AddCluster` and `AddUdpFrontend` arrive in either order, and
349    /// neither must clobber the other's contribution to a manager's
350    /// `ClusterConfig`.
351    cluster_udp_config: HashMap<ClusterId, sozu_command::proto::command::UdpClusterConfig>,
352    registry: Registry,
353    sessions: Rc<RefCell<SessionManager>>,
354    #[allow(dead_code)]
355    pool: Rc<RefCell<Pool>>,
356    /// Fixed hash seed injected once into every manager. It is deliberately the
357    /// same constant on every worker and across restarts so HRW/Maglev affinity
358    /// is reproducible cluster-wide — a per-worker random seed would scatter the
359    /// same flow key onto a different backend on each worker and reshuffle it on
360    /// every restart, defeating the documented affinity-stability contract (see
361    /// [`crate::load_balancing::DEFAULT_HASH_SEED`]).
362    hash_seed: u64,
363    /// Global `max_connections` (from `ServerConfig`). Used to clamp a UDP
364    /// listener's auto `max_flows` so a single listener cannot inflate the
365    /// shared `SessionManager` slab and starve HTTP/TCP.
366    max_connections: usize,
367    /// Global `buffer_size` (from `ServerConfig`). Used to clamp a listener's
368    /// `max_rx_datagram_size` so a hostile `AddUdpListener` cannot allocate a
369    /// multi-GB recv buffer.
370    buffer_size: usize,
371    /// Endpoint-bound active health prober (TCP probe + hysteresis +
372    /// fail-open). Driven from the server event loop via
373    /// [`UdpProxy::health_poll`] / [`UdpProxy::health_ready`].
374    health: UdpHealthChecker,
375}
376
377impl UdpProxy {
378    pub fn new(
379        registry: Registry,
380        sessions: Rc<RefCell<SessionManager>>,
381        pool: Rc<RefCell<Pool>>,
382        backends: Rc<RefCell<BackendMap>>,
383        max_connections: usize,
384        buffer_size: usize,
385    ) -> UdpProxy {
386        // Fixed seed, identical on every worker and across restarts: HRW/Maglev
387        // affinity must be reproducible cluster-wide. A per-worker/per-restart
388        // random seed (as a `process::id()` + `Instant::now()` mix would be)
389        // would route the same flow key to a different backend on each worker
390        // and reshuffle it on every restart. Reuse the LB module's canonical
391        // affinity seed so UDP and the HTTP/TCP affinity hashers agree.
392        let hash_seed = crate::load_balancing::DEFAULT_HASH_SEED;
393        UdpProxy {
394            backends,
395            listeners: HashMap::new(),
396            listener_sessions: HashMap::new(),
397            managers: HashMap::new(),
398            cluster_for_listener: HashMap::new(),
399            cluster_udp_config: HashMap::new(),
400            fronts: HashMap::new(),
401            registry,
402            sessions,
403            pool,
404            hash_seed,
405            max_connections,
406            buffer_size,
407            health: UdpHealthChecker::new(),
408        }
409    }
410
411    /// Drive the UDP health prober one event-loop step (server calls this once
412    /// per iteration, mirroring `HealthChecker::poll`). Non-blocking.
413    pub fn health_poll(&mut self) {
414        let registry = self.registry.try_clone();
415        if let Ok(registry) = registry {
416            self.health.poll(&self.backends, &registry);
417        }
418    }
419
420    /// Record mio readiness for a UDP health-probe socket.
421    pub fn health_ready(&mut self, token: Token) {
422        self.health.ready(token);
423    }
424
425    /// Whether `token` is a UDP health-probe socket this proxy owns.
426    pub fn health_owns_token(&self, token: Token) -> bool {
427        self.health.owns_token(token)
428    }
429
430    pub fn add_listener(
431        &mut self,
432        config: UdpListenerConfig,
433        token: Token,
434    ) -> Result<Token, ProxyError> {
435        match self.listeners.entry(token) {
436            Entry::Vacant(entry) => {
437                let mut config = config;
438                let max_flows = effective_max_flows(config.max_flows, self.max_connections);
439                // Defense in depth: cap the recv-buffer sizing to `buffer_size`
440                // so a hostile `AddUdpListener` (`max_rx_datagram_size =
441                // u32::MAX`) can't allocate a multi-GB buffer. Write the clamped
442                // value back into the stored config so the manager AND the
443                // session's `recv_buf` (sized from `config` in `new()`) agree on
444                // the same bound.
445                let max_rx = clamp_max_rx(config.max_rx_datagram_size as usize, self.buffer_size);
446                config.max_rx_datagram_size = max_rx as u32;
447                let front = Duration::from_secs(u64::from(config.front_timeout));
448                let back = Duration::from_secs(u64::from(config.back_timeout));
449                let listener = UdpListener::new(config, token).map_err(ProxyError::AddListener)?;
450                entry.insert(Rc::new(RefCell::new(listener)));
451                let cluster_cfg = ClusterConfig {
452                    front_timeout: front,
453                    back_timeout: back,
454                    ..Default::default()
455                };
456                self.managers.insert(
457                    token,
458                    Rc::new(RefCell::new(UdpManager::new(
459                        cluster_cfg,
460                        max_flows,
461                        max_rx,
462                        self.hash_seed,
463                    ))),
464                );
465                Ok(token)
466            }
467            _ => Err(ProxyError::ListenerAlreadyPresent),
468        }
469    }
470
471    pub fn remove_listener(&mut self, address: SocketAddr) -> SessionIsToBeClosed {
472        let len = self.listeners.len();
473        let mut removed_tokens = Vec::new();
474        self.listeners.retain(|token, l| {
475            if l.borrow().address == address {
476                removed_tokens.push(*token);
477                false
478            } else {
479                true
480            }
481        });
482        let now = Instant::now();
483        for token in removed_tokens {
484            self.cluster_for_listener.remove(&token);
485            // Drive per-flow teardown THROUGH the manager (emits FlowEvicted +
486            // CloseFlow per flow) BEFORE dropping the manager, so the
487            // active-flows gauge is decremented once per flow and the per-flow
488            // upstream sockets + slab slots are freed. Removing the manager first
489            // would silently leak the gauge by N.
490            if let Some(session) = self.listener_sessions.remove(&token) {
491                session.borrow_mut().close_all_flows(now);
492            }
493            self.managers.remove(&token);
494        }
495        self.listeners.len() < len
496    }
497
498    pub fn activate_listener(
499        &self,
500        addr: &SocketAddr,
501        udp_socket: Option<UdpSocket>,
502    ) -> Result<Token, ProxyError> {
503        let listener = self
504            .listeners
505            .values()
506            .find(|listener| listener.borrow().address == *addr)
507            .ok_or(ProxyError::NoListenerFound(*addr))?;
508
509        listener.borrow_mut().activate(&self.registry, udp_socket)
510    }
511
512    /// Build the [`UdpListenerSession`] that drives this listener's datagrams.
513    /// The server inserts the returned session into the slab **at the listener
514    /// token**, replacing the `ListenSession` placeholder, so the generic
515    /// readiness path reaches `UdpListenerSession::update_readiness`. Returns
516    /// `None` if the listener token is unknown.
517    pub fn build_session(&mut self, token: Token) -> Option<Rc<RefCell<UdpListenerSession>>> {
518        let listener = self.listeners.get(&token)?.clone();
519        let manager = self.managers.get(&token)?.clone();
520        let registry = self.registry.try_clone().ok()?;
521        let session = Rc::new(RefCell::new(UdpListenerSession::new(
522            listener,
523            manager,
524            self.backends.clone(),
525            registry,
526            self.sessions.clone(),
527            token,
528        )));
529        // Keep a clone so soft/hard stop can drive per-flow teardown.
530        self.listener_sessions.insert(token, session.clone());
531        Some(session)
532    }
533
534    pub fn give_back_listeners(&mut self) -> Vec<(SocketAddr, UdpSocket)> {
535        self.listeners
536            .values()
537            .filter_map(|listener| {
538                let mut owned = listener.borrow_mut();
539                if let Some(socket) = owned.socket.take() {
540                    owned.active = false;
541                    return Some((owned.address, socket));
542                }
543                None
544            })
545            .collect()
546    }
547
548    pub fn give_back_listener(
549        &mut self,
550        address: SocketAddr,
551    ) -> Result<(Token, UdpSocket), ProxyError> {
552        let listener = self
553            .listeners
554            .values()
555            .find(|listener| listener.borrow().address == address)
556            .ok_or(ProxyError::NoListenerFound(address))?;
557
558        let (token, taken) = {
559            let mut owned = listener.borrow_mut();
560            let taken = owned.socket.take().ok_or(ProxyError::UnactivatedListener)?;
561            owned.active = false;
562            (owned.token, taken)
563        };
564        // Deactivating removes the listener from service: tear down its active
565        // flows THROUGH the manager so the per-flow upstream slab slots + fds
566        // don't dangle and the active-flows gauge is decremented once per flow
567        // (the manager is RETAINED here — not removed from `self.managers` — so
568        // `close_all` also resets its flow table, keeping manager and shell
569        // consistent for a later reactivate).
570        if let Some(session) = self.listener_sessions.remove(&token) {
571            session.borrow_mut().close_all_flows(Instant::now());
572        }
573        Ok((token, taken))
574    }
575
576    pub fn update_listener(&mut self, patch: UpdateUdpListenerConfig) -> Result<(), ProxyError> {
577        let address: SocketAddr = patch.address.into();
578        let listener = self
579            .listeners
580            .values()
581            .find(|l| l.borrow().address == address)
582            .ok_or(ProxyError::NoListenerFound(address))?;
583        {
584            let mut l = listener.borrow_mut();
585            l.update_config(&patch);
586            // Clamp the stored rx size in place (defense in depth): keep the
587            // listener config, the manager, and the session's `recv_buf` agreeing
588            // on the same `buffer_size`-bounded value.
589            l.config.max_rx_datagram_size =
590                clamp_max_rx(l.config.max_rx_datagram_size as usize, self.buffer_size) as u32;
591        }
592
593        // Reflect the timeout / cap / rx-size changes into the manager for new
594        // flows. Existing flows keep their captured config (stable contract).
595        if let Some(token) = self
596            .listeners
597            .iter()
598            .find(|(_, l)| l.borrow().address == address)
599            .map(|(t, _)| *t)
600            && let Some(mgr) = self.managers.get(&token)
601        {
602            let now = Instant::now();
603            let (cfg, max_flows, max_rx) = {
604                let l = listener.borrow();
605                (
606                    self.cluster_config_for(&l, token),
607                    effective_max_flows(l.config.max_flows, self.max_connections),
608                    // Defense in depth: clamp the raised rx size to `buffer_size`
609                    // so a hostile `UpdateUdpListener` can't grow the recv buffer
610                    // past the global cap.
611                    clamp_max_rx(l.config.max_rx_datagram_size as usize, self.buffer_size),
612                )
613            };
614            {
615                let mut m = mgr.borrow_mut();
616                m.handle_input(ManagerInput::Config(ConfigEvent::SetCluster(cfg)), now);
617                m.handle_input(
618                    ManagerInput::Config(ConfigEvent::SetMaxFlows(max_flows)),
619                    now,
620                );
621                m.handle_input(
622                    ManagerInput::Config(ConfigEvent::SetMaxRxDatagramSize(max_rx)),
623                    now,
624                );
625            }
626            // Resize the live session's recv scratch to match the (clamped) new
627            // rx size. Without this, a config that RAISES `max_rx_datagram_size`
628            // would leave `recv_buf` at its old (smaller) length: `recv_from`
629            // would kernel-truncate datagrams between the old and new size while
630            // the manager's `len > max_rx` check (now the larger value) passes
631            // them, forwarding them truncated. The `+ 1` mirrors `new()`: it lets
632            // the manager observe `len == max_rx + 1 > max_rx` and drop an
633            // oversized datagram (`DropReason::Truncated`) instead of silently
634            // truncating it. Existing flows keep their captured config; only the
635            // shared recv buffer tracks the live rx size.
636            if let Some(session) = self.listener_sessions.get(&token) {
637                session.borrow_mut().resize_recv_buf(max_rx);
638            }
639        }
640        Ok(())
641    }
642
643    pub fn add_udp_front(&mut self, front: RequestUdpFrontend) -> Result<(), ProxyError> {
644        let address = front.address.into();
645        let token = {
646            let mut listener = self
647                .listeners
648                .values()
649                .find(|l| l.borrow().address == address)
650                .ok_or(ProxyError::NoListenerFound(address))?
651                .borrow_mut();
652            self.fronts
653                .insert(front.cluster_id.to_string(), listener.token);
654            listener.set_tags(address.to_string(), Some(front.tags));
655            listener.cluster_id = Some(front.cluster_id.clone());
656            listener.token
657        };
658        self.cluster_for_listener
659            .insert(token, front.cluster_id.clone());
660
661        // Commit the cluster routing into the manager so admitted flows know
662        // which cluster to `SelectBackend` against.
663        if let Some(mgr) = self.managers.get(&token) {
664            let listener = self.listeners.get(&token).unwrap();
665            let cfg = {
666                let l = listener.borrow();
667                self.cluster_config_for(&l, token)
668            };
669            mgr.borrow_mut().handle_input(
670                ManagerInput::Config(ConfigEvent::SetCluster(cfg)),
671                Instant::now(),
672            );
673        }
674        Ok(())
675    }
676
677    pub fn remove_udp_front(&mut self, front: RequestUdpFrontend) -> Result<(), ProxyError> {
678        let address = front.address.into();
679        let token = {
680            let mut listener = match self
681                .listeners
682                .values()
683                .find(|l| l.borrow().address == address)
684            {
685                Some(l) => l.borrow_mut(),
686                None => return Err(ProxyError::NoListenerFound(address)),
687            };
688            listener.set_tags(address.to_string(), None);
689            if let Some(cluster_id) = listener.cluster_id.take() {
690                self.fronts.remove(&cluster_id);
691            }
692            listener.token
693        };
694        self.cluster_for_listener.remove(&token);
695        // Drop the routing in the manager — new datagrams now have no backend.
696        if let Some(mgr) = self.managers.get(&token) {
697            mgr.borrow_mut().handle_input(
698                ManagerInput::Config(ConfigEvent::SetCluster(ClusterConfig::default())),
699                Instant::now(),
700            );
701        }
702        Ok(())
703    }
704
705    /// Build a [`ClusterConfig`] for a listener from its current frontend
706    /// cluster routing + timeouts. The per-cluster knobs (responses/requests/
707    /// PPv2/affinity) are populated by [`apply_cluster`](Self::apply_cluster)
708    /// when an `AddCluster` carries a `udp` block; absent that they stay at the
709    /// proto defaults.
710    fn cluster_config_for(&self, listener: &UdpListener, _token: Token) -> ClusterConfig {
711        let cluster = listener.cluster_id.clone().unwrap_or_default();
712        let mut cfg = ClusterConfig {
713            cluster: cluster.clone(),
714            front_timeout: Duration::from_secs(u64::from(listener.config.front_timeout)),
715            back_timeout: Duration::from_secs(u64::from(listener.config.back_timeout)),
716            ..Default::default()
717        };
718        // Fold in the cluster's cached UDP knobs (set by a prior or later
719        // `AddCluster`) so the order of `AddCluster` vs `AddUdpFrontend` does not
720        // matter — both rebuilds of a manager's `ClusterConfig` converge on the
721        // same per-cluster contract.
722        if let Some(udp) = self.cluster_udp_config.get(&cluster) {
723            apply_udp_knobs(&mut cfg, udp);
724        }
725        cfg
726    }
727
728    /// Apply an `AddCluster` to every listener routing to it: fold the UDP
729    /// cluster knobs (affinity / responses / requests / PPv2) into the
730    /// manager's `ClusterConfig`, and rebuild the LB policy (HRW/Maglev table)
731    /// via the shared `BackendMap`.
732    fn apply_cluster(&mut self, cluster: &Cluster) {
733        // 1. LB policy (HRW/Maglev rebuild happens inside the BackendMap).
734        self.backends
735            .borrow_mut()
736            .set_load_balancing_policy_for_cluster(
737                &cluster.cluster_id,
738                LoadBalancingAlgorithms::try_from(cluster.load_balancing).unwrap_or_default(),
739                cluster
740                    .load_metric
741                    .and_then(|n| LoadMetric::try_from(n).ok()),
742            );
743
744        // 1b. Health settings from the cluster's `udp.health` block, if any.
745        // Mode HEALTH_OFF / no health block disables probing for this cluster.
746        let health_settings = cluster.udp.as_ref().and_then(|udp| {
747            udp.health.as_ref().and_then(|h| {
748                let mode = h
749                    .mode
750                    .and_then(|m| sozu_command::proto::command::UdpHealthMode::try_from(m).ok());
751                match mode {
752                    Some(sozu_command::proto::command::UdpHealthMode::HealthOff) => None,
753                    // TCP_PROBE (default when a health block is present) and
754                    // UDP_PROBE both schedule the TCP-probe state machine; the
755                    // app UDP-probe payload rides along for the secondary check.
756                    _ => Some(health::UdpHealthSettings::from_proto(h)),
757                }
758            })
759        });
760        self.health
761            .set_cluster(&cluster.cluster_id, health_settings, &self.registry);
762
763        // 1c. Cache the UDP knobs so a frontend added in EITHER order picks them
764        // up via `cluster_config_for`. An `AddCluster` without a `udp` block
765        // clears the cache (back to proto defaults).
766        match &cluster.udp {
767            Some(udp) => {
768                self.cluster_udp_config
769                    .insert(cluster.cluster_id.clone(), udp.clone());
770            }
771            None => {
772                self.cluster_udp_config.remove(&cluster.cluster_id);
773            }
774        }
775
776        // 2. Per-cluster UDP knobs into the managers routing to this cluster.
777        // `cluster_config_for` now folds the cached knobs in, so the rebuild is
778        // identical regardless of whether the frontend or the cluster came
779        // first.
780        let now = Instant::now();
781        let tokens: Vec<Token> = self
782            .cluster_for_listener
783            .iter()
784            .filter(|(_, c)| **c == cluster.cluster_id)
785            .map(|(t, _)| *t)
786            .collect();
787        for token in tokens {
788            let Some(listener) = self.listeners.get(&token) else {
789                continue;
790            };
791            let cfg = {
792                let l = listener.borrow();
793                self.cluster_config_for(&l, token)
794            };
795            if let Some(mgr) = self.managers.get(&token) {
796                mgr.borrow_mut()
797                    .handle_input(ManagerInput::Config(ConfigEvent::SetCluster(cfg)), now);
798            }
799        }
800    }
801
802    /// Inherent dispatch entry point — the server calls this directly (UDP does
803    /// not implement `ProxyConfiguration`). Handles UDP frontends, cluster
804    /// config, listener removal, and stop. No accept / create_session.
805    pub fn notify(&mut self, message: WorkerRequest) -> WorkerResponse {
806        let request_type = match message.content.request_type {
807            Some(t) => t,
808            None => return WorkerResponse::error(message.id, "Empty request"),
809        };
810        match request_type {
811            RequestType::AddUdpFrontend(front) => match self.add_udp_front(front) {
812                Ok(()) => WorkerResponse::ok(message.id),
813                Err(err) => WorkerResponse::error(message.id, err),
814            },
815            RequestType::RemoveUdpFrontend(front) => match self.remove_udp_front(front) {
816                Ok(()) => WorkerResponse::ok(message.id),
817                Err(err) => WorkerResponse::error(message.id, err),
818            },
819            RequestType::AddCluster(cluster) => {
820                self.apply_cluster(&cluster);
821                WorkerResponse::ok(message.id)
822            }
823            RequestType::RemoveCluster(cluster_id) => {
824                let tokens: Vec<Token> = self
825                    .cluster_for_listener
826                    .iter()
827                    .filter(|(_, c)| **c == cluster_id)
828                    .map(|(t, _)| *t)
829                    .collect();
830                for token in tokens {
831                    if let Some(mgr) = self.managers.get(&token) {
832                        mgr.borrow_mut().handle_input(
833                            ManagerInput::Config(ConfigEvent::SetCluster(ClusterConfig::default())),
834                            Instant::now(),
835                        );
836                    }
837                }
838                self.cluster_udp_config.remove(&cluster_id);
839                self.health.remove_cluster(&cluster_id, &self.registry);
840                WorkerResponse::ok(message.id)
841            }
842            RequestType::SoftStop(_) => {
843                info!(
844                    "{} {} processing soft shutdown",
845                    log_module_context!(),
846                    message.id
847                );
848                // Drain: admit no new flows. Then actively tear down existing
849                // flows so the worker reaches `base_sessions_count` and exits
850                // promptly instead of waiting out each flow's idle timeout. UDP
851                // has no half-sent response to preserve, so an immediate flow
852                // teardown on soft-stop is the right graceful behavior (a stray
853                // in-flight reply may be lost, which is acceptable for a
854                // best-effort datagram proxy).
855                let now = Instant::now();
856                for mgr in self.managers.values() {
857                    mgr.borrow_mut()
858                        .handle_input(ManagerInput::Config(ConfigEvent::Drain), now);
859                }
860                // Drive teardown through each manager (FlowEvicted + CloseFlow
861                // per flow) so the active-flows gauge balances to zero.
862                for session in self.listener_sessions.values() {
863                    session.borrow_mut().close_all_flows(now);
864                }
865                self.listener_sessions.clear();
866                let listeners: HashMap<_, _> = self.listeners.drain().collect();
867                for l in listeners.values() {
868                    l.borrow_mut()
869                        .socket
870                        .take()
871                        .map(|mut sock| self.registry.deregister(&mut sock));
872                }
873                WorkerResponse::processing(message.id)
874            }
875            RequestType::HardStop(_) => {
876                info!("{} {} hard shutdown", log_module_context!(), message.id);
877                let now = Instant::now();
878                // Drive teardown through each manager (FlowEvicted + CloseFlow
879                // per flow) so the active-flows gauge balances to zero before the
880                // managers are dropped below.
881                for session in self.listener_sessions.values() {
882                    session.borrow_mut().close_all_flows(now);
883                }
884                self.listener_sessions.clear();
885                let mut listeners: HashMap<_, _> = self.listeners.drain().collect();
886                for (_, l) in listeners.drain() {
887                    l.borrow_mut()
888                        .socket
889                        .take()
890                        .map(|mut sock| self.registry.deregister(&mut sock));
891                }
892                self.managers.clear();
893                WorkerResponse::ok(message.id)
894            }
895            RequestType::Status(_) => {
896                info!("{} {} status", log_module_context!(), message.id);
897                WorkerResponse::ok(message.id)
898            }
899            RequestType::RemoveListener(remove) => {
900                if !self.remove_listener(remove.address.into()) {
901                    WorkerResponse::error(
902                        message.id,
903                        format!("no UDP listener to remove at address {:?}", remove.address),
904                    )
905                } else {
906                    WorkerResponse::ok(message.id)
907                }
908            }
909            command => {
910                debug!(
911                    "{} {} unsupported message for UDP proxy, ignoring {:?}",
912                    log_module_context!(),
913                    message.id,
914                    command
915                );
916                WorkerResponse::error(message.id, "unsupported message")
917            }
918        }
919    }
920}
921
922/// Fold a proto [`UdpClusterConfig`](sozu_command::proto::command::UdpClusterConfig)
923/// into a [`ClusterConfig`], applying the proto defaults for absent fields.
924/// Single source of truth so `apply_cluster` (live push) and
925/// `cluster_config_for` (frontend add / rebuild) never diverge.
926fn apply_udp_knobs(cfg: &mut ClusterConfig, udp: &sozu_command::proto::command::UdpClusterConfig) {
927    cfg.affinity_with_port = matches!(
928        udp.affinity_key
929            .and_then(|k| UdpAffinityKey::try_from(k).ok()),
930        Some(UdpAffinityKey::SourceIpPort)
931    );
932    cfg.responses = udp.responses.unwrap_or(0);
933    cfg.requests = udp.requests.unwrap_or(0);
934    cfg.send_proxy_protocol = udp.send_proxy_protocol.unwrap_or(false);
935    cfg.proxy_protocol_every_datagram = udp.proxy_protocol_every_datagram.unwrap_or(false);
936}
937
938/// Fallback auto `max_flows` when `RLIMIT_NOFILE` can't be read.
939const DEFAULT_AUTO_MAX_FLOWS: usize = 1024;
940
941/// `max_flows == 0` means "auto": ~70% of the soft `RLIMIT_NOFILE`, so the fd
942/// budget adapts to the host without hand-tuning (one fd + one slab slot per
943/// flow). Falls back to a conservative constant when the limit can't be read.
944///
945/// The auto derivation is clamped to `slab_headroom` (the global
946/// `max_connections`): every admitted flow consumes one shared `SessionManager`
947/// slab slot, so an unclamped ~70%-of-RLIMIT value on a host with a very large
948/// fd limit could try to inflate the slab to hundreds of thousands of entries
949/// and starve HTTP/TCP. An *explicitly configured* `max_flows` is honoured as-is
950/// (the operator opted in); only the auto value is capped. A `slab_headroom` of
951/// 0 (no connection budget configured) disables the clamp.
952fn effective_max_flows(configured: u32, slab_headroom: usize) -> usize {
953    if configured != 0 {
954        return configured as usize;
955    }
956    let auto = {
957        #[cfg(unix)]
958        {
959            let mut limit = libc::rlimit {
960                rlim_cur: 0,
961                rlim_max: 0,
962            };
963            // SAFETY: `getrlimit` writes a fully-initialised `rlimit` into
964            // `limit`; we read the result only on success (`== 0`).
965            let ret = unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limit) };
966            if ret == 0 && limit.rlim_cur > 0 {
967                let soft = limit.rlim_cur;
968                ((soft.saturating_mul(7)) / 10).max(1) as usize
969            } else {
970                DEFAULT_AUTO_MAX_FLOWS
971            }
972        }
973        #[cfg(not(unix))]
974        {
975            DEFAULT_AUTO_MAX_FLOWS
976        }
977    };
978    if slab_headroom == 0 {
979        auto
980    } else {
981        auto.min(slab_headroom).max(1)
982    }
983}
984
985/// Clamp a configured `max_rx_datagram_size` to the global `buffer_size`
986/// (defense in depth): the per-session `recv_buf` is sized `max_rx + 1`, so a
987/// raw worker `AddUdpListener`/`UpdateUdpListener` carrying e.g.
988/// `max_rx_datagram_size = u32::MAX` must NOT be able to allocate a ~4 GB
989/// buffer. A `buffer_size` of 0 (unset) leaves the value untouched.
990fn clamp_max_rx(configured: usize, buffer_size: usize) -> usize {
991    if buffer_size == 0 {
992        configured
993    } else {
994        configured.min(buffer_size)
995    }
996}
997
998/// The `ProxySession` backing one UDP listener. The server's generic readiness
999/// path drives this (UDP is not in the listen-accept arm). It owns the per-flow
1000/// connected upstream sockets and demuxes events by token.
1001pub struct UdpListenerSession {
1002    /// The listener this session serves.
1003    listener: Rc<RefCell<UdpListener>>,
1004    /// The sans-io manager for this listener.
1005    manager: Rc<RefCell<UdpManager>>,
1006    /// Shared backend map for LB selection.
1007    backends: Rc<RefCell<BackendMap>>,
1008    /// Cloned mio registry for per-flow socket (de)registration.
1009    registry: Registry,
1010    /// Slab the per-flow upstream tokens are inserted into (the same `Rc` the
1011    /// listener-session is registered under: multi-token pattern).
1012    sessions: Rc<RefCell<SessionManager>>,
1013    /// The listener's own token (client recv + timer key).
1014    listener_token: Token,
1015    /// Listener address (for logging).
1016    address: SocketAddr,
1017    /// Per-flow connected upstream sockets, keyed by their slab token.
1018    upstream_sockets: HashMap<Token, UdpSocket>,
1019    /// Per-flow bounded egress queues for the forward path, keyed by the
1020    /// upstream token. A datagram lands here only when the connected upstream
1021    /// socket returns `WouldBlock`; the socket is then reregistered
1022    /// `READABLE | WRITABLE` and drained on the next writable event. Dropped
1023    /// together with the socket on flow close (no leak, gauge stays correct).
1024    upstream_write_queues: HashMap<Token, WriteQueue>,
1025    /// Bounded egress queue for the client-return path (replies fanned back
1026    /// through the single listener socket via `send_to`). Engaged only when the
1027    /// listener socket returns `WouldBlock`; the listener is then reregistered
1028    /// `READABLE | WRITABLE` and drained on its next writable event.
1029    client_write_queue: WriteQueue,
1030    /// `upstream_token -> FlowId` for NAT-return demux.
1031    upstream_to_flow: HashMap<Token, FlowId>,
1032    /// `FlowId -> upstream_token` to tear down on close.
1033    flow_to_upstream: HashMap<FlowId, Token>,
1034    /// `FlowId -> admission Instant` for `udp.flow.duration` on close.
1035    flow_started: HashMap<FlowId, Instant>,
1036    /// `FlowId -> (client, backend)` for the close access log.
1037    flow_endpoints: HashMap<FlowId, (SocketAddr, Option<SocketAddr>)>,
1038    /// The client source whose datagram is currently being drained. Lets
1039    /// `on_send_to_backend` resolve the right per-flow upstream socket for an
1040    /// already-established flow (where no `OpenUpstream` precedes the send).
1041    /// `None` while draining backend-side or timer-driven outputs.
1042    in_flight_client: Option<SocketAddr>,
1043    /// The flow whose upstream was just opened in this drain pass — covers the
1044    /// new-flow path where `OpenUpstream{flow}` immediately precedes the first
1045    /// `SendToBackend`.
1046    in_flight_flow: Option<FlowId>,
1047    /// Shadow of the manager's flow table: normalised client key → `FlowId`.
1048    /// Lets the shell resolve the owning flow for a `SendToBackend` on an
1049    /// established flow from the in-flight client source. Kept in lockstep with
1050    /// `OpenUpstream` / `CloseFlow`.
1051    client_key_to_flow: HashMap<SocketAddr, FlowId>,
1052    /// Reusable recv scratch buffer, sized to `max_rx_datagram_size`.
1053    recv_buf: Vec<u8>,
1054    /// The currently-armed `TIMER` handle, so a re-arm cancels the previous
1055    /// deadline instead of leaking timer-slab entries (the manager only emits a
1056    /// fresh `ArmTimer` when the deadline actually changes).
1057    timer_handle: Option<crate::timer::Timeout>,
1058}
1059
1060impl UdpListenerSession {
1061    #[allow(clippy::too_many_arguments)]
1062    pub fn new(
1063        listener: Rc<RefCell<UdpListener>>,
1064        manager: Rc<RefCell<UdpManager>>,
1065        backends: Rc<RefCell<BackendMap>>,
1066        registry: Registry,
1067        sessions: Rc<RefCell<SessionManager>>,
1068        listener_token: Token,
1069    ) -> UdpListenerSession {
1070        let (address, max_rx) = {
1071            let l = listener.borrow();
1072            (l.address, l.config.max_rx_datagram_size as usize)
1073        };
1074        UdpListenerSession {
1075            listener,
1076            manager,
1077            backends,
1078            registry,
1079            sessions,
1080            listener_token,
1081            address,
1082            upstream_sockets: HashMap::new(),
1083            upstream_write_queues: HashMap::new(),
1084            client_write_queue: WriteQueue::new(CLIENT_WRITE_QUEUE_CAP),
1085            upstream_to_flow: HashMap::new(),
1086            flow_to_upstream: HashMap::new(),
1087            flow_started: HashMap::new(),
1088            flow_endpoints: HashMap::new(),
1089            in_flight_client: None,
1090            in_flight_flow: None,
1091            client_key_to_flow: HashMap::new(),
1092            // Size the recv scratch to `max_rx + 1`, NOT `max_rx`: a UDP
1093            // `recv_from` truncates the datagram to the buffer length and
1094            // silently discards the tail. If the buffer were exactly `max_rx`,
1095            // an oversized datagram would arrive as a `max_rx`-byte payload —
1096            // indistinguishable from a legal one — and be forwarded truncated.
1097            // The extra byte lets the manager observe `len == max_rx + 1 >
1098            // max_rx` and drop it (`DropReason::Truncated`) instead.
1099            recv_buf: vec![0u8; max_rx.saturating_add(1).max(1)],
1100            timer_handle: None,
1101        }
1102    }
1103
1104    /// Resize the recv scratch buffer to `max_rx + 1` (the same `+ 1` sizing as
1105    /// [`new`](Self::new): the extra byte lets the manager observe an oversized
1106    /// datagram as `len == max_rx + 1 > max_rx` and drop it as `Truncated`
1107    /// rather than silently forwarding a kernel-truncated payload). Called from
1108    /// `UdpProxy::update_listener` when a config push changes the listener's
1109    /// `max_rx_datagram_size`. Resizing larger zero-fills the new tail; resizing
1110    /// smaller truncates the (idle, between-datagram) scratch — both are safe
1111    /// because the buffer holds no live datagram across calls.
1112    fn resize_recv_buf(&mut self, max_rx: usize) {
1113        self.recv_buf.resize(max_rx.saturating_add(1).max(1), 0u8);
1114    }
1115
1116    /// Normalise a client source the same way the active manager config keys
1117    /// flows (4-tuple when `affinity_with_port`, else source-IP with port 0).
1118    fn client_key(&self, src: SocketAddr) -> SocketAddr {
1119        let with_port = self.manager.borrow().affinity_with_port();
1120        if with_port {
1121            src
1122        } else {
1123            let mut s = src;
1124            s.set_port(0);
1125            s
1126        }
1127    }
1128
1129    /// Drain every datagram waiting on the listener socket into the manager.
1130    /// Edge-triggered epoll: loop `recv_from` to `WouldBlock`.
1131    fn ingest_client(&mut self, now: Instant) {
1132        // Pull the socket out behind a short borrow then operate on it.
1133        loop {
1134            let result = {
1135                let listener = self.listener.borrow();
1136                let Some(socket) = listener.socket.as_ref() else {
1137                    return;
1138                };
1139                socket.recv_from(&mut self.recv_buf)
1140            };
1141            match result {
1142                Ok((len, src)) => {
1143                    // Mark the in-flight client so `on_send_to_backend` can
1144                    // resolve the owning flow's upstream socket for an
1145                    // already-established flow (no `OpenUpstream` precedes it).
1146                    self.in_flight_client = Some(src);
1147                    self.in_flight_flow = None;
1148                    let len = len.min(self.recv_buf.len());
1149                    // Borrow the payload via a split so the mutable borrow of
1150                    // `self.manager` does not alias `self.recv_buf`.
1151                    let payload: &[u8] = &self.recv_buf[..len];
1152                    // SAFETY-free: `handle_input` only reads the slice; the
1153                    // borrow checker is satisfied because `recv_buf` and
1154                    // `manager` are disjoint fields.
1155                    let mgr = self.manager.clone();
1156                    mgr.borrow_mut()
1157                        .handle_input(ManagerInput::ClientDatagram { src, payload }, now);
1158                    // Drain outputs after each datagram to bound queue growth.
1159                    self.drain_outputs(now);
1160                    self.in_flight_client = None;
1161                }
1162                Err(ref e) if e.kind() == ErrorKind::WouldBlock => break,
1163                Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
1164                Err(e) => {
1165                    debug!(
1166                        "{} recv_from error on UDP listener: {}",
1167                        log_context!(self),
1168                        e
1169                    );
1170                    break;
1171                }
1172            }
1173        }
1174    }
1175
1176    /// Drain datagrams waiting on one flow's connected upstream socket into the
1177    /// manager as `BackendDatagram`s.
1178    fn ingest_upstream(&mut self, upstream_token: Token, now: Instant) {
1179        let Some(&flow) = self.upstream_to_flow.get(&upstream_token) else {
1180            return;
1181        };
1182        loop {
1183            let result = {
1184                let Some(socket) = self.upstream_sockets.get(&upstream_token) else {
1185                    return;
1186                };
1187                socket.recv(&mut self.recv_buf)
1188            };
1189            match result {
1190                Ok(len) => {
1191                    let len = len.min(self.recv_buf.len());
1192                    let payload: &[u8] = &self.recv_buf[..len];
1193                    let mgr = self.manager.clone();
1194                    mgr.borrow_mut()
1195                        .handle_input(ManagerInput::BackendDatagram { flow, payload }, now);
1196                    self.drain_outputs(now);
1197                }
1198                Err(ref e) if e.kind() == ErrorKind::WouldBlock => break,
1199                Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
1200                Err(e) => {
1201                    debug!(
1202                        "{} recv error on upstream socket: {}",
1203                        log_context!(self),
1204                        e
1205                    );
1206                    break;
1207                }
1208            }
1209        }
1210    }
1211
1212    /// Drain the manager's output queue, acting on each `Output`.
1213    fn drain_outputs(&mut self, now: Instant) {
1214        let mgr = self.manager.clone();
1215        loop {
1216            let out = mgr.borrow_mut().poll_output();
1217            let Some(out) = out else { break };
1218            match out {
1219                Output::SelectBackend { flow, cluster, key } => {
1220                    self.on_select_backend(flow, &cluster, key, now)
1221                }
1222                Output::OpenUpstream { flow, backend } => self.on_open_upstream(flow, backend, now),
1223                Output::SendToBackend(transmit) => self.on_send_to_backend(transmit),
1224                Output::SendToClient(transmit) => self.on_send_to_client(transmit),
1225                Output::ArmTimer(deadline) => self.arm_timer(deadline, now),
1226                Output::Metric(ev) => Self::record_metric(ev),
1227                Output::CloseFlow(flow) => self.on_close_flow(flow),
1228                Output::Drop(reason) => Self::record_drop(reason),
1229            }
1230        }
1231    }
1232
1233    fn on_select_backend(&mut self, flow: FlowId, cluster: &str, key: u64, now: Instant) {
1234        let resolved = self
1235            .backends
1236            .borrow_mut()
1237            .backend_from_cluster_id_with_key(cluster, Some(key));
1238        match resolved {
1239            Ok((backend, addr)) => {
1240                self.manager.borrow_mut().handle_input(
1241                    ManagerInput::BackendResolved {
1242                        flow,
1243                        backend,
1244                        addr,
1245                    },
1246                    now,
1247                );
1248            }
1249            Err(e) => {
1250                debug!(
1251                    "{} no backend for cluster {}: {}; aborting flow {}",
1252                    log_context!(self),
1253                    cluster,
1254                    e,
1255                    flow
1256                );
1257                incr!(names::udp::DROPPED_NO_BACKEND);
1258                // Abort the flow instead of leaving it parked AwaitingBackend:
1259                // the manager already counted it (FlowCreated, +1 gauge, slab +
1260                // admission slot), so without this it would squat a `max_flows`
1261                // slot for the full idle timeout while every later datagram is
1262                // dropped. `abort_flow` enqueues FlowEvicted + CloseFlow, which
1263                // the surrounding `drain_outputs` loop processes — freeing the
1264                // slot immediately and balancing the gauge via FlowEvicted.
1265                self.manager
1266                    .borrow_mut()
1267                    .abort_flow(flow, now, CloseReason::Aborted);
1268            }
1269        }
1270    }
1271
1272    fn on_open_upstream(&mut self, flow: FlowId, backend: SocketAddr, now: Instant) {
1273        let mut socket = match udp_connect(backend) {
1274            Ok(socket) => socket,
1275            Err(e) => {
1276                // EMFILE/ENFILE/connect refusal → shed this flow, never panic.
1277                warn!(
1278                    "{} could not open upstream socket to {}: {}; shedding flow {}",
1279                    log_context!(self),
1280                    backend,
1281                    e,
1282                    flow
1283                );
1284                incr!(names::udp::FLOWS_SHED);
1285                // The manager already moved this flow toward Established and
1286                // counted it; abort it so the `max_flows` slot frees immediately
1287                // (FlowEvicted balances the gauge) instead of squatting until the
1288                // idle timeout. Keep the FLOWS_SHED metric above for the
1289                // EMFILE/refused case. `abort_flow` enqueues FlowEvicted +
1290                // CloseFlow for the surrounding `drain_outputs` loop.
1291                self.manager
1292                    .borrow_mut()
1293                    .abort_flow(flow, now, CloseReason::Aborted);
1294                return;
1295            }
1296        };
1297        // Multi-token pattern (template tcp.rs:1029-1053): a fresh slab slot
1298        // under the SAME listener-session Rc, registered READABLE so its
1299        // readiness reaches `Server::ready` → demuxed back to this session by
1300        // `update_readiness`. The flow-table cap (`max_flows`) already bounds
1301        // how many upstream sockets/slots can exist, so the slab cannot grow
1302        // unbounded here.
1303        let upstream_token = {
1304            let mut s = self.sessions.borrow_mut();
1305            let listener_session = s.slab[self.listener_token.0].clone();
1306            let entry = s.slab.vacant_entry();
1307            let token = Token(entry.key());
1308            entry.insert(listener_session);
1309            token
1310        };
1311        if let Err(e) = self
1312            .registry
1313            .register(&mut socket, upstream_token, Interest::READABLE)
1314        {
1315            error!(
1316                "{} could not register upstream socket: {}",
1317                log_context!(self),
1318                e
1319            );
1320            self.sessions.borrow_mut().slab.try_remove(upstream_token.0);
1321            // The flow is Established in the manager but has no usable upstream
1322            // socket: abort it so its `max_flows` slot frees now (FlowEvicted
1323            // balances the gauge) rather than squatting until idle timeout.
1324            self.manager
1325                .borrow_mut()
1326                .abort_flow(flow, now, CloseReason::Aborted);
1327            return;
1328        }
1329        self.upstream_sockets.insert(upstream_token, socket);
1330        self.upstream_to_flow.insert(upstream_token, flow);
1331        self.flow_to_upstream.insert(flow, upstream_token);
1332        // `flow_to_upstream` and `upstream_to_flow` are inverse maps: the token
1333        // in one points back to the flow in the other. A broken inverse would let
1334        // a backend reply demux to the wrong client (a NAT-return mismatch).
1335        debug_assert_eq!(
1336            self.upstream_to_flow.get(&upstream_token),
1337            Some(&flow),
1338            "upstream_to_flow must map the new token back to its flow"
1339        );
1340        debug_assert_eq!(
1341            self.flow_to_upstream.get(&flow),
1342            Some(&upstream_token),
1343            "flow_to_upstream must map the flow back to its upstream token"
1344        );
1345        self.flow_started.insert(flow, Instant::now());
1346        // The client source for this flow is the one currently in flight.
1347        let client = self.in_flight_client.unwrap_or(self.address);
1348        self.flow_endpoints.insert(flow, (client, Some(backend)));
1349        if let Some(src) = self.in_flight_client {
1350            let key = self.client_key(src);
1351            self.client_key_to_flow.insert(key, flow);
1352            // The shadow flow-table only ever holds live flows: the flow we just
1353            // mapped must have a live upstream token (it is the one we just
1354            // opened). Pairs the on-close drop in `on_close_flow`.
1355            debug_assert!(
1356                self.flow_to_upstream.contains_key(&flow),
1357                "client_key_to_flow points at flow {flow} with no live upstream token"
1358            );
1359        }
1360        // This flow's first SendToBackend (if any) follows immediately.
1361        self.in_flight_flow = Some(flow);
1362    }
1363
1364    fn on_send_to_backend(&mut self, transmit: crate::protocol::udp::Transmit) {
1365        // Resolve the owning flow precisely (NOT by `transmit.dst`, which two
1366        // flows to the same backend would alias — sending on the wrong
1367        // connected socket misroutes that backend's reply to the wrong client).
1368        //   * new flow:  `OpenUpstream{flow}` set `in_flight_flow` just before.
1369        //   * established flow: resolve via the in-flight client source through
1370        //     the shell-side `client_key -> flow` shadow of the flow table.
1371        let flow = self.in_flight_flow.or_else(|| {
1372            self.in_flight_client
1373                .map(|src| self.client_key(src))
1374                .and_then(|key| self.client_key_to_flow.get(&key).copied())
1375        });
1376        let token = flow.and_then(|f| self.flow_to_upstream.get(&f).copied());
1377        let Some(token) = token else {
1378            // No resolved flow / upstream socket: this is not a queue-full drop.
1379            incr!(names::udp::DROPPED_UNKNOWN_FLOW);
1380            return;
1381        };
1382        let Some(socket) = self.upstream_sockets.get(&token) else {
1383            // Socket already gone (flow closed mid-drain): unknown-flow, not
1384            // queue-full.
1385            incr!(names::udp::DROPPED_UNKNOWN_FLOW);
1386            return;
1387        };
1388        // If a queue is already backed up for this flow, preserve FIFO order —
1389        // do NOT jump the line with the fast-path `send`. Append (or drop on
1390        // overflow) and let the WRITABLE drain catch up.
1391        if let Some(q) = self.upstream_write_queues.get_mut(&token)
1392            && !q.is_empty()
1393        {
1394            if !q.push(transmit.dst, transmit.payload) {
1395                debug!("{} upstream write queue full, dropping", log_context!(self));
1396                incr!(names::udp::DROPPED_WQ_FULL);
1397            }
1398            return;
1399        }
1400        match socket.send(&transmit.payload) {
1401            Ok(_) => {}
1402            Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
1403                // Kernel send buffer full: enqueue (bounded) and arm WRITABLE so
1404                // the next writable event drains it. Drop + metric only at cap.
1405                let q = self
1406                    .upstream_write_queues
1407                    .entry(token)
1408                    .or_insert_with(|| WriteQueue::new(UPSTREAM_WRITE_QUEUE_CAP));
1409                if q.push(transmit.dst, transmit.payload) {
1410                    self.arm_upstream_writable(token);
1411                } else {
1412                    debug!("{} upstream write queue full, dropping", log_context!(self));
1413                    incr!(names::udp::DROPPED_WQ_FULL);
1414                }
1415            }
1416            Err(e) => {
1417                debug!("{} upstream send error: {}", log_context!(self), e);
1418                // Hard send error (e.g. ECONNREFUSED), not a queue-full drop.
1419                incr!(names::udp::DROPPED_SEND_ERROR);
1420            }
1421        }
1422    }
1423
1424    /// Reregister a flow's connected upstream socket for `READABLE | WRITABLE`
1425    /// so a queued forward datagram gets a writable wake (the edge-triggered
1426    /// analog of `signal_pending_write`). Idempotent enough — mio coalesces a
1427    /// repeated interest set.
1428    fn arm_upstream_writable(&mut self, token: Token) {
1429        if let Some(socket) = self.upstream_sockets.get_mut(&token)
1430            && let Err(e) =
1431                self.registry
1432                    .reregister(socket, token, Interest::READABLE | Interest::WRITABLE)
1433        {
1434            debug!(
1435                "{} could not arm WRITABLE on upstream socket: {}",
1436                log_context!(self),
1437                e
1438            );
1439        }
1440    }
1441
1442    /// Drop a flow's upstream socket back to `READABLE`-only once its write queue
1443    /// has fully drained, so an empty socket no longer wakes the loop on every
1444    /// writable edge (a permanently-WRITABLE UDP socket would busy-loop).
1445    fn disarm_upstream_writable(&mut self, token: Token) {
1446        if let Some(socket) = self.upstream_sockets.get_mut(&token)
1447            && let Err(e) = self.registry.reregister(socket, token, Interest::READABLE)
1448        {
1449            debug!(
1450                "{} could not disarm WRITABLE on upstream socket: {}",
1451                log_context!(self),
1452                e
1453            );
1454        }
1455    }
1456
1457    /// Drain a flow's upstream write queue on a writable event. Re-sends in FIFO
1458    /// order until `WouldBlock` or empty; on empty, drops WRITABLE interest.
1459    fn drain_upstream_queue(&mut self, token: Token) {
1460        let Some(mut queue) = self.upstream_write_queues.remove(&token) else {
1461            return;
1462        };
1463        let socket = self.upstream_sockets.get(&token);
1464        let Some(socket) = socket else {
1465            // Socket gone (flow closed mid-drain): discard the queue.
1466            return;
1467        };
1468        let emptied = queue.drain(|_dst, payload| match socket.send(payload) {
1469            Ok(_) => SendOutcome::Sent,
1470            Err(ref e) if e.kind() == ErrorKind::WouldBlock => SendOutcome::WouldBlock,
1471            Err(_) => SendOutcome::Dropped,
1472        });
1473        if emptied {
1474            self.disarm_upstream_writable(token);
1475        } else {
1476            // Still backed up: put the queue back and keep WRITABLE armed.
1477            self.upstream_write_queues.insert(token, queue);
1478        }
1479    }
1480
1481    fn on_send_to_client(&mut self, transmit: crate::protocol::udp::Transmit) {
1482        // Preserve FIFO: if the client-return queue is already backed up, append
1483        // (or drop on overflow) rather than jumping the line via the fast path.
1484        if !self.client_write_queue.is_empty() {
1485            if !self.client_write_queue.push(transmit.dst, transmit.payload) {
1486                debug!("{} client write queue full, dropping", log_context!(self));
1487                incr!(names::udp::DROPPED_WQ_FULL);
1488            }
1489            return;
1490        }
1491        let send_result = {
1492            let listener = self.listener.borrow();
1493            let Some(socket) = listener.socket.as_ref() else {
1494                return;
1495            };
1496            socket.send_to(&transmit.payload, transmit.dst)
1497        };
1498        match send_result {
1499            Ok(_) => {}
1500            Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
1501                // Listener send buffer full: enqueue (bounded) + arm WRITABLE on
1502                // the listener so the next writable event drains it.
1503                if self.client_write_queue.push(transmit.dst, transmit.payload) {
1504                    self.arm_client_writable();
1505                } else {
1506                    debug!("{} client write queue full, dropping", log_context!(self));
1507                    incr!(names::udp::DROPPED_WQ_FULL);
1508                }
1509            }
1510            Err(e) => {
1511                debug!("{} client send_to error: {}", log_context!(self), e);
1512                // Hard send_to error, not a queue-full drop.
1513                incr!(names::udp::DROPPED_SEND_ERROR);
1514            }
1515        }
1516    }
1517
1518    /// Reregister the listener socket for `READABLE | WRITABLE` so a queued
1519    /// client-return datagram gets a writable wake. The listener token routes
1520    /// the writable event back into `update_readiness`.
1521    fn arm_client_writable(&mut self) {
1522        let listener = self.listener.borrow();
1523        let fd = match listener.socket.as_ref() {
1524            Some(socket) => socket.as_raw_fd(),
1525            None => return,
1526        };
1527        if let Err(e) = self.registry.reregister(
1528            &mut SourceFd(&fd),
1529            self.listener_token,
1530            Interest::READABLE | Interest::WRITABLE,
1531        ) {
1532            debug!(
1533                "{} could not arm WRITABLE on listener socket: {}",
1534                log_context!(self),
1535                e
1536            );
1537        }
1538    }
1539
1540    /// Drop the listener socket back to `READABLE`-only once the client-return
1541    /// queue is empty (a permanently-WRITABLE listener would busy-loop).
1542    fn disarm_client_writable(&mut self) {
1543        let listener = self.listener.borrow();
1544        let fd = match listener.socket.as_ref() {
1545            Some(socket) => socket.as_raw_fd(),
1546            None => return,
1547        };
1548        if let Err(e) =
1549            self.registry
1550                .reregister(&mut SourceFd(&fd), self.listener_token, Interest::READABLE)
1551        {
1552            debug!(
1553                "{} could not disarm WRITABLE on listener socket: {}",
1554                log_context!(self),
1555                e
1556            );
1557        }
1558    }
1559
1560    /// Drain the client-return write queue on a listener writable event. Re-sends
1561    /// in FIFO order until `WouldBlock` or empty; on empty, drops WRITABLE.
1562    fn drain_client_queue(&mut self) {
1563        let mut queue = std::mem::replace(&mut self.client_write_queue, WriteQueue::new(0));
1564        let emptied = {
1565            let listener = self.listener.borrow();
1566            let Some(socket) = listener.socket.as_ref() else {
1567                // Listener socket gone: discard the queue and restore an empty one.
1568                self.client_write_queue = WriteQueue::new(CLIENT_WRITE_QUEUE_CAP);
1569                return;
1570            };
1571            queue.drain(|dst, payload| match socket.send_to(payload, *dst) {
1572                Ok(_) => SendOutcome::Sent,
1573                Err(ref e) if e.kind() == ErrorKind::WouldBlock => SendOutcome::WouldBlock,
1574                Err(_) => SendOutcome::Dropped,
1575            })
1576        };
1577        // Restore the (possibly still-backed-up) queue; preserve its capacity.
1578        queue.cap = CLIENT_WRITE_QUEUE_CAP;
1579        self.client_write_queue = queue;
1580        if emptied {
1581            self.disarm_client_writable();
1582        }
1583    }
1584
1585    /// Map the single manager-wide `ArmTimer(deadline)` onto the thread-local
1586    /// `TIMER`, keyed by the **listener token**: when it fires, the run loop
1587    /// calls `Server::timeout(listener_token)` → `session.timeout(token)` →
1588    /// `manager.handle_timeout(now)`.
1589    fn arm_timer(&mut self, deadline: Instant, now: Instant) {
1590        let delay = deadline.saturating_duration_since(now);
1591        TIMER.with(|timer| {
1592            let mut timer = timer.borrow_mut();
1593            // Cancel the previous deadline so re-arming does not leak timer-slab
1594            // entries. The manager only emits a fresh `ArmTimer` on a real
1595            // deadline change, so cancellations are rare.
1596            if let Some(old) = self.timer_handle.take() {
1597                let _ = timer.cancel_timeout(&old);
1598            }
1599            self.timer_handle = Some(timer.set_timeout(delay, self.listener_token));
1600        });
1601    }
1602
1603    fn on_close_flow(&mut self, flow: FlowId) {
1604        if let Some(token) = self.flow_to_upstream.remove(&flow) {
1605            if let Some(mut socket) = self.upstream_sockets.remove(&token)
1606                && let Err(e) = self.registry.deregister(&mut socket)
1607            {
1608                debug!("{} deregister upstream on close: {}", log_context!(self), e);
1609            }
1610            // Drop any queued (un-drained) forward datagrams with the socket so
1611            // the per-flow queue cannot leak; gauge correctness is preserved
1612            // because the queue holds bytes, not flow-count state.
1613            self.upstream_write_queues.remove(&token);
1614            self.upstream_to_flow.remove(&token);
1615            self.sessions.borrow_mut().slab.try_remove(token.0);
1616            // On close, all per-flow maps drop the flow together: neither
1617            // direction of the upstream-token map may still reference it.
1618            debug_assert!(
1619                !self.upstream_to_flow.values().any(|&f| f == flow),
1620                "on_close_flow left upstream_to_flow referencing closed flow {flow}"
1621            );
1622            debug_assert!(
1623                !self.flow_to_upstream.contains_key(&flow),
1624                "on_close_flow left flow_to_upstream entry for closed flow {flow}"
1625            );
1626        }
1627        if let Some(started) = self.flow_started.remove(&flow) {
1628            let duration = started.elapsed();
1629            time!(names::udp::FLOW_DURATION, duration.as_millis());
1630        }
1631        let (client, backend) = self
1632            .flow_endpoints
1633            .remove(&flow)
1634            .unwrap_or((self.address, None));
1635        // Drop the shadow flow-table entry if it still points at this flow.
1636        let key = self.client_key(client);
1637        if self.client_key_to_flow.get(&key) == Some(&flow) {
1638            self.client_key_to_flow.remove(&key);
1639        }
1640        // The shadow flow-table must no longer map THIS flow id. A surviving
1641        // entry would misroute a later established-flow `SendToBackend` onto a
1642        // freed upstream token.
1643        debug_assert!(
1644            !self.client_key_to_flow.values().any(|&f| f == flow),
1645            "on_close_flow left client_key_to_flow referencing closed flow {flow}"
1646        );
1647        info!("{} flow closed", log_flow_context!(flow, client, backend));
1648    }
1649
1650    fn record_metric(ev: MetricEvent) {
1651        match ev {
1652            MetricEvent::FlowCreated => {
1653                incr!(names::udp::FLOWS_CREATED);
1654                gauge_add!(names::udp::ACTIVE_FLOWS, 1);
1655            }
1656            MetricEvent::FlowEvicted => {
1657                incr!(names::udp::FLOWS_EVICTED);
1658                gauge_add!(names::udp::ACTIVE_FLOWS, -1);
1659            }
1660            MetricEvent::FlowShed => {
1661                incr!(names::udp::FLOWS_SHED);
1662            }
1663            MetricEvent::DatagramIn(bytes) => {
1664                incr!(names::udp::DATAGRAMS_IN);
1665                count!(names::udp::BYTES_IN, bytes as i64);
1666            }
1667            MetricEvent::DatagramOut(bytes) => {
1668                incr!(names::udp::DATAGRAMS_OUT);
1669                count!(names::udp::BYTES_OUT, bytes as i64);
1670            }
1671            MetricEvent::DatagramDropped(reason) => Self::record_drop(reason),
1672        }
1673    }
1674
1675    fn record_drop(reason: DropReason) {
1676        incr!(names::udp::DATAGRAMS_DROPPED);
1677        match reason {
1678            DropReason::Invalid => incr!(names::udp::DROPPED_INVALID),
1679            DropReason::Truncated => incr!(names::udp::DROPPED_TRUNCATED),
1680            DropReason::NoBackend => incr!(names::udp::DROPPED_NO_BACKEND),
1681            DropReason::Shed => incr!(names::udp::DROPPED_SHED),
1682            DropReason::UnknownFlow => incr!(names::udp::DROPPED_UNKNOWN_FLOW),
1683        }
1684    }
1685
1686    /// Tear down every active flow on this listener **through the manager**, so
1687    /// each close emits `FlowEvicted` + `CloseFlow` exactly once and the shell's
1688    /// normal [`on_close_flow`](Self::on_close_flow) handler frees the upstream
1689    /// socket + slab slot and decrements `udp.active_flows`. Used on soft/hard
1690    /// stop, listener remove, and listener deactivate so the worker reaches its
1691    /// `base_sessions_count` and exits promptly instead of waiting out every
1692    /// flow's idle timeout — and so the active-flows gauge does not leak by N
1693    /// (the bug the old direct-teardown path had: it cleared the shell maps
1694    /// without telling the manager, so `FlowEvicted` never fired).
1695    ///
1696    /// On a deactivate where the manager is *retained* (not dropped), this also
1697    /// resets the manager's flow table to empty, keeping manager and shell
1698    /// consistent. The listener socket and the listener-session slab slot are
1699    /// left intact (the listener slot is part of `base_sessions_count` and
1700    /// reclaimed when the worker exits); only the connectionless per-flow slots
1701    /// — which are NOT counted in `nb_connections` and so removed without
1702    /// `decr` — are freed.
1703    pub fn close_all_flows(&mut self, now: Instant) {
1704        // Drive teardown through the manager: it emits one `FlowEvicted` +
1705        // `CloseFlow` per live flow into its output queue. Draining those runs
1706        // `record_metric(FlowEvicted)` (the single gauge decrement) and
1707        // `on_close_flow` (frees socket + slab slot + shell maps) per flow.
1708        self.manager.borrow_mut().close_all(now);
1709        self.drain_outputs(now);
1710        // After `close_all`, the manager's flow table is empty and it armed no
1711        // new timer (`reschedule` emits `ArmTimer` only on a real deadline
1712        // change, and an empty table has no deadline). Cancel any residual shell
1713        // timer so it can't fire against a now-flowless listener.
1714        if let Some(handle) = self.timer_handle.take() {
1715            TIMER.with(|timer| {
1716                let _ = timer.borrow_mut().cancel_timeout(&handle);
1717            });
1718        }
1719    }
1720}
1721
1722impl ProxySession for UdpListenerSession {
1723    fn protocol(&self) -> Protocol {
1724        Protocol::UDPListen
1725    }
1726
1727    fn update_readiness(&mut self, token: Token, events: Ready) {
1728        // WRITABLE first: a previously-`WouldBlock` socket can now accept queued
1729        // egress. Drain before reading so the kernel send buffer has room before
1730        // this pass enqueues more (and so a writable-only event still drains).
1731        if events.is_writable() {
1732            if token == self.listener_token {
1733                self.drain_client_queue();
1734            } else if self.upstream_to_flow.contains_key(&token) {
1735                self.drain_upstream_queue(token);
1736            }
1737        }
1738        if !events.is_readable() {
1739            return;
1740        }
1741        let now = Instant::now();
1742        if token == self.listener_token {
1743            self.ingest_client(now);
1744        } else if self.upstream_to_flow.contains_key(&token) {
1745            self.ingest_upstream(token, now);
1746        }
1747    }
1748
1749    fn ready(&mut self, _session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
1750        // All work happens in `update_readiness` (it has the firing token; the
1751        // generic `ready()` does not). Never close the listener session here —
1752        // it lives for the listener's lifetime.
1753        false
1754    }
1755
1756    fn timeout(&mut self, token: Token) -> SessionIsToBeClosed {
1757        if token == self.listener_token {
1758            let now = Instant::now();
1759            self.manager.borrow_mut().handle_timeout(now);
1760            self.drain_outputs(now);
1761            // Re-arm: the manager emits a fresh ArmTimer via poll_output if a
1762            // flow is still scheduled (handled inside drain_outputs). Nothing
1763            // to do here. Never close the listener on a flow timeout.
1764        }
1765        false
1766    }
1767
1768    fn close(&mut self) {
1769        // Tear down any still-live flows THROUGH the manager (FlowEvicted +
1770        // CloseFlow per flow) so `udp.active_flows` balances to zero even if the
1771        // server reaps this listener session directly without a prior
1772        // proxy-driven `close_all_flows`. In the common path
1773        // (`close_all_flows` already ran) the manager has no live flows, so this
1774        // is a cheap no-op. Also cancels the residual idle timer.
1775        self.close_all_flows(Instant::now());
1776        // Any leftover egress queues / shell maps are dropped here (the per-flow
1777        // ones were already freed by the manager-driven close above; this only
1778        // resets the client-return queue, which is not flow-state).
1779        self.upstream_write_queues.clear();
1780        self.client_write_queue = WriteQueue::new(CLIENT_WRITE_QUEUE_CAP);
1781        self.upstream_to_flow.clear();
1782        self.flow_to_upstream.clear();
1783        // Deregister + drop the listener socket. Never shutdown(Both) — UDP has
1784        // no connection to shut down; just deregister + drop the fds.
1785        let mut listener = self.listener.borrow_mut();
1786        if let Some(socket) = listener.socket.as_ref() {
1787            let fd = socket.as_raw_fd();
1788            let _ = self.registry.deregister(&mut SourceFd(&fd));
1789        }
1790        listener.active = false;
1791    }
1792
1793    fn last_event(&self) -> Instant {
1794        // The listener session lives for the listener's lifetime — it is never a
1795        // zombie even when idle. Mirror `ListenSession::last_event` (which
1796        // returns `now`) so the `zombie_check` (which has no listener-protocol
1797        // exclusion) never reaps a quiet UDP listener. Per-flow idle reaping is
1798        // handled by the manager's timer wheel, not the zombie sweep.
1799        Instant::now()
1800    }
1801
1802    fn print_session(&self) {
1803        error!(
1804            "{} UDP listener session: {} active flows, {} upstream sockets",
1805            log_context!(self),
1806            self.manager.borrow().flow_count(),
1807            self.upstream_sockets.len(),
1808        );
1809    }
1810
1811    fn frontend_token(&self) -> Token {
1812        self.listener_token
1813    }
1814
1815    fn shutting_down(&mut self) -> SessionIsToBeClosed {
1816        // The listener session is a *listener*, not a connection: it was never
1817        // counted by `SessionManager::incr` (only accepted sessions are), so it
1818        // MUST NOT be routed through the connection-counted
1819        // `shut_down_sessions_by_frontend_tokens` path — that calls `decr`,
1820        // which underflows `nb_connections` and panics (`assert!(nb != 0)` at
1821        // `server.rs`). This mirrors `ListenSession::shutting_down` (which also
1822        // returns `false`). Soft-stop draining is owned by
1823        // `UdpProxy::notify(SoftStop)`: it flips every manager to `Drain` and
1824        // deregisters the listener sockets, so existing flows reach teardown and
1825        // no new flow is admitted. The slot is reclaimed when the worker exits.
1826        false
1827    }
1828
1829    fn cluster_id(&self) -> Option<String> {
1830        self.listener.borrow().cluster_id.clone()
1831    }
1832}
1833
1834#[allow(unused_imports)]
1835pub(crate) use {log_context, log_flow_context, log_module_context};
1836
1837#[cfg(test)]
1838mod tests {
1839    use super::*;
1840
1841    use std::cell::Cell;
1842
1843    #[test]
1844    fn effective_max_flows_explicit_value_is_used() {
1845        // An explicit value is honoured verbatim, ignoring the slab headroom
1846        // clamp (the operator opted in).
1847        assert_eq!(effective_max_flows(42, 0), 42);
1848        assert_eq!(effective_max_flows(42, 10), 42);
1849    }
1850
1851    #[test]
1852    fn effective_max_flows_auto_is_positive() {
1853        // 0 = auto: derives ~70% of RLIMIT_NOFILE, always >= 1.
1854        assert!(effective_max_flows(0, 0) >= 1);
1855    }
1856
1857    #[test]
1858    fn effective_max_flows_auto_is_clamped_to_slab_headroom() {
1859        // Auto derivation is capped at the slab headroom (max_connections) so a
1860        // UDP listener can't inflate the shared slab. A headroom of 0 disables
1861        // the clamp.
1862        assert!(effective_max_flows(0, 4) <= 4);
1863        assert!(effective_max_flows(0, 4) >= 1);
1864    }
1865
1866    #[test]
1867    fn clamp_max_rx_respects_buffer_size() {
1868        // The configured rx size is clamped to buffer_size; an unset (0)
1869        // buffer_size leaves it untouched.
1870        assert_eq!(clamp_max_rx(u32::MAX as usize, 16_384), 16_384);
1871        assert_eq!(clamp_max_rx(1_024, 16_384), 1_024);
1872        assert_eq!(clamp_max_rx(u32::MAX as usize, 0), u32::MAX as usize);
1873    }
1874
1875    fn addr(port: u16) -> SocketAddr {
1876        SocketAddr::from(([127, 0, 0, 1], port))
1877    }
1878
1879    /// A fake socket whose `send` outcome is scripted per call. Lets the
1880    /// `WriteQueue` state machine be exercised with zero real I/O: we feed a
1881    /// sequence of `SendOutcome`s and record what was sent in order.
1882    struct FakeSocket {
1883        /// Outcomes returned by successive `send` calls (front = next).
1884        script: RefCell<VecDeque<SendOutcome>>,
1885        /// Payloads that `Sent`/`Dropped` actually consumed, in order.
1886        consumed: RefCell<Vec<Vec<u8>>>,
1887        /// Default outcome once the script is exhausted.
1888        default: Cell<bool>, // true = Sent, false = WouldBlock
1889    }
1890
1891    impl FakeSocket {
1892        fn new(script: Vec<SendOutcome>, default_sent: bool) -> Self {
1893            FakeSocket {
1894                script: RefCell::new(script.into()),
1895                consumed: RefCell::new(Vec::new()),
1896                default: Cell::new(default_sent),
1897            }
1898        }
1899
1900        fn send(&self, payload: &[u8]) -> SendOutcome {
1901            let outcome = self.script.borrow_mut().pop_front().unwrap_or({
1902                if self.default.get() {
1903                    SendOutcome::Sent
1904                } else {
1905                    SendOutcome::WouldBlock
1906                }
1907            });
1908            if matches!(outcome, SendOutcome::Sent | SendOutcome::Dropped) {
1909                self.consumed.borrow_mut().push(payload.to_vec());
1910            }
1911            outcome
1912        }
1913    }
1914
1915    #[test]
1916    fn write_queue_push_until_full_then_drops() {
1917        let mut q = WriteQueue::new(2);
1918        assert!(q.is_empty());
1919        assert!(q.push(addr(1), vec![1]));
1920        assert!(q.push(addr(2), vec![2]));
1921        assert_eq!(q.len(), 2);
1922        // At capacity: the third push is rejected (caller drops + counts).
1923        assert!(!q.push(addr(3), vec![3]));
1924        assert_eq!(q.len(), 2);
1925    }
1926
1927    #[test]
1928    fn write_queue_drains_in_fifo_order_on_writable() {
1929        let mut q = WriteQueue::new(8);
1930        for i in 0..4u8 {
1931            assert!(q.push(addr(i as u16), vec![i]));
1932        }
1933        // All sends succeed: drain empties the queue, FIFO preserved.
1934        let sock = FakeSocket::new(vec![], true);
1935        let emptied = q.drain(|_dst, payload| sock.send(payload));
1936        assert!(emptied);
1937        assert!(q.is_empty());
1938        assert_eq!(
1939            *sock.consumed.borrow(),
1940            vec![vec![0u8], vec![1u8], vec![2u8], vec![3u8]]
1941        );
1942    }
1943
1944    #[test]
1945    fn write_queue_stops_on_wouldblock_and_resumes() {
1946        let mut q = WriteQueue::new(8);
1947        for i in 0..3u8 {
1948            assert!(q.push(addr(i as u16), vec![i]));
1949        }
1950        // First send ok, then WouldBlock: drain sends one and stops, leaving two.
1951        let sock = FakeSocket::new(vec![SendOutcome::Sent, SendOutcome::WouldBlock], false);
1952        let emptied = q.drain(|_dst, payload| sock.send(payload));
1953        assert!(!emptied);
1954        assert_eq!(q.len(), 2);
1955        assert_eq!(*sock.consumed.borrow(), vec![vec![0u8]]);
1956        // Front is still the second datagram (FIFO preserved across the stall).
1957        assert_eq!(q.queue.front().unwrap().1, vec![1u8]);
1958        // Second writable event: now everything goes through.
1959        let sock2 = FakeSocket::new(vec![], true);
1960        let emptied2 = q.drain(|_dst, payload| sock2.send(payload));
1961        assert!(emptied2);
1962        assert!(q.is_empty());
1963        assert_eq!(*sock2.consumed.borrow(), vec![vec![1u8], vec![2u8]]);
1964    }
1965
1966    #[test]
1967    fn write_queue_hard_error_drops_one_and_continues() {
1968        let mut q = WriteQueue::new(8);
1969        for i in 0..3u8 {
1970            assert!(q.push(addr(i as u16), vec![i]));
1971        }
1972        // Middle datagram hard-errors: it is popped + skipped, the rest proceed.
1973        let sock = FakeSocket::new(
1974            vec![SendOutcome::Sent, SendOutcome::Dropped, SendOutcome::Sent],
1975            true,
1976        );
1977        let emptied = q.drain(|_dst, payload| sock.send(payload));
1978        assert!(emptied);
1979        assert!(q.is_empty());
1980        // The dropped datagram (1) was consumed-for-accounting but the queue is
1981        // empty and the surviving datagrams (0, 2) went out in order.
1982        assert_eq!(
1983            *sock.consumed.borrow(),
1984            vec![vec![0u8], vec![1u8], vec![2u8]]
1985        );
1986    }
1987
1988    #[test]
1989    fn write_queue_empties_cleanly_when_already_empty() {
1990        let mut q = WriteQueue::new(4);
1991        let sock = FakeSocket::new(vec![], true);
1992        // Draining an empty queue is a no-op that reports emptied = true.
1993        let emptied = q.drain(|_dst, payload| sock.send(payload));
1994        assert!(emptied);
1995        assert!(q.is_empty());
1996        assert!(sock.consumed.borrow().is_empty());
1997    }
1998}