Skip to main content

sozu_lib/
udp.rs

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