Skip to main content

sozu_lib/
tcp.rs

1use std::{
2    cell::RefCell,
3    collections::{BTreeMap, HashMap, hash_map::Entry},
4    io::ErrorKind,
5    net::{Shutdown, SocketAddr},
6    rc::Rc,
7    time::{Duration, Instant},
8};
9
10use mio::{
11    Interest, Registry, Token,
12    net::{TcpListener as MioTcpListener, TcpStream as MioTcpStream},
13};
14use rusty_ulid::Ulid;
15use socket2::SockRef;
16use sozu_command::{
17    ObjectKind,
18    config::{
19        DEFAULT_SNI_PREREAD_MAX_BYTES, DEFAULT_SNI_PREREAD_TIMEOUT, MAX_LOOP_ITERATIONS,
20        MIN_SNI_PREREAD_MAX_BYTES, validate_sni_pattern,
21    },
22    listener_key::ListenerKey,
23    logging::{EndpointRecord, LogContext, ansi_palette},
24    proto::command::request::RequestType,
25};
26
27use crate::metrics::names;
28use crate::router::pattern_trie::{InsertResult, TrieNode};
29use crate::{
30    AcceptError, BackendConnectAction, BackendConnectionError, BackendConnectionStatus, CachedTags,
31    ListenerError, ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession,
32    Readiness, SessionIsToBeClosed, SessionMetrics, SessionResult, StateMachineBuilder,
33    backends::{Backend, BackendMap},
34    load_balancing::affinity_key_from_ip,
35    pool::{Checkout, Pool},
36    protocol::mux::router::cluster_reads_affinity_key,
37    protocol::{
38        Pipe,
39        pipe::WebSocketContext,
40        proxy_protocol::{
41            expect::ExpectProxyProtocol, relay::RelayProxyProtocol, send::SendProxyProtocol,
42        },
43        tcp_preread::{AlpnMatcher, PrereadConfig, shell::SniPreread},
44    },
45    retry::RetryPolicy,
46    server::{ListenToken, SessionManager, push_event},
47    socket::{server_bind, stats::socket_rtt},
48    sozu_command::{
49        proto::command::{
50            Event, EventKind, ProxyProtocolConfig, RequestTcpFrontend, TcpListenerConfig,
51            UpdateTcpListenerConfig, WorkerRequest, WorkerResponse,
52        },
53        ready::Ready,
54        state::ClusterId,
55    },
56    timer::TimeoutContainer,
57};
58
59StateMachineBuilder! {
60    /// The various Stages of a TCP connection:
61    ///
62    /// 1. optional SniPreread (SNI-routed listeners only, sozu-proxy/sozu#1279)
63    /// 2. optional (ExpectProxyProtocol | SendProxyProtocol | RelayProxyProtocol)
64    /// 3. Pipe
65    enum TcpStateMachine {
66        Pipe(Pipe<MioTcpStream, TcpListener>),
67        SendProxyProtocol(SendProxyProtocol<MioTcpStream>),
68        RelayProxyProtocol(RelayProxyProtocol<MioTcpStream>),
69        ExpectProxyProtocol(ExpectProxyProtocol<MioTcpStream>),
70        SniPreread(SniPreread<MioTcpStream>),
71    }
72}
73
74/// This macro is defined uniquely in this module to help the tracking of kawa h1
75/// issues inside Sōzu. Colored output uses the unified log-context scheme:
76/// bold bright-white protocol label, light-grey `Session` keyword, gray keys
77/// and bright-white values.
78macro_rules! log_context {
79    ($self:expr) => {{
80        let (open, reset, grey, gray, white) = ansi_palette();
81        format!(
82            "{gray}{ctx}{reset}\t{open}TCP{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend}{reset}, {gray}backend{reset}={white}{backend}{reset})\t >>>",
83            open = open,
84            reset = reset,
85            grey = grey,
86            gray = gray,
87            white = white,
88            ctx = $self.log_context(),
89            frontend = $self.frontend_token.0,
90            backend = $self
91                .backend_token
92                .map(|token| token.0.to_string())
93                .unwrap_or_else(|| "<none>".to_string()),
94        )
95    }};
96}
97
98/// Module-level prefix for log lines emitted from this file when no
99/// [`TcpSession`] is in scope. Produces a bold bright-white `TCP` label
100/// (uniform with the per-session `log_context!`) when the logger is in
101/// colored mode. Used by [`TcpProxy`] callbacks (notify, accept,
102/// create_session, soft_stop, hard_stop, status) and the `testing`
103/// helper module which own a listener/token map but have no
104/// `frontend_token` of their own.
105macro_rules! log_module_context {
106    () => {{
107        let (open, reset, _, _, _) = sozu_command::logging::ansi_palette();
108        format!("{open}TCP{reset}\t >>>", open = open, reset = reset)
109    }};
110}
111
112pub struct TcpSession {
113    backend_buffer: Option<Checkout>,
114    backend_connected: BackendConnectionStatus,
115    backend_id: Option<String>,
116    /// Address of the backend `connect_to_backend` last dialed, handed to the
117    /// `Pipe` for its access log (`Pipe::set_backend_address`), so the log
118    /// never asks the socket with `getpeername(2)`.
119    backend_address: Option<SocketAddr>,
120    backend_token: Option<Token>,
121    backend: Option<Rc<RefCell<Backend>>>,
122    cluster_id: Option<ClusterId>,
123    /// Configured lifetime of `cluster_id` captured when this session routes.
124    /// Kept on the session rather than the public `SessionMetrics` value so
125    /// delayed TCP and Pipe emissions cannot target a replacement cluster.
126    cluster_metrics_incarnation: crate::metrics::ClusterMetricsCapture,
127    configured_backend_timeout: Duration,
128    connection_attempt: u8,
129    container_backend_timeout: TimeoutContainer,
130    container_frontend_timeout: TimeoutContainer,
131    frontend_address: Option<SocketAddr>,
132    frontend_buffer: Option<Checkout>,
133    frontend_token: Token,
134    has_been_closed: SessionIsToBeClosed,
135    last_event: Instant,
136    listener: Rc<RefCell<TcpListener>>,
137    metrics: SessionMetrics,
138    proxy: Rc<RefCell<TcpProxy>>,
139    request_id: Ulid,
140    state: TcpStateMachine,
141    /// `true` once `connect_to_backend` has accounted this session
142    /// against the per-(cluster, source-IP) connection counter. Drives
143    /// the symmetric `untrack_all_cluster_ip` call in `close`. The flag
144    /// is per-session, not per-attempt: a TCP session has at most one
145    /// `(cluster, ip)` slot, so the SessionManager-side idempotency
146    /// already covers retries — this flag exists only to short-circuit
147    /// the close path's untrack when the feature is disabled or no
148    /// admit ever ran.
149    cluster_ip_tracked: bool,
150    /// SNI-preread routing result (sozu-proxy/sozu#1279), captured once by
151    /// `upgrade_sni_preread` for every `proxy_protocol` case and consumed
152    /// (`Option::take`) at the point the session actually reaches `Pipe`:
153    /// immediately in `build_pipe_from_preread` for
154    /// `Expect`/`Relay`/`None`, or one `ready()` cycle later in
155    /// `upgrade_send` for `SendHeader` (which transitions through
156    /// `SendProxyProtocol` first). `None` for every non-SNI-routed session.
157    routed_sni: Option<String>,
158    /// Paired with `routed_sni`: the client's first ALPN offer, mapped to a
159    /// known `&'static str` label (`"h2"` / `"http/1.1"`) for the access
160    /// log, or `None` if the client offered nothing recognized. Sōzu never
161    /// negotiates ALPN itself on the TCP passthrough path -- the backend
162    /// terminates TLS -- so this is informational (the client's
163    /// preference), not a negotiated value.
164    routed_alpn_label: Option<&'static str>,
165    /// Canonical access-log tags key for the MATCHED SNI/ALPN frontend
166    /// (`sni_tags_key`), rebuilt in `upgrade_sni_preread` from the route
167    /// decision's `matched_sni_pattern` + `matched_alpn`. `None` for every
168    /// non-SNI-routed session, whose tags stay keyed by the bare listener
169    /// address exactly as before SNI routing existed. Unlike `routed_sni`
170    /// it is never consumed: `log_request` reads it for the session-level
171    /// access log, and the `Pipe` receives a clone at upgrade time for the
172    /// post-upgrade log.
173    tags_key: Option<String>,
174}
175
176impl TcpSession {
177    #[allow(clippy::too_many_arguments)]
178    fn new(
179        backend_buffer: Checkout,
180        backend_id: Option<String>,
181        cluster_id: Option<ClusterId>,
182        configured_backend_timeout: Duration,
183        configured_connect_timeout: Duration,
184        configured_frontend_timeout: Duration,
185        frontend_buffer: Checkout,
186        frontend_token: Token,
187        listener: Rc<RefCell<TcpListener>>,
188        proxy_protocol: Option<ProxyProtocolConfig>,
189        proxy: Rc<RefCell<TcpProxy>>,
190        socket: MioTcpStream,
191        peer: SocketAddr,
192        wait_time: Duration,
193    ) -> TcpSession {
194        // The address `accept(2)` returned with `socket`.
195        let frontend_address = Some(peer);
196        let mut frontend_buffer_session = None;
197        let mut backend_buffer_session = None;
198
199        let request_id = Ulid::generate();
200
201        let container_frontend_timeout =
202            TimeoutContainer::new(configured_frontend_timeout, frontend_token);
203        let container_backend_timeout = TimeoutContainer::new_empty(configured_connect_timeout);
204        let cluster_metrics_incarnation = cluster_id
205            .as_deref()
206            .map_or_else(Default::default, crate::metrics::capture_cluster_metrics);
207
208        let state = match proxy_protocol {
209            Some(ProxyProtocolConfig::RelayHeader) => {
210                backend_buffer_session = Some(backend_buffer);
211                gauge_add!(names::protocol::PROXY_RELAY, 1);
212                TcpStateMachine::RelayProxyProtocol(RelayProxyProtocol::new(
213                    socket,
214                    frontend_token,
215                    request_id,
216                    None,
217                    frontend_buffer,
218                ))
219            }
220            Some(ProxyProtocolConfig::ExpectHeader) => {
221                frontend_buffer_session = Some(frontend_buffer);
222                backend_buffer_session = Some(backend_buffer);
223                gauge_add!(names::protocol::PROXY_EXPECT, 1);
224                TcpStateMachine::ExpectProxyProtocol(ExpectProxyProtocol::new(
225                    container_frontend_timeout.clone(),
226                    socket,
227                    frontend_token,
228                    request_id,
229                ))
230            }
231            Some(ProxyProtocolConfig::SendHeader) => {
232                frontend_buffer_session = Some(frontend_buffer);
233                backend_buffer_session = Some(backend_buffer);
234                gauge_add!(names::protocol::PROXY_SEND, 1);
235                TcpStateMachine::SendProxyProtocol(SendProxyProtocol::new(
236                    socket,
237                    frontend_token,
238                    request_id,
239                    None,
240                ))
241            }
242            None => {
243                gauge_add!(names::protocol::TCP, 1);
244                let mut pipe = Pipe::new(
245                    backend_buffer,
246                    backend_id.clone(),
247                    None,
248                    None,
249                    None,
250                    None,
251                    cluster_id.clone(),
252                    frontend_buffer,
253                    frontend_token,
254                    socket,
255                    listener.clone(),
256                    Protocol::TCP,
257                    request_id,
258                    request_id,
259                    frontend_address,
260                    WebSocketContext::Tcp,
261                );
262                pipe.set_cluster_id(cluster_id.clone());
263                pipe.set_cluster_metrics_incarnation(cluster_metrics_incarnation);
264                TcpStateMachine::Pipe(pipe)
265            }
266        };
267
268        let metrics = SessionMetrics::new(Some(wait_time));
269        //FIXME: timeout usage
270
271        TcpSession {
272            backend_buffer: backend_buffer_session,
273            backend_connected: BackendConnectionStatus::NotConnected,
274            backend_id,
275            backend_address: None,
276            backend_token: None,
277            backend: None,
278            cluster_id,
279            cluster_metrics_incarnation,
280            configured_backend_timeout,
281            connection_attempt: 0,
282            container_backend_timeout,
283            container_frontend_timeout,
284            frontend_address,
285            frontend_buffer: frontend_buffer_session,
286            frontend_token,
287            has_been_closed: false,
288            last_event: Instant::now(),
289            listener,
290            metrics,
291            proxy,
292            request_id,
293            state,
294            cluster_ip_tracked: false,
295            routed_sni: None,
296            routed_alpn_label: None,
297            tags_key: None,
298        }
299    }
300
301    /// Construct a session that starts in [`TcpStateMachine::SniPreread`]
302    /// instead of resolving a `proxy_protocol` up front -- the cluster (and
303    /// therefore the per-cluster `proxy_protocol`) is only known once
304    /// [`crate::protocol::tcp_preread::SniPrereadCore`] decides a route.
305    /// Mirrors [`Self::new`]'s tail; kept as a separate constructor rather
306    /// than folding a synthetic sentinel into `proxy_protocol:
307    /// Option<ProxyProtocolConfig>` (a proto-generated enum this crate does
308    /// not own).
309    #[allow(clippy::too_many_arguments)]
310    fn new_sni_preread(
311        backend_buffer: Checkout,
312        configured_backend_timeout: Duration,
313        configured_connect_timeout: Duration,
314        frontend_buffer: Checkout,
315        frontend_token: Token,
316        listener: Rc<RefCell<TcpListener>>,
317        proxy: Rc<RefCell<TcpProxy>>,
318        socket: MioTcpStream,
319        peer: SocketAddr,
320        wait_time: Duration,
321        preread_timeout: Duration,
322        effective_max_bytes: usize,
323    ) -> TcpSession {
324        // The address `accept(2)` returned with `socket`.
325        let frontend_address = Some(peer);
326        let request_id = Ulid::generate();
327
328        // Armed with the SHORT preread timeout directly (not the listener's
329        // configured front_timeout) -- `upgrade_sni_preread` restores the
330        // configured duration on the SAME container once routed, so there is
331        // exactly one `TimeoutContainer` for the frontend token throughout,
332        // never a diverging clone (a clone independently rearmed to a
333        // shorter duration would strand `TcpSession::readable`'s own
334        // unconditional `reset()` on a since-cancelled timer-wheel entry).
335        let container_frontend_timeout = TimeoutContainer::new(preread_timeout, frontend_token);
336        let container_backend_timeout = TimeoutContainer::new_empty(configured_connect_timeout);
337
338        let state = TcpStateMachine::SniPreread(SniPreread::new(
339            socket,
340            frontend_token,
341            request_id,
342            frontend_buffer,
343            effective_max_bytes,
344        ));
345
346        // Enter the `SniPreread` state: +1 the active gauge exactly once, and
347        // unconditionally, so every one of the two `-1` decrements has a
348        // matching increment. The gauge is decremented on precisely one of the
349        // two mutually-exclusive exits: the "upgrade" exit in
350        // `upgrade_sni_preread` (which first transitions `self.state` away from
351        // `SniPreread`, so `close()` cannot re-decrement), and the
352        // "reject"/"teardown" exit in `close()`'s `StateMarker::SniPreread`
353        // arm. A session therefore nets to 0 and never underflows.
354        gauge_add!(names::tcp::sni_preread::ACTIVE, 1);
355
356        let metrics = SessionMetrics::new(Some(wait_time));
357
358        TcpSession {
359            backend_buffer: Some(backend_buffer),
360            backend_connected: BackendConnectionStatus::NotConnected,
361            backend_id: None,
362            backend_address: None,
363            backend_token: None,
364            backend: None,
365            cluster_id: None,
366            cluster_metrics_incarnation: Default::default(),
367            configured_backend_timeout,
368            connection_attempt: 0,
369            container_backend_timeout,
370            container_frontend_timeout,
371            frontend_address,
372            frontend_buffer: None,
373            frontend_token,
374            has_been_closed: false,
375            last_event: Instant::now(),
376            listener,
377            metrics,
378            proxy,
379            request_id,
380            state,
381            cluster_ip_tracked: false,
382            routed_sni: None,
383            routed_alpn_label: None,
384            tags_key: None,
385        }
386    }
387
388    /// Source-IP for per-(cluster, source-IP) accounting.
389    ///
390    /// Prefer the parsed PROXY-v2 source from whichever upgrade phase is
391    /// in flight, then the post-upgrade `Pipe.session_address`, finally
392    /// the raw TCP `peer_addr` captured at session creation. The
393    /// `Pipe::session_address` itself is already PROXY-v2-aware after
394    /// `expect.rs::into_pipe` and `relay.rs::into_pipe`.
395    fn effective_session_address(&self) -> Option<SocketAddr> {
396        match &self.state {
397            TcpStateMachine::Pipe(pipe) => pipe.get_session_address(),
398            TcpStateMachine::ExpectProxyProtocol(epp) => {
399                epp.addresses.as_ref().and_then(|pa| pa.source())
400            }
401            TcpStateMachine::RelayProxyProtocol(rpp) => {
402                rpp.addresses.as_ref().and_then(|pa| pa.source())
403            }
404            TcpStateMachine::SniPreread(preread) => preread.outcome().and_then(|o| o.proxy_source),
405            TcpStateMachine::SendProxyProtocol(_) | TcpStateMachine::FailedUpgrade(_) => None,
406        }
407        .or(self.frontend_address)
408    }
409
410    fn log_request(&self) {
411        let listener = self.listener.borrow();
412        let context = self.log_context();
413        self.metrics
414            .register_end_of_session_for_incarnation(&context, self.cluster_metrics_incarnation);
415        // SNI-routed sessions carry the matched front's own tags key
416        // (`sni_tags_key`, stashed by `upgrade_sni_preread`); everything
417        // else keeps the historical bare-address key.
418        let address_key = TcpFrontendTagsKey::Address(*listener.get_addr()).to_string();
419        let tags_key = self.tags_key.as_deref().unwrap_or(&address_key);
420        info_access!(
421            on_failure: { incr!(names::access_logs::UNSENT) },
422            message: None,
423            context,
424            session_address: self.frontend_address,
425            backend_address: None,
426            protocol: "TCP",
427            endpoint: EndpointRecord::Tcp,
428            tags: listener.get_tags(tags_key),
429            client_rtt: socket_rtt(self.state.front_socket()),
430            server_rtt: None,
431            user_agent: None,
432            x_request_id: None,
433            // Sōzu never terminates TLS on the TCP path (the frontend is a
434            // raw `MioTcpStream`), so no negotiated version/cipher exists.
435            // A preread SNI/ALPN (SNI-routed listeners) is stamped on the
436            // `Pipe`'s own access log via `set_tls_metadata` at upgrade
437            // time; this pre-Pipe log site emits `None` for all four TLS
438            // fields and the parsed XFF chain.
439            tls_version: None,
440            tls_cipher: None,
441            tls_sni: None,
442            tls_alpn: None,
443            xff_chain: None,
444            service_time: self.metrics.service_time(),
445            response_time: self.metrics.backend_response_time(),
446            request_time: self.metrics.request_time(),
447            start_time_ns: self.metrics.start_wall_ns(),
448            bytes_in: self.metrics.bin,
449            bytes_out: self.metrics.bout,
450            otel: None,
451        );
452    }
453
454    fn front_hup(&mut self) -> SessionResult {
455        let listener = self.listener.borrow();
456        match &mut self.state {
457            TcpStateMachine::Pipe(pipe) => pipe.frontend_hup(&mut self.metrics),
458            // No access log here, mirroring `readable()`'s own error paths
459            // for the other pre-Pipe states (none of them call
460            // `log_request()` either): the shell itself decides silent vs.
461            // metered based on whether any bytes were ever received.
462            TcpStateMachine::SniPreread(preread) => {
463                let cfg = listener.preread_config(preread.effective_max_bytes());
464                preread.on_front_closed(&cfg);
465                SessionResult::Close
466            }
467            _ => {
468                self.log_request();
469                SessionResult::Close
470            }
471        }
472    }
473
474    fn back_hup(&mut self) -> SessionResult {
475        // `SniPreread` falls into the wildcard catch-all below (unconditional
476        // close + access log), same as Send/Relay/Expect: a backend HUP
477        // while still prereading is an ordinary connect-time failure with no
478        // preread-specific accounting to do (the core only ever reasons
479        // about frontend bytes).
480        match &mut self.state {
481            TcpStateMachine::Pipe(pipe) => pipe.backend_hup(&mut self.metrics),
482            _ => {
483                self.log_request();
484                SessionResult::Close
485            }
486        }
487    }
488
489    fn log_context(&self) -> LogContext<'_> {
490        LogContext {
491            session_id: self.request_id,
492            request_id: Some(self.request_id),
493            cluster_id: self.cluster_id.as_deref(),
494            backend_id: self.backend_id.as_deref(),
495        }
496    }
497
498    fn readable(&mut self) -> SessionResult {
499        // The absolute SNI-preread deadline (armed once, at session
500        // creation) must stand while undecided -- see
501        // `frontend_timeout_resets_on_readable`'s doc.
502        if frontend_timeout_resets_on_readable(&self.state)
503            && !self.container_frontend_timeout.reset()
504        {
505            error!(
506                "{} Could not reset frontend timeout on readable",
507                log_context!(self)
508            );
509        }
510        if self.backend_connected == BackendConnectionStatus::Connected
511            && !self.container_backend_timeout.reset()
512        {
513            error!(
514                "{} Could not reset backend timeout on readable",
515                log_context!(self)
516            );
517        }
518        let listener = self.listener.borrow();
519        let result = match &mut self.state {
520            TcpStateMachine::Pipe(pipe) => pipe.readable(&mut self.metrics),
521            TcpStateMachine::RelayProxyProtocol(pp) => pp.readable(&mut self.metrics),
522            TcpStateMachine::ExpectProxyProtocol(pp) => pp.readable(&mut self.metrics),
523            TcpStateMachine::SendProxyProtocol(_) => SessionResult::Continue,
524            TcpStateMachine::SniPreread(preread) => {
525                let cfg = listener.preread_config(preread.effective_max_bytes());
526                preread.readable(&mut self.metrics, &cfg)
527            }
528            TcpStateMachine::FailedUpgrade(_) => unreachable!(),
529        };
530        drop(listener);
531
532        // Sync `cluster_id` the moment SNI preread lands a route, so
533        // `connect_to_backend`'s cluster source (`self.cluster_id.clone().or_else(...)`)
534        // sees it without waiting for a second dispatch.
535        if let TcpStateMachine::SniPreread(preread) = &self.state
536            && self.cluster_id.is_none()
537            && let Some(outcome) = preread.outcome()
538        {
539            self.cluster_id = Some(outcome.cluster.clone());
540            self.cluster_metrics_incarnation =
541                crate::metrics::capture_cluster_metrics(outcome.cluster.as_ref());
542            // Restore the listener's configured `front_timeout` THE MOMENT
543            // routing succeeds, not only once the backend connect completes
544            // (previously done only in `upgrade_sni_preread`, which can run
545            // one or more `ready()` cycles later): a slow-but-legitimate
546            // backend connect must be bounded by
547            // `front_timeout`/`connect_timeout`, never by the short
548            // `sni_preread_timeout` that only makes sense while a route
549            // decision is still pending (sozu-proxy/sozu#1290). This is
550            // also the point from which
551            // `frontend_timeout_resets_on_readable` starts resetting this
552            // container again on every future `readable()`.
553            self.container_frontend_timeout
554                .set_duration(Duration::from_secs(
555                    self.listener.borrow().config.front_timeout as u64,
556                ));
557        }
558
559        result
560    }
561
562    fn writable(&mut self) -> SessionResult {
563        match &mut self.state {
564            TcpStateMachine::Pipe(pipe) => pipe.writable(&mut self.metrics),
565            _ => SessionResult::Continue,
566        }
567    }
568
569    fn back_readable(&mut self) -> SessionResult {
570        if !self.container_frontend_timeout.reset() {
571            error!(
572                "{} Could not reset frontend timeout on back_readable",
573                log_context!(self)
574            );
575        }
576        if !self.container_backend_timeout.reset() {
577            error!(
578                "{} Could not reset backend timeout on back_readable",
579                log_context!(self)
580            );
581        }
582
583        match &mut self.state {
584            TcpStateMachine::Pipe(pipe) => pipe.backend_readable(&mut self.metrics),
585            _ => SessionResult::Continue,
586        }
587    }
588
589    fn back_writable(&mut self) -> SessionResult {
590        match &mut self.state {
591            TcpStateMachine::Pipe(pipe) => pipe.backend_writable(&mut self.metrics),
592            TcpStateMachine::RelayProxyProtocol(pp) => pp.back_writable(&mut self.metrics),
593            TcpStateMachine::SendProxyProtocol(pp) => pp.back_writable(&mut self.metrics),
594            // The FIRST backend-writable event while routed drives the
595            // upgrade out of `SniPreread` -- see
596            // `SniPreread::back_writable`'s doc and `upgrade_sni_preread`.
597            TcpStateMachine::SniPreread(preread) => preread.back_writable(),
598            TcpStateMachine::ExpectProxyProtocol(_) => SessionResult::Continue,
599            TcpStateMachine::FailedUpgrade(_) => {
600                unreachable!()
601            }
602        }
603    }
604
605    fn back_socket_mut(&mut self) -> Option<&mut MioTcpStream> {
606        match &mut self.state {
607            TcpStateMachine::Pipe(pipe) => pipe.back_socket_mut(),
608            TcpStateMachine::SendProxyProtocol(pp) => pp.back_socket_mut(),
609            TcpStateMachine::RelayProxyProtocol(pp) => pp.back_socket_mut(),
610            TcpStateMachine::SniPreread(preread) => preread.back_socket_mut(),
611            TcpStateMachine::ExpectProxyProtocol(_) => None,
612            TcpStateMachine::FailedUpgrade(_) => unreachable!(),
613        }
614    }
615
616    pub fn upgrade(&mut self) -> SessionIsToBeClosed {
617        let new_state = match self.state.take() {
618            TcpStateMachine::SendProxyProtocol(spp) => self.upgrade_send(spp),
619            TcpStateMachine::RelayProxyProtocol(rpp) => self.upgrade_relay(rpp),
620            TcpStateMachine::ExpectProxyProtocol(epp) => self.upgrade_expect(epp),
621            TcpStateMachine::SniPreread(preread) => self.upgrade_sni_preread(preread),
622            TcpStateMachine::Pipe(_) => None,
623            TcpStateMachine::FailedUpgrade(_) => todo!(),
624        };
625
626        match new_state {
627            Some(state) => {
628                self.state = state;
629                false
630            } // The state stays FailedUpgrade, but the Session should be closed right after
631
632            None => true,
633        }
634    }
635
636    fn upgrade_send(
637        &mut self,
638        send_proxy_protocol: SendProxyProtocol<MioTcpStream>,
639    ) -> Option<TcpStateMachine> {
640        if self.backend_buffer.is_some() && self.frontend_buffer.is_some() {
641            let mut pipe = send_proxy_protocol.into_pipe(
642                self.frontend_buffer.take().unwrap(),
643                self.backend_buffer.take().unwrap(),
644                self.listener.clone(),
645            );
646
647            // `SendProxyProtocol::into_pipe` overwrites the whole readiness
648            // (clobbering the backend-writable interest `Pipe::new` armed for a
649            // non-empty inherited frontend buffer) and re-inserts only
650            // READABLE. That is harmless for a legacy `SendHeader` session
651            // (empty accumulator) but strands the coalesced payload tail
652            // carried in from `SniPreread`'s `SendHeader` branch -- the
653            // sozu-proxy/sozu#1279 close-before-flush truncation, where bytes
654            // already queued for the backend were dropped instead of flushed.
655            // Re-run the inherited-write arm by feeding the pipe's
656            // own current events back through `restore_readiness_events`; it is
657            // additive (never clears interest) and a no-op when the buffers are
658            // empty, so the legacy path is unaffected.
659            let frontend_event = pipe.frontend_readiness.event;
660            let backend_event = pipe.backend_readiness.event;
661            pipe.restore_readiness_events(frontend_event, backend_event);
662
663            pipe.set_cluster_id(self.cluster_id.clone());
664            pipe.set_cluster_metrics_incarnation(self.cluster_metrics_incarnation);
665            pipe.set_backend_address(self.backend_address);
666            // Only `Some` when this `SendProxyProtocol` was itself reached
667            // via `upgrade_sni_preread`'s `SendHeader` branch (sozu-proxy/sozu#1279)
668            // -- a legacy, non-SNI-routed `SendHeader` cluster never
669            // populates these fields, so this is a no-op for it.
670            if let Some(sni) = self.routed_sni.take() {
671                pipe.set_tls_metadata(None, None, Some(sni), self.routed_alpn_label.take());
672            }
673            // `None` for legacy sessions (keeps the bare-address tags
674            // lookup); the matched front's composed key for SNI-routed ones.
675            pipe.set_tags_key(self.tags_key.clone());
676            gauge_add!(names::protocol::PROXY_SEND, -1);
677            gauge_add!(names::protocol::TCP, 1);
678            return Some(TcpStateMachine::Pipe(pipe));
679        }
680
681        error!(
682            "{} Missing the frontend or backend buffer queue, we can't switch to a pipe",
683            log_context!(self)
684        );
685        None
686    }
687
688    fn upgrade_relay(&mut self, rpp: RelayProxyProtocol<MioTcpStream>) -> Option<TcpStateMachine> {
689        if self.backend_buffer.is_some() {
690            let mut pipe =
691                rpp.into_pipe(self.backend_buffer.take().unwrap(), self.listener.clone());
692            pipe.set_cluster_id(self.cluster_id.clone());
693            pipe.set_cluster_metrics_incarnation(self.cluster_metrics_incarnation);
694            pipe.set_backend_address(self.backend_address);
695            gauge_add!(names::protocol::PROXY_RELAY, -1);
696            gauge_add!(names::protocol::TCP, 1);
697            return Some(TcpStateMachine::Pipe(pipe));
698        }
699
700        error!(
701            "{} Missing the backend buffer queue, we can't switch to a pipe",
702            log_context!(self)
703        );
704        None
705    }
706
707    fn upgrade_expect(
708        &mut self,
709        epp: ExpectProxyProtocol<MioTcpStream>,
710    ) -> Option<TcpStateMachine> {
711        if self.frontend_buffer.is_some() && self.backend_buffer.is_some() {
712            let mut pipe = epp.into_pipe(
713                self.frontend_buffer.take().unwrap(),
714                self.backend_buffer.take().unwrap(),
715                None,
716                None,
717                self.listener.clone(),
718            );
719
720            pipe.set_cluster_id(self.cluster_id.clone());
721            pipe.set_cluster_metrics_incarnation(self.cluster_metrics_incarnation);
722            pipe.set_backend_address(self.backend_address);
723            gauge_add!(names::protocol::PROXY_EXPECT, -1);
724            gauge_add!(names::protocol::TCP, 1);
725            return Some(TcpStateMachine::Pipe(pipe));
726        }
727
728        error!(
729            "{} Missing the backend buffer queue, we can't switch to a pipe",
730            log_context!(self)
731        );
732        None
733    }
734
735    /// Dispatch out of [`TcpStateMachine::SniPreread`] once its backend has
736    /// connected, by the ROUTED cluster's `proxy_protocol` config:
737    ///
738    /// - `Some(SendHeader)` -> `SendProxyProtocol` synthesizes its OWN PPv2
739    ///   header for the backend, so any inbound PPv2 prefix this listener's
740    ///   `expect_proxy` preread already parsed (`content_offset` bytes) is
741    ///   dropped from the accumulator first: the wire order is `[synth
742    ///   PPv2][ClientHello...]`, never both headers back to back.
743    /// - `Some(ExpectHeader)` -> the inbound PPv2 prefix is consumed the
744    ///   same way (Sōzu terminates it locally; the backend never sees a
745    ///   PROXY header at all), then straight into `Pipe`.
746    /// - `Some(RelayHeader)` -> NO consume: the already-parsed inbound
747    ///   header bytes ARE the header this backend expects, replayed
748    ///   verbatim ahead of the ClientHello.
749    /// - `None` -> also consumed: a listener with `expect_proxy` but a
750    ///   `None`-proxy_protocol cluster still must not leak the stray
751    ///   inbound PPv2 prefix onto a backend that expects none -- the
752    ///   preread parsed those bytes for routing only, and a backend with
753    ///   no PROXY-protocol contract would read them as part of the TLS
754    ///   stream.
755    ///
756    /// `tcp.sni_preread.duration` is recorded on EVERY exit from this
757    /// function, including the four defensive early returns below: once
758    /// `preread.outcome()`/`preread`'s `SniPreread` value is consumed by the
759    /// `TcpStateMachine::FailedUpgrade`/`Pipe` transition its caller drives,
760    /// `close()`'s `StateMarker::SniPreread` arm can no longer reach a
761    /// `SniPreread` to read `started_at()` from, so recording later is not an
762    /// option. `tcp.sni_preread.active`, by contrast, is decremented exactly
763    /// once, on the "upgrade" exit named by the gauge's `-1 on every exit`
764    /// contract; the "reject"/"teardown" exits are each other's counterpart
765    /// in `SniPreread::handle_output` (metric only) and `TcpSession::close`'s
766    /// `StateMarker::SniPreread` arm (gauge).
767    /// Shared abort path for `upgrade_sni_preread`'s early-return guards:
768    /// logs `reason` through the same envelope as the rest of this module,
769    /// then records `tcp.sni_preread.duration` -- see the long comment on
770    /// `upgrade_sni_preread` for why that metric must fire on every exit --
771    /// before returning `None`.
772    fn abort_sni_preread_upgrade(
773        &self,
774        preread: &SniPreread<MioTcpStream>,
775        reason: &str,
776    ) -> Option<TcpStateMachine> {
777        error!("{} {}", log_context!(self), reason);
778        time!(
779            names::tcp::sni_preread::DURATION,
780            preread.started_at().elapsed().as_millis() as i64
781        );
782        None
783    }
784
785    fn upgrade_sni_preread(
786        &mut self,
787        mut preread: SniPreread<MioTcpStream>,
788    ) -> Option<TcpStateMachine> {
789        // Every early return below (a route decision missing, or the
790        // backend socket/token/buffer not yet wired) must happen BEFORE the
791        // `tcp.sni_preread.active` gauge is touched: `close()`'s
792        // `StateMarker::SniPreread` arm runs unconditionally whenever
793        // `self.state` is still (or, via `FailedUpgrade`, was last)
794        // `SniPreread` -- decrementing here AND there for the same session
795        // would underflow the gauge on this (defensive, should-never-happen)
796        // failure path. The gauge is deferred to `close()` on these paths,
797        // but the duration is NOT: it is recorded right before each `return
798        // None` below, symmetric with the success path's `time!` call.
799        let Some(outcome) = preread.outcome().cloned() else {
800            return self.abort_sni_preread_upgrade(
801                &preread,
802                "upgrade_sni_preread called before a route decision",
803            );
804        };
805        let Some(backend_socket) = preread.backend.take() else {
806            return self.abort_sni_preread_upgrade(
807                &preread,
808                "SNI preread upgrade with no backend socket set",
809            );
810        };
811        let Some(backend_token) = preread.backend_token else {
812            return self.abort_sni_preread_upgrade(
813                &preread,
814                "SNI preread upgrade with no backend token set",
815            );
816        };
817        let Some(back_buffer) = self.backend_buffer.take() else {
818            return self.abort_sni_preread_upgrade(
819                &preread,
820                "SNI preread upgrade with no backend buffer queued",
821            );
822        };
823
824        gauge_add!(names::tcp::sni_preread::ACTIVE, -1);
825        time!(
826            names::tcp::sni_preread::DURATION,
827            preread.started_at().elapsed().as_millis() as i64
828        );
829
830        // `TcpSession::readable` normally captured this route (and its
831        // metrics incarnation) already; only capture when it did not, so the
832        // incarnation stays the one observed at routing time.
833        if self.cluster_id.as_ref() != Some(&outcome.cluster) {
834            self.cluster_id = Some(outcome.cluster.clone());
835            self.cluster_metrics_incarnation =
836                crate::metrics::capture_cluster_metrics(outcome.cluster.as_ref());
837        }
838        // `container_frontend_timeout` is NOT restored here anymore: by the
839        // time this runs, `TcpSession::readable`'s route-capture block has
840        // already restored it to the listener's configured `front_timeout`
841        // the moment the route decision first became visible (potentially
842        // one or more `ready()` cycles before this upgrade, while the
843        // backend was still connecting) -- see that block's doc
844        // (sozu-proxy/sozu#1290). Restoring it again here
845        // would just re-arm the same duration a second time.
846        // Access-log tagging: stash the routed SNI/ALPN for
847        // whichever of the four `proxy_protocol` branches below eventually
848        // reaches `Pipe` -- immediately via `build_pipe_from_preread` for
849        // `Expect`/`Relay`/`None`, or one `ready()` cycle later via
850        // `upgrade_send` for `SendHeader` (see that method and the
851        // `routed_sni` field doc).
852        self.routed_sni = Some(outcome.sni.clone());
853        self.routed_alpn_label = known_alpn_label(&outcome.alpn);
854        // Rebuild the MATCHED front's tags key from the route decision's
855        // identity (`matched_sni_pattern` is the trie key — the configured
856        // pattern, not the client's concrete SNI — and `matched_alpn` the
857        // winning matcher), so the access log emits the tags of the front
858        // that actually routed this session, not whichever front was added
859        // last. Must compose the same key `add_tcp_front` stored — see
860        // `sni_tags_key`'s canonical-form doc.
861        self.tags_key = Some(sni_tags_key(
862            self.listener.borrow().get_addr(),
863            &outcome.matched_sni_pattern,
864            &alpn_matcher_protocols(&outcome.matched_alpn),
865        ));
866
867        let proxy_protocol = self
868            .proxy
869            .borrow()
870            .configs
871            .get(&outcome.cluster)
872            .and_then(|c| c.proxy_protocol);
873
874        let frontend_event = preread.frontend_readiness.event;
875        let backend_event = preread.backend_readiness.event;
876        let mut frontend_buffer = preread.frontend_buffer;
877        let frontend = preread.frontend;
878        let frontend_token = preread.frontend_token;
879        let request_id = preread.request_id;
880
881        match proxy_protocol {
882            Some(ProxyProtocolConfig::SendHeader) => {
883                frontend_buffer.consume(outcome.content_offset);
884                self.frontend_buffer = Some(frontend_buffer);
885                self.backend_buffer = Some(back_buffer);
886                gauge_add!(names::protocol::PROXY_SEND, 1);
887                let mut spp = SendProxyProtocol::new(
888                    frontend,
889                    frontend_token,
890                    request_id,
891                    Some(backend_socket),
892                );
893                spp.frontend_readiness.event = frontend_event;
894                spp.backend_readiness.event = backend_event;
895                spp.set_back_token(backend_token);
896                spp.set_back_connected(BackendConnectionStatus::Connected);
897                Some(TcpStateMachine::SendProxyProtocol(spp))
898            }
899            Some(ProxyProtocolConfig::ExpectHeader) | None => {
900                frontend_buffer.consume(outcome.content_offset);
901                Some(self.build_pipe_from_preread(
902                    back_buffer,
903                    frontend_buffer,
904                    frontend,
905                    frontend_token,
906                    frontend_event,
907                    backend_event,
908                    backend_socket,
909                    backend_token,
910                    request_id,
911                    outcome.proxy_source,
912                    outcome.cluster,
913                ))
914            }
915            Some(ProxyProtocolConfig::RelayHeader) => Some(self.build_pipe_from_preread(
916                back_buffer,
917                frontend_buffer,
918                frontend,
919                frontend_token,
920                frontend_event,
921                backend_event,
922                backend_socket,
923                backend_token,
924                request_id,
925                outcome.proxy_source,
926                outcome.cluster,
927            )),
928        }
929    }
930
931    #[allow(clippy::too_many_arguments)]
932    fn build_pipe_from_preread(
933        &mut self,
934        back_buffer: Checkout,
935        frontend_buffer: Checkout,
936        frontend: MioTcpStream,
937        frontend_token: Token,
938        frontend_event: Ready,
939        backend_event: Ready,
940        backend_socket: MioTcpStream,
941        backend_token: Token,
942        request_id: Ulid,
943        proxy_source: Option<SocketAddr>,
944        cluster_id: ClusterId,
945    ) -> TcpStateMachine {
946        let addr = proxy_source.or(self.frontend_address);
947        let mut pipe = Pipe::new(
948            back_buffer,
949            self.backend_id.clone(),
950            Some(backend_socket),
951            None,
952            None,
953            None,
954            Some(cluster_id),
955            frontend_buffer,
956            frontend_token,
957            frontend,
958            self.listener.clone(),
959            Protocol::TCP,
960            request_id,
961            request_id,
962            addr,
963            WebSocketContext::Tcp,
964        );
965        pipe.set_cluster_metrics_incarnation(self.cluster_metrics_incarnation);
966        // `Pipe::new` armed backend-writable for the inherited frontend
967        // accumulator (the ClientHello + any coalesced payload) via
968        // `arm_inherited_buffer_writes`. Restore the preread's readiness
969        // events through `restore_readiness_events` rather than a bare
970        // `pipe.frontend_readiness.event = …` / `pipe.backend_readiness.event
971        // = …` pair: it sets both `.event`s and THEN re-runs the inherited
972        // arm, so the synthetic backend-writable event survives even in the
973        // case where the restored `backend_event` does not itself carry
974        // WRITABLE (the byte-for-byte drain of the accumulator must not depend
975        // on that). The eventual flush-on-close of that accumulator is
976        // guaranteed by `Pipe::readable`'s half-close drain (sozu-proxy/sozu#1279).
977        pipe.restore_readiness_events(frontend_event, backend_event);
978        pipe.set_back_token(backend_token);
979        pipe.set_backend_address(self.backend_address);
980        // Access-log tagging: reaching `Pipe` straight from
981        // `SniPreread` (Expect/Relay/None) -- unlike `SendHeader`, which
982        // detours through `SendProxyProtocol` first (see `upgrade_send`).
983        if let Some(sni) = self.routed_sni.take() {
984            pipe.set_tls_metadata(None, None, Some(sni), self.routed_alpn_label.take());
985        }
986        // The matched front's composed tags key (always `Some` here — this
987        // is only reachable from `upgrade_sni_preread`, which just set it).
988        pipe.set_tags_key(self.tags_key.clone());
989        gauge_add!(names::protocol::TCP, 1);
990        TcpStateMachine::Pipe(pipe)
991    }
992
993    fn front_readiness(&mut self) -> &mut Readiness {
994        match &mut self.state {
995            TcpStateMachine::Pipe(pipe) => &mut pipe.frontend_readiness,
996            TcpStateMachine::SendProxyProtocol(pp) => &mut pp.frontend_readiness,
997            TcpStateMachine::RelayProxyProtocol(pp) => &mut pp.frontend_readiness,
998            TcpStateMachine::ExpectProxyProtocol(pp) => &mut pp.frontend_readiness,
999            TcpStateMachine::SniPreread(preread) => &mut preread.frontend_readiness,
1000            TcpStateMachine::FailedUpgrade(_) => unreachable!(),
1001        }
1002    }
1003
1004    fn back_readiness(&mut self) -> Option<&mut Readiness> {
1005        match &mut self.state {
1006            TcpStateMachine::Pipe(pipe) => Some(&mut pipe.backend_readiness),
1007            TcpStateMachine::SendProxyProtocol(pp) => Some(&mut pp.backend_readiness),
1008            TcpStateMachine::RelayProxyProtocol(pp) => Some(&mut pp.backend_readiness),
1009            TcpStateMachine::SniPreread(preread) => Some(&mut preread.backend_readiness),
1010            TcpStateMachine::ExpectProxyProtocol(_) => None,
1011            TcpStateMachine::FailedUpgrade(_) => unreachable!(),
1012        }
1013    }
1014
1015    fn set_back_socket(&mut self, socket: MioTcpStream) {
1016        match &mut self.state {
1017            TcpStateMachine::Pipe(pipe) => pipe.set_back_socket(socket),
1018            TcpStateMachine::SendProxyProtocol(pp) => pp.set_back_socket(socket),
1019            TcpStateMachine::RelayProxyProtocol(pp) => pp.set_back_socket(socket),
1020            TcpStateMachine::SniPreread(preread) => preread.set_back_socket(socket),
1021            TcpStateMachine::ExpectProxyProtocol(_) => {
1022                error!(
1023                    "{} We should not set the back socket for the expect proxy protocol",
1024                    log_context!(self)
1025                );
1026                panic!(
1027                    "{} We should not set the back socket for the expect proxy protocol",
1028                    log_context!(self)
1029                );
1030            }
1031            TcpStateMachine::FailedUpgrade(_) => unreachable!(),
1032        }
1033    }
1034
1035    fn set_back_token(&mut self, token: Token) {
1036        // The frontend must own a token distinct from the backend's: the two
1037        // index different slab slots, so wiring the same token to both would
1038        // alias two sessions onto one slot.
1039        debug_assert_ne!(
1040            token, self.frontend_token,
1041            "backend token must differ from the frontend token"
1042        );
1043        self.backend_token = Some(token);
1044
1045        match &mut self.state {
1046            TcpStateMachine::Pipe(pipe) => pipe.set_back_token(token),
1047            TcpStateMachine::SendProxyProtocol(pp) => pp.set_back_token(token),
1048            TcpStateMachine::SniPreread(preread) => preread.set_back_token(token),
1049            TcpStateMachine::RelayProxyProtocol(pp) => pp.set_back_token(token),
1050            TcpStateMachine::ExpectProxyProtocol(_) => self.backend_token = Some(token),
1051            TcpStateMachine::FailedUpgrade(_) => unreachable!(),
1052        }
1053
1054        // Postcondition: the session now owns exactly the token it was asked
1055        // to register — every arm above (including the Expect arm, which only
1056        // stores the session-side token) leaves `backend_token == Some(token)`.
1057        debug_assert_eq!(
1058            self.backend_token,
1059            Some(token),
1060            "set_back_token must leave the session owning the registered token"
1061        );
1062    }
1063
1064    fn set_backend_id(&mut self, id: String) {
1065        self.backend_id = Some(id.clone());
1066        if let TcpStateMachine::Pipe(pipe) = &mut self.state {
1067            pipe.set_backend_id(Some(id));
1068        }
1069    }
1070
1071    /// Record the dialed backend's address; a session that is not a `Pipe`
1072    /// yet hands it over at upgrade time instead.
1073    fn set_backend_address(&mut self, address: SocketAddr) {
1074        self.backend_address = Some(address);
1075        if let TcpStateMachine::Pipe(pipe) = &mut self.state {
1076            pipe.set_backend_address(Some(address));
1077        }
1078    }
1079
1080    fn back_connected(&self) -> BackendConnectionStatus {
1081        self.backend_connected
1082    }
1083
1084    fn set_back_connected(&mut self, status: BackendConnectionStatus) {
1085        let last = self.backend_connected;
1086        // Transitioning INTO `Connected` bumps the backend-connection gauge by
1087        // exactly +1. Doing so from an already-`Connected` state would
1088        // double-count (gauge drift that only `close_backend`'s single -1
1089        // would later reconcile, leaving the gauge permanently +1). The
1090        // promotion always comes from a `Connecting` (the normal handshake
1091        // completion in `ready_inner`) — never from `Connected` itself.
1092        debug_assert!(
1093            status != BackendConnectionStatus::Connected
1094                || last != BackendConnectionStatus::Connected,
1095            "set_back_connected(Connected) must not run on an already-Connected backend (gauge would double-count)"
1096        );
1097        self.backend_connected = status;
1098
1099        // Postcondition: the requested status is now in effect.
1100        debug_assert_eq!(
1101            self.backend_connected, status,
1102            "set_back_connected must record the requested status"
1103        );
1104
1105        if status == BackendConnectionStatus::Connected {
1106            gauge_add!(names::backend::CONNECTIONS, 1);
1107            gauge_add!(
1108                names::backend::CONNECTIONS_PER_BACKEND,
1109                1,
1110                self.cluster_id.as_deref(),
1111                self.metrics.backend_id.as_deref(),
1112                self.cluster_metrics_incarnation
1113            );
1114
1115            // the back timeout was of connect_timeout duration before,
1116            // now that we're connected, move to backend_timeout duration
1117            self.container_backend_timeout
1118                .set_duration(self.configured_backend_timeout);
1119            self.container_frontend_timeout.reset();
1120
1121            if let TcpStateMachine::SendProxyProtocol(spp) = &mut self.state {
1122                spp.set_back_connected(BackendConnectionStatus::Connected);
1123            }
1124
1125            if let Some(backend) = self.backend.as_ref() {
1126                let mut backend = backend.borrow_mut();
1127
1128                if backend.retry_policy.is_down() {
1129                    incr!(
1130                        names::backend::UP,
1131                        self.cluster_id.as_deref(),
1132                        self.metrics.backend_id.as_deref(),
1133                        self.cluster_metrics_incarnation
1134                    );
1135                    gauge!(
1136                        names::backend::AVAILABLE,
1137                        1,
1138                        self.cluster_id.as_deref(),
1139                        self.metrics.backend_id.as_deref(),
1140                        self.cluster_metrics_incarnation
1141                    );
1142                    info!(
1143                        "{} backend server {} at {} is up",
1144                        log_context!(self),
1145                        backend.backend_id,
1146                        backend.address
1147                    );
1148                    push_event(Event {
1149                        kind: EventKind::BackendUp as i32,
1150                        backend_id: Some(backend.backend_id.to_owned()),
1151                        address: Some(backend.address.into()),
1152                        cluster_id: None,
1153                        metric_detail: None,
1154                    });
1155                }
1156
1157                if let BackendConnectionStatus::Connecting(start) = last {
1158                    let now = Instant::now();
1159                    backend.set_connection_time(now - start, now);
1160                }
1161
1162                //successful connection, rest failure counter
1163                backend.failures = 0;
1164                backend.retry_policy.succeed(Instant::now());
1165            }
1166        }
1167    }
1168
1169    fn remove_backend(&mut self) {
1170        if let Some(backend) = self.backend.take() {
1171            (*backend.borrow_mut()).dec_connections();
1172        }
1173
1174        self.backend_token = None;
1175
1176        // Postcondition: the backend handle and its token are torn down
1177        // together — neither may outlive the other (a dangling token would
1178        // leave a stale slab reference; a dangling handle would over-count
1179        // backend connections).
1180        debug_assert!(
1181            self.backend.is_none(),
1182            "remove_backend must release the backend handle"
1183        );
1184        debug_assert!(
1185            self.backend_token.is_none(),
1186            "remove_backend must clear the backend token"
1187        );
1188    }
1189
1190    fn fail_backend_connection(&mut self) {
1191        if let Some(backend) = self.backend.as_ref() {
1192            let backend = &mut *backend.borrow_mut();
1193            backend.failures += 1;
1194
1195            let already_unavailable = backend.retry_policy.is_down();
1196            // The TCP proxy is not simulated: it keeps the wall clock and the
1197            // thread-local generator at its own edge (#1684).
1198            backend.retry_policy.fail(Instant::now(), &mut rand::rng());
1199            incr!(
1200                names::backend::CONNECTIONS_ERROR,
1201                self.cluster_id.as_deref(),
1202                self.metrics.backend_id.as_deref(),
1203                self.cluster_metrics_incarnation
1204            );
1205            if !already_unavailable && backend.retry_policy.is_down() {
1206                error!(
1207                    "{} backend server {} at {} is down",
1208                    log_context!(self),
1209                    backend.backend_id,
1210                    backend.address
1211                );
1212                incr!(
1213                    names::backend::DOWN,
1214                    self.cluster_id.as_deref(),
1215                    self.metrics.backend_id.as_deref(),
1216                    self.cluster_metrics_incarnation
1217                );
1218                gauge!(
1219                    names::backend::AVAILABLE,
1220                    0,
1221                    self.cluster_id.as_deref(),
1222                    self.metrics.backend_id.as_deref(),
1223                    self.cluster_metrics_incarnation
1224                );
1225
1226                push_event(Event {
1227                    kind: EventKind::BackendDown as i32,
1228                    backend_id: Some(backend.backend_id.to_owned()),
1229                    address: Some(backend.address.into()),
1230                    cluster_id: None,
1231                    metric_detail: None,
1232                });
1233            }
1234        }
1235    }
1236
1237    pub fn test_back_socket(&mut self) -> SessionIsToBeClosed {
1238        match self.back_socket_mut() {
1239            Some(ref mut s) => {
1240                let mut tmp = [0u8; 1];
1241                let res = s.peek(&mut tmp[..]);
1242
1243                match res {
1244                    // if the socket is half open, it will report 0 bytes read (EOF)
1245                    Ok(0) => false,
1246                    Ok(_) => true,
1247                    Err(e) => matches!(e.kind(), std::io::ErrorKind::WouldBlock),
1248                }
1249            }
1250            None => false,
1251        }
1252    }
1253
1254    pub fn cancel_timeouts(&mut self) {
1255        self.container_frontend_timeout.cancel();
1256        self.container_backend_timeout.cancel();
1257    }
1258
1259    /// Full cross-field invariant sweep for the TCP session state machine.
1260    ///
1261    /// Run as a run-to-completion postcondition at the END of `ready()` (the
1262    /// only public entry point that drives the front/back token + readiness
1263    /// state machine). These are OUR-logic invariants — never reachable from
1264    /// hostile traffic — so a violation is a bug in Sōzu, not a malformed
1265    /// peer. Compiled out in release.
1266    #[cfg(debug_assertions)]
1267    fn check_invariants(&self) {
1268        // Connection-attempt budget: every retry path increments
1269        // `connection_attempt` and `connect_to_backend` refuses once the
1270        // counter reaches the cluster's `max_connection_attempts`, so the
1271        // value never exceeds the largest budget that can be configured (and
1272        // resets to 0 on success). The live budget itself is no bound here:
1273        // `SetMaxConnectionAttempts` or an `AddCluster` may lower it while a
1274        // session is retrying.
1275        debug_assert!(
1276            u32::from(self.connection_attempt)
1277                <= sozu_command::config::MAX_CONNECTION_ATTEMPTS_UPPER_BOUND,
1278            "connection_attempt ({}) must never exceed the largest attempt budget",
1279            self.connection_attempt,
1280        );
1281
1282        // Token ownership: a fully-connected backend always owns a backend
1283        // token (set by `set_back_token` during `connect_to_backend`, before
1284        // the status can ever flip to `Connected`). The `Connecting` phase is
1285        // deliberately excluded: there is a transient window inside
1286        // `connect_to_backend` where the status is `Connecting` but the token
1287        // has not been wired yet — that window never spans a `ready()`
1288        // boundary, so the postcondition still holds here.
1289        if self.backend_connected == BackendConnectionStatus::Connected {
1290            debug_assert!(
1291                self.backend_token.is_some(),
1292                "a Connected backend must own a backend token"
1293            );
1294        }
1295
1296        // A live backend handle implies the matching token is present: the
1297        // two are wired together in `connect_to_backend` and torn down
1298        // together in `remove_backend` (which clears the token) — they must
1299        // never drift apart. (For the pure-TCP proxy `backend` is currently
1300        // always `None`, so this is a guard against a future regression that
1301        // starts populating it without the token.)
1302        if self.backend.is_some() {
1303            debug_assert!(
1304                self.backend_token.is_some(),
1305                "a live backend handle must have a backend token"
1306            );
1307        }
1308
1309        // Once the session has been closed it is terminal: the backend has
1310        // been released and the per-(cluster, source-IP) slot untracked.
1311        if self.has_been_closed {
1312            debug_assert!(
1313                self.backend.is_none(),
1314                "a closed session must have released its backend handle"
1315            );
1316            debug_assert!(
1317                !self.cluster_ip_tracked,
1318                "a closed session must have untracked its (cluster, source-IP) slot"
1319            );
1320        }
1321    }
1322
1323    /// Attempt a fresh backend connect, exactly like `ready_inner`'s
1324    /// top-of-function gate -- but callable a second time from inside the
1325    /// dispatch loop. A `SniPreread` session's route decision can complete
1326    /// INSIDE `readable()`'s own dispatch (the SAME `ready_inner` call), and
1327    /// without a second attempt right after that dispatch the session would
1328    /// stall until an unrelated readiness event re-entered `ready_inner`.
1329    ///
1330    /// A no-op whenever `back_connected() != NotConnected` (already
1331    /// attempted, or backend already up), the state is a NOT-YET-ROUTED
1332    /// `SniPreread` (the cluster -- and therefore the backend to dial -- is
1333    /// unknown until `SniPrereadCore` decides), or the state is still
1334    /// `ExpectProxyProtocol` (see below), so this changes nothing for any
1335    /// other pre-existing state/path.
1336    ///
1337    /// `ExpectProxyProtocol` has NO backend side at all: `back_readiness`
1338    /// returns `None` for it and `set_back_socket` panics outright ("We
1339    /// should not set the back socket for the expect proxy protocol"). The
1340    /// backend is dialed from the `Pipe` that `upgrade_expect` installs once
1341    /// the inbound PROXY header has been parsed -- the same `ready()` pass
1342    /// re-enters `ready_inner` after the upgrade, so nothing is deferred
1343    /// beyond the header. Without this arm the top-of-`ready_inner` connect
1344    /// gate fired on the FIRST readiness event of a freshly accepted socket
1345    /// (the WRITABLE epoll reports before the client has sent a byte) and
1346    /// drove `connect_to_backend` straight into that panic, killing the
1347    /// worker and every other session on it.
1348    fn attempt_backend_connect_if_needed(
1349        &mut self,
1350        session: &Rc<RefCell<dyn ProxySession>>,
1351    ) -> Option<SessionResult> {
1352        if self.back_connected() != BackendConnectionStatus::NotConnected {
1353            return None;
1354        }
1355        if matches!(&self.state, TcpStateMachine::SniPreread(preread) if !preread.is_routed()) {
1356            return None;
1357        }
1358        if matches!(&self.state, TcpStateMachine::ExpectProxyProtocol(_)) {
1359            return None;
1360        }
1361
1362        let connection_result = self.connect_to_backend(session.clone());
1363        if let Err(err) = &connection_result {
1364            match err {
1365                // Already logged at warn! + metered at the retry-budget
1366                // gate in connect_to_backend; avoid double-emission.
1367                BackendConnectionError::MaxConnectionRetries(_) => trace!(
1368                    "{} Error connecting to backend: {}",
1369                    log_context!(self),
1370                    err
1371                ),
1372                _ => warn!(
1373                    "{} Error connecting to backend: {}",
1374                    log_context!(self),
1375                    err
1376                ),
1377            }
1378        }
1379        handle_connection_result(connection_result)
1380    }
1381
1382    fn ready_inner(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionResult {
1383        let mut counter = 0;
1384
1385        let back_connected = self.back_connected();
1386        if back_connected.is_connecting() {
1387            // A `Connecting` backend always carries a backend readiness:
1388            // `connect_to_backend` is the only writer of that status, and it
1389            // never runs for the two states `back_readiness` has none for
1390            // (`ExpectProxyProtocol`, guarded in
1391            // `attempt_backend_connect_if_needed`; `FailedUpgrade`, which
1392            // `close()` short-circuits at its `state.failed()` gate). Read it
1393            // once instead of unwrapping twice, and treat the impossible
1394            // `None` as the broken invariant it is: a backend handshake with
1395            // no readiness slot can never complete, so close rather than
1396            // panic the worker.
1397            let Some(back_event) = self.back_readiness().map(|readiness| readiness.event) else {
1398                error!(
1399                    "{} backend is connecting but the session state carries no backend readiness, closing",
1400                    log_context!(self)
1401                );
1402                return SessionResult::Close;
1403            };
1404            if back_event.is_hup() && !self.test_back_socket() {
1405                //retry connecting the backend
1406                debug!(
1407                    "{} error connecting to backend, trying again",
1408                    log_context!(self)
1409                );
1410                self.connection_attempt += 1;
1411                self.fail_backend_connection();
1412
1413                // trigger a backend reconnection
1414                self.close_backend();
1415                let connection_result = self.connect_to_backend(session.clone());
1416                if let Err(err) = &connection_result {
1417                    match err {
1418                        // Already logged at warn! + metered at the retry-budget
1419                        // gate in connect_to_backend; avoid double-emission.
1420                        BackendConnectionError::MaxConnectionRetries(_) => trace!(
1421                            "{} Error connecting to backend: {}",
1422                            log_context!(self),
1423                            err
1424                        ),
1425                        _ => warn!(
1426                            "{} Error connecting to backend: {}",
1427                            log_context!(self),
1428                            err
1429                        ),
1430                    }
1431                }
1432
1433                if let Some(state_result) = handle_connection_result(connection_result) {
1434                    return state_result;
1435                }
1436            } else if back_event != Ready::EMPTY {
1437                self.connection_attempt = 0;
1438                self.set_back_connected(BackendConnectionStatus::Connected);
1439            }
1440        } else if back_connected == BackendConnectionStatus::NotConnected
1441            && let Some(state_result) = self.attempt_backend_connect_if_needed(&session)
1442        {
1443            return state_result;
1444        }
1445
1446        // A client that half-closes right behind its PROXY-v2 header and
1447        // payload can deliver both as ONE `READABLE | HUP` event (Linux
1448        // coalesces `EPOLLIN | EPOLLRDHUP`). `front_hup` closes every
1449        // pre-`Pipe` state (`SniPreread` without an access log), so
1450        // honouring HUP first would drop a complete, already-queued header
1451        // (sozu-proxy/sozu#1823).
1452        // Drain the expect state's readable bytes first: a parsed header
1453        // returns `Upgrade` and the recursive `ready()` hands the still-set
1454        // HUP to `Pipe::frontend_hup`, which keeps the session alive while
1455        // request bytes remain in the kernel or the pipe's buffers. An empty
1456        // or truncated header
1457        // ends with READABLE cleared (`readable` removes it on every
1458        // zero-byte read) or a `Close`, so a dead client still closes,
1459        // through `readable`'s own zero-byte arm or the HUP check below.
1460        if let TcpStateMachine::ExpectProxyProtocol(_) = self.state {
1461            while (self.front_readiness().interest & self.front_readiness().event).is_readable()
1462                && self.front_readiness().event.is_hup()
1463                && counter < MAX_LOOP_ITERATIONS
1464            {
1465                let session_result = self.readable();
1466                if session_result != SessionResult::Continue {
1467                    return session_result;
1468                }
1469                counter += 1;
1470            }
1471        }
1472
1473        if self.front_readiness().event.is_hup() {
1474            let session_result = self.front_hup();
1475            if session_result != SessionResult::Continue {
1476                return session_result;
1477            }
1478            // `front_hup` drained in-flight request bytes and wants the
1479            // session kept alive (`Pipe::frontend_hup`'s in-flight branch):
1480            // the client already sent FIN, so under edge-triggered epoll no
1481            // further frontend event will ever arrive -- returning here
1482            // would stall the session forever waiting for a wake-up that
1483            // never comes. Clear the now-consumed HUP bit and fall through
1484            // into the loop below so `readable` (drains the kernel tail) and
1485            // `back_writable` (flushes `frontend_buffer`) can run
1486            // synchronously in this same pass, exactly how a backend HUP is
1487            // already handled inside the loop.
1488            self.front_readiness().event.remove(Ready::HUP);
1489        }
1490
1491        while counter < MAX_LOOP_ITERATIONS {
1492            let front_interest = self.front_readiness().interest & self.front_readiness().event;
1493            let back_interest = self
1494                .back_readiness()
1495                .map(|r| r.interest & r.event)
1496                .unwrap_or(Ready::EMPTY);
1497
1498            trace!(
1499                "{} Frontend interest({:?}) and backend interest({:?})",
1500                log_context!(self),
1501                front_interest,
1502                back_interest
1503            );
1504
1505            if front_interest == Ready::EMPTY && back_interest == Ready::EMPTY {
1506                break;
1507            }
1508
1509            if self
1510                .back_readiness()
1511                .map(|r| r.event.is_hup())
1512                .unwrap_or(false)
1513                && self.front_readiness().interest.is_writable()
1514                && !self.front_readiness().event.is_writable()
1515            {
1516                break;
1517            }
1518
1519            if front_interest.is_readable() {
1520                let session_result = self.readable();
1521                if session_result != SessionResult::Continue {
1522                    return session_result;
1523                }
1524                // A `SniPreread` route decision can complete INSIDE this
1525                // very `readable()` call; without a second attempt here the
1526                // session would stall until an unrelated readiness event
1527                // re-entered `ready_inner` to reach the top-of-function
1528                // connect gate. A no-op for every other state/backend
1529                // status (see `attempt_backend_connect_if_needed`'s guard).
1530                if let Some(state_result) = self.attempt_backend_connect_if_needed(&session) {
1531                    return state_result;
1532                }
1533            }
1534
1535            if back_interest.is_writable() {
1536                let session_result = self.back_writable();
1537                if session_result != SessionResult::Continue {
1538                    return session_result;
1539                }
1540            }
1541
1542            if back_interest.is_readable() {
1543                let session_result = self.back_readable();
1544                if session_result != SessionResult::Continue {
1545                    return session_result;
1546                }
1547            }
1548
1549            if front_interest.is_writable() {
1550                let session_result = self.writable();
1551                if session_result != SessionResult::Continue {
1552                    return session_result;
1553                }
1554            }
1555
1556            if back_interest.is_hup() {
1557                let session_result = self.back_hup();
1558                if session_result != SessionResult::Continue {
1559                    return session_result;
1560                }
1561            }
1562
1563            if front_interest.is_error() {
1564                error!(
1565                    "{} Frontend socket error, disconnecting",
1566                    log_context!(self)
1567                );
1568                self.front_readiness().interest = Ready::EMPTY;
1569                if let Some(r) = self.back_readiness() {
1570                    r.interest = Ready::EMPTY;
1571                }
1572
1573                return SessionResult::Close;
1574            }
1575
1576            if back_interest.is_error() && self.back_hup() == SessionResult::Close {
1577                self.front_readiness().interest = Ready::EMPTY;
1578                if let Some(r) = self.back_readiness() {
1579                    r.interest = Ready::EMPTY;
1580                }
1581
1582                error!("{} backend socket error, disconnecting", log_context!(self));
1583                return SessionResult::Close;
1584            }
1585
1586            counter += 1;
1587        }
1588
1589        if counter >= MAX_LOOP_ITERATIONS {
1590            error!(
1591                "{} Handling session went through {} iterations, there's a probable infinite loop bug, closing the connection",
1592                log_context!(self),
1593                MAX_LOOP_ITERATIONS
1594            );
1595
1596            incr!(names::tcp::INFINITE_LOOP_ERROR);
1597
1598            let front_interest = self.front_readiness().interest & self.front_readiness().event;
1599            let back_interest = self
1600                .back_readiness()
1601                .map(|r| r.interest & r.event)
1602                .unwrap_or(Ready::EMPTY);
1603
1604            let back = self.back_readiness().cloned();
1605
1606            error!(
1607                "{} readiness: front {:?} / back {:?} | front: {:?} | back: {:?} ",
1608                log_context!(self),
1609                self.front_readiness(),
1610                back,
1611                front_interest,
1612                back_interest
1613            );
1614
1615            self.print_session();
1616
1617            return SessionResult::Close;
1618        }
1619
1620        SessionResult::Continue
1621    }
1622
1623    /// TCP session closes its backend on its own, without defering this task to the state
1624    fn close_backend(&mut self) {
1625        // No `EPOLL_CTL_DEL` for the backend socket: its last `close(2)`
1626        // takes it out of the epoll set, and it comes before the next
1627        // `epoll_wait`, so no later result carries the freed token for it.
1628        // The result being dispatched may still hold one of its events, and
1629        // another session can take the freed key before that event's turn:
1630        // `Server::ready_if_still_owned` (`lib/src/server.rs`) drops it.
1631        // From `close()`, the server drops the session right after. From
1632        // `ready_inner`'s retry, `connect_to_backend` either replaces the
1633        // socket (`set_back_socket` drops the old one) or fails, and every
1634        // failure closes the session (`handle_connection_result`). No session
1635        // socket is ever duplicated (no `dup`, `try_clone` or worker fork).
1636        if let Some(token) = self.backend_token
1637            && self.back_socket_mut().is_some()
1638        {
1639            self.proxy
1640                .borrow()
1641                .sessions
1642                .borrow_mut()
1643                .slab
1644                .try_remove(token.0);
1645        }
1646        self.remove_backend();
1647
1648        let back_connected = self.back_connected();
1649        if back_connected != BackendConnectionStatus::NotConnected {
1650            if let Some(r) = self.back_readiness() {
1651                r.event = Ready::EMPTY;
1652            }
1653
1654            let log_context = log_context!(self);
1655            if let Some(sock) = self.back_socket_mut() {
1656                // TCP-only backend in the pure-TCP proxy: no outbound TLS
1657                // buffer to truncate, so `Shutdown::Both` is the right call.
1658                // If the TCP listener ever gains an inline TLS upgrade,
1659                // switch to `Shutdown::Write` here.
1660                if let Err(e) = sock.shutdown(Shutdown::Both)
1661                    && e.kind() != ErrorKind::NotConnected
1662                {
1663                    error!(
1664                        "{} Error closing back socket({:?}): {:?}",
1665                        log_context, sock, e
1666                    );
1667                }
1668            }
1669        }
1670
1671        // The -1 here pairs with the +1 in `set_back_connected(Connected)`:
1672        // we decrement the gauge exactly once, iff this session had actually
1673        // reached `Connected`. A `Connecting`/`NotConnected` backend never
1674        // bumped the gauge, so it must not decrement it either — that
1675        // asymmetry would underflow the gauge (a correctness bug, never a
1676        // rounding issue).
1677        if back_connected == BackendConnectionStatus::Connected {
1678            gauge_add!(names::backend::CONNECTIONS, -1);
1679            gauge_add!(
1680                names::backend::CONNECTIONS_PER_BACKEND,
1681                -1,
1682                self.cluster_id.as_deref(),
1683                self.metrics.backend_id.as_deref(),
1684                self.cluster_metrics_incarnation
1685            );
1686        }
1687
1688        self.set_back_connected(BackendConnectionStatus::NotConnected);
1689
1690        // Postcondition: the backend is fully torn down — `remove_backend`
1691        // cleared the token/handle above and the status is now `NotConnected`,
1692        // so a subsequent `connect_to_backend` starts from a clean slate.
1693        debug_assert_eq!(
1694            self.backend_connected,
1695            BackendConnectionStatus::NotConnected,
1696            "close_backend must leave the backend NotConnected"
1697        );
1698        debug_assert!(
1699            self.backend_token.is_none(),
1700            "close_backend must clear the backend token"
1701        );
1702    }
1703
1704    fn connect_to_backend(
1705        &mut self,
1706        session_rc: Rc<RefCell<dyn ProxySession>>,
1707    ) -> Result<BackendConnectAction, BackendConnectionError> {
1708        // Precondition: the retry budget can sit AT the ceiling (the gate
1709        // below converts that into `MaxConnectionRetries`) but the increment
1710        // in `ready_inner` must never have pushed it past the largest budget
1711        // that can be configured.
1712        debug_assert!(
1713            u32::from(self.connection_attempt)
1714                <= sozu_command::config::MAX_CONNECTION_ATTEMPTS_UPPER_BOUND,
1715            "connection_attempt ({}) overflowed the largest attempt budget before the retry gate",
1716            self.connection_attempt,
1717        );
1718
1719        // Prefer the SNI-routed cluster (set by `TcpSession::readable` once
1720        // `SniPrereadCore` decides) over the listener's legacy no-SNI
1721        // catch-all -- a listener never configures both (sozu-proxy/sozu#1279),
1722        // but this order is also simply correct for the routed case, where
1723        // `listener.cluster_id` is `None`.
1724        let cluster_id = self
1725            .cluster_id
1726            .clone()
1727            .or_else(|| self.listener.borrow().cluster_id.clone())
1728            .ok_or(BackendConnectionError::NotFound(ObjectKind::TcpCluster))?;
1729
1730        // Capture the metrics incarnation exactly once, when the session is
1731        // first routed. Every path that sets `cluster_id` (session creation,
1732        // SNI preread) captures alongside it, so a retry with `cluster_id`
1733        // already set keeps the original capture even when it is `None`.
1734        if self.cluster_id.is_none() {
1735            self.cluster_metrics_incarnation =
1736                crate::metrics::capture_cluster_metrics(cluster_id.as_ref());
1737            self.cluster_id = Some(cluster_id.clone());
1738        }
1739
1740        // The cluster's own budget when it sets one, the worker's otherwise
1741        // (sozu-proxy/sozu#1800).
1742        let max_attempts = {
1743            let proxy = self.proxy.borrow();
1744            let cluster_attempts = proxy
1745                .configs
1746                .get(&cluster_id)
1747                .and_then(|c| c.max_connection_attempts);
1748            proxy
1749                .sessions
1750                .borrow()
1751                .effective_max_connection_attempts(cluster_attempts)
1752        };
1753        if u32::from(self.connection_attempt) >= max_attempts {
1754            incr!(
1755                names::backend::CONNECT_RETRIES_EXHAUSTED,
1756                self.cluster_id.as_deref(),
1757                self.metrics.backend_id.as_deref(),
1758                self.cluster_metrics_incarnation
1759            );
1760            warn!(
1761                "{} Max connection attempt reached ({})",
1762                log_context!(self),
1763                self.connection_attempt
1764            );
1765            return Err(BackendConnectionError::MaxConnectionRetries(Some(
1766                cluster_id,
1767            )));
1768        }
1769
1770        if self.proxy.borrow().sessions.borrow().at_capacity() {
1771            return Err(BackendConnectionError::MaxSessionsMemory);
1772        }
1773
1774        // Per-(cluster, source-IP) connection limit gate (TCP). The
1775        // source IP comes from `effective_session_address`, which folds
1776        // a parsed PROXY-v2 source over the raw `peer_addr`. The mux's
1777        // Router does the same gate for HTTP/HTTPS sessions; here it
1778        // runs for raw TCP. Rejection produces a graceful TCP FIN via
1779        // `BackendConnectionError::TooManyConnectionsPerIp` →
1780        // `handle_connection_result` → `SessionResult::Close` — TCP has
1781        // no HTTP envelope to carry a 429 / `Retry-After`.
1782        let (cluster_max_connections_per_ip, cluster_max_connections_per_subnet, keyed) = self
1783            .proxy
1784            .borrow()
1785            .configs
1786            .get(&cluster_id)
1787            .map(|c| {
1788                (
1789                    c.max_connections_per_ip,
1790                    c.max_connections_per_subnet,
1791                    c.reads_affinity_key,
1792                )
1793            })
1794            .unwrap_or((None, None, false));
1795        if let Some(ip) = self.effective_session_address().map(|sa| sa.ip()) {
1796            let sessions_rc = self.proxy.borrow().sessions.clone();
1797            // Both caps, through the one combined gate the mux uses too,
1798            // so raw TCP and HTTP/HTTPS can never disagree about what
1799            // "admitted" means.
1800            let at_limit = sessions_rc.borrow().cluster_connection_at_limit(
1801                self.frontend_token,
1802                &cluster_id,
1803                &ip,
1804                cluster_max_connections_per_ip,
1805                cluster_max_connections_per_subnet,
1806            );
1807            if at_limit {
1808                debug!(
1809                    "{} per-(cluster, source-IP/subnet) limit hit for cluster {} from {}",
1810                    log_context!(self),
1811                    cluster_id,
1812                    ip
1813                );
1814                return Err(BackendConnectionError::TooManyConnectionsPerIp { cluster_id });
1815            }
1816            sessions_rc.borrow_mut().track_cluster_connection(
1817                self.frontend_token,
1818                cluster_id.clone(),
1819                ip,
1820                cluster_max_connections_per_subnet,
1821            );
1822            self.cluster_ip_tracked = true;
1823        }
1824
1825        // `HRW` and `MAGLEV` pin a TCP client on its source IP, and shuffle
1826        // sharding ranks its shard by it — the PROXY-v2 source when the
1827        // listener expects one, as for the gate above. A TCP session has no
1828        // request to read a header or cookie from, so the source IP is the
1829        // only key. Any other cluster ignores a key, so none is derived.
1830        let affinity_key = if keyed {
1831            self.effective_session_address()
1832                .map(|address| affinity_key_from_ip(address.ip()))
1833        } else {
1834            None
1835        };
1836        let (backend, mut stream) = self
1837            .proxy
1838            .borrow()
1839            .backends
1840            .borrow_mut()
1841            .backend_from_cluster_id(&cluster_id, affinity_key, Instant::now())
1842            .map_err(BackendConnectionError::Backend)?;
1843
1844        // A `unix_path` backend has no TCP layer to set TCP_NODELAY on.
1845        if backend.borrow().unix_path.is_none()
1846            && let Err(e) = stream.set_nodelay(true)
1847        {
1848            error!(
1849                "{} Error setting nodelay on back socket({:?}): {:?}",
1850                log_context!(self),
1851                stream,
1852                e
1853            );
1854        }
1855        // Keep the selected backend's handle, as the mux keeps its own in
1856        // its `BackendRegistry`: `fail_backend_connection` and
1857        // `set_back_connected` charge it (failure counter, retry policy,
1858        // UP/DOWN transitions), and `remove_backend` releases the connection
1859        // `Backend::try_connect` counted on it.
1860        self.backend = Some(Rc::clone(&backend));
1861        self.backend_connected = BackendConnectionStatus::Connecting(Instant::now());
1862
1863        let back_token = {
1864            let proxy = self.proxy.borrow();
1865            let mut s = proxy.sessions.borrow_mut();
1866            let entry = s.slab.vacant_entry();
1867            let back_token = Token(entry.key());
1868            let _entry = entry.insert(session_rc.clone());
1869            back_token
1870        };
1871
1872        if let Err(e) = self.proxy.borrow().registry.register(
1873            &mut stream,
1874            back_token,
1875            Interest::READABLE | Interest::WRITABLE,
1876        ) {
1877            error!(
1878                "{} Error registering back socket({:?}): {:?}",
1879                log_context!(self),
1880                stream,
1881                e
1882            );
1883        }
1884
1885        self.container_backend_timeout.set(back_token);
1886
1887        self.set_back_token(back_token);
1888        self.set_back_socket(stream);
1889
1890        self.metrics.backend_id = Some(Rc::from(backend.borrow().backend_id.as_str()));
1891        self.metrics.backend_start();
1892        self.set_backend_id(backend.borrow().backend_id.clone());
1893        self.set_backend_address(backend.borrow().address);
1894
1895        // Postcondition of a successful New connect: the session is wired to
1896        // its freshly-registered backend token and the status reflects an
1897        // in-flight handshake (`Connecting`). The promotion to `Connected`
1898        // happens later in `ready_inner` once the socket signals writable.
1899        debug_assert!(
1900            self.backend_token.is_some(),
1901            "a New backend connection must own its backend token"
1902        );
1903        debug_assert!(
1904            self.backend_connected.is_connecting(),
1905            "a New backend connection must be in the Connecting state"
1906        );
1907
1908        Ok(BackendConnectAction::New)
1909    }
1910}
1911
1912impl ProxySession for TcpSession {
1913    fn close(&mut self) {
1914        if self.has_been_closed {
1915            return;
1916        }
1917
1918        // Past the idempotency guard the session is closing for the first
1919        // time: every gauge-restore / untrack below must run exactly once, so
1920        // re-entry on an already-closed session would double-decrement.
1921        debug_assert!(
1922            !self.has_been_closed,
1923            "close() body must only run on a not-yet-closed session"
1924        );
1925
1926        // TODO: the state should handle the timeouts
1927        trace!("{} Closing TCP session", log_context!(self));
1928        self.metrics.service_stop();
1929
1930        // Drain the per-(cluster, source-IP) accounting before any
1931        // early-return path below. The fail / non-fail close branches
1932        // both count, and the SessionManager-side untrack is idempotent
1933        // (no-op when the slot was never tracked) so this is safe even
1934        // when `cluster_ip_tracked` is false.
1935        if self.cluster_ip_tracked {
1936            self.proxy
1937                .borrow()
1938                .sessions
1939                .borrow_mut()
1940                .untrack_all_cluster_ip(self.frontend_token);
1941            self.cluster_ip_tracked = false;
1942        }
1943
1944        // Restore gauges. `SniPreread` is the "reject"/"teardown" half of
1945        // `tcp.sni_preread.active`'s "-1 on every exit" contract -- the
1946        // "upgrade" exit already decremented it in `upgrade_sni_preread`,
1947        // which also transitions `self.state` away from `SniPreread` before
1948        // `close()` can ever observe that marker again. A session that
1949        // reaches `close()` still marked `SniPreread` (directly, or via
1950        // `FailedUpgrade(SniPreread)` if `upgrade_sni_preread` itself failed)
1951        // therefore never had its gauge/duration accounted for yet.
1952        match self.state.marker() {
1953            StateMarker::Pipe => gauge_add!(names::protocol::TCP, -1),
1954            StateMarker::SendProxyProtocol => gauge_add!(names::protocol::PROXY_SEND, -1),
1955            StateMarker::RelayProxyProtocol => gauge_add!(names::protocol::PROXY_RELAY, -1),
1956            StateMarker::ExpectProxyProtocol => gauge_add!(names::protocol::PROXY_EXPECT, -1),
1957            StateMarker::SniPreread => {
1958                gauge_add!(names::tcp::sni_preread::ACTIVE, -1);
1959                if let TcpStateMachine::SniPreread(preread) = &self.state {
1960                    time!(
1961                        names::tcp::sni_preread::DURATION,
1962                        preread.started_at().elapsed().as_millis() as i64
1963                    );
1964                }
1965            }
1966        }
1967
1968        if self.state.failed() {
1969            match self.state.marker() {
1970                StateMarker::Pipe => incr!(names::tcp::UPGRADE_PIPE_FAILED),
1971                StateMarker::SendProxyProtocol => incr!(names::tcp::UPGRADE_SEND_FAILED),
1972                StateMarker::RelayProxyProtocol => incr!(names::tcp::UPGRADE_RELAY_FAILED),
1973                StateMarker::ExpectProxyProtocol => incr!(names::tcp::UPGRADE_EXPECT_FAILED),
1974                StateMarker::SniPreread => incr!(names::tcp::UPGRADE_SNI_PREREAD_FAILED),
1975            }
1976            return;
1977        }
1978
1979        self.cancel_timeouts();
1980
1981        let front_socket = self.state.front_socket();
1982        // TCP listener is plaintext at this layer — `Shutdown::Both` does not
1983        // truncate any TLS write buffer, so the canonical anti-pattern
1984        // (forces a TCP RST on the read direction, dropping in-flight bytes)
1985        // does not apply. Move to `Shutdown::Write` if a TLS upgrade ever
1986        // wraps this listener.
1987        if let Err(e) = front_socket.shutdown(Shutdown::Both) {
1988            // error 107 NotConnected can happen when was never fully connected, or was already disconnected due to error
1989            if e.kind() != ErrorKind::NotConnected {
1990                error!(
1991                    "{} Error shutting down front socket({:?}): {:?}",
1992                    log_context!(self),
1993                    front_socket,
1994                    e
1995                );
1996            }
1997        }
1998
1999        // Free the frontend slot. No `EPOLL_CTL_DEL`: the server drops the
2000        // session right after `close()`, and that last `close(2)` of the
2001        // front socket takes it out of the epoll set (see `close_backend`).
2002        self.proxy
2003            .borrow()
2004            .sessions
2005            .borrow_mut()
2006            .slab
2007            .try_remove(self.frontend_token.0);
2008
2009        self.close_backend();
2010        self.has_been_closed = true;
2011
2012        // Postcondition of the normal close path: the session is terminal and
2013        // every accounting slot has been released — `close_backend` cleared
2014        // the backend token, and the per-(cluster, source-IP) untrack above
2015        // reset the flag. The idempotency guard now short-circuits any repeat.
2016        debug_assert!(self.has_been_closed, "close() must mark the session closed");
2017        debug_assert!(
2018            self.backend_token.is_none(),
2019            "close() must leave no dangling backend token"
2020        );
2021        debug_assert!(
2022            !self.cluster_ip_tracked,
2023            "close() must untrack the (cluster, source-IP) slot"
2024        );
2025    }
2026
2027    fn timeout(&mut self, token: Token) -> SessionIsToBeClosed {
2028        // The frontend and backend slots are distinct tokens, so the two
2029        // dispatch arms below are mutually exclusive — a single token can
2030        // never match both. (Obsolete tokens matching neither are tolerated
2031        // and fall through to the `false` arm.)
2032        debug_assert!(
2033            self.backend_token != Some(self.frontend_token),
2034            "frontend and backend tokens must never collide"
2035        );
2036        if self.frontend_token == token {
2037            self.container_frontend_timeout.triggered();
2038            // The preread deadline firing always closes the session either
2039            // way (matches every other state's front-timeout behavior).
2040            // Route-aware: only feed `Input::Timeout` into the core while
2041            // still UNDECIDED, for its `tcp.sni_preread.rejected.fragmented`
2042            // metric + log side effect. Once a route has already latched
2043            // (backend connect still pending), this same front-timeout
2044            // firing is a plain "connect/upgrade took too long" close, NOT a
2045            // fresh preread verdict -- re-feeding `Input::Timeout` into an
2046            // already-decided core would just replay the SAME latched
2047            // `Output::Routed` through `SniPreread::handle_output`'s
2048            // `Routed` arm a SECOND time: double-incrementing
2049            // `tcp.sni_preread.routed` in release, and tripping its
2050            // `debug_assert!(self.outcome.is_none(), ...)` in debug
2051            // (sozu-proxy/sozu#1290).
2052            if let TcpStateMachine::SniPreread(preread) = &mut self.state {
2053                if preread.is_routed() {
2054                    debug!(
2055                        "{} frontend timeout while a routed SNI-preread session was still \
2056                         waiting on its backend connect",
2057                        log_context!(self)
2058                    );
2059                } else {
2060                    let listener = self.listener.borrow();
2061                    let cfg = listener.preread_config(preread.effective_max_bytes());
2062                    preread.on_timeout(&cfg);
2063                }
2064            }
2065            return true;
2066        }
2067        if self.backend_token == Some(token) {
2068            self.container_backend_timeout.triggered();
2069            return true;
2070        }
2071        // invalid token, obsolete timeout triggered
2072        false
2073    }
2074
2075    fn protocol(&self) -> Protocol {
2076        Protocol::TCP
2077    }
2078
2079    fn update_readiness(&mut self, token: Token, events: Ready) {
2080        trace!(
2081            "{} token {:?} got event {}",
2082            log_context!(self),
2083            token,
2084            super::ready_to_string(events)
2085        );
2086
2087        self.last_event = Instant::now();
2088        self.metrics.wait_start();
2089
2090        if self.frontend_token == token {
2091            self.front_readiness().event = self.front_readiness().event | events;
2092        } else if self.backend_token == Some(token)
2093            && let Some(r) = self.back_readiness()
2094        {
2095            r.event |= events;
2096        }
2097    }
2098
2099    fn ready(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
2100        self.metrics.service_start();
2101
2102        let session_result = self.ready_inner(session.clone());
2103
2104        let to_bo_closed = match session_result {
2105            SessionResult::Close => true,
2106            SessionResult::Continue => false,
2107            SessionResult::Upgrade => match self.upgrade() {
2108                false => self.ready(session),
2109                true => true,
2110            },
2111        };
2112
2113        self.metrics.service_stop();
2114
2115        // Run-to-completion postcondition: the front/back token + readiness
2116        // state machine must satisfy its cross-field invariants after every
2117        // `ready()` pass. Cfg-guarded so the call (and `check_invariants`
2118        // itself) is absent from release builds.
2119        #[cfg(debug_assertions)]
2120        self.check_invariants();
2121
2122        to_bo_closed
2123    }
2124
2125    fn shutting_down(&mut self) -> SessionIsToBeClosed {
2126        true
2127    }
2128
2129    /// A TCP session is targeted by the backend it dialled last.
2130    fn targets(&self, target: &crate::SessionTarget) -> bool {
2131        self.cluster_id.as_deref().is_some_and(|cluster_id| {
2132            target.matches(cluster_id, self.backend_id.as_deref(), self.backend_address)
2133        })
2134    }
2135
2136    fn last_event(&self) -> Instant {
2137        self.last_event
2138    }
2139
2140    fn print_session(&self) {
2141        let state: String = match &self.state {
2142            TcpStateMachine::ExpectProxyProtocol(_) => String::from("Expect"),
2143            TcpStateMachine::SendProxyProtocol(_) => String::from("Send"),
2144            TcpStateMachine::RelayProxyProtocol(_) => String::from("Relay"),
2145            TcpStateMachine::Pipe(_) => String::from("TCP"),
2146            TcpStateMachine::SniPreread(_) => String::from("SniPreread"),
2147            TcpStateMachine::FailedUpgrade(marker) => format!("FailedUpgrade({marker:?})"),
2148        };
2149
2150        let front_readiness = match &self.state {
2151            TcpStateMachine::ExpectProxyProtocol(expect) => Some(&expect.frontend_readiness),
2152            TcpStateMachine::SendProxyProtocol(send) => Some(&send.frontend_readiness),
2153            TcpStateMachine::RelayProxyProtocol(relay) => Some(&relay.frontend_readiness),
2154            TcpStateMachine::Pipe(pipe) => Some(&pipe.frontend_readiness),
2155            TcpStateMachine::SniPreread(preread) => Some(&preread.frontend_readiness),
2156            TcpStateMachine::FailedUpgrade(_) => None,
2157        };
2158
2159        let back_readiness = match &self.state {
2160            TcpStateMachine::SendProxyProtocol(send) => Some(&send.backend_readiness),
2161            TcpStateMachine::RelayProxyProtocol(relay) => Some(&relay.backend_readiness),
2162            TcpStateMachine::Pipe(pipe) => Some(&pipe.backend_readiness),
2163            TcpStateMachine::SniPreread(preread) => Some(&preread.backend_readiness),
2164            TcpStateMachine::ExpectProxyProtocol(_) => None,
2165            TcpStateMachine::FailedUpgrade(_) => None,
2166        };
2167
2168        error!(
2169            "\
2170{} Session ({:?})
2171\tFrontend:
2172\t\ttoken: {:?}\treadiness: {:?}
2173\tBackend:
2174\t\ttoken: {:?}\treadiness: {:?}\tstatus: {:?}\tcluster id: {:?}",
2175            log_context!(self),
2176            state,
2177            self.frontend_token,
2178            front_readiness,
2179            self.backend_token,
2180            back_readiness,
2181            self.backend_connected,
2182            self.cluster_id
2183        );
2184        error!("Metrics: {:?}", self.metrics);
2185    }
2186
2187    fn frontend_token(&self) -> Token {
2188        self.frontend_token
2189    }
2190}
2191
2192pub struct TcpListener {
2193    active: SessionIsToBeClosed,
2194    address: SocketAddr,
2195    cluster_id: Option<ClusterId>,
2196    config: TcpListenerConfig,
2197    listener: Option<MioTcpListener>,
2198    /// A socket this listener holds but has NOT registered.
2199    ///
2200    /// `activate()` parks an inherited socket here when `Registry::register`
2201    /// fails, instead of letting the `?` drop and close a descriptor
2202    /// `Listeners::get_*` has already removed from the SCM table (sozu#1342).
2203    /// The next activation reuses it rather than binding a second one.
2204    ///
2205    /// Deliberately NOT `listener`: every other consumer reads that field's `Some` as
2206    /// "registered and live" — `soft_stop` and `hard_stop` deregister it and
2207    /// report the failure, `give_back_listener` hands it back as an activated
2208    /// socket, `accept` accepts on it. A parked socket is none of those things,
2209    /// and putting it there made a failed registration answer `ENOENT` from
2210    /// `deregister` and fail the whole soft stop.
2211    parked_listener: Option<MioTcpListener>,
2212    /// Set by `activate()`, cleared by the first `accept()` that answers
2213    /// `WouldBlock`: while set, `accept()` sets `TCP_NODELAY` on each socket
2214    /// it returns, because a connection queued before `activate()` set the
2215    /// flag on the listener did not inherit it. See the field of the same
2216    /// name on `HttpListener` (`lib/src/http.rs`).
2217    nodelay_backlog: bool,
2218    /// SNI -> `(AlpnMatcher, ClusterId)` route table (sozu-proxy/sozu#1279).
2219    /// Populated by `add_tcp_front`/`remove_tcp_front` from
2220    /// `RequestTcpFrontend.sni`/`.alpn`; empty for a listener whose fronts
2221    /// are all no-SNI (the legacy `cluster_id` catch-all). A listener never
2222    /// mixes both (enforced at config load, `command/src/config.rs`), but
2223    /// `create_session`'s routing gate stays defensive and checks both.
2224    sni_routes: TrieNode<Vec<(AlpnMatcher, ClusterId)>>,
2225    tags: BTreeMap<String, CachedTags>,
2226    token: Token,
2227}
2228
2229impl ListenerHandler for TcpListener {
2230    fn get_addr(&self) -> &SocketAddr {
2231        &self.address
2232    }
2233
2234    fn get_tags(&self, key: &str) -> Option<&CachedTags> {
2235        self.tags.get(key)
2236    }
2237
2238    fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
2239        match tags {
2240            Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
2241            None => self.tags.remove(&key),
2242        };
2243    }
2244
2245    fn protocol(&self) -> Protocol {
2246        Protocol::TCP
2247    }
2248
2249    fn public_address(&self) -> SocketAddr {
2250        self.config
2251            .public_address
2252            .map(|addr| addr.into())
2253            .unwrap_or(self.address)
2254    }
2255}
2256
2257impl TcpListener {
2258    /// This listener's identity: its address, plus the network interface its
2259    /// socket is bound to when it has one.
2260    pub fn key(&self) -> ListenerKey {
2261        ListenerKey::new(self.address, self.config.interface.as_deref())
2262    }
2263
2264    /// Whether this listener is the one `key` names.
2265    pub fn is(&self, key: &ListenerKey) -> bool {
2266        key.matches(&self.address, self.config.interface.as_deref())
2267    }
2268
2269    fn new(config: TcpListenerConfig, token: Token) -> Result<TcpListener, ListenerError> {
2270        Ok(TcpListener {
2271            cluster_id: None,
2272            listener: None,
2273            parked_listener: None,
2274            nodelay_backlog: false,
2275            token,
2276            address: config.address.into(),
2277            config,
2278            active: false,
2279            sni_routes: TrieNode::root(),
2280            tags: BTreeMap::new(),
2281        })
2282    }
2283
2284    /// Validate that a worker can build this TCP listener configuration WITHOUT
2285    /// constructing the full listener or binding a socket. TCP listener
2286    /// construction has no fallible config today (no rustls context, no answer
2287    /// templates; a bad bind surfaces later as an `ActivateListener` failure),
2288    /// so this currently always succeeds. It exists for surface parity with the
2289    /// HTTP/HTTPS validators the main process calls before committing an
2290    /// `Add*Listener` to `ConfigState` and fanning it out (sozu#1301), and is
2291    /// the hook for any future TCP-config validation.
2292    pub fn validate_config(_config: &TcpListenerConfig) -> Result<(), ListenerError> {
2293        Ok(())
2294    }
2295
2296    /// Build the [`PrereadConfig`] this listener's `SniPreread` sessions
2297    /// feed to [`crate::protocol::tcp_preread::SniPrereadCore::handle_input`].
2298    /// `routes`/`inbound_proxy`/`timeout` come straight from the listener
2299    /// config; `effective_max_bytes` is session-specific (already clamped to
2300    /// that session's buffer capacity at construction, see `create_session`),
2301    /// so it is passed in rather than re-derived here.
2302    fn preread_config(&self, effective_max_bytes: usize) -> PrereadConfig<'_> {
2303        PrereadConfig {
2304            routes: &self.sni_routes,
2305            inbound_proxy: self.config.expect_proxy,
2306            max_bytes: effective_max_bytes,
2307            timeout: Duration::from_secs(u64::from(
2308                self.config
2309                    .sni_preread_timeout
2310                    .unwrap_or(DEFAULT_SNI_PREREAD_TIMEOUT),
2311            )),
2312            accept_wildcard: true,
2313        }
2314    }
2315
2316    /// Validate an incoming `AddTcpFrontend` against this listener's
2317    /// CURRENT routing state before any mutation, mirroring
2318    /// `command/src/config.rs`'s TOML config-load TCP SNI/ALPN invariants
2319    /// (sozu-proxy/sozu#1279):
2320    ///
2321    /// - `alpn` set with no `sni`: the worker's no-SNI catch-all path never
2322    ///   consults `alpn`, so the protocol list would silently never be
2323    ///   enforced (mirrors `ConfigError::AlpnWithoutSni`).
2324    /// - a no-SNI frontend added to a listener that already has SNI-scoped
2325    ///   routes, or an SNI-scoped frontend added to a listener that already
2326    ///   has a no-SNI catch-all cluster (mirrors
2327    ///   `ConfigError::TcpListenerMixesSniAndNoSni`).
2328    /// - an ALPN protocol, or a catch-all (empty `alpn`), that overlaps an
2329    ///   existing route already registered for the same `(address, sni)`
2330    ///   (mirrors `ConfigError::TcpFrontendAlpnOverlap` /
2331    ///   `TcpFrontendMultipleAlpnCatchAll`).
2332    ///
2333    /// Config-load already rejects all of these shapes for requests built
2334    /// from a TOML file, but `AddTcpFrontend` can also arrive directly over
2335    /// the command socket, or via `LoadState` replay of a hand-edited or
2336    /// stale state file, bypassing config.rs entirely -- the worker must
2337    /// not silently corrupt its own routing table when that happens.
2338    fn validate_new_tcp_front(&self, front: &RequestTcpFrontend) -> Result<(), ProxyError> {
2339        let reject = |reason: String| {
2340            Err(ProxyError::InvalidTcpFrontend {
2341                address: self.address,
2342                reason,
2343            })
2344        };
2345
2346        match &front.sni {
2347            None => {
2348                if !front.alpn.is_empty() {
2349                    return reject(format!(
2350                        "alpn = {:?} set without sni: alpn only matches within an SNI-scoped \
2351                         preread, so a frontend without sni would silently ignore its alpn list",
2352                        front.alpn
2353                    ));
2354                }
2355                if !self.sni_routes.is_empty() {
2356                    return reject(
2357                        "a no-SNI frontend cannot be added to a listener that already has \
2358                         SNI-scoped routes"
2359                            .to_string(),
2360                    );
2361                }
2362            }
2363            Some(sni) => {
2364                if self.cluster_id.is_some() {
2365                    return reject(
2366                        "an SNI-scoped frontend cannot be added to a listener that already has \
2367                         a no-SNI catch-all cluster"
2368                            .to_string(),
2369                    );
2370                }
2371
2372                // SNI SHAPE check: delegate to the SAME validator config-load
2373                // uses (`sozu_command::config::validate_sni_pattern`) rather
2374                // than a hand-rolled partial check. A direct `AddTcpFrontend`
2375                // over the command socket, or a `LoadState` replay, bypasses
2376                // config.rs entirely, so the worker boundary must enforce
2377                // the identical rule -- including the checks a bare '/'/'*'
2378                // scan used to miss: empty string, non-ASCII, and any empty
2379                // label (leading/trailing/consecutive dots). An unvalidated
2380                // leading-empty-label pattern like `.example.com` would
2381                // otherwise reach `insert_sni_route` ->
2382                // `pattern_trie::insert_recursive`'s RELEASE-mode
2383                // `assert_ne!(partial_key, &b""[..])` and crash the worker.
2384                let normalized_sni = match validate_sni_pattern(sni) {
2385                    Ok(normalized) => normalized,
2386                    Err(config_error) => {
2387                        return reject(format!(
2388                            "sni {sni:?} failed SNI shape validation: {config_error}"
2389                        ));
2390                    }
2391                };
2392
2393                // Same key as `insert_sni_route`'s own lookup, so this checks
2394                // against exactly the entries the new route would be appended
2395                // alongside. This is bookkeeping over the key's OWN node, not
2396                // the routing lookup (`preread_config` keeps `true` for
2397                // that), so `accept_wildcard` must make the lookup EXACT for
2398                // both key shapes:
2399                //
2400                // - literal key -> `false`. With `true`, a literal key with
2401                //   no child yet (e.g. `a.example.com` when only
2402                //   `*.example.com` exists) falls back to the sibling
2403                //   wildcard's entries (`pattern_trie.rs`'s `lookup`
2404                //   wildcard-fallback branch), misattributing the wildcard's
2405                //   catch-all to the exact key (falsely rejecting a
2406                //   legitimate exact catch-all) — and symmetrically,
2407                //   `insert_sni_route`/`remove_sni_route` would corrupt the
2408                //   WILDCARD's `Vec` instead of touching a distinct
2409                //   exact-key node.
2410                // - wildcard key -> `true`. Unlike `lookup_mut` (which
2411                //   short-circuits `partial_key == b"*"` before consulting
2412                //   `accept_wildcard`, so insert/remove stay on `false`),
2413                //   the immutable `lookup` reaches a wildcard entry ONLY
2414                //   through the fallback branch; with `false` an existing
2415                //   `*.example.com` entry is invisible here and a duplicate
2416                //   catch-all / overlapping-ALPN wildcard front bypasses
2417                //   validation. For a wildcard key, `true` IS the exact
2418                //   self-lookup: the traversal descends the key's own
2419                //   literal ancestry, and `insert` never creates a literal
2420                //   `*` child that could shadow the node's `wildcard` slot.
2421                //
2422                // `starts_with(b"*.")` matches every wildcard shape
2423                // config-load can emit: `command/src/config.rs`'s
2424                // `validate_sni_pattern` only admits a single leading `*.`
2425                // label (any other `*` placement is rejected), and a plain
2426                // hostname key traverses literal children in both `lookup`
2427                // and `insert_recursive`. A bare `*` key (config-load
2428                // rejects it; only a bypassing IPC request can carry one)
2429                // is the known gap: `lookup_mut`'s short-circuit maps it to
2430                // the wildcard slot while this immutable self-lookup (flag
2431                // `false`) cannot see an existing entry there, so duplicate
2432                // bare-`*` fronts are not detected -- sibling keys stay
2433                // untouched either way.
2434                //
2435                // `normalized_sni` (not a fresh `sni.to_ascii_lowercase()`)
2436                // is used here: `validate_sni_pattern` already returned the
2437                // canonical lowercased form above, and reusing it keeps this
2438                // function's notion of "the key" identical to what
2439                // `insert_sni_route`/`remove_sni_route` compute from the
2440                // same input.
2441                let key = normalized_sni.into_bytes();
2442                let accept_wildcard_for_self_lookup = key.starts_with(b"*.");
2443                if let Some((_, existing)) = self
2444                    .sni_routes
2445                    .domain_lookup(&key, accept_wildcard_for_self_lookup)
2446                {
2447                    let new_is_catch_all = front.alpn.is_empty();
2448                    for (matcher, _cluster_id) in existing {
2449                        match matcher {
2450                            AlpnMatcher::Any if new_is_catch_all => {
2451                                return reject(format!(
2452                                    "sni {sni:?} already has a catch-all (empty alpn) \
2453                                     frontend: at most one frontend per (address, sni) may \
2454                                     omit alpn"
2455                                ));
2456                            }
2457                            AlpnMatcher::OneOf(protocols) => {
2458                                if let Some(overlap) = front
2459                                    .alpn
2460                                    .iter()
2461                                    .find(|protocol| protocols.contains(protocol.as_bytes()))
2462                                {
2463                                    return reject(format!(
2464                                        "sni {sni:?} already has a frontend matching ALPN \
2465                                         protocol {overlap:?}: ALPN matchers for the same \
2466                                         (address, sni) must not overlap"
2467                                    ));
2468                                }
2469                            }
2470                            AlpnMatcher::Any => {}
2471                        }
2472                    }
2473                }
2474            }
2475        }
2476
2477        Ok(())
2478    }
2479
2480    /// Add one `(AlpnMatcher, ClusterId)` entry to this listener's SNI route
2481    /// table, appending to the SNI key's existing `Vec` if one is already
2482    /// present rather than clobbering it (multiple ALPN-scoped fronts can
2483    /// share the same SNI). `sni` is defensively lowercased: the
2484    /// SNI-preread core normalizes (lowercase, no trailing dot) before ever
2485    /// looking a route up, so the table key must already be in that form.
2486    ///
2487    /// Returns `Err` when the underlying trie insert reports
2488    /// [`InsertResult::Failed`] (a malformed key, e.g. an empty label) —
2489    /// the caller (`TcpProxy::add_tcp_front`) must propagate this rather
2490    /// than silently keep going with an add that reports success while the
2491    /// route table never gained a usable entry. With `validate_new_tcp_front`
2492    /// enforcing the shared SNI shape validator before this ever runs, a
2493    /// `Failed` result should be unreachable here — the `debug_assert_ne!`
2494    /// below asserts OUR OWN validated invariant, not raw wire/IPC input,
2495    /// and is a loud Sōzu-internal-bug canary in debug builds; the `Err`
2496    /// path is the release-mode graceful fallback if that invariant is ever
2497    /// violated by a future bug.
2498    fn insert_sni_route(
2499        &mut self,
2500        sni: String,
2501        alpn: Vec<String>,
2502        cluster_id: ClusterId,
2503    ) -> Result<(), ProxyError> {
2504        let (key, matcher) = route_key_and_matcher(&sni, alpn);
2505        // `accept_wildcard: false` — see `validate_new_tcp_front`'s comment:
2506        // an exact key must never fall back to a sibling wildcard's entry,
2507        // or this push would corrupt the WILDCARD's route `Vec` instead of
2508        // creating this key's own node.
2509        match self.sni_routes.domain_lookup_mut(&key, false) {
2510            Some((_, entries)) => {
2511                entries.push((matcher, cluster_id));
2512                Ok(())
2513            }
2514            None => {
2515                let insert_result = self
2516                    .sni_routes
2517                    .domain_insert(key, vec![(matcher, cluster_id)]);
2518                debug_assert_ne!(
2519                    insert_result,
2520                    InsertResult::Failed,
2521                    "insert_sni_route's key must already be validated by validate_new_tcp_front"
2522                );
2523                if insert_result == InsertResult::Failed {
2524                    error!(
2525                        "{} SNI route insert failed for {:?} despite passing shape \
2526                         validation -- rejecting to avoid a silently dead route",
2527                        log_module_context!(),
2528                        sni
2529                    );
2530                    return Err(ProxyError::InvalidTcpFrontend {
2531                        address: self.address,
2532                        reason: format!(
2533                            "internal error: the route table rejected sni {sni:?} despite \
2534                             passing shape validation"
2535                        ),
2536                    });
2537                }
2538                Ok(())
2539            }
2540        }
2541    }
2542
2543    /// Symmetric counterpart to [`Self::insert_sni_route`]: removes the
2544    /// matching `(AlpnMatcher, ClusterId)` entry, then `domain_remove`s the
2545    /// SNI key itself once its `Vec` is empty (no stranded empty entries in
2546    /// the trie).
2547    fn remove_sni_route(&mut self, sni: String, alpn: Vec<String>, cluster_id: &str) {
2548        let (key, matcher) = route_key_and_matcher(&sni, alpn);
2549        // `accept_wildcard: false` — same reasoning as `insert_sni_route`:
2550        // removing the exact key must never reach into and strip a sibling
2551        // wildcard's entries.
2552        if let Some((_, entries)) = self.sni_routes.domain_lookup_mut(&key, false) {
2553            entries.retain(|(m, c)| !(*m == matcher && **c == *cluster_id));
2554            if entries.is_empty() {
2555                self.sni_routes.domain_remove(&key);
2556            }
2557        }
2558    }
2559
2560    pub fn activate(
2561        &mut self,
2562        registry: &Registry,
2563        tcp_listener: Option<MioTcpListener>,
2564    ) -> Result<Token, ProxyError> {
2565        if self.active {
2566            return Ok(self.token);
2567        }
2568        let address: SocketAddr = self.config.address.into();
2569
2570        // Reuse a socket a previous attempt parked here after a failed
2571        // registration, rather than binding a second one. Taking it
2572        // unconditionally is what keeps the two from coexisting: if the caller
2573        // ALSO handed one over, the parked socket is disposed of deliberately
2574        // below instead of being silently overwritten.
2575        let parked = self.parked_listener.take();
2576        let mut listener = match (tcp_listener, parked) {
2577            (Some(inherited), Some(parked)) => {
2578                // Not reachable while `Listeners::get_*` removes the entry it
2579                // hands over and `scm_listeners` is written once: a parked
2580                // socket means this address's entry was already consumed, so
2581                // the caller cannot have another. Neither of those is enforced
2582                // here, so dispose of the loser explicitly and loudly rather
2583                // than letting an assignment close it — that silent close is
2584                // sozu#1342 itself.
2585                warn!(
2586                    "{} closing the socket parked on the TCP listener for {}: an \
2587                     inherited socket arrived for the same address and is the one being \
2588                     activated",
2589                    log_module_context!(),
2590                    address
2591                );
2592                drop(parked);
2593                inherited
2594            }
2595            (Some(inherited), None) => inherited,
2596            (None, Some(parked)) => parked,
2597            (None, None) => server_bind(address, self.config.interface.as_deref())
2598                .map_err(|e| ProxyError::BindToSocket(address, e))?,
2599        };
2600
2601        // Once per listener, on whichever socket won above — freshly bound,
2602        // inherited over SCM_RIGHTS, or parked. Every socket accepted from now
2603        // on inherits it; see `nodelay_backlog` for the ones already queued.
2604        if let Err(e) = SockRef::from(&listener).set_tcp_nodelay(true) {
2605            error!(
2606                "{} error setting nodelay on listen socket({:?}): {:?}",
2607                log_module_context!(),
2608                listener,
2609                e
2610            );
2611        }
2612
2613        let registration = registry
2614            .register(&mut listener, self.token, Interest::READABLE)
2615            .map_err(ProxyError::RegisterListener);
2616        if let Err(error) = registration {
2617            // Park the socket instead of letting the `?` drop it. `register`
2618            // is fallible (EEXIST for a descriptor still registered because
2619            // `notify_deactivate_listener` only logs a failed deregister,
2620            // EBADF, ENOMEM), and a socket still held in a local is dropped —
2621            // and closed — on the way out. For an SCM-inherited socket that is
2622            // the sozu#1342 close in a second place: `Listeners::get_*` has
2623            // already removed the entry, so nothing else owns the descriptor.
2624            // It goes to `parked_listener`, never to `listener`, because every other
2625            // consumer of that field reads `Some` as "registered and live".
2626            self.parked_listener = Some(listener);
2627            return Err(error);
2628        }
2629
2630        self.listener = Some(listener);
2631        self.nodelay_backlog = true;
2632        self.active = true;
2633        Ok(self.token)
2634    }
2635
2636    /// Apply a partial-update patch to this TCP listener's live configuration.
2637    ///
2638    /// Fields absent in the patch (i.e. `None`) are preserved unchanged.
2639    pub fn update_config(&mut self, patch: &UpdateTcpListenerConfig) -> Result<(), ListenerError> {
2640        if let Some(v) = patch.public_address {
2641            self.config.public_address = Some(v);
2642        }
2643        if let Some(v) = patch.expect_proxy {
2644            self.config.expect_proxy = v;
2645        }
2646        if let Some(v) = patch.front_timeout {
2647            self.config.front_timeout = v;
2648        }
2649        if let Some(v) = patch.back_timeout {
2650            self.config.back_timeout = v;
2651        }
2652        if let Some(v) = patch.connect_timeout {
2653            self.config.connect_timeout = v;
2654        }
2655        Ok(())
2656    }
2657}
2658
2659fn handle_connection_result(
2660    connection_result: Result<BackendConnectAction, BackendConnectionError>,
2661) -> Option<SessionResult> {
2662    match connection_result {
2663        // reuse connection or send a default answer, we can continue
2664        Ok(BackendConnectAction::Reuse) => None,
2665        Ok(BackendConnectAction::New) | Ok(BackendConnectAction::Replace) => {
2666            // we must wait for an event
2667            Some(SessionResult::Continue)
2668        }
2669        Err(_) => {
2670            // in case of BackendConnectionError::Backend(BackendError::ConnectionFailures(..))
2671            // we may want to retry instead of closing
2672            Some(SessionResult::Close)
2673        }
2674    }
2675}
2676
2677/// `min(listener.config.sni_preread_max_bytes, frontend_buffer.capacity())`,
2678/// floored at [`MIN_SNI_PREREAD_MAX_BYTES`] -- the SNI-preread core has no
2679/// independent backstop of its own (`SniPrereadCore::handle_input` trusts
2680/// `PrereadConfig::max_bytes` entirely), so the shell must never hand it a
2681/// cap the checked-out buffer cannot actually hold (the `min`), NOR a cap so
2682/// small the preread read is zero-length and spins until the loop guard (the
2683/// `max`). Config-load rejects a sub-floor `sni_preread_max_bytes` loudly
2684/// (`ConfigError::SniPrereadMaxBytesTooSmall`), but a `0` knob from a direct
2685/// `sozu listener tcp add` CLI/IPC request, or a stale `LoadState`
2686/// replay, bypasses that check and reaches the worker -- this floor degrades
2687/// it to the 5-byte TLS-record-header minimum instead, killing the spin for
2688/// EVERY config source at the single point of use. `buffer_capacity` is
2689/// always `>= MIN_SNI_PREREAD_MAX_BYTES` in practice (buffers are KB-sized),
2690/// so the `min` never fights the `max`.
2691fn effective_sni_preread_max_bytes(configured: Option<u32>, buffer_capacity: usize) -> usize {
2692    (configured.unwrap_or(DEFAULT_SNI_PREREAD_MAX_BYTES) as usize)
2693        .min(buffer_capacity)
2694        .max(MIN_SNI_PREREAD_MAX_BYTES as usize)
2695}
2696
2697/// Whether `TcpSession::readable`'s frontend-timeout reset should fire for
2698/// the CURRENT state. `false` only while the session is an UNDECIDED
2699/// `SniPreread`: the preread deadline armed at session creation
2700/// (`new_sni_preread`) is an ABSOLUTE budget, not a per-fragment idle timer
2701/// -- resetting it on every `readable()` event would let a client
2702/// trickling one byte just before each expiry hold the session (and both
2703/// its checked-out buffers) open far past the configured
2704/// `sni_preread_timeout` (sozu-proxy/sozu#1290). Every
2705/// other state -- a `SniPreread` that has already routed, or any state
2706/// reached after it -- resets normally: once routed, the frontend timeout
2707/// reverts to being a genuine per-read idle timer (see the `front_timeout`
2708/// restore in `readable()`'s route-capture block).
2709fn frontend_timeout_resets_on_readable(state: &TcpStateMachine) -> bool {
2710    !matches!(
2711        state,
2712        TcpStateMachine::SniPreread(preread) if preread.outcome().is_none()
2713    )
2714}
2715
2716/// Access-log ALPN tag for an SNI-routed TCP session: the client's FIRST
2717/// offered protocol (client preference order, matching
2718/// `SniPrereadCore::route`'s own routing precedence), mapped to a known
2719/// `&'static str` label -- the same two labels `https.rs`'s own ALPN
2720/// negotiation records (`"h2"` / `"http/1.1"`) -- so `tcp.sni_preread`
2721/// sessions and terminated-TLS sessions chart under the same values.
2722/// `None` for an empty offer or an unrecognized protocol: Sōzu never
2723/// terminates TLS on this path, so this is the client's stated preference,
2724/// not a negotiated outcome.
2725fn known_alpn_label(offered: &[Vec<u8>]) -> Option<&'static str> {
2726    match offered.first().map(Vec::as_slice) {
2727        Some(b"h2") => Some("h2"),
2728        Some(b"http/1.1") => Some("http/1.1"),
2729        _ => None,
2730    }
2731}
2732
2733/// Worker-internal, collision-free identity for a TCP frontend's access-log
2734/// tags entry (sozu-proxy/sozu#1290). ALPN protocol
2735/// identifiers are opaque RFC 7301 byte strings -- nothing forbids a `,` or
2736/// `|` inside one -- so a naive `sorted_alpn.join(",")` string key let two
2737/// legal, DISJOINT fronts collide: a single protocol `"a,b"` and the pair
2738/// `["a", "b"]` both joined to the identical string `"a,b"`, so the second
2739/// `add_tcp_front` silently clobbered the first front's tags entry.
2740///
2741/// `TcpListener::tags: BTreeMap<String, CachedTags>` and
2742/// `Pipe::set_tags_key(Option<String>)` (`lib/src/protocol/pipe.rs`) both
2743/// key on a plain `String` -- crossing either boundary still needs one --
2744/// so this type does not replace that storage; it is the SINGLE place that
2745/// composes the string, via [`Self::fmt`]'s length-prefixed per-protocol
2746/// encoding, so no choice of in-band separator can be confused with a
2747/// protocol-name boundary the way a bare `join(",")` could.
2748#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2749enum TcpFrontendTagsKey {
2750    /// Legacy no-SNI catch-all front: keyed by the bare listener address,
2751    /// exactly as before SNI routing existed.
2752    Address(SocketAddr),
2753    /// An SNI-scoped front. `alpn` is sorted (and deduped, since a plain
2754    /// `Vec<String>` cannot enforce uniqueness the way
2755    /// [`AlpnMatcher::OneOf`]'s `BTreeSet` does) so two constructions from
2756    /// the same logical protocol set always compare equal regardless of
2757    /// the caller's original order.
2758    Sni {
2759        address: SocketAddr,
2760        sni: String,
2761        alpn: Vec<String>,
2762    },
2763}
2764
2765impl TcpFrontendTagsKey {
2766    /// The identity triple `command/src/state.rs`'s `add_tcp_frontend`
2767    /// deduplicates on: `sni` is lowercased to match `insert_sni_route`'s
2768    /// trie key, `alpn` sorted + deduped so `add_tcp_front`'s operator
2769    /// order and the route-time rebuild's [`AlpnMatcher::OneOf`] `BTreeSet`
2770    /// order (`alpn_matcher_protocols`) always agree.
2771    fn sni(address: SocketAddr, sni: &str, alpn: &[String]) -> Self {
2772        let mut alpn: Vec<String> = alpn.to_vec();
2773        alpn.sort_unstable();
2774        alpn.dedup();
2775        TcpFrontendTagsKey::Sni {
2776            address,
2777            sni: sni.to_ascii_lowercase(),
2778            alpn,
2779        }
2780    }
2781}
2782
2783impl std::fmt::Display for TcpFrontendTagsKey {
2784    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2785        match self {
2786            TcpFrontendTagsKey::Address(address) => write!(f, "{address}"),
2787            TcpFrontendTagsKey::Sni { address, sni, alpn } => {
2788                write!(f, "{address}|{sni}|")?;
2789                for protocol in alpn {
2790                    // `<len>:<bytes>` per protocol: the length prefix is
2791                    // CHECKED against the following bytes, never scanned
2792                    // for, so a `,`/`:` embedded in one protocol name can
2793                    // never be misread as a boundary between two --
2794                    // unlike the old bare `join(",")`.
2795                    write!(f, "{}:{protocol}", protocol.len())?;
2796                }
2797                Ok(())
2798            }
2799        }
2800    }
2801}
2802
2803/// Canonical access-log tags key for an SNI-scoped TCP frontend: one
2804/// listener can carry many SNI/ALPN fronts, each with its own `tags`, so
2805/// keying tags by the bare listener address (the pre-SNI behavior, kept
2806/// verbatim for no-SNI fronts) would let the LAST added front clobber every
2807/// sibling and let removing ANY front clear tags for the whole address. See
2808/// [`TcpFrontendTagsKey`] for the collision-free composition.
2809fn sni_tags_key(address: &SocketAddr, sni: &str, alpn: &[String]) -> String {
2810    TcpFrontendTagsKey::sni(*address, sni, alpn).to_string()
2811}
2812
2813/// The matched [`AlpnMatcher`]'s protocols as strings, for rebuilding the
2814/// [`sni_tags_key`] a route decision maps to: `Any` is the empty-`alpn`
2815/// catch-all front, `OneOf` yields its (BTreeSet-sorted) protocol list.
2816/// `from_utf8_lossy` is defensive — matcher protocols originate from
2817/// config/IPC `String`s, never raw network bytes.
2818fn alpn_matcher_protocols(matcher: &AlpnMatcher) -> Vec<String> {
2819    match matcher {
2820        AlpnMatcher::Any => Vec::new(),
2821        AlpnMatcher::OneOf(set) => set
2822            .iter()
2823            .map(|protocol| String::from_utf8_lossy(protocol).into_owned())
2824            .collect(),
2825    }
2826}
2827
2828/// Shared `(trie key, ALPN matcher)` construction for
2829/// [`TcpListener::insert_sni_route`] and [`TcpListener::remove_sni_route`]:
2830/// `sni` lowercased into the trie key, `alpn` collapsed to
2831/// [`AlpnMatcher::Any`] when empty (catch-all) or [`AlpnMatcher::OneOf`]
2832/// otherwise. `alpn` is taken by value since neither caller needs it again
2833/// afterward.
2834fn route_key_and_matcher(sni: &str, alpn: Vec<String>) -> (Vec<u8>, AlpnMatcher) {
2835    let key = sni.to_ascii_lowercase().into_bytes();
2836    let matcher = if alpn.is_empty() {
2837        AlpnMatcher::Any
2838    } else {
2839        AlpnMatcher::OneOf(alpn.into_iter().map(String::into_bytes).collect())
2840    };
2841    (key, matcher)
2842}
2843
2844#[derive(Debug)]
2845pub struct ClusterConfiguration {
2846    proxy_protocol: Option<ProxyProtocolConfig>,
2847    /// Whether selection reads the client's affinity key: the policy is
2848    /// `HRW` or `MAGLEV`, or the cluster shards. `TcpSession::connect_to_backend`
2849    /// derives the key only then.
2850    reads_affinity_key: bool,
2851    /// Per-cluster override of the global per-(cluster, source-IP)
2852    /// connection limit. `None` inherits the global default,
2853    /// `Some(0)` is explicit "unlimited", `Some(n > 0)` overrides.
2854    /// Resolved against `SessionManager::effective_max_connections_per_ip`
2855    /// at admit time in `connect_to_backend`.
2856    pub max_connections_per_ip: Option<u64>,
2857    /// Per-cluster override of the global per-(cluster, source-SUBNET)
2858    /// connection limit. Same three-state semantics as
2859    /// `max_connections_per_ip` above, resolved against
2860    /// `SessionManager::effective_max_connections_per_subnet` at admit
2861    /// time. An independent second cap: both must admit.
2862    pub max_connections_per_subnet: Option<u64>,
2863    /// Per-cluster override of the global backend connection attempt
2864    /// budget, resolved against
2865    /// `SessionManager::effective_max_connection_attempts` in
2866    /// `connect_to_backend`. `None` inherits the global value.
2867    pub max_connection_attempts: Option<u32>,
2868}
2869
2870pub struct TcpProxy {
2871    fronts: HashMap<ClusterId, Token>,
2872    backends: Rc<RefCell<BackendMap>>,
2873    listeners: HashMap<Token, Rc<RefCell<TcpListener>>>,
2874    configs: HashMap<ClusterId, ClusterConfiguration>,
2875    registry: Registry,
2876    sessions: Rc<RefCell<SessionManager>>,
2877    pool: Rc<RefCell<Pool>>,
2878}
2879
2880impl TcpProxy {
2881    pub fn new(
2882        registry: Registry,
2883        sessions: Rc<RefCell<SessionManager>>,
2884        pool: Rc<RefCell<Pool>>,
2885        backends: Rc<RefCell<BackendMap>>,
2886    ) -> TcpProxy {
2887        TcpProxy {
2888            backends,
2889            listeners: HashMap::new(),
2890            configs: HashMap::new(),
2891            fronts: HashMap::new(),
2892            registry,
2893            sessions,
2894            pool,
2895        }
2896    }
2897
2898    pub fn add_listener(
2899        &mut self,
2900        config: TcpListenerConfig,
2901        token: Token,
2902    ) -> Result<Token, ProxyError> {
2903        if self.listeners.contains_key(&token) {
2904            return Err(ProxyError::ListenerAlreadyPresent);
2905        }
2906        let mut tcp_listener = TcpListener::new(config, token).map_err(ProxyError::AddListener)?;
2907        // TCP frontends name their listener by address alone, so a listener
2908        // added next to one already on its address (on another interface)
2909        // takes over the routes that one serves: every listener on an address
2910        // holds the same ones, whenever it was added (sozu-proxy/sozu#719).
2911        if let Some(sibling) = self
2912            .listeners_at(tcp_listener.address)
2913            .ok()
2914            .and_then(|listeners| listeners.into_iter().next())
2915        {
2916            let sibling = sibling.borrow();
2917            tcp_listener.cluster_id.clone_from(&sibling.cluster_id);
2918            tcp_listener.sni_routes = sibling.sni_routes.clone();
2919            tcp_listener.tags = sibling.tags.clone();
2920        }
2921        match self.listeners.entry(token) {
2922            Entry::Vacant(entry) => {
2923                entry.insert(Rc::new(RefCell::new(tcp_listener)));
2924                Ok(token)
2925            }
2926            _ => Err(ProxyError::ListenerAlreadyPresent),
2927        }
2928    }
2929
2930    /// Add one listener and restore routes retained in the worker state when
2931    /// no live sibling can provide them.
2932    ///
2933    /// The targeted replay happens only when this is the sole live listener
2934    /// on its address, so the normal address-wide frontend fan-out reaches
2935    /// only the newly inserted listener. Any failure removes that listener and
2936    /// every global frontend-token entry created for it.
2937    pub(crate) fn add_listener_with_retained_frontends(
2938        &mut self,
2939        config: TcpListenerConfig,
2940        token: Token,
2941        retained_frontends: Vec<RequestTcpFrontend>,
2942    ) -> Result<Token, ProxyError> {
2943        let address = config.address.into();
2944        let had_sibling = self.listeners_at(address).is_ok();
2945        self.add_listener(config, token)?;
2946
2947        if !had_sibling {
2948            for frontend in retained_frontends {
2949                if let Err(error) = self.add_tcp_front(frontend) {
2950                    self.listeners.remove(&token);
2951                    self.fronts
2952                        .retain(|_, listener_token| *listener_token != token);
2953                    return Err(error);
2954                }
2955            }
2956        }
2957
2958        Ok(token)
2959    }
2960
2961    pub fn remove_listener(&mut self, address: &ListenerKey) -> SessionIsToBeClosed {
2962        let len = self.listeners.len();
2963
2964        self.listeners.retain(|_, l| !l.borrow().is(address));
2965        self.listeners.len() < len
2966    }
2967
2968    /// What this proxy would do with a socket handed to
2969    /// [`activate_listener`](Self::activate_listener) for `addr`. See
2970    /// [`crate::InheritedSocketFate`].
2971    ///
2972    /// The event loop is single-threaded, so nothing can change the listener's
2973    /// `active` flag between this answer and the `activate_listener` call that
2974    /// acts on it.
2975    pub fn inherited_socket_fate(&self, addr: &ListenerKey) -> crate::InheritedSocketFate {
2976        use crate::InheritedSocketFate;
2977        match self
2978            .listeners
2979            .values()
2980            .find(|listener| listener.borrow().is(addr))
2981        {
2982            None => InheritedSocketFate::Unclaimed,
2983            Some(listener) if listener.borrow().active => InheritedSocketFate::Refused,
2984            Some(_) => InheritedSocketFate::Adopted,
2985        }
2986    }
2987
2988    pub fn activate_listener(
2989        &self,
2990        addr: &ListenerKey,
2991        tcp_listener: Option<MioTcpListener>,
2992    ) -> Result<Token, ProxyError> {
2993        let listener = self
2994            .listeners
2995            .values()
2996            .find(|listener| listener.borrow().is(addr))
2997            .ok_or(ProxyError::NoListenerFound(addr.clone()))?;
2998
2999        listener.borrow_mut().activate(&self.registry, tcp_listener)
3000    }
3001
3002    /// The slab token reserved for the listener at `address`, if this proxy
3003    /// holds one.
3004    ///
3005    /// A listener owns exactly one slab slot for its whole `AddListener` ->
3006    /// `RemoveListener` lifetime, and no `remove_listener` implementation
3007    /// touches the session slab. `Server` therefore reads the token here
3008    /// BEFORE dropping the listener, so that reserved slot is released exactly
3009    /// once, at the end of the lifetime.
3010    pub fn listener_token(&self, address: &ListenerKey) -> Option<Token> {
3011        self.listeners
3012            .iter()
3013            .find(|(_, listener)| listener.borrow().is(address))
3014            .map(|(token, _)| *token)
3015    }
3016
3017    /// The no-SNI cluster the listener at `address` routes to, for tests
3018    /// that drive the proxy through `Server::notify_proxys` (`lib/src/server.rs`).
3019    #[cfg(test)]
3020    pub(crate) fn listener_cluster(&self, address: &ListenerKey) -> Option<ClusterId> {
3021        let token = self.listener_token(address)?;
3022        self.listeners[&token].borrow().cluster_id.clone()
3023    }
3024
3025    pub fn give_back_listeners(&mut self) -> Vec<(ListenerKey, MioTcpListener)> {
3026        self.listeners
3027            .values()
3028            .filter_map(|listener| {
3029                let mut owned = listener.borrow_mut();
3030                if let Some(listener) = owned.listener.take() {
3031                    // Reset `active` so a subsequent `activate()` re-binds
3032                    // instead of short-circuiting on the stale flag.
3033                    owned.active = false;
3034                    return Some((owned.key(), listener));
3035                }
3036
3037                None
3038            })
3039            .collect()
3040    }
3041
3042    pub fn give_back_listener(
3043        &mut self,
3044        address: &ListenerKey,
3045    ) -> Result<(Token, MioTcpListener), ProxyError> {
3046        let listener = self
3047            .listeners
3048            .values()
3049            .find(|listener| listener.borrow().is(address))
3050            .ok_or(ProxyError::NoListenerFound(address.clone()))?;
3051
3052        let mut owned = listener.borrow_mut();
3053
3054        let taken_listener = owned
3055            .listener
3056            .take()
3057            .ok_or(ProxyError::UnactivatedListener)?;
3058
3059        // Reset `active` so a subsequent `activate()` re-binds instead of
3060        // short-circuiting on the stale flag.
3061        owned.active = false;
3062
3063        Ok((owned.token, taken_listener))
3064    }
3065
3066    /// Apply a partial-update patch to the identified TCP listener.
3067    pub fn update_listener(&mut self, patch: UpdateTcpListenerConfig) -> Result<(), ProxyError> {
3068        let address: SocketAddr = patch.address.into();
3069        let listener = self
3070            .listeners
3071            .values()
3072            .find(|l| l.borrow().address == address)
3073            .ok_or(ProxyError::NoListenerFound(address.into()))?;
3074        listener
3075            .borrow_mut()
3076            .update_config(&patch)
3077            .map_err(|listener_error| ProxyError::ListenerActivation {
3078                address,
3079                listener_error,
3080            })
3081    }
3082
3083    /// Every listener on `address`, whatever its interface. A TCP frontend
3084    /// names its listener by address alone, so it applies to all the
3085    /// listeners sharing that address on different interfaces.
3086    fn listeners_at(
3087        &self,
3088        address: SocketAddr,
3089    ) -> Result<Vec<Rc<RefCell<TcpListener>>>, ProxyError> {
3090        let mut listeners: Vec<_> = self
3091            .listeners
3092            .iter()
3093            .filter(|(_, listener)| listener.borrow().address == address)
3094            .map(|(token, listener)| (*token, listener.clone()))
3095            .collect();
3096        // Token order, so a failure is reported the same way on every run.
3097        listeners.sort_by_key(|(token, _)| *token);
3098        let listeners: Vec<_> = listeners
3099            .into_iter()
3100            .map(|(_, listener)| listener)
3101            .collect();
3102        if listeners.is_empty() {
3103            return Err(ProxyError::NoListenerFound(address.into()));
3104        }
3105        Ok(listeners)
3106    }
3107
3108    pub fn add_tcp_front(&mut self, front: RequestTcpFrontend) -> Result<(), ProxyError> {
3109        let address = front.address.into();
3110        let listeners = self.listeners_at(address)?;
3111
3112        // Hard-reject a request that would corrupt this listener's SNI/ALPN
3113        // routing invariants BEFORE any mutation below. Config-load
3114        // (`command/src/config.rs`, sozu-proxy/sozu#1279) already rejects
3115        // the same shapes for TOML-sourced requests, but `AddTcpFrontend`
3116        // can also arrive directly over the command socket, or via
3117        // `LoadState` replay of a hand-edited/stale state file, bypassing
3118        // config.rs entirely. Every listener on the address is checked before
3119        // any of them is touched.
3120        for listener in &listeners {
3121            listener.borrow().validate_new_tcp_front(&front)?;
3122        }
3123
3124        let cluster_id = ClusterId::from(front.cluster_id);
3125        for listener in listeners {
3126            let mut listener = listener.borrow_mut();
3127            self.fronts.insert(cluster_id.clone(), listener.token);
3128
3129            match &front.sni {
3130                Some(sni) => {
3131                    // Per-frontend tags key: many SNI/ALPN fronts share one
3132                    // listener, so the bare-address key (kept for no-SNI fronts
3133                    // below) would clobber siblings — see `sni_tags_key`.
3134                    listener.set_tags(
3135                        sni_tags_key(&address, sni, &front.alpn),
3136                        Some(front.tags.clone()),
3137                    );
3138                    listener.insert_sni_route(
3139                        sni.to_owned(),
3140                        front.alpn.clone(),
3141                        cluster_id.clone(),
3142                    )?;
3143                }
3144                None => {
3145                    listener.set_tags(
3146                        TcpFrontendTagsKey::Address(address).to_string(),
3147                        Some(front.tags.clone()),
3148                    );
3149                    listener.cluster_id = Some(cluster_id.clone());
3150                }
3151            }
3152
3153            // POST: the mixing invariant must hold after every successful add —
3154            // `validate_new_tcp_front` is the enforcement point above, this is
3155            // the cheap live re-check that it actually held.
3156            debug_assert!(
3157                listener.cluster_id.is_none() || listener.sni_routes.is_empty(),
3158                "a TCP listener must never mix a no-SNI catch-all cluster with SNI-scoped routes"
3159            );
3160        }
3161
3162        Ok(())
3163    }
3164
3165    pub fn remove_tcp_front(&mut self, front: RequestTcpFrontend) -> Result<(), ProxyError> {
3166        let address = front.address.into();
3167
3168        for listener in self.listeners_at(address)? {
3169            let mut listener = listener.borrow_mut();
3170            match &front.sni {
3171                Some(sni) => {
3172                    // Clear ONLY this front's own tags entry (`sni_tags_key`) —
3173                    // the pre-SNI bare-address removal here used to strip tags
3174                    // for every sibling front on the listener.
3175                    listener.set_tags(sni_tags_key(&address, sni, &front.alpn), None);
3176                    listener.remove_sni_route(
3177                        sni.to_owned(),
3178                        front.alpn.clone(),
3179                        &front.cluster_id,
3180                    );
3181                    self.fronts.remove(front.cluster_id.as_str());
3182                }
3183                None => {
3184                    listener.set_tags(TcpFrontendTagsKey::Address(address).to_string(), None);
3185                    if let Some(cluster_id) = listener.cluster_id.take() {
3186                        self.fronts.remove(&cluster_id);
3187                    }
3188                }
3189            }
3190        }
3191
3192        Ok(())
3193    }
3194}
3195
3196impl ProxyConfiguration for TcpProxy {
3197    fn notify(&mut self, message: WorkerRequest) -> WorkerResponse {
3198        let request_type = match message.content.request_type {
3199            Some(t) => t,
3200            None => return WorkerResponse::error(message.id, "Empty request"),
3201        };
3202        match request_type {
3203            RequestType::AddTcpFrontend(front) => {
3204                if let Err(err) = self.add_tcp_front(front) {
3205                    return WorkerResponse::error(message.id, err);
3206                }
3207
3208                WorkerResponse::ok(message.id)
3209            }
3210            RequestType::RemoveTcpFrontend(front) => {
3211                if let Err(err) = self.remove_tcp_front(front) {
3212                    return WorkerResponse::error(message.id, err);
3213                }
3214
3215                WorkerResponse::ok(message.id)
3216            }
3217            RequestType::SoftStop(_) => {
3218                info!(
3219                    "{} {} processing soft shutdown",
3220                    log_module_context!(),
3221                    message.id
3222                );
3223                let listeners = std::mem::take(&mut self.listeners);
3224                for l in listeners.values() {
3225                    l.borrow_mut()
3226                        .listener
3227                        .take()
3228                        .map(|mut sock| self.registry.deregister(&mut sock));
3229                }
3230                WorkerResponse::processing(message.id)
3231            }
3232            RequestType::HardStop(_) => {
3233                info!("{} {} hard shutdown", log_module_context!(), message.id);
3234                for (_, l) in std::mem::take(&mut self.listeners) {
3235                    l.borrow_mut()
3236                        .listener
3237                        .take()
3238                        .map(|mut sock| self.registry.deregister(&mut sock));
3239                }
3240                WorkerResponse::ok(message.id)
3241            }
3242            RequestType::Status(_) => {
3243                info!("{} {} status", log_module_context!(), message.id);
3244                WorkerResponse::ok(message.id)
3245            }
3246            RequestType::AddCluster(cluster) => {
3247                let config = ClusterConfiguration {
3248                    proxy_protocol: cluster
3249                        .proxy_protocol
3250                        .and_then(|n| ProxyProtocolConfig::try_from(n).ok()),
3251                    reads_affinity_key: cluster_reads_affinity_key(&cluster),
3252                    max_connections_per_ip: cluster.max_connections_per_ip,
3253                    max_connections_per_subnet: cluster.max_connections_per_subnet,
3254                    max_connection_attempts: cluster.max_connection_attempts,
3255                };
3256                self.configs.insert(cluster.cluster_id.into(), config);
3257                WorkerResponse::ok(message.id)
3258            }
3259            RequestType::RemoveCluster(cluster_id) => {
3260                self.configs.remove(cluster_id.as_str());
3261                WorkerResponse::ok(message.id)
3262            }
3263            RequestType::RemoveListener(remove) => {
3264                if !self.remove_listener(&remove.listener_key()) {
3265                    WorkerResponse::error(
3266                        message.id,
3267                        format!("no TCP listener to remove at address {:?}", remove.address),
3268                    )
3269                } else {
3270                    WorkerResponse::ok(message.id)
3271                }
3272            }
3273            command => {
3274                debug!(
3275                    "{} {} unsupported message for TCP proxy, ignoring {:?}",
3276                    log_module_context!(),
3277                    message.id,
3278                    command
3279                );
3280                WorkerResponse::error(message.id, "unsupported message")
3281            }
3282        }
3283    }
3284
3285    fn accept(&mut self, token: ListenToken) -> Result<(MioTcpStream, SocketAddr), AcceptError> {
3286        let internal_token = Token(token.0);
3287        if let Some(listener) = self.listeners.get(&internal_token) {
3288            let mut listener = listener.borrow_mut();
3289            let accepted = match &listener.listener {
3290                Some(tcp_listener) => tcp_listener.accept(),
3291                None => return Err(AcceptError::IoError),
3292            };
3293            match accepted {
3294                Ok((frontend_sock, peer)) => {
3295                    if listener.nodelay_backlog
3296                        && let Err(e) = frontend_sock.set_nodelay(true)
3297                    {
3298                        error!(
3299                            "{} error setting nodelay on front socket({:?}): {:?}",
3300                            log_module_context!(),
3301                            frontend_sock,
3302                            e
3303                        );
3304                    }
3305                    Ok((frontend_sock, peer))
3306                }
3307                Err(e) if e.kind() == ErrorKind::WouldBlock => {
3308                    // The backlog queued before `activate()` is drained.
3309                    listener.nodelay_backlog = false;
3310                    Err(AcceptError::WouldBlock)
3311                }
3312                Err(e) => {
3313                    error!("{} accept() IO error: {:?}", log_module_context!(), e);
3314                    Err(AcceptError::IoError)
3315                }
3316            }
3317        } else {
3318            Err(AcceptError::IoError)
3319        }
3320    }
3321
3322    fn create_session(
3323        &mut self,
3324        mut frontend_sock: MioTcpStream,
3325        peer: SocketAddr,
3326        token: ListenToken,
3327        wait_time: Duration,
3328        proxy: Rc<RefCell<Self>>,
3329    ) -> Result<(), AcceptError> {
3330        let listener_token = Token(token.0);
3331
3332        let listener = self
3333            .listeners
3334            .get(&listener_token)
3335            .ok_or(AcceptError::IoError)?;
3336
3337        let owned = listener.borrow();
3338        let mut pool = self.pool.borrow_mut();
3339
3340        let (front_buffer, back_buffer) = match (pool.checkout(), pool.checkout()) {
3341            (Some(fb), Some(bb)) => (fb, bb),
3342            _ => {
3343                error!("{} could not get buffers from pool", log_module_context!());
3344                error!(
3345                    "{} Buffer capacity has been reached, stopping to accept new connections for now",
3346                    log_module_context!()
3347                );
3348                gauge!(names::accept_queue::BACKPRESSURE, 1);
3349                self.sessions.borrow_mut().can_accept = false;
3350
3351                return Err(AcceptError::BufferCapacityReached);
3352            }
3353        };
3354
3355        // A listener may route either by a legacy no-SNI catch-all cluster
3356        // OR by SNI-scoped routes (never both -- enforced at config load,
3357        // sozu-proxy/sozu#1279); reject only when NEITHER is configured.
3358        if owned.cluster_id.is_none() && owned.sni_routes.is_empty() {
3359            error!(
3360                "{} listener at address {:?} has no linked cluster",
3361                log_module_context!(),
3362                owned.address
3363            );
3364            return Err(AcceptError::IoError);
3365        }
3366
3367        // No `set_nodelay` here: the socket has `TCP_NODELAY` from its
3368        // listener, or from `TcpProxy::accept` if it was queued before the
3369        // listener set it.
3370
3371        let mut session_manager = self.sessions.borrow_mut();
3372        let entry = session_manager.slab.vacant_entry();
3373        let frontend_token = Token(entry.key());
3374
3375        if let Err(register_error) = self.registry.register(
3376            &mut frontend_sock,
3377            frontend_token,
3378            Interest::READABLE | Interest::WRITABLE,
3379        ) {
3380            error!(
3381                "{} error registering front socket({:?}): {:?}",
3382                log_module_context!(),
3383                frontend_sock,
3384                register_error
3385            );
3386            return Err(AcceptError::RegisterError);
3387        }
3388
3389        let session = if !owned.sni_routes.is_empty() {
3390            // Routing decides the cluster post-accept; the effective
3391            // preread cap can never exceed what the checked-out buffer can
3392            // actually hold, regardless of the configured knob.
3393            let effective_max_bytes = effective_sni_preread_max_bytes(
3394                owned.config.sni_preread_max_bytes,
3395                front_buffer.capacity(),
3396            );
3397            let preread_timeout = Duration::from_secs(u64::from(
3398                owned
3399                    .config
3400                    .sni_preread_timeout
3401                    .unwrap_or(DEFAULT_SNI_PREREAD_TIMEOUT),
3402            ));
3403            TcpSession::new_sni_preread(
3404                back_buffer,
3405                Duration::from_secs(owned.config.back_timeout as u64),
3406                Duration::from_secs(owned.config.connect_timeout as u64),
3407                front_buffer,
3408                frontend_token,
3409                listener.clone(),
3410                proxy,
3411                frontend_sock,
3412                peer,
3413                wait_time,
3414                preread_timeout,
3415                effective_max_bytes,
3416            )
3417        } else {
3418            let proxy_protocol = self
3419                .configs
3420                .get(owned.cluster_id.as_ref().unwrap())
3421                .and_then(|c| c.proxy_protocol);
3422            TcpSession::new(
3423                back_buffer,
3424                None,
3425                owned.cluster_id.clone(),
3426                Duration::from_secs(owned.config.back_timeout as u64),
3427                Duration::from_secs(owned.config.connect_timeout as u64),
3428                Duration::from_secs(owned.config.front_timeout as u64),
3429                front_buffer,
3430                frontend_token,
3431                listener.clone(),
3432                proxy_protocol,
3433                proxy,
3434                frontend_sock,
3435                peer,
3436                wait_time,
3437            )
3438        };
3439        incr!(names::tcp::REQUESTS);
3440
3441        let session = Rc::new(RefCell::new(session));
3442        entry.insert(session);
3443
3444        Ok(())
3445    }
3446}
3447
3448pub mod testing {
3449    use crate::testing::*;
3450
3451    /// This is not directly used by Sōzu but is available for example and testing purposes
3452    pub fn start_tcp_worker(
3453        config: TcpListenerConfig,
3454        max_buffers: usize,
3455        buffer_size: usize,
3456        channel: ProxyChannel,
3457    ) -> anyhow::Result<()> {
3458        let address = config.address.into();
3459
3460        let ServerParts {
3461            event_loop,
3462            registry,
3463            sessions,
3464            pool,
3465            backends,
3466            client_scm_socket: _,
3467            server_scm_socket,
3468            server_config,
3469        } = prebuild_server(max_buffers, buffer_size, true)?;
3470
3471        let token = {
3472            let mut sessions = sessions.borrow_mut();
3473            let entry = sessions.slab.vacant_entry();
3474            let key = entry.key();
3475            let _ = entry.insert(Rc::new(RefCell::new(ListenSession {
3476                protocol: Protocol::TCPListen,
3477            })));
3478            Token(key)
3479        };
3480
3481        let mut proxy = TcpProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
3482        proxy
3483            .add_listener(config, token)
3484            .with_context(|| "Failed at creating adding the listener")?;
3485        proxy
3486            .activate_listener(&address, None)
3487            .with_context(|| "Failed at creating activating the listener")?;
3488
3489        let mut server = Server::new(
3490            event_loop,
3491            channel,
3492            server_scm_socket,
3493            sessions,
3494            pool,
3495            backends,
3496            None,
3497            None,
3498            Some(proxy),
3499            server_config,
3500            None,
3501            false,
3502        )
3503        .with_context(|| "Failed at creating server")?;
3504
3505        debug!("{} starting event loop", log_module_context!());
3506        server.run();
3507        debug!("{} ending event loop", log_module_context!());
3508        Ok(())
3509    }
3510}
3511
3512#[cfg(test)]
3513mod tests {
3514    use std::{
3515        io::{Read, Write},
3516        net::{Shutdown, TcpListener, TcpStream},
3517        str,
3518        sync::{
3519            Arc, Barrier,
3520            atomic::{AtomicBool, Ordering},
3521        },
3522        thread,
3523        time::Duration,
3524    };
3525
3526    use sozu_command::{
3527        channel::Channel,
3528        config::ListenerBuilder,
3529        proto::command::{
3530            LoadBalancingParams, RequestTcpFrontend, SocketAddress, SoftStop, WorkerRequest,
3531            WorkerResponse, request::RequestType,
3532        },
3533    };
3534
3535    use super::testing::start_tcp_worker;
3536    use crate::testing::*;
3537
3538    /*
3539    #[test]
3540    #[cfg(target_pointer_width = "64")]
3541    fn size_test() {
3542      assert_size!(Pipe<mio::net::TcpStream>, 224);
3543      assert_size!(SendProxyProtocol<mio::net::TcpStream>, 144);
3544      assert_size!(RelayProxyProtocol<mio::net::TcpStream>, 152);
3545      assert_size!(ExpectProxyProtocol<mio::net::TcpStream>, 520);
3546      assert_size!(State, 528);
3547      // fails depending on the platform?
3548      //assert_size!(Session, 808);
3549    }*/
3550
3551    #[test]
3552    fn round_trip() {
3553        setup_test_logger!();
3554        let barrier = Arc::new(Barrier::new(2));
3555        let test_finished = Arc::new(AtomicBool::new(false));
3556
3557        let front_port1 = provide_port();
3558        let front_port2 = provide_port();
3559
3560        let backend_port = start_server(barrier.clone(), test_finished.clone());
3561        let mut command =
3562            start_proxy(backend_port, front_port1, front_port2).expect("Could not start proxy");
3563        barrier.wait();
3564
3565        thread::scope(|_s| {
3566            let front_addr = format!("127.0.0.1:{front_port1}");
3567
3568            let mut s1 = TcpStream::connect(&front_addr).expect("could not connect");
3569            s1.set_read_timeout(Some(Duration::from_secs(5)))
3570                .expect("could not set read timeout on s1");
3571
3572            let s3 = TcpStream::connect(&front_addr).expect("could not connect");
3573
3574            let mut s2 = TcpStream::connect(&front_addr).expect("could not connect");
3575            s2.set_read_timeout(Some(Duration::from_secs(5)))
3576                .expect("could not set read timeout on s2");
3577
3578            s1.write_all(b"hello ").expect("could not write to s1");
3579            println!("s1 sent");
3580
3581            s2.write_all(b"pouet pouet").expect("could not write to s2");
3582            println!("s2 sent");
3583
3584            let mut res = [0; 128];
3585            s1.write_all(b"coucou").expect("could not write to s1");
3586
3587            s3.shutdown(Shutdown::Both).expect("could not shutdown s3");
3588
3589            let sz2 = s2
3590                .read(&mut res[..])
3591                .expect("could not read from socket s2");
3592            println!("s2 received {:?}", str::from_utf8(&res[..sz2]));
3593            assert_eq!(&res[..sz2], &b"pouet pouet"[..]);
3594
3595            // Read in a loop: a single read() on a TCP stream is not
3596            // guaranteed to return all echoed data if the second write's
3597            // round trip (client → proxy → backend → proxy → client) is
3598            // still in flight when we poll.
3599            let expected = b"hello coucou";
3600            let mut total = 0;
3601            while total < expected.len() {
3602                let sz = s1
3603                    .read(&mut res[total..])
3604                    .expect("could not read from socket s1");
3605                assert!(sz > 0, "connection closed before receiving all data");
3606                total += sz;
3607            }
3608            println!(
3609                "s1 received again({}): {:?}",
3610                total,
3611                str::from_utf8(&res[..total])
3612            );
3613            assert_eq!(&res[..total], &expected[..]);
3614
3615            // Signal the echo server to stop
3616            test_finished.store(true, Ordering::Relaxed);
3617
3618            // Send SoftStop to the sozu worker so server.run() exits cleanly
3619            command
3620                .write_message(&WorkerRequest {
3621                    id: "ID_SOFTSTOP".to_owned(),
3622                    content: RequestType::SoftStop(SoftStop {}).into(),
3623                })
3624                .expect("could not send SoftStop to sozu worker");
3625        });
3626    }
3627
3628    /// Start an echo server on an ephemeral port.
3629    /// Returns the port the server is listening on.
3630    fn start_server(barrier: Arc<Barrier>, test_finished: Arc<AtomicBool>) -> u16 {
3631        let listener =
3632            TcpListener::bind("127.0.0.1:0").expect("could not bind echo server listener");
3633        let port = listener
3634            .local_addr()
3635            .expect("could not get echo server local address")
3636            .port();
3637
3638        listener
3639            .set_nonblocking(true)
3640            .expect("could not set echo server listener to non-blocking");
3641
3642        thread::spawn(move || {
3643            barrier.wait();
3644            let mut count: u8 = 0;
3645            loop {
3646                match listener.accept() {
3647                    Ok((mut stream, _)) => {
3648                        let finished = test_finished.clone();
3649                        thread::spawn(move || {
3650                            println!("got a new client: {count}");
3651                            stream
3652                                .set_read_timeout(Some(Duration::from_secs(2)))
3653                                .expect("could not set read timeout on echo client");
3654                            let mut buf = [0; 128];
3655                            loop {
3656                                match stream.read(&mut buf[..]) {
3657                                    Ok(0) => break,
3658                                    Ok(sz) => {
3659                                        println!(
3660                                            "ECHO[{count}] got \"{:?}\"",
3661                                            str::from_utf8(&buf[..sz])
3662                                        );
3663                                        stream
3664                                            .write_all(&buf[..sz])
3665                                            .expect("could not echo data back");
3666                                    }
3667                                    Err(ref e)
3668                                        if e.kind() == std::io::ErrorKind::WouldBlock
3669                                            || e.kind() == std::io::ErrorKind::TimedOut =>
3670                                    {
3671                                        if finished.load(Ordering::Relaxed) {
3672                                            println!("backend server stopping (client handler)");
3673                                            break;
3674                                        }
3675                                    }
3676                                    Err(_) => break,
3677                                }
3678                            }
3679                        });
3680                        count = count.wrapping_add(1);
3681                    }
3682                    Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
3683                        if test_finished.load(Ordering::Relaxed) {
3684                            println!("backend server stopping (accept loop)");
3685                            break;
3686                        }
3687                        thread::sleep(Duration::from_millis(50));
3688                    }
3689                    Err(e) => {
3690                        println!("connection failed: {e:?}");
3691                    }
3692                }
3693            }
3694        });
3695
3696        port
3697    }
3698
3699    /// Start a sozu TCP proxy worker with the given backend and frontend ports.
3700    fn start_proxy(
3701        backend_port: u16,
3702        front_port1: u16,
3703        front_port2: u16,
3704    ) -> anyhow::Result<Channel<WorkerRequest, WorkerResponse>> {
3705        let config = ListenerBuilder::new_tcp(SocketAddress::new_v4(127, 0, 0, 1, front_port1))
3706            .to_tcp(None)
3707            .expect("could not create listener config");
3708
3709        let (mut command, channel) =
3710            Channel::generate(1000, 10000).with_context(|| "should create a channel")?;
3711        let _jg = thread::spawn(move || {
3712            setup_test_logger!();
3713            start_tcp_worker(config, 100, 16384, channel).expect("could not start the tcp server");
3714        });
3715
3716        command
3717            .blocking()
3718            .expect("could not set command channel to blocking");
3719        {
3720            let front = RequestTcpFrontend {
3721                cluster_id: "yolo".to_owned(),
3722                address: SocketAddress::new_v4(127, 0, 0, 1, front_port1),
3723                ..Default::default()
3724            };
3725            let backend = sozu_command_lib::response::Backend {
3726                cluster_id: "yolo".into(),
3727                backend_id: "yolo-0".to_owned(),
3728                address: SocketAddress::new_v4(127, 0, 0, 1, backend_port).into(),
3729                load_balancing_parameters: Some(LoadBalancingParams::default()),
3730                sticky_id: None,
3731                backup: None,
3732                unix_path: None,
3733            };
3734
3735            // A backend belongs to its cluster: add the cluster first.
3736            command
3737                .write_message(&WorkerRequest {
3738                    id: "ID_YOLO0".to_owned(),
3739                    content: RequestType::AddCluster(sozu_command::proto::command::Cluster {
3740                        cluster_id: "yolo".to_owned(),
3741                        ..Default::default()
3742                    })
3743                    .into(),
3744                })
3745                .expect("could not send AddCluster");
3746            command
3747                .write_message(&WorkerRequest {
3748                    id: "ID_YOLO1".to_owned(),
3749                    content: RequestType::AddTcpFrontend(front).into(),
3750                })
3751                .expect("could not send AddTcpFrontend for front1");
3752            command
3753                .write_message(&WorkerRequest {
3754                    id: "ID_YOLO2".to_owned(),
3755                    content: RequestType::AddBackend(backend.to_add_backend()).into(),
3756                })
3757                .expect("could not send AddBackend for front1");
3758        }
3759        {
3760            let front = RequestTcpFrontend {
3761                cluster_id: "yolo".to_owned(),
3762                address: SocketAddress::new_v4(127, 0, 0, 1, front_port2),
3763                ..Default::default()
3764            };
3765            let backend = sozu_command::response::Backend {
3766                cluster_id: "yolo".into(),
3767                backend_id: "yolo-0".to_owned(),
3768                address: SocketAddress::new_v4(127, 0, 0, 1, backend_port).into(),
3769                load_balancing_parameters: Some(LoadBalancingParams::default()),
3770                sticky_id: None,
3771                backup: None,
3772                unix_path: None,
3773            };
3774            command
3775                .write_message(&WorkerRequest {
3776                    id: "ID_YOLO3".to_owned(),
3777                    content: RequestType::AddTcpFrontend(front).into(),
3778                })
3779                .expect("could not send AddTcpFrontend for front2");
3780            command
3781                .write_message(&WorkerRequest {
3782                    id: "ID_YOLO4".to_owned(),
3783                    content: RequestType::AddBackend(backend.to_add_backend()).into(),
3784                })
3785                .expect("could not send AddBackend for front2");
3786        }
3787
3788        for _ in 0..5 {
3789            println!(
3790                "read_message: {:?}",
3791                command
3792                    .read_message()
3793                    .with_context(|| "could not read message")?
3794            );
3795        }
3796
3797        Ok(command)
3798    }
3799}
3800
3801/// Unit coverage for the SNI-preread routing shell added for
3802/// sozu-proxy/sozu#1279: the route-table mutations (`add_tcp_front` /
3803/// `remove_tcp_front`), the `AlpnMatcher` mapping, the effective preread
3804/// cap, and the routing gate's data invariants. None of this needs a live
3805/// socket or event loop -- it is a separate module (rather than nested in
3806/// the `tests` module above) purely to avoid that module's `use
3807/// std::net::TcpListener` import shadowing `super::TcpListener` (this
3808/// crate's listener struct).
3809#[cfg(test)]
3810mod sni_routing_tests {
3811    use sozu_command::{config::ListenerBuilder, proto::command::SocketAddress};
3812
3813    use super::*;
3814    use crate::testing::{ServerParts, prebuild_server, provide_port};
3815
3816    fn test_listener() -> TcpListener {
3817        let config = ListenerBuilder::new_tcp(SocketAddress::new_v4(127, 0, 0, 1, provide_port()))
3818            .to_tcp(None)
3819            .expect("could not build a TcpListenerConfig for the test");
3820        TcpListener::new(config, Token(0)).expect("could not build a bare TcpListener for the test")
3821    }
3822
3823    fn frontend(cluster_id: &str, sni: Option<&str>, alpn: &[&str]) -> RequestTcpFrontend {
3824        RequestTcpFrontend {
3825            cluster_id: cluster_id.to_owned(),
3826            address: SocketAddress::new_v4(127, 0, 0, 1, provide_port()),
3827            sni: sni.map(str::to_owned),
3828            alpn: alpn.iter().map(|p| p.to_string()).collect(),
3829            ..Default::default()
3830        }
3831    }
3832
3833    // ---- effective_sni_preread_max_bytes ------------------------------
3834
3835    #[test]
3836    fn effective_max_bytes_falls_back_to_default_when_unconfigured() {
3837        assert_eq!(
3838            effective_sni_preread_max_bytes(None, 65536),
3839            DEFAULT_SNI_PREREAD_MAX_BYTES as usize
3840        );
3841    }
3842
3843    #[test]
3844    fn effective_max_bytes_is_the_min_of_knob_and_capacity() {
3845        assert_eq!(effective_sni_preread_max_bytes(Some(8192), 16384), 8192);
3846        assert_eq!(effective_sni_preread_max_bytes(Some(32768), 16384), 16384);
3847        assert_eq!(effective_sni_preread_max_bytes(Some(16384), 16384), 16384);
3848    }
3849
3850    #[test]
3851    fn effective_max_bytes_never_below_the_floor() {
3852        // A `sni_preread_max_bytes = 0` knob reaching the worker from a
3853        // direct `sozu listener tcp add`/`update` CLI/IPC request (or a stale
3854        // LoadState replay) bypasses config.rs's loud MIN_SNI_PREREAD_MAX_BYTES
3855        // load-time reject. Without the floor the shell would issue
3856        // zero-length preread reads and spin until the loop guard closes each
3857        // session; the floor degrades a sub-minimum knob to the 5-byte
3858        // TLS-record-header minimum instead.
3859        assert_eq!(
3860            effective_sni_preread_max_bytes(Some(0), 16384),
3861            MIN_SNI_PREREAD_MAX_BYTES as usize,
3862            "a 0 knob must degrade to the floor, never 0 (would spin the preread)"
3863        );
3864        assert_eq!(
3865            effective_sni_preread_max_bytes(Some(3), 16384),
3866            MIN_SNI_PREREAD_MAX_BYTES as usize,
3867            "any sub-floor knob must be raised to the floor"
3868        );
3869        // The floor itself, and anything above it, are respected unchanged.
3870        assert_eq!(
3871            effective_sni_preread_max_bytes(Some(MIN_SNI_PREREAD_MAX_BYTES), 16384),
3872            MIN_SNI_PREREAD_MAX_BYTES as usize
3873        );
3874        assert_eq!(
3875            effective_sni_preread_max_bytes(Some(MIN_SNI_PREREAD_MAX_BYTES + 1), 16384),
3876            (MIN_SNI_PREREAD_MAX_BYTES + 1) as usize
3877        );
3878    }
3879
3880    // ---- known_alpn_label (access-log tagging) -------------------------
3881
3882    #[test]
3883    fn known_alpn_label_picks_the_clients_first_offer() {
3884        assert_eq!(
3885            known_alpn_label(&[b"h2".to_vec(), b"http/1.1".to_vec()]),
3886            Some("h2")
3887        );
3888        assert_eq!(
3889            known_alpn_label(&[b"http/1.1".to_vec(), b"h2".to_vec()]),
3890            Some("http/1.1"),
3891            "client preference order must win, not a fixed h2-first priority"
3892        );
3893    }
3894
3895    #[test]
3896    fn known_alpn_label_is_none_for_empty_or_unrecognized_offers() {
3897        assert_eq!(known_alpn_label(&[]), None);
3898        assert_eq!(known_alpn_label(&[b"spdy/1".to_vec()]), None);
3899    }
3900
3901    // ---- AlpnMatcher mapping + route-table add/remove symmetry --------
3902
3903    #[test]
3904    fn empty_alpn_maps_to_any_non_empty_maps_to_one_of() {
3905        let mut listener = test_listener();
3906        listener
3907            .insert_sni_route("example.com".to_owned(), vec![], "cluster-any".into())
3908            .expect("insert_sni_route must succeed for a valid test SNI");
3909        listener
3910            .insert_sni_route(
3911                "h2.example.com".to_owned(),
3912                vec!["h2".to_owned(), "http/1.1".to_owned()],
3913                "cluster-h2".into(),
3914            )
3915            .expect("insert_sni_route must succeed for a valid test SNI");
3916
3917        let (_, any_entries) = listener
3918            .sni_routes
3919            .domain_lookup(b"example.com", true)
3920            .expect("example.com must be routable");
3921        assert_eq!(any_entries, &vec![(AlpnMatcher::Any, "cluster-any".into())]);
3922
3923        let (_, h2_entries) = listener
3924            .sni_routes
3925            .domain_lookup(b"h2.example.com", true)
3926            .expect("h2.example.com must be routable");
3927        assert_eq!(
3928            h2_entries,
3929            &vec![(
3930                AlpnMatcher::OneOf([b"h2".to_vec(), b"http/1.1".to_vec()].into_iter().collect()),
3931                "cluster-h2".into()
3932            )]
3933        );
3934    }
3935
3936    #[test]
3937    fn insert_sni_route_appends_under_the_same_sni() {
3938        let mut listener = test_listener();
3939        listener
3940            .insert_sni_route(
3941                "example.com".to_owned(),
3942                vec!["h2".to_owned()],
3943                "cluster-h2".into(),
3944            )
3945            .expect("insert_sni_route must succeed for a valid test SNI");
3946        listener
3947            .insert_sni_route("example.com".to_owned(), vec![], "cluster-default".into())
3948            .expect("insert_sni_route must succeed for a valid test SNI");
3949
3950        let (_, entries) = listener
3951            .sni_routes
3952            .domain_lookup(b"example.com", true)
3953            .expect("example.com must be routable");
3954        assert_eq!(entries.len(), 2, "both fronts must share the SNI's Vec");
3955    }
3956
3957    #[test]
3958    fn remove_sni_route_drops_only_the_matching_entry() {
3959        let mut listener = test_listener();
3960        listener
3961            .insert_sni_route(
3962                "example.com".to_owned(),
3963                vec!["h2".to_owned()],
3964                "cluster-h2".into(),
3965            )
3966            .expect("insert_sni_route must succeed for a valid test SNI");
3967        listener
3968            .insert_sni_route("example.com".to_owned(), vec![], "cluster-default".into())
3969            .expect("insert_sni_route must succeed for a valid test SNI");
3970
3971        listener.remove_sni_route(
3972            "example.com".to_owned(),
3973            vec!["h2".to_owned()],
3974            "cluster-h2",
3975        );
3976
3977        let (_, entries) = listener
3978            .sni_routes
3979            .domain_lookup(b"example.com", true)
3980            .expect("example.com must still be routable via the remaining entry");
3981        assert_eq!(
3982            entries,
3983            &vec![(AlpnMatcher::Any, "cluster-default".into())],
3984            "removing one entry must not disturb the other"
3985        );
3986    }
3987
3988    #[test]
3989    fn remove_sni_route_empties_the_trie_key_when_the_last_entry_goes() {
3990        let mut listener = test_listener();
3991        listener
3992            .insert_sni_route("example.com".to_owned(), vec![], "cluster-a".into())
3993            .expect("insert_sni_route must succeed for a valid test SNI");
3994        assert!(!listener.sni_routes.is_empty());
3995
3996        listener.remove_sni_route("example.com".to_owned(), vec![], "cluster-a");
3997
3998        assert!(
3999            listener.sni_routes.is_empty(),
4000            "domain_remove must run once the SNI's Vec empties, leaving no stranded key"
4001        );
4002        assert!(
4003            listener
4004                .sni_routes
4005                .domain_lookup(b"example.com", true)
4006                .is_none()
4007        );
4008    }
4009
4010    #[test]
4011    fn remove_sni_route_on_an_absent_sni_is_a_harmless_no_op() {
4012        let mut listener = test_listener();
4013        listener
4014            .insert_sni_route("example.com".to_owned(), vec![], "cluster-a".into())
4015            .expect("insert_sni_route must succeed for a valid test SNI");
4016
4017        // Removing a route for a SNI that was never inserted must not panic
4018        // and must not disturb the existing route.
4019        listener.remove_sni_route("other.example.net".to_owned(), vec![], "cluster-a");
4020
4021        assert!(
4022            listener
4023                .sni_routes
4024                .domain_lookup(b"example.com", true)
4025                .is_some()
4026        );
4027    }
4028
4029    // ---- exact-key bookkeeping must not fall back to a sibling wildcard
4030    // (route-table corruption caught in sozu-proxy/sozu#1290 review) ----
4031
4032    #[test]
4033    fn insert_sni_route_creates_a_distinct_node_for_an_exact_key_over_a_sibling_wildcard() {
4034        let mut listener = test_listener();
4035        // Wildcard catch-all first.
4036        listener
4037            .insert_sni_route(
4038                "*.example.com".to_owned(),
4039                vec![],
4040                "cluster-wildcard".into(),
4041            )
4042            .expect("insert_sni_route must succeed for a valid test SNI");
4043        // Exact ALPN-scoped route for one specific subdomain.
4044        listener
4045            .insert_sni_route(
4046                "a.example.com".to_owned(),
4047                vec!["h2".to_owned()],
4048                "cluster-a-h2".into(),
4049            )
4050            .expect("insert_sni_route must succeed for a valid test SNI");
4051
4052        // The exact key must have gotten its OWN trie node -- with
4053        // `accept_wildcard: true` this lookup would instead fall back to
4054        // (and the insert above would have corrupted) the wildcard's node,
4055        // since no literal `a` child existed yet at insert time.
4056        let (_, a_entries) = listener
4057            .sni_routes
4058            .domain_lookup(b"a.example.com", false)
4059            .expect("a.example.com must have a distinct exact-key node");
4060        assert_eq!(
4061            a_entries,
4062            &vec![(
4063                AlpnMatcher::OneOf([b"h2".to_vec()].into_iter().collect()),
4064                "cluster-a-h2".into()
4065            )],
4066            "the exact key's own Vec must hold only its own entry, not the wildcard's"
4067        );
4068
4069        // Any OTHER subdomain must still resolve to ONLY the wildcard, via
4070        // the same `accept_wildcard: true` lookup the routing path uses
4071        // (`preread_config`) -- it must never see `a.example.com`'s h2 route.
4072        let (_, b_entries) = listener
4073            .sni_routes
4074            .domain_lookup(b"b.example.com", true)
4075            .expect("b.example.com must fall back to the wildcard catch-all");
4076        assert_eq!(
4077            b_entries,
4078            &vec![(AlpnMatcher::Any, "cluster-wildcard".into())],
4079            "an unrelated subdomain must see ONLY the wildcard's entry"
4080        );
4081    }
4082
4083    #[test]
4084    fn validate_new_tcp_front_accepts_an_exact_catch_all_sibling_of_a_wildcard_catch_all() {
4085        let mut listener = test_listener();
4086        listener
4087            .insert_sni_route(
4088                "*.example.com".to_owned(),
4089                vec![],
4090                "cluster-wildcard".into(),
4091            )
4092            .expect("insert_sni_route must succeed for a valid test SNI");
4093
4094        // An exact catch-all for one subdomain must be accepted: it is a
4095        // SIBLING of the wildcard's catch-all, not a duplicate of it. With
4096        // `accept_wildcard: true` this lookup would wrongly find the
4097        // wildcard's own `AlpnMatcher::Any` entry and reject it as "already
4098        // has a catch-all".
4099        let front = frontend("cluster-a", Some("a.example.com"), &[]);
4100        assert!(
4101            listener.validate_new_tcp_front(&front).is_ok(),
4102            "an exact catch-all must be accepted when only a SIBLING wildcard has a catch-all"
4103        );
4104    }
4105
4106    #[test]
4107    fn validate_new_tcp_front_rejects_a_duplicate_wildcard_catch_all() {
4108        let mut listener = test_listener();
4109        listener
4110            .insert_sni_route(
4111                "*.example.com".to_owned(),
4112                vec![],
4113                "cluster-wildcard".into(),
4114            )
4115            .expect("insert_sni_route must succeed for a valid test SNI");
4116
4117        // A SECOND catch-all for the SAME wildcard key is an ambiguous
4118        // duplicate and must be rejected. The immutable trie `lookup` has no
4119        // literal-`*` short-circuit (only `lookup_mut` does), so a plain
4120        // `accept_wildcard: false` self-lookup never sees the existing
4121        // wildcard entry and waves the duplicate through -- which
4122        // `insert_sni_route` (lookup_mut, short-circuit present) would then
4123        // happily append.
4124        let front = frontend("cluster-dup", Some("*.example.com"), &[]);
4125        assert!(
4126            listener.validate_new_tcp_front(&front).is_err(),
4127            "a second catch-all on the same wildcard SNI must be rejected as a duplicate"
4128        );
4129    }
4130
4131    #[test]
4132    fn validate_new_tcp_front_rejects_overlapping_alpn_on_the_same_wildcard() {
4133        let mut listener = test_listener();
4134        listener
4135            .insert_sni_route(
4136                "*.example.com".to_owned(),
4137                vec!["h2".to_owned()],
4138                "cluster-wildcard-h2".into(),
4139            )
4140            .expect("insert_sni_route must succeed for a valid test SNI");
4141
4142        // Same wildcard key, overlapping ALPN protocol: ambiguous, must be
4143        // rejected (same bypass as the duplicate catch-all above).
4144        let front = frontend("cluster-dup", Some("*.example.com"), &["h2"]);
4145        assert!(
4146            listener.validate_new_tcp_front(&front).is_err(),
4147            "an overlapping ALPN matcher on the same wildcard SNI must be rejected"
4148        );
4149    }
4150
4151    #[test]
4152    fn validate_new_tcp_front_accepts_a_disjoint_alpn_addition_on_the_same_wildcard() {
4153        let mut listener = test_listener();
4154        listener
4155            .insert_sni_route(
4156                "*.example.com".to_owned(),
4157                vec![],
4158                "cluster-wildcard".into(),
4159            )
4160            .expect("insert_sni_route must succeed for a valid test SNI");
4161
4162        // A non-overlapping ALPN-scoped addition alongside the wildcard's
4163        // catch-all stays legal -- the wildcard-aware self-lookup must not
4164        // over-reject.
4165        let front = frontend("cluster-h2", Some("*.example.com"), &["h2"]);
4166        assert!(
4167            listener.validate_new_tcp_front(&front).is_ok(),
4168            "a disjoint ALPN addition on the same wildcard SNI must be accepted"
4169        );
4170    }
4171
4172    /// The worker boundary must reject malformed SNI SHAPES that a direct
4173    /// `AddTcpFrontend` (command socket) or `LoadState` replay could carry
4174    /// past config.rs's `validate_sni_pattern`: a `/.../` label would be
4175    /// inserted into the `pattern_trie` as a REGEX route and a misplaced
4176    /// `*` as an unintended wildcard — silently widening routing.
4177    #[test]
4178    fn validate_new_tcp_front_rejects_malformed_sni_shapes() {
4179        let listener = test_listener();
4180
4181        for bad in [
4182            "/[a-z]+/.example.com",
4183            "foo/bar.example.com",
4184            "a.*.example.com",
4185            "*.*.example.com",
4186            "*",
4187        ] {
4188            let front = frontend("cluster-a", Some(bad), &[]);
4189            assert!(
4190                listener.validate_new_tcp_front(&front).is_err(),
4191                "malformed SNI shape {bad:?} must be rejected at the worker boundary"
4192            );
4193        }
4194
4195        // The two documented-legal shapes must still pass.
4196        for good in ["a.example.com", "*.example.com"] {
4197            let front = frontend("cluster-a", Some(good), &[]);
4198            assert!(
4199                listener.validate_new_tcp_front(&front).is_ok(),
4200                "legal SNI shape {good:?} must still be accepted"
4201            );
4202        }
4203    }
4204
4205    /// `validate_new_tcp_front`'s OLD hand-rolled shape check only rejected
4206    /// `/` and a misplaced `*`, letting an empty label (leading dot,
4207    /// trailing dot, consecutive dots), an empty string, or a non-ASCII
4208    /// pattern reach `insert_sni_route` -> `pattern_trie::insert_recursive`,
4209    /// whose RELEASE-mode `assert_ne!(partial_key, &b""[..])` panics the
4210    /// worker on a leading-empty-label key like `.example.com`. This is the
4211    /// worker-boundary counterpart to `config.rs`'s `validate_sni_pattern`
4212    /// tests: every shape the shared validator rejects at config-load must
4213    /// also be rejected here, for `AddTcpFrontend`/`LoadState` requests that
4214    /// bypass config.rs entirely.
4215    #[test]
4216    fn add_tcp_front_enforces_the_shared_sni_validator_at_the_worker_boundary() {
4217        let mut proxy = test_proxy();
4218        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4219        let config = ListenerBuilder::new_tcp(address)
4220            .to_tcp(None)
4221            .expect("could not build listener config");
4222        let token = Token(0);
4223        proxy
4224            .add_listener(config, token)
4225            .expect("could not add listener");
4226
4227        for bad in [
4228            ".example.com",     // leading empty label -- the pattern_trie-crashing shape
4229            "example.com.",     // trailing empty label
4230            "a..b.example.com", // consecutive dots -- empty middle label
4231            "",                 // empty pattern
4232            "exämple.com",      // non-ASCII
4233        ] {
4234            let front = frontend("cluster-a", Some(bad), &[]);
4235            let front = RequestTcpFrontend { address, ..front };
4236            match proxy.add_tcp_front(front) {
4237                Err(ProxyError::InvalidTcpFrontend { .. }) => {}
4238                other => panic!("malformed sni {bad:?} must be rejected, got {other:?}"),
4239            }
4240
4241            let listener = proxy
4242                .listeners
4243                .get(&token)
4244                .expect("listener must be present")
4245                .borrow();
4246            assert!(
4247                listener.sni_routes.is_empty(),
4248                "a rejected sni {bad:?} must leave sni_routes empty"
4249            );
4250            assert!(
4251                listener.cluster_id.is_none(),
4252                "a rejected sni {bad:?} must leave cluster_id unset"
4253            );
4254        }
4255
4256        // A space IS valid ASCII, is not '*'/'/', and splits into non-empty
4257        // labels ("exa", "mple.com") -- none of the shared validator's rules
4258        // (empty pattern, non-ASCII, misplaced '*', empty label) cover "not a
4259        // valid hostname character" in general, so this shape is ACCEPTED,
4260        // not rejected. Documenting the validator's actual behavior rather
4261        // than assuming a hostname-shaped string with a space would be
4262        // caught too.
4263        let space_front = frontend("cluster-space", Some("exa mple.com"), &[]);
4264        let space_front = RequestTcpFrontend {
4265            address,
4266            ..space_front
4267        };
4268        assert!(
4269            proxy.add_tcp_front(space_front).is_ok(),
4270            "the shared SNI validator does not reject an embedded space -- \
4271             it is valid ASCII with no empty label"
4272        );
4273    }
4274
4275    // ---- per-frontend access-log tags keying (sozu-proxy/sozu#1290) ----
4276
4277    #[test]
4278    fn sni_fronts_keep_distinct_tags_under_distinct_keys() {
4279        let mut proxy = test_proxy();
4280        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4281        let config = ListenerBuilder::new_tcp(address)
4282            .to_tcp(None)
4283            .expect("could not build listener config");
4284        let token = Token(0);
4285        proxy
4286            .add_listener(config, token)
4287            .expect("could not add listener");
4288
4289        let front_a = RequestTcpFrontend {
4290            cluster_id: "cluster-a".to_owned(),
4291            address,
4292            sni: Some("a.example.com".to_owned()),
4293            alpn: vec![],
4294            tags: std::collections::BTreeMap::from([("team".to_owned(), "alpha".to_owned())]),
4295        };
4296        let front_b = RequestTcpFrontend {
4297            cluster_id: "cluster-b".to_owned(),
4298            address,
4299            sni: Some("b.example.com".to_owned()),
4300            alpn: vec!["h2".to_owned()],
4301            tags: std::collections::BTreeMap::from([("team".to_owned(), "beta".to_owned())]),
4302        };
4303        proxy
4304            .add_tcp_front(front_a)
4305            .expect("add_tcp_front A must succeed");
4306        proxy
4307            .add_tcp_front(front_b)
4308            .expect("add_tcp_front B must succeed");
4309
4310        let std_address: SocketAddr = address.into();
4311        let key_a = sni_tags_key(&std_address, "a.example.com", &[]);
4312        let key_b = sni_tags_key(&std_address, "b.example.com", &["h2".to_owned()]);
4313
4314        let listener = proxy
4315            .listeners
4316            .get(&token)
4317            .expect("listener must be present")
4318            .borrow();
4319        let tags_a = listener
4320            .get_tags(&key_a)
4321            .expect("front A's tags must live under its own composed key");
4322        assert_eq!(
4323            tags_a.tags.get("team").map(String::as_str),
4324            Some("alpha"),
4325            "front A's tags must survive front B's add, not be clobbered by it"
4326        );
4327        let tags_b = listener
4328            .get_tags(&key_b)
4329            .expect("front B's tags must live under its own composed key");
4330        assert_eq!(tags_b.tags.get("team").map(String::as_str), Some("beta"));
4331    }
4332
4333    #[test]
4334    fn removing_one_sni_front_clears_only_its_own_tags() {
4335        let mut proxy = test_proxy();
4336        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4337        let config = ListenerBuilder::new_tcp(address)
4338            .to_tcp(None)
4339            .expect("could not build listener config");
4340        let token = Token(0);
4341        proxy
4342            .add_listener(config, token)
4343            .expect("could not add listener");
4344
4345        let front_a = RequestTcpFrontend {
4346            cluster_id: "cluster-a".to_owned(),
4347            address,
4348            sni: Some("a.example.com".to_owned()),
4349            alpn: vec![],
4350            tags: std::collections::BTreeMap::from([("team".to_owned(), "alpha".to_owned())]),
4351        };
4352        let front_b = RequestTcpFrontend {
4353            cluster_id: "cluster-b".to_owned(),
4354            address,
4355            sni: Some("b.example.com".to_owned()),
4356            alpn: vec!["h2".to_owned()],
4357            tags: std::collections::BTreeMap::from([("team".to_owned(), "beta".to_owned())]),
4358        };
4359        proxy
4360            .add_tcp_front(front_a.clone())
4361            .expect("add_tcp_front A must succeed");
4362        proxy
4363            .add_tcp_front(front_b)
4364            .expect("add_tcp_front B must succeed");
4365
4366        proxy
4367            .remove_tcp_front(front_a)
4368            .expect("remove_tcp_front A must succeed");
4369
4370        let std_address: SocketAddr = address.into();
4371        let key_a = sni_tags_key(&std_address, "a.example.com", &[]);
4372        let key_b = sni_tags_key(&std_address, "b.example.com", &["h2".to_owned()]);
4373
4374        let listener = proxy
4375            .listeners
4376            .get(&token)
4377            .expect("listener must be present")
4378            .borrow();
4379        assert!(
4380            listener.get_tags(&key_a).is_none(),
4381            "removing front A must clear its own tags entry"
4382        );
4383        assert!(
4384            listener.get_tags(&key_b).is_some(),
4385            "removing front A must NOT clear sibling front B's tags"
4386        );
4387    }
4388
4389    /// A session routed to front A must look tags up under front A's key:
4390    /// the key `add_tcp_front` stores and the key `upgrade_sni_preread`
4391    /// rebuilds from the route decision (`matched_sni_pattern` +
4392    /// `matched_alpn`) must be identical, regardless of the operator's
4393    /// original ALPN order or SNI casing, and must never collide with the
4394    /// bare-address key used by no-SNI fronts.
4395    #[test]
4396    fn route_time_tags_key_rebuild_matches_the_add_time_key() {
4397        let address: SocketAddr = "127.0.0.1:9000".parse().expect("test address");
4398
4399        // Wildcard front, operator wrote mixed case + reverse ALPN order.
4400        let add_time = sni_tags_key(
4401            &address,
4402            "*.Example.COM",
4403            &["http/1.1".to_owned(), "h2".to_owned()],
4404        );
4405        let matcher =
4406            AlpnMatcher::OneOf([b"h2".to_vec(), b"http/1.1".to_vec()].into_iter().collect());
4407        let route_time = sni_tags_key(
4408            &address,
4409            "*.example.com", // matched_sni_pattern: the lowercased trie key
4410            &alpn_matcher_protocols(&matcher),
4411        );
4412        assert_eq!(
4413            add_time, route_time,
4414            "add-time and route-time keys must agree for the same front"
4415        );
4416
4417        // Catch-all front: empty alpn at add time <-> AlpnMatcher::Any.
4418        assert_eq!(
4419            sni_tags_key(&address, "a.example.com", &[]),
4420            sni_tags_key(
4421                &address,
4422                "a.example.com",
4423                &alpn_matcher_protocols(&AlpnMatcher::Any)
4424            )
4425        );
4426
4427        // A composed key never collides with the bare-address key.
4428        assert_ne!(add_time, address.to_string());
4429    }
4430
4431    /// Regression from the sozu-proxy/sozu#1290 review: ALPN protocol
4432    /// identifiers are opaque byte strings -- nothing forbids a `,` inside
4433    /// one -- so a SINGLE protocol `"a,b"` and the DISJOINT pair `["a",
4434    /// "b"]` are two legal, distinct `AlpnMatcher`s on the same
4435    /// `(address, sni)`, yet the naive `sorted_alpn.join(",")` key collapses
4436    /// both to the literal string `"a,b"`.
4437    #[test]
4438    fn alpn_sets_differing_only_by_an_embedded_separator_get_distinct_tags_keys() {
4439        let address: SocketAddr = "127.0.0.1:9001".parse().expect("test address");
4440        let key_joined = sni_tags_key(&address, "example.com", &["a,b".to_owned()]);
4441        let key_split = sni_tags_key(&address, "example.com", &["a".to_owned(), "b".to_owned()]);
4442        assert_ne!(
4443            key_joined, key_split,
4444            "a single \"a,b\" protocol must not collide with the disjoint [\"a\", \"b\"] pair"
4445        );
4446    }
4447
4448    /// End-to-end through `TcpProxy`: both fronts are accepted (they are
4449    /// genuinely disjoint `AlpnMatcher`s, so `validate_new_tcp_front`'s
4450    /// overlap check does not reject either), each keeps its OWN tags under
4451    /// its own composed key, and removing one leaves the other's tags
4452    /// intact.
4453    #[test]
4454    fn alpn_sets_differing_only_by_an_embedded_separator_keep_distinct_tags() {
4455        let mut proxy = test_proxy();
4456        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4457        let config = ListenerBuilder::new_tcp(address)
4458            .to_tcp(None)
4459            .expect("could not build listener config");
4460        let token = Token(0);
4461        proxy
4462            .add_listener(config, token)
4463            .expect("could not add listener");
4464
4465        let front_joined = RequestTcpFrontend {
4466            cluster_id: "cluster-joined".to_owned(),
4467            address,
4468            sni: Some("example.com".to_owned()),
4469            alpn: vec!["a,b".to_owned()],
4470            tags: std::collections::BTreeMap::from([("variant".to_owned(), "joined".to_owned())]),
4471        };
4472        let front_split = RequestTcpFrontend {
4473            cluster_id: "cluster-split".to_owned(),
4474            address,
4475            sni: Some("example.com".to_owned()),
4476            alpn: vec!["a".to_owned(), "b".to_owned()],
4477            tags: std::collections::BTreeMap::from([("variant".to_owned(), "split".to_owned())]),
4478        };
4479
4480        proxy
4481            .add_tcp_front(front_joined.clone())
4482            .expect("the single \"a,b\" protocol front must be accepted");
4483        proxy.add_tcp_front(front_split.clone()).expect(
4484            "the disjoint [\"a\", \"b\"] front must be accepted -- it is NOT the same ALPN \
4485                 set as [\"a,b\"]",
4486        );
4487
4488        let std_address: SocketAddr = address.into();
4489        let key_joined = sni_tags_key(&std_address, "example.com", &["a,b".to_owned()]);
4490        let key_split = sni_tags_key(
4491            &std_address,
4492            "example.com",
4493            &["a".to_owned(), "b".to_owned()],
4494        );
4495        assert_ne!(key_joined, key_split);
4496
4497        {
4498            let listener = proxy
4499                .listeners
4500                .get(&token)
4501                .expect("listener must be present")
4502                .borrow();
4503            assert_eq!(
4504                listener
4505                    .get_tags(&key_joined)
4506                    .and_then(|t| t.tags.get("variant"))
4507                    .map(String::as_str),
4508                Some("joined"),
4509                "the joined front's tags must live under its own composed key"
4510            );
4511            assert_eq!(
4512                listener
4513                    .get_tags(&key_split)
4514                    .and_then(|t| t.tags.get("variant"))
4515                    .map(String::as_str),
4516                Some("split"),
4517                "the split front's tags must live under its own DISTINCT composed key"
4518            );
4519        }
4520
4521        proxy
4522            .remove_tcp_front(front_joined)
4523            .expect("remove the joined front");
4524
4525        let listener = proxy
4526            .listeners
4527            .get(&token)
4528            .expect("listener must be present")
4529            .borrow();
4530        assert!(
4531            listener.get_tags(&key_joined).is_none(),
4532            "removing the joined front must clear its own tags entry"
4533        );
4534        assert_eq!(
4535            listener
4536                .get_tags(&key_split)
4537                .and_then(|t| t.tags.get("variant"))
4538                .map(String::as_str),
4539            Some("split"),
4540            "removing the joined front must not disturb its sibling's tags"
4541        );
4542    }
4543
4544    #[test]
4545    fn remove_sni_route_for_an_absent_exact_key_does_not_strip_a_sibling_wildcards_entry() {
4546        let mut listener = test_listener();
4547        listener
4548            .insert_sni_route(
4549                "*.example.com".to_owned(),
4550                vec![],
4551                "cluster-wildcard".into(),
4552            )
4553            .expect("insert_sni_route must succeed for a valid test SNI");
4554
4555        // "a.example.com" was never inserted as its own route -- only the
4556        // wildcard catch-all exists. A remove targeting the exact host
4557        // (e.g. a stale `RemoveTcpFrontend` replayed from a hand-edited
4558        // `LoadState`) must be a no-op here, not reach into and strip the
4559        // WILDCARD's own catch-all entry.
4560        listener.remove_sni_route("a.example.com".to_owned(), vec![], "cluster-wildcard");
4561
4562        let (_, wildcard_entries) = listener
4563            .sni_routes
4564            .domain_lookup(b"b.example.com", true)
4565            .expect(
4566                "the wildcard catch-all must survive a remove targeting an unrelated exact key",
4567            );
4568        assert_eq!(
4569            wildcard_entries,
4570            &vec![(AlpnMatcher::Any, "cluster-wildcard".into())]
4571        );
4572    }
4573
4574    // ---- routing gate data invariant: never both cluster_id AND routes ----
4575
4576    #[test]
4577    fn a_no_sni_front_leaves_the_route_table_empty() {
4578        let mut listener = test_listener();
4579        listener.cluster_id = Some("legacy-catch-all".into());
4580        assert!(
4581            listener.sni_routes.is_empty(),
4582            "a listener with only a no-SNI front must never populate sni_routes"
4583        );
4584    }
4585
4586    #[test]
4587    fn an_sni_scoped_front_leaves_cluster_id_unset() {
4588        let mut listener = test_listener();
4589        listener
4590            .insert_sni_route("example.com".to_owned(), vec![], "cluster-a".into())
4591            .expect("insert_sni_route must succeed for a valid test SNI");
4592        assert!(
4593            listener.cluster_id.is_none(),
4594            "a listener with only SNI-scoped fronts must never populate the legacy cluster_id"
4595        );
4596    }
4597
4598    // ---- end-to-end through TcpProxy::add_tcp_front / remove_tcp_front ----
4599
4600    fn test_proxy() -> TcpProxy {
4601        let ServerParts {
4602            registry,
4603            sessions,
4604            pool,
4605            backends,
4606            ..
4607        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
4608        TcpProxy::new(registry, sessions, pool, backends)
4609    }
4610
4611    /// Two listeners on one address, one bound to `lo` and one to no
4612    /// interface, coexist in a worker: each is activated, handed back and
4613    /// removed under its own (address, interface) key, each socket carries its
4614    /// own `SO_BINDTODEVICE`, and a frontend — which names its listener by
4615    /// address alone — reaches both.
4616    #[cfg(target_os = "linux")]
4617    #[test]
4618    fn listeners_sharing_an_address_on_different_interfaces_coexist() {
4619        use socket2::SockRef;
4620
4621        let mut proxy = test_proxy();
4622        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4623        let bare = ListenerKey::from(address);
4624        let on_lo = ListenerKey::new(address, Some("lo"));
4625        for (token, interface) in [(Token(0), None), (Token(1), Some("lo"))] {
4626            let config = ListenerBuilder::new_tcp(address)
4627                .with_interface(interface)
4628                .to_tcp(None)
4629                .expect("could not build listener config");
4630            proxy
4631                .add_listener(config, token)
4632                .expect("a listener on another interface must not collide");
4633        }
4634        assert_eq!(proxy.listener_token(&bare), Some(Token(0)));
4635        assert_eq!(proxy.listener_token(&on_lo), Some(Token(1)));
4636        assert_eq!(
4637            proxy.listener_token(&ListenerKey::new(address, Some("wg0"))),
4638            None,
4639            "an interface nobody added names no listener"
4640        );
4641
4642        proxy
4643            .add_tcp_front(RequestTcpFrontend {
4644                cluster_id: "cluster-a".to_owned(),
4645                address,
4646                ..Default::default()
4647            })
4648            .expect("add_tcp_front must succeed");
4649        for token in [Token(0), Token(1)] {
4650            assert_eq!(
4651                proxy.listeners[&token].borrow().cluster_id.as_deref(),
4652                Some("cluster-a"),
4653                "the frontend must reach the listener at {token:?}"
4654            );
4655        }
4656
4657        for key in [&bare, &on_lo] {
4658            if let Err(error) = proxy.activate_listener(key, None) {
4659                let message = error.to_string();
4660                if message.contains("Operation not permitted") {
4661                    eprintln!(
4662                        "SKIPPED listeners_sharing_an_address_on_different_interfaces_coexist: \
4663                         this environment may not bind a socket to an interface: {message}"
4664                    );
4665                    return;
4666                }
4667                panic!("activating {key}: {message}");
4668            }
4669        }
4670        let mut handed_back = proxy.give_back_listeners();
4671        handed_back.sort_by(|(a, _), (b, _)| a.cmp(b));
4672        let keys: Vec<&ListenerKey> = handed_back.iter().map(|(key, _)| key).collect();
4673        assert_eq!(keys, vec![&bare, &on_lo]);
4674        for (key, socket) in &handed_back {
4675            assert_eq!(
4676                SockRef::from(socket)
4677                    .device()
4678                    .expect("getsockopt(SO_BINDTODEVICE)"),
4679                key.interface
4680                    .as_ref()
4681                    .map(|interface| interface.as_bytes().to_vec()),
4682                "the socket handed back for {key} must be bound to its interface"
4683            );
4684        }
4685
4686        proxy.remove_listener(&on_lo);
4687        assert_eq!(proxy.listener_token(&on_lo), None);
4688        assert_eq!(
4689            proxy.listener_token(&bare),
4690            Some(Token(0)),
4691            "removing the lo listener must keep the one without interface"
4692        );
4693    }
4694
4695    /// A TCP listener added on an address that already routes to a cluster
4696    /// takes that route over from the listener already there — the no-SNI
4697    /// cluster and the SNI routes alike — so it forwards from its first
4698    /// connection (sozu-proxy/sozu#719).
4699    #[test]
4700    fn a_listener_added_next_to_a_sibling_takes_over_its_routes() {
4701        let mut proxy = test_proxy();
4702        let catch_all = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4703        let by_sni = SocketAddress::new_v4(0, 0, 0, 0, provide_port());
4704        for (token, address, sni) in [
4705            (Token(0), catch_all, None),
4706            (Token(2), by_sni, Some("example.com".to_owned())),
4707        ] {
4708            let config = ListenerBuilder::new_tcp(address)
4709                .to_tcp(None)
4710                .expect("could not build listener config");
4711            proxy.add_listener(config, token).expect("add the listener");
4712            proxy
4713                .add_tcp_front(RequestTcpFrontend {
4714                    cluster_id: "cluster-a".to_owned(),
4715                    address,
4716                    sni,
4717                    ..Default::default()
4718                })
4719                .expect("add_tcp_front must succeed");
4720        }
4721        for (token, address) in [(Token(1), catch_all), (Token(3), by_sni)] {
4722            let config = ListenerBuilder::new_tcp(address)
4723                .with_interface(Some("lo"))
4724                .to_tcp(None)
4725                .expect("could not build listener config");
4726            proxy.add_listener(config, token).expect("add the sibling");
4727        }
4728
4729        assert_eq!(
4730            proxy.listeners[&Token(1)].borrow().cluster_id.as_deref(),
4731            Some("cluster-a"),
4732            "the listener added after the frontend must route to its cluster"
4733        );
4734        let sibling = proxy.listeners[&Token(3)].borrow();
4735        let (_, entries) = sibling
4736            .sni_routes
4737            .domain_lookup(b"example.com", true)
4738            .expect("the listener added after the SNI frontend must route it");
4739        assert_eq!(entries, &vec![(AlpnMatcher::Any, "cluster-a".into())]);
4740    }
4741
4742    /// Traffic that arrives through an interface goes to the listener bound to
4743    /// it, not to the listener on the same address bound to none: Linux
4744    /// prefers the device-bound socket, and the two never share a
4745    /// `SO_REUSEPORT` group. Here a bare `0.0.0.0:P` and a `lo`-bound
4746    /// `0.0.0.0:P` both listen, a client connects through `127.0.0.1`, and
4747    /// only the `lo` listener has a connection to accept.
4748    #[cfg(target_os = "linux")]
4749    #[test]
4750    fn traffic_through_an_interface_reaches_the_listener_bound_to_it() {
4751        let mut proxy = test_proxy();
4752        let port = provide_port();
4753        let address = SocketAddress::new_v4(0, 0, 0, 0, port);
4754        for (token, interface) in [(Token(0), None), (Token(1), Some("lo"))] {
4755            let config = ListenerBuilder::new_tcp(address)
4756                .with_interface(interface)
4757                .to_tcp(None)
4758                .expect("could not build listener config");
4759            proxy.add_listener(config, token).expect("add the listener");
4760            let key = ListenerKey::new(address, interface);
4761            if let Err(error) = proxy.activate_listener(&key, None) {
4762                let message = error.to_string();
4763                if message.contains("Operation not permitted") {
4764                    eprintln!(
4765                        "SKIPPED traffic_through_an_interface_reaches_the_listener_bound_to_it: \
4766                         this environment may not bind a socket to an interface: {message}"
4767                    );
4768                    return;
4769                }
4770                panic!("activating {key}: {message}");
4771            }
4772        }
4773
4774        // Several connections: with the binding ignored, both sockets would
4775        // share one `SO_REUSEPORT` group and each connection would land on
4776        // either of them.
4777        const CONNECTIONS: usize = 8;
4778        let _clients: Vec<_> = (0..CONNECTIONS)
4779            .map(|_| {
4780                std::net::TcpStream::connect(("127.0.0.1", port))
4781                    .expect("connect through the loopback interface")
4782            })
4783            .collect();
4784        // The handshakes are complete once `connect` returns; poll briefly in
4785        // case an accept queue entry is not visible yet.
4786        let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
4787        let mut accepted = 0;
4788        while accepted < CONNECTIONS {
4789            match proxy.accept(ListenToken(1)) {
4790                Ok(_) => accepted += 1,
4791                Err(AcceptError::WouldBlock) if std::time::Instant::now() < deadline => {
4792                    std::thread::sleep(std::time::Duration::from_millis(10));
4793                }
4794                Err(error) => panic!(
4795                    "the lo listener must accept every connection, got {accepted} of \
4796                     {CONNECTIONS}: {error:?}"
4797                ),
4798            }
4799        }
4800        assert!(
4801            matches!(proxy.accept(ListenToken(0)), Err(AcceptError::WouldBlock)),
4802            "the listener bound to no interface must not receive loopback traffic"
4803        );
4804    }
4805
4806    #[test]
4807    fn add_then_remove_sni_front_round_trips_through_tcp_proxy() {
4808        let mut proxy = test_proxy();
4809        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4810        let config = ListenerBuilder::new_tcp(address)
4811            .to_tcp(None)
4812            .expect("could not build listener config");
4813        let token = Token(0);
4814        proxy
4815            .add_listener(config, token)
4816            .expect("could not add listener");
4817
4818        let front = RequestTcpFrontend {
4819            cluster_id: "cluster-a".to_owned(),
4820            address,
4821            sni: Some("Example.COM".to_owned()),
4822            alpn: vec![],
4823            ..Default::default()
4824        };
4825        proxy
4826            .add_tcp_front(front.clone())
4827            .expect("add_tcp_front must succeed");
4828
4829        {
4830            let listener = proxy
4831                .listeners
4832                .get(&token)
4833                .expect("listener must be present")
4834                .borrow();
4835            assert!(listener.cluster_id.is_none());
4836            let (_, entries) = listener
4837                .sni_routes
4838                // Lowercased at insert time regardless of wire-form casing.
4839                .domain_lookup(b"example.com", true)
4840                .expect("example.com must be routable after add_tcp_front");
4841            assert_eq!(entries, &vec![(AlpnMatcher::Any, "cluster-a".into())]);
4842        }
4843        assert_eq!(proxy.fronts.get("cluster-a"), Some(&token));
4844
4845        proxy
4846            .remove_tcp_front(front)
4847            .expect("remove_tcp_front must succeed");
4848
4849        {
4850            let listener = proxy
4851                .listeners
4852                .get(&token)
4853                .expect("listener must be present")
4854                .borrow();
4855            assert!(
4856                listener.sni_routes.is_empty(),
4857                "remove_tcp_front must leave no stranded route"
4858            );
4859        }
4860        assert_eq!(
4861            proxy.fronts.get("cluster-a"),
4862            None,
4863            "remove_tcp_front must undo add_tcp_front's self.fronts bookkeeping"
4864        );
4865    }
4866
4867    #[test]
4868    fn add_then_remove_legacy_no_sni_front_round_trips() {
4869        let mut proxy = test_proxy();
4870        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4871        let config = ListenerBuilder::new_tcp(address)
4872            .to_tcp(None)
4873            .expect("could not build listener config");
4874        let token = Token(0);
4875        proxy
4876            .add_listener(config, token)
4877            .expect("could not add listener");
4878
4879        let front = frontend("cluster-legacy", None, &[]);
4880        let front = RequestTcpFrontend { address, ..front };
4881        proxy
4882            .add_tcp_front(front.clone())
4883            .expect("add_tcp_front must succeed");
4884
4885        assert_eq!(
4886            proxy
4887                .listeners
4888                .get(&token)
4889                .expect("listener must be present")
4890                .borrow()
4891                .cluster_id,
4892            Some("cluster-legacy".into())
4893        );
4894
4895        proxy
4896            .remove_tcp_front(front)
4897            .expect("remove_tcp_front must succeed");
4898
4899        assert_eq!(
4900            proxy
4901                .listeners
4902                .get(&token)
4903                .expect("listener must be present")
4904                .borrow()
4905                .cluster_id,
4906            None
4907        );
4908        assert_eq!(proxy.fronts.get("cluster-legacy"), None);
4909    }
4910
4911    // ---- add_tcp_front hard-rejects routing-corrupting requests --------
4912    //
4913    // Worker-side mirror of `command/src/config.rs`'s TOML config-load
4914    // invariants (sozu-proxy/sozu#1279 hardening): `AddTcpFrontend` can
4915    // reach the worker directly over the command socket, or via `LoadState`
4916    // replay, bypassing config.rs entirely, so `add_tcp_front` must defend
4917    // itself rather than rely on a debug-only assertion.
4918
4919    #[test]
4920    fn add_tcp_front_rejects_alpn_without_sni() {
4921        let mut proxy = test_proxy();
4922        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4923        let config = ListenerBuilder::new_tcp(address)
4924            .to_tcp(None)
4925            .expect("could not build listener config");
4926        proxy
4927            .add_listener(config, Token(0))
4928            .expect("could not add listener");
4929
4930        let front = frontend("cluster-a", None, &["h2"]);
4931        let front = RequestTcpFrontend { address, ..front };
4932        match proxy.add_tcp_front(front) {
4933            Err(ProxyError::InvalidTcpFrontend { .. }) => {}
4934            other => panic!("expected InvalidTcpFrontend, got {other:?}"),
4935        }
4936    }
4937
4938    #[test]
4939    fn add_tcp_front_rejects_no_sni_front_on_listener_with_sni_routes() {
4940        let mut proxy = test_proxy();
4941        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4942        let config = ListenerBuilder::new_tcp(address)
4943            .to_tcp(None)
4944            .expect("could not build listener config");
4945        proxy
4946            .add_listener(config, Token(0))
4947            .expect("could not add listener");
4948
4949        let sni_front = frontend("cluster-a", Some("example.com"), &[]);
4950        let sni_front = RequestTcpFrontend {
4951            address,
4952            ..sni_front
4953        };
4954        proxy
4955            .add_tcp_front(sni_front)
4956            .expect("the first, SNI-scoped frontend must be accepted");
4957
4958        let no_sni_front = frontend("cluster-b", None, &[]);
4959        let no_sni_front = RequestTcpFrontend {
4960            address,
4961            ..no_sni_front
4962        };
4963        match proxy.add_tcp_front(no_sni_front) {
4964            Err(ProxyError::InvalidTcpFrontend { .. }) => {}
4965            other => panic!("expected InvalidTcpFrontend, got {other:?}"),
4966        }
4967    }
4968
4969    #[test]
4970    fn add_tcp_front_rejects_sni_front_on_listener_with_no_sni_cluster() {
4971        let mut proxy = test_proxy();
4972        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
4973        let config = ListenerBuilder::new_tcp(address)
4974            .to_tcp(None)
4975            .expect("could not build listener config");
4976        proxy
4977            .add_listener(config, Token(0))
4978            .expect("could not add listener");
4979
4980        let no_sni_front = frontend("cluster-a", None, &[]);
4981        let no_sni_front = RequestTcpFrontend {
4982            address,
4983            ..no_sni_front
4984        };
4985        proxy
4986            .add_tcp_front(no_sni_front)
4987            .expect("the first, no-SNI frontend must be accepted");
4988
4989        let sni_front = frontend("cluster-b", Some("example.com"), &[]);
4990        let sni_front = RequestTcpFrontend {
4991            address,
4992            ..sni_front
4993        };
4994        match proxy.add_tcp_front(sni_front) {
4995            Err(ProxyError::InvalidTcpFrontend { .. }) => {}
4996            other => panic!("expected InvalidTcpFrontend, got {other:?}"),
4997        }
4998    }
4999
5000    #[test]
5001    fn add_tcp_front_rejects_alpn_overlap_on_same_sni() {
5002        let mut proxy = test_proxy();
5003        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5004        let config = ListenerBuilder::new_tcp(address)
5005            .to_tcp(None)
5006            .expect("could not build listener config");
5007        proxy
5008            .add_listener(config, Token(0))
5009            .expect("could not add listener");
5010
5011        let first = frontend("cluster-a", Some("example.com"), &["h2"]);
5012        let first = RequestTcpFrontend { address, ..first };
5013        proxy
5014            .add_tcp_front(first)
5015            .expect("the first frontend must be accepted");
5016
5017        // Second frontend shares "h2" with the first on the same sni.
5018        let second = frontend("cluster-b", Some("example.com"), &["h2", "http/1.1"]);
5019        let second = RequestTcpFrontend { address, ..second };
5020        match proxy.add_tcp_front(second) {
5021            Err(ProxyError::InvalidTcpFrontend { .. }) => {}
5022            other => panic!("expected InvalidTcpFrontend, got {other:?}"),
5023        }
5024    }
5025
5026    #[test]
5027    fn add_tcp_front_rejects_duplicate_catch_all_on_same_sni() {
5028        let mut proxy = test_proxy();
5029        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5030        let config = ListenerBuilder::new_tcp(address)
5031            .to_tcp(None)
5032            .expect("could not build listener config");
5033        proxy
5034            .add_listener(config, Token(0))
5035            .expect("could not add listener");
5036
5037        let first = frontend("cluster-a", Some("example.com"), &[]);
5038        let first = RequestTcpFrontend { address, ..first };
5039        proxy
5040            .add_tcp_front(first)
5041            .expect("the first catch-all frontend must be accepted");
5042
5043        let second = frontend("cluster-b", Some("example.com"), &[]);
5044        let second = RequestTcpFrontend { address, ..second };
5045        match proxy.add_tcp_front(second) {
5046            Err(ProxyError::InvalidTcpFrontend { .. }) => {}
5047            other => panic!("expected InvalidTcpFrontend, got {other:?}"),
5048        }
5049    }
5050
5051    /// The valid, intended shape (sozu-proxy/sozu#1279's whole reason for
5052    /// existing) must still be accepted: disjoint, non-empty `alpn` lists
5053    /// on the same `(address, sni)`, and a catch-all alongside a
5054    /// specific-protocol entry.
5055    #[test]
5056    fn add_tcp_front_accepts_disjoint_alpn_and_catch_all_on_same_sni() {
5057        let mut proxy = test_proxy();
5058        let address = SocketAddress::new_v4(127, 0, 0, 1, provide_port());
5059        let config = ListenerBuilder::new_tcp(address)
5060            .to_tcp(None)
5061            .expect("could not build listener config");
5062        proxy
5063            .add_listener(config, Token(0))
5064            .expect("could not add listener");
5065
5066        let h2 = frontend("cluster-h2", Some("example.com"), &["h2"]);
5067        let h2 = RequestTcpFrontend { address, ..h2 };
5068        proxy
5069            .add_tcp_front(h2)
5070            .expect("disjoint alpn frontend must be accepted");
5071
5072        let http11 = frontend("cluster-http11", Some("example.com"), &["http/1.1"]);
5073        let http11 = RequestTcpFrontend { address, ..http11 };
5074        proxy
5075            .add_tcp_front(http11)
5076            .expect("second disjoint alpn frontend must be accepted");
5077
5078        let catch_all = frontend("cluster-default", Some("example.com"), &[]);
5079        let catch_all = RequestTcpFrontend {
5080            address,
5081            ..catch_all
5082        };
5083        proxy
5084            .add_tcp_front(catch_all)
5085            .expect("a catch-all alongside specific-protocol entries must be accepted");
5086    }
5087
5088    // ---- tcp.sni_preread.active gauge accounting ----------------------
5089
5090    /// Read the current process-local `tcp.sni_preread.active` gauge,
5091    /// treating an absent key as 0. `dump_local_proxy_metrics` is a
5092    /// non-draining filter over the proxy `MetricsMap`, so repeated reads are
5093    /// side-effect free and the key is the raw metric name.
5094    fn sni_preread_active_gauge() -> i64 {
5095        use sozu_command::proto::command::filtered_metrics::Inner;
5096        crate::metrics::METRICS.with(|metrics| {
5097            metrics
5098                .borrow_mut()
5099                .dump_local_proxy_metrics()
5100                .get(names::tcp::sni_preread::ACTIVE)
5101                .and_then(|fm| fm.inner.as_ref())
5102                .and_then(|inner| match inner {
5103                    Inner::Gauge(v) => Some(*v as i64),
5104                    _ => None,
5105                })
5106                .unwrap_or(0)
5107        })
5108    }
5109
5110    #[test]
5111    fn entering_sni_preread_increments_the_active_gauge() {
5112        // Regression guard for the missing-`+1` gauge bug
5113        // (sozu-proxy/sozu#1279): `new_sni_preread` must bump
5114        // `tcp.sni_preread.active` by exactly one when a session ENTERS the
5115        // state, so each of the two `-1` decrements -- the "upgrade" exit in
5116        // `upgrade_sni_preread` and the "reject"/"teardown" exit in `close()`'s
5117        // `StateMarker::SniPreread` arm -- has a matching increment. Without
5118        // this `+1` the first `-1` underflows a fresh-zero gauge (clamped to 0,
5119        // ERROR-logged), pinning the gauge at 0 and rendering the e2e gauge
5120        // assertion vacuous.
5121        //
5122        // `METRICS` is a thread-local shared across unit tests on the same
5123        // worker thread, so this asserts the DELTA around one constructor call
5124        // (robust to any starting value), not an absolute reading. The
5125        // net-zero-per-session contract spans the full lifecycle (accept ->
5126        // live backend connect -> upgrade/teardown) and is the behavioural job
5127        // of the e2e gauge assertion
5128        // (`test_tcp_sni_per_ip_limiter_rejects_second_then_admits_after_release`
5129        // in `e2e/src/tests/tcp_sni_tests.rs`), not reproducible at this unit
5130        // level.
5131        let ServerParts {
5132            registry,
5133            sessions,
5134            pool,
5135            backends,
5136            ..
5137        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
5138
5139        let proxy = Rc::new(RefCell::new(TcpProxy::new(
5140            registry,
5141            sessions,
5142            pool.clone(),
5143            backends,
5144        )));
5145        let listener = Rc::new(RefCell::new(test_listener()));
5146
5147        let (front_buffer, back_buffer) = {
5148            let mut pool = pool.borrow_mut();
5149            (
5150                pool.checkout().expect("front buffer checkout must succeed"),
5151                pool.checkout().expect("back buffer checkout must succeed"),
5152            )
5153        };
5154
5155        // A non-blocking connect to a (likely unused) loopback port returns a
5156        // real `MioTcpStream` handle immediately, regardless of whether the
5157        // connection completes; `new_sni_preread` reads nothing from it.
5158        let peer: SocketAddr = format!("127.0.0.1:{}", provide_port())
5159            .parse()
5160            .expect("loopback address must parse");
5161        let socket = MioTcpStream::connect(peer).expect("mio connect must return a socket handle");
5162
5163        let before = sni_preread_active_gauge();
5164        let session = TcpSession::new_sni_preread(
5165            back_buffer,
5166            Duration::from_secs(30),
5167            Duration::from_secs(30),
5168            front_buffer,
5169            Token(0),
5170            listener,
5171            proxy,
5172            socket,
5173            peer,
5174            Duration::from_millis(0),
5175            Duration::from_secs(3),
5176            16384,
5177        );
5178        let after = sni_preread_active_gauge();
5179
5180        // The session is measured while still in `SniPreread`; hold it across
5181        // the read so no future `Drop` side effect could race the measurement.
5182        assert!(matches!(session.state, TcpStateMachine::SniPreread(_)));
5183
5184        assert_eq!(
5185            after - before,
5186            1,
5187            "entering the SniPreread state must increment tcp.sni_preread.active by exactly one"
5188        );
5189    }
5190
5191    // ---- absolute preread deadline + route-aware timeout (sozu-proxy/sozu#1290) ----
5192
5193    /// `frontend_timeout_resets_on_readable` must gate the reset on exactly
5194    /// one thing: whether the CURRENT state is an undecided `SniPreread`.
5195    /// Constructed directly (no socket I/O needed) since the predicate is a
5196    /// pure function of `&TcpStateMachine`.
5197    #[test]
5198    fn frontend_timeout_reset_is_gated_on_sni_preread_decision() {
5199        let mut pool = crate::pool::Pool::with_capacity(1, 1, 16 * 1024);
5200        let frontend_buffer = pool.checkout().expect("frontend buffer");
5201        let socket = MioTcpStream::connect(
5202            format!("127.0.0.1:{}", provide_port())
5203                .parse()
5204                .expect("loopback address must parse"),
5205        )
5206        .expect("mio connect must return a socket handle");
5207
5208        let undecided = TcpStateMachine::SniPreread(SniPreread::new(
5209            socket,
5210            Token(0),
5211            Ulid::generate(),
5212            frontend_buffer,
5213            16384,
5214        ));
5215        assert!(
5216            !frontend_timeout_resets_on_readable(&undecided),
5217            "an undecided SniPreread must not have its absolute deadline reset"
5218        );
5219
5220        // Every OTHER state resets normally -- represented here by
5221        // `ExpectProxyProtocol`, cheaply constructible without driving a
5222        // real route decision (unlike a ROUTED `SniPreread`, whose
5223        // `outcome` field has no test-only setter and can only become
5224        // `Some` by parsing a real ClientHello -- see
5225        // `frontend_timeout_restored_and_timeout_after_route_is_not_double_counted`
5226        // below for that scenario end-to-end).
5227        let socket2 = MioTcpStream::connect(
5228            format!("127.0.0.1:{}", provide_port())
5229                .parse()
5230                .expect("loopback address must parse"),
5231        )
5232        .expect("mio connect must return a socket handle");
5233        let container = crate::timer::TimeoutContainer::new_empty(Duration::from_secs(5));
5234        let other = TcpStateMachine::ExpectProxyProtocol(ExpectProxyProtocol::new(
5235            container,
5236            socket2,
5237            Token(1),
5238            Ulid::generate(),
5239        ));
5240        assert!(
5241            frontend_timeout_resets_on_readable(&other),
5242            "every non-preread-undecided state must keep resetting its frontend timeout"
5243        );
5244    }
5245
5246    /// Minimal single-record TLS ClientHello wire carrying only a
5247    /// `server_name` extension for `host` -- hand-built rather than reusing
5248    /// `tcp_preread::parser`'s test-only wire-building helpers (`mod
5249    /// parser` is private to `tcp_preread`, unreachable from this sibling
5250    /// module) to drive a REAL route decision through
5251    /// `TcpSession::readable()` for the regression test below.
5252    fn minimal_client_hello_wire(host: &str) -> Vec<u8> {
5253        let mut name_list = vec![0u8]; // name_type = host_name
5254        name_list.extend_from_slice(&(host.len() as u16).to_be_bytes());
5255        name_list.extend_from_slice(host.as_bytes());
5256        let mut sni_ext_data = Vec::new();
5257        sni_ext_data.extend_from_slice(&(name_list.len() as u16).to_be_bytes());
5258        sni_ext_data.extend_from_slice(&name_list);
5259        let mut sni_ext = Vec::new();
5260        sni_ext.extend_from_slice(&0x0000u16.to_be_bytes()); // server_name extension type
5261        sni_ext.extend_from_slice(&(sni_ext_data.len() as u16).to_be_bytes());
5262        sni_ext.extend_from_slice(&sni_ext_data);
5263
5264        let mut body = Vec::new();
5265        body.extend_from_slice(&[0x03, 0x03]); // legacy_version
5266        body.extend_from_slice(&[0u8; 32]); // random
5267        body.push(0); // session_id: empty
5268        body.extend_from_slice(&[0x00, 0x02, 0x13, 0x01]); // cipher_suites
5269        body.push(1); // compression_methods length
5270        body.push(0); // compression_method: null
5271        body.extend_from_slice(&(sni_ext.len() as u16).to_be_bytes()); // extensions block length
5272        body.extend_from_slice(&sni_ext);
5273
5274        let mut handshake = Vec::new();
5275        handshake.push(1u8); // msg_type = client_hello
5276        let hs_len = body.len() as u32;
5277        handshake.extend_from_slice(&hs_len.to_be_bytes()[1..4]);
5278        handshake.extend_from_slice(&body);
5279
5280        let mut record = Vec::new();
5281        record.push(22u8); // ContentType::handshake
5282        record.extend_from_slice(&[0x03, 0x03]); // legacy record version
5283        record.extend_from_slice(&(handshake.len() as u16).to_be_bytes());
5284        record.extend_from_slice(&handshake);
5285        record
5286    }
5287
5288    /// Read the current process-local `tcp.sni_preread.routed` counter.
5289    /// Same non-draining-read pattern as `sni_preread_active_gauge`, but
5290    /// for a `Count` metric instead of a `Gauge`.
5291    fn sni_preread_routed_count() -> i64 {
5292        use sozu_command::proto::command::filtered_metrics::Inner;
5293        crate::metrics::METRICS.with(|metrics| {
5294            metrics
5295                .borrow_mut()
5296                .dump_local_proxy_metrics()
5297                .get(names::tcp::sni_preread::ROUTED)
5298                .and_then(|fm| fm.inner.as_ref())
5299                .and_then(|inner| match inner {
5300                    Inner::Count(v) => Some(*v),
5301                    _ => None,
5302                })
5303                .unwrap_or(0)
5304        })
5305    }
5306
5307    /// End-to-end regression from the sozu-proxy/sozu#1290 review:
5308    ///
5309    /// (a) the moment a real ClientHello routes, `container_frontend_timeout`
5310    ///     must already carry the listener's configured `front_timeout` --
5311    ///     not just once `upgrade_sni_preread` eventually runs (which can be
5312    ///     one or more `ready()` cycles later, after the backend connects);
5313    /// (b) a front-timeout firing AFTER that route decision (backend connect
5314    ///     still pending) must not re-feed `Input::Timeout` into the
5315    ///     already-decided core: pre-fix, doing so replayed the SAME latched
5316    ///     `Output::Routed` through `SniPreread::handle_output`'s `Routed`
5317    ///     arm a second time, double-incrementing `tcp.sni_preread.routed`
5318    ///     (release) and tripping `debug_assert!(self.outcome.is_none(),
5319    ///     ...)` (debug -- this test runs in a debug build, so pre-fix it
5320    ///     panics here).
5321    #[test]
5322    fn frontend_timeout_restored_and_timeout_after_route_is_not_double_counted() {
5323        let ServerParts {
5324            registry,
5325            sessions,
5326            pool,
5327            backends,
5328            ..
5329        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
5330        let proxy = Rc::new(RefCell::new(TcpProxy::new(
5331            registry,
5332            sessions,
5333            pool.clone(),
5334            backends,
5335        )));
5336
5337        let mut bare_listener = test_listener();
5338        bare_listener
5339            .insert_sni_route("example.com".to_owned(), vec![], "cluster-a".into())
5340            .expect("insert_sni_route must succeed for a valid test SNI");
5341        let configured_front_timeout = bare_listener.config.front_timeout;
5342        let listener = Rc::new(RefCell::new(bare_listener));
5343
5344        let std_listener = std::net::TcpListener::bind("127.0.0.1:0").expect("bind test listener");
5345        let addr = std_listener.local_addr().expect("listener local addr");
5346        let mut client = std::net::TcpStream::connect(addr).expect("connect test client");
5347        let (server, server_peer) = std_listener.accept().expect("accept test server");
5348        server.set_nonblocking(true).expect("server nonblocking");
5349
5350        let (front_buffer, back_buffer) = {
5351            let mut pool = pool.borrow_mut();
5352            (
5353                pool.checkout().expect("front buffer checkout must succeed"),
5354                pool.checkout().expect("back buffer checkout must succeed"),
5355            )
5356        };
5357
5358        let mut session = TcpSession::new_sni_preread(
5359            back_buffer,
5360            Duration::from_secs(30),
5361            Duration::from_secs(30),
5362            front_buffer,
5363            Token(0),
5364            listener,
5365            proxy,
5366            MioTcpStream::from_std(server),
5367            server_peer,
5368            Duration::from_millis(0),
5369            Duration::from_secs(3),
5370            16384,
5371        );
5372
5373        {
5374            use std::io::Write as _;
5375            client
5376                .write_all(&minimal_client_hello_wire("example.com"))
5377                .expect("write ClientHello");
5378            client.flush().ok();
5379        }
5380
5381        let routed_before = sni_preread_routed_count();
5382        for _ in 0..10 {
5383            if session.cluster_id.is_some() {
5384                break;
5385            }
5386            let _ = session.readable();
5387        }
5388        assert_eq!(
5389            session.cluster_id.as_deref(),
5390            Some("cluster-a"),
5391            "the session must have routed on a valid ClientHello for a configured SNI"
5392        );
5393        assert_eq!(
5394            sni_preread_routed_count() - routed_before,
5395            1,
5396            "routing must count tcp.sni_preread.routed exactly once"
5397        );
5398
5399        // (a) front_timeout is restored the moment routing succeeds.
5400        assert_eq!(
5401            session.container_frontend_timeout.duration(),
5402            Duration::from_secs(configured_front_timeout as u64),
5403            "the frontend timeout must already carry the configured front_timeout right after \
5404             routing, not only once the backend connects"
5405        );
5406
5407        // (b) a timeout firing after the route already latched must not
5408        // double-count tcp.sni_preread.routed, and (running in a debug
5409        // build) must not panic on SniPreread::handle_output's
5410        // `debug_assert!(self.outcome.is_none(), ...)`.
5411        let _ = session.timeout(Token(0));
5412        assert_eq!(
5413            sni_preread_routed_count() - routed_before,
5414            1,
5415            "a timeout firing after the route already latched must not double-count \
5416             tcp.sni_preread.routed"
5417        );
5418
5419        drop(client);
5420    }
5421
5422    /// A complete PROXY-v2 `PROXY` command header for IPv4, the exact shape
5423    /// `ExpectProxyProtocol::readable` accepts (28 bytes: 12-byte magic,
5424    /// version+command, family, address-block length, then the two addresses
5425    /// and ports).
5426    fn proxy_protocol_v2_ipv4_header() -> [u8; 28] {
5427        [
5428            0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54,
5429            0x0A, // PROXY-v2 magic
5430            0x21, // version 2, command PROXY
5431            0x11, // AF_INET over STREAM
5432            0x00, 0x0C, // address block length = 12
5433            127, 0, 0, 1, // source address
5434            127, 0, 0, 1, // destination address
5435            0x1F, 0x90, // source port 8080
5436            0x10, 0x68, // destination port 4200
5437        ]
5438    }
5439
5440    /// A live `ExpectHeader` session plus everything that must outlive it:
5441    /// dropping either listener would break the connect the tests drive.
5442    struct ExpectProxyFixture {
5443        session: Rc<RefCell<TcpSession>>,
5444        /// The same session, type-erased -- `ready()` takes the handle the
5445        /// server dispatches with, and `connect_to_backend` inserts a clone
5446        /// of it under the backend token.
5447        proxy_session: Rc<RefCell<dyn ProxySession>>,
5448        frontend_token: Token,
5449        client: std::net::TcpStream,
5450        _frontend_listener: std::net::TcpListener,
5451        _backend_listener: std::net::TcpListener,
5452    }
5453
5454    /// Build a session for a TCP cluster configured with `expect_proxy = true`
5455    /// (`ProxyProtocolConfig::ExpectHeader`), wired to a real bound backend and a real accepted
5456    /// frontend socket, and registered in the slab exactly like
5457    /// `create_session` does (the frontend slot is reserved BEFORE the
5458    /// session exists, so `connect_to_backend`'s backend token lands on a
5459    /// different key -- `set_back_token` asserts the two differ).
5460    fn expect_proxy_fixture() -> ExpectProxyFixture {
5461        let ServerParts {
5462            registry,
5463            sessions,
5464            pool,
5465            backends,
5466            ..
5467        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
5468
5469        let backend_listener =
5470            std::net::TcpListener::bind("127.0.0.1:0").expect("bind test backend");
5471        let backend_address = backend_listener
5472            .local_addr()
5473            .expect("test backend local addr");
5474        backends.borrow_mut().add_backend(
5475            "cluster-expect",
5476            Backend::new("cluster-expect-1", backend_address, None, None, None),
5477        );
5478
5479        let proxy = Rc::new(RefCell::new(TcpProxy::new(
5480            registry,
5481            sessions.clone(),
5482            pool.clone(),
5483            backends,
5484        )));
5485        let listener = Rc::new(RefCell::new(test_listener()));
5486
5487        let frontend_listener =
5488            std::net::TcpListener::bind("127.0.0.1:0").expect("bind test frontend listener");
5489        let frontend_address = frontend_listener
5490            .local_addr()
5491            .expect("test frontend local addr");
5492        let client = std::net::TcpStream::connect(frontend_address).expect("connect test client");
5493        let (frontend, frontend_peer) = frontend_listener
5494            .accept()
5495            .expect("accept the client connection");
5496        frontend
5497            .set_nonblocking(true)
5498            .expect("frontend socket nonblocking");
5499
5500        let (frontend_buffer, backend_buffer) = {
5501            let mut pool = pool.borrow_mut();
5502            (
5503                pool.checkout().expect("front buffer checkout must succeed"),
5504                pool.checkout().expect("back buffer checkout must succeed"),
5505            )
5506        };
5507
5508        let frontend_token = {
5509            let mut session_manager = sessions.borrow_mut();
5510            let entry = session_manager.slab.vacant_entry();
5511            let token = Token(entry.key());
5512            entry.insert(Rc::new(RefCell::new(crate::server::ListenSession {
5513                protocol: Protocol::TCPListen,
5514            })));
5515            token
5516        };
5517
5518        let session = Rc::new(RefCell::new(TcpSession::new(
5519            backend_buffer,
5520            None,
5521            Some("cluster-expect".into()),
5522            Duration::from_secs(30),
5523            Duration::from_secs(30),
5524            Duration::from_secs(30),
5525            frontend_buffer,
5526            frontend_token,
5527            listener,
5528            Some(ProxyProtocolConfig::ExpectHeader),
5529            proxy,
5530            MioTcpStream::from_std(frontend),
5531            frontend_peer,
5532            Duration::from_millis(0),
5533        )));
5534        let proxy_session: Rc<RefCell<dyn ProxySession>> = session.clone();
5535        sessions.borrow_mut().slab[frontend_token.0] = proxy_session.clone();
5536
5537        assert!(
5538            matches!(
5539                session.borrow().state,
5540                TcpStateMachine::ExpectProxyProtocol(_)
5541            ),
5542            "an ExpectHeader cluster must start its session in the expect state"
5543        );
5544
5545        ExpectProxyFixture {
5546            session,
5547            proxy_session,
5548            frontend_token,
5549            client,
5550            _frontend_listener: frontend_listener,
5551            _backend_listener: backend_listener,
5552        }
5553    }
5554
5555    /// A TCP cluster configured with `expect_proxy = true`
5556    /// (`ProxyProtocolConfig::ExpectHeader`) starts its sessions
5557    /// in [`TcpStateMachine::ExpectProxyProtocol`], a state with NO backend
5558    /// side at all: `back_readiness` returns `None` and `set_back_socket`
5559    /// panics outright ("We should not set the back socket for the expect
5560    /// proxy protocol"). The backend may only be dialed once the inbound
5561    /// header has been parsed and `upgrade_expect` has swapped in a `Pipe`.
5562    ///
5563    /// `ready_inner`'s top-of-function connect gate, however, fires on the
5564    /// FIRST readiness event of a freshly accepted socket -- including the
5565    /// WRITABLE epoll reports for a socket that has sent no byte yet. Without
5566    /// the `ExpectProxyProtocol` arm of `attempt_backend_connect_if_needed`'s
5567    /// guard, completing the TCP handshake was enough to drive
5568    /// `connect_to_backend` into that panic, killing the worker and every
5569    /// other session on it.
5570    ///
5571    /// To SEE THIS RED: delete the `TcpStateMachine::ExpectProxyProtocol(_)`
5572    /// arm from `attempt_backend_connect_if_needed`'s early-return guard.
5573    /// The first `ready()` below then panics inside `connect_to_backend` ->
5574    /// `set_back_socket` before any assertion is reached.
5575    #[test]
5576    fn expect_proxy_dials_the_backend_only_after_the_header_is_parsed() {
5577        use std::io::Write as _;
5578
5579        let mut fixture = expect_proxy_fixture();
5580        let (session, proxy_session, frontend_token) = (
5581            fixture.session.clone(),
5582            fixture.proxy_session.clone(),
5583            fixture.frontend_token,
5584        );
5585
5586        // The handshake alone: epoll reports the accepted socket WRITABLE
5587        // before the client has sent a single byte.
5588        session
5589            .borrow_mut()
5590            .update_readiness(frontend_token, Ready::WRITABLE);
5591        let closed = session.borrow_mut().ready(proxy_session.clone());
5592        assert!(
5593            !closed,
5594            "a session still waiting for its PROXY header must stay open"
5595        );
5596        {
5597            let session = session.borrow();
5598            assert!(
5599                matches!(session.state, TcpStateMachine::ExpectProxyProtocol(_)),
5600                "the session must still be expecting its PROXY header"
5601            );
5602            assert_eq!(
5603                session.back_connected(),
5604                BackendConnectionStatus::NotConnected,
5605                "no backend may be dialed while the PROXY header is still unparsed"
5606            );
5607            assert!(
5608                session.backend_token.is_none(),
5609                "no backend token may be wired while the PROXY header is still unparsed"
5610            );
5611        }
5612
5613        // The header lands: NOW the session upgrades out of the expect state
5614        // and that very same connect gate is allowed to dial.
5615        fixture
5616            .client
5617            .write_all(&proxy_protocol_v2_ipv4_header())
5618            .expect("write the PROXY-v2 header");
5619        fixture.client.flush().ok();
5620        session
5621            .borrow_mut()
5622            .update_readiness(frontend_token, Ready::READABLE);
5623        let _ = session.borrow_mut().ready(proxy_session.clone());
5624        {
5625            let session = session.borrow();
5626            assert!(
5627                !matches!(session.state, TcpStateMachine::ExpectProxyProtocol(_)),
5628                "a parsed PROXY header must move the session out of the expect state"
5629            );
5630            assert_ne!(
5631                session.back_connected(),
5632                BackendConnectionStatus::NotConnected,
5633                "the backend must be dialed once the header has been parsed"
5634            );
5635            assert!(
5636                session.backend_token.is_some(),
5637                "a dialed backend must own its backend token"
5638            );
5639        }
5640    }
5641
5642    /// Queue `wire` on the fixture's client, half-close its write side, then
5643    /// observe the accepted frontend socket through a real epoll instance
5644    /// registered only AFTER both the bytes and the FIN are queued.
5645    ///
5646    /// On Linux a `shutdown(SHUT_WR)` behind pending bytes is reported as one
5647    /// event carrying `EPOLLIN | EPOLLRDHUP`, which `Ready::from(&Event)`
5648    /// maps to `READABLE | HUP` without `WRITE_CLOSED`: the peer ended its
5649    /// request stream but still owns a usable response stream. The asserts
5650    /// pin that coalesced shape, so the tests that call this exercise the
5651    /// real kernel event rather than a synthetic readiness word.
5652    #[cfg(target_os = "linux")]
5653    fn half_close_and_observe_frontend(fixture: &mut ExpectProxyFixture, wire: &[u8]) -> Ready {
5654        use std::{io::Write as _, net::Shutdown, os::fd::AsRawFd, time::Duration};
5655
5656        use mio::{Events, Interest, Poll, Token, unix::SourceFd};
5657
5658        fixture
5659            .client
5660            .write_all(wire)
5661            .expect("write the request stream");
5662        fixture
5663            .client
5664            .shutdown(Shutdown::Write)
5665            .expect("half-close the client write side");
5666
5667        let frontend_fd = fixture.session.borrow().state.front_socket().as_raw_fd();
5668        let mut source = SourceFd(&frontend_fd);
5669        let mut poll = Poll::new().expect("create frontend poll");
5670        let observed_token = Token(7);
5671        poll.registry()
5672            .register(
5673                &mut source,
5674                observed_token,
5675                Interest::READABLE | Interest::WRITABLE,
5676            )
5677            .expect("register accepted frontend socket");
5678        let mut events = Events::with_capacity(4);
5679        poll.poll(&mut events, Some(Duration::from_secs(2)))
5680            .expect("poll accepted frontend socket");
5681        let ready = events
5682            .iter()
5683            .find(|event| event.token() == observed_token)
5684            .map(Ready::from)
5685            .expect("the queued request and FIN produce an epoll event");
5686        assert!(
5687            ready.is_readable(),
5688            "the queued FIN must be reported readable: {ready:?}"
5689        );
5690        assert!(
5691            ready.is_hup(),
5692            "the peer FIN must be reported as HUP: {ready:?}"
5693        );
5694        assert!(
5695            !ready.is_write_closed(),
5696            "EPOLLRDHUP leaves the response direction open: {ready:?}"
5697        );
5698        ready
5699    }
5700
5701    /// A real Linux `shutdown(SHUT_WR)` can report the final request bytes
5702    /// and `EPOLLRDHUP` in the same epoll event (`READABLE | HUP`, see
5703    /// `half_close_and_observe_frontend`). An `ExpectProxyProtocol` session
5704    /// must consume the complete PROXY-v2 header before acting on HUP,
5705    /// upgrade to `Pipe`, drain the payload already queued behind the
5706    /// header, and forward it to the backend (sozu-proxy/sozu#1823).
5707    ///
5708    /// To SEE THIS RED: delete the `ExpectProxyProtocol` drain that
5709    /// `TcpSession::ready_inner` runs before its frontend-HUP check. HUP is
5710    /// then handled first, `front_hup`'s wildcard arm closes the expect state
5711    /// before the already-buffered header is parsed, and the `!closed`
5712    /// assertion fails.
5713    #[cfg(target_os = "linux")]
5714    #[test]
5715    fn expect_proxy_half_close_drains_payload_to_the_backend() {
5716        use std::{io::Read as _, time::Duration};
5717
5718        let mut fixture = expect_proxy_fixture();
5719
5720        let payload = b"request-after-proxy-header";
5721        let mut wire = proxy_protocol_v2_ipv4_header().to_vec();
5722        wire.extend_from_slice(payload);
5723        let ready = half_close_and_observe_frontend(&mut fixture, &wire);
5724
5725        fixture
5726            .session
5727            .borrow_mut()
5728            .update_readiness(fixture.frontend_token, ready);
5729        let closed = fixture
5730            .session
5731            .borrow_mut()
5732            .ready(fixture.proxy_session.clone());
5733        assert!(
5734            !closed,
5735            "READABLE|HUP must parse the header and retain the response direction"
5736        );
5737        assert!(
5738            !matches!(
5739                fixture.session.borrow().state,
5740                TcpStateMachine::ExpectProxyProtocol(_)
5741            ),
5742            "the complete header must upgrade the expect state"
5743        );
5744
5745        let (mut backend, _) = fixture
5746            ._backend_listener
5747            .accept()
5748            .expect("the parsed header triggers the backend dial");
5749        backend
5750            .set_read_timeout(Some(Duration::from_secs(2)))
5751            .expect("set backend read timeout");
5752        backend
5753            .set_write_timeout(Some(Duration::from_secs(2)))
5754            .expect("set backend write timeout");
5755
5756        let backend_token = fixture
5757            .session
5758            .borrow()
5759            .backend_token
5760            .expect("the upgraded session owns a backend token");
5761        // The backend connects and the payload drained behind the header is
5762        // flushed to it. Whether the pipe then stays open for a response is
5763        // `Pipe::check_connections`' half-close policy (it keeps a
5764        // frontend-closed pipe only while request bytes are in flight), the
5765        // same for every TCP session with or without a PROXY header, so it is
5766        // deliberately not asserted here.
5767        fixture
5768            .session
5769            .borrow_mut()
5770            .update_readiness(backend_token, Ready::WRITABLE);
5771        let _ = fixture
5772            .session
5773            .borrow_mut()
5774            .ready(fixture.proxy_session.clone());
5775
5776        let mut backend_received = vec![0; payload.len()];
5777        backend
5778            .read_exact(&mut backend_received)
5779            .expect("backend receives the payload queued before FIN");
5780        assert_eq!(backend_received, payload);
5781    }
5782
5783    /// The negative space of the drain above: a bare TCP health check
5784    /// (connect, zero bytes, FIN) observed as `READABLE | HUP` must still
5785    /// close at once and never dial the backend. The drain reads EOF with
5786    /// nothing accumulated, and `ExpectProxyProtocol::readable`'s zero-byte
5787    /// `SocketResult::Closed` arm closes the session -- the same outcome as
5788    /// when the FIN arrives in its own event.
5789    #[cfg(target_os = "linux")]
5790    #[test]
5791    fn expect_proxy_zero_byte_half_close_closes_without_dialing() {
5792        let mut fixture = expect_proxy_fixture();
5793        let ready = half_close_and_observe_frontend(&mut fixture, &[]);
5794
5795        fixture
5796            .session
5797            .borrow_mut()
5798            .update_readiness(fixture.frontend_token, ready);
5799        let closed = fixture
5800            .session
5801            .borrow_mut()
5802            .ready(fixture.proxy_session.clone());
5803        assert!(
5804            closed,
5805            "a zero-byte client half-close must close the session"
5806        );
5807        let session = fixture.session.borrow();
5808        assert_eq!(
5809            session.back_connected(),
5810            BackendConnectionStatus::NotConnected,
5811            "a zero-byte health check must never dial the backend"
5812        );
5813        assert!(
5814            session.backend_token.is_none(),
5815            "a zero-byte health check must never wire a backend token"
5816        );
5817    }
5818
5819    /// A client that sends only part of a PROXY-v2 header and then FIN can
5820    /// never complete it: once the drain has consumed every readable byte,
5821    /// the frontend HUP must still close the session instead of leaving it
5822    /// to the frontend timeout, and no backend may be dialed.
5823    #[cfg(target_os = "linux")]
5824    #[test]
5825    fn expect_proxy_partial_header_half_close_closes_without_dialing() {
5826        let mut fixture = expect_proxy_fixture();
5827        let header = proxy_protocol_v2_ipv4_header();
5828        let ready = half_close_and_observe_frontend(&mut fixture, &header[..10]);
5829
5830        fixture
5831            .session
5832            .borrow_mut()
5833            .update_readiness(fixture.frontend_token, ready);
5834        let closed = fixture
5835            .session
5836            .borrow_mut()
5837            .ready(fixture.proxy_session.clone());
5838        assert!(
5839            closed,
5840            "an incomplete PROXY header followed by FIN must close the session"
5841        );
5842        let session = fixture.session.borrow();
5843        assert_eq!(
5844            session.back_connected(),
5845            BackendConnectionStatus::NotConnected,
5846            "an incomplete PROXY header must never dial the backend"
5847        );
5848    }
5849
5850    /// Structural safety for `ready_inner`'s `Connecting` branch: it reads
5851    /// the backend readiness, which `back_readiness` has none of for
5852    /// `ExpectProxyProtocol` (and `unreachable!`s on for `FailedUpgrade`).
5853    ///
5854    /// No input sequence reaches this pairing -- `connect_to_backend` is the
5855    /// only writer of `Connecting` and can no longer run in the expect state
5856    /// (see the test above), and no transition ever enters
5857    /// `ExpectProxyProtocol` from another state -- so the state is
5858    /// synthesized here directly. The branch must degrade to a logged close
5859    /// rather than take the whole worker down with it: a backend handshake
5860    /// with no readiness slot can never complete.
5861    ///
5862    /// To SEE THIS RED: restore either `self.back_readiness().unwrap()` in
5863    /// `ready_inner`'s `Connecting` branch -- the `None` this test installs
5864    /// then panics with "called `Option::unwrap()` on a `None` value".
5865    #[test]
5866    fn connecting_backend_without_readiness_closes_instead_of_panicking() {
5867        let fixture = expect_proxy_fixture();
5868        let (session, proxy_session) = (fixture.session.clone(), fixture.proxy_session.clone());
5869
5870        // The invariant break itself: `Connecting` while the state still has
5871        // no backend side.
5872        session.borrow_mut().backend_connected =
5873            BackendConnectionStatus::Connecting(Instant::now());
5874        assert!(
5875            session.borrow_mut().back_readiness().is_none(),
5876            "the expect state must have no backend readiness -- the premise of this test"
5877        );
5878
5879        let closed = session.borrow_mut().ready(proxy_session.clone());
5880        assert!(
5881            closed,
5882            "a connecting backend with no readiness slot must close the session"
5883        );
5884    }
5885
5886    /// Drive an `ExpectHeader` fixture through its PROXY header, which
5887    /// upgrades it to `Pipe` and dials the backend in that same `ready()`.
5888    fn expect_proxy_fixture_dialed() -> ExpectProxyFixture {
5889        use std::io::Write as _;
5890
5891        let mut fixture = expect_proxy_fixture();
5892        fixture
5893            .client
5894            .write_all(&proxy_protocol_v2_ipv4_header())
5895            .expect("write the PROXY-v2 header");
5896        fixture
5897            .session
5898            .borrow_mut()
5899            .update_readiness(fixture.frontend_token, Ready::READABLE);
5900        let proxy_session = fixture.proxy_session.clone();
5901        let _ = fixture.session.borrow_mut().ready(proxy_session);
5902        {
5903            let session = fixture.session.borrow();
5904            assert!(
5905                matches!(session.state, TcpStateMachine::Pipe(_)),
5906                "precondition: the parsed header upgrades the session to Pipe"
5907            );
5908            assert!(
5909                session.backend_token.is_some(),
5910                "precondition: the Pipe dialed its backend"
5911            );
5912        }
5913        fixture
5914    }
5915
5916    /// The `Pipe`'s access log reports the address `connect_to_backend`
5917    /// dialed, handed over by `TcpSession::set_backend_address`, rather than
5918    /// a `getpeername(2)` on the backend socket.
5919    ///
5920    /// To SEE THIS RED: remove the `self.set_backend_address(...)` call from
5921    /// `connect_to_backend`. The pipe then reports `None`.
5922    #[test]
5923    fn a_dialed_pipe_reports_the_backend_address_it_was_given() {
5924        let fixture = expect_proxy_fixture_dialed();
5925        let backend_address = fixture
5926            ._backend_listener
5927            .local_addr()
5928            .expect("test backend local addr");
5929        let session = fixture.session.borrow();
5930        let TcpStateMachine::Pipe(pipe) = &session.state else {
5931            unreachable!("expect_proxy_fixture_dialed checked the Pipe state");
5932        };
5933        assert_eq!(
5934            pipe.get_backend_address(),
5935            Some(backend_address),
5936            "the pipe must report the backend address the session dialed"
5937        );
5938    }
5939
5940    /// Closing a TCP session issues no `EPOLL_CTL_DEL` for its front or
5941    /// back socket: the last `close(2)` of each, when the server drops the
5942    /// session right after `close()`, takes it out of the epoll set.
5943    ///
5944    /// The test reads the kernel's own epoll table (`/proc/self/fdinfo`), so
5945    /// it tells "never deregistered" from "removed by the close".
5946    ///
5947    /// To SEE THIS RED: put back
5948    /// `let _ = self.proxy.borrow().registry.deregister(&mut mio::unix::SourceFd(&front_socket.as_raw_fd()));`
5949    /// in `TcpSession::close` (or the matching backend call in
5950    /// `close_backend`). The first assertion after `close()` then fails.
5951    #[cfg(target_os = "linux")]
5952    #[test]
5953    fn closed_tcp_sessions_leave_their_sockets_to_close() {
5954        use std::os::fd::AsRawFd as _;
5955
5956        use crate::protocol::mux::test_support::epoll_watches;
5957
5958        let fixture = expect_proxy_fixture_dialed();
5959        let session = fixture.session.clone();
5960        let frontend_token = fixture.frontend_token;
5961        let registry = session
5962            .borrow()
5963            .proxy
5964            .borrow()
5965            .registry
5966            .try_clone()
5967            .expect("the test registry must clone");
5968
5969        // `create_session` registers the front socket; the fixture builds the
5970        // session directly, so register it the same way here.
5971        let (front_fd, back_fd) = {
5972            let mut session = session.borrow_mut();
5973            let front_fd = session.state.front_socket().as_raw_fd();
5974            registry
5975                .register(
5976                    &mut mio::unix::SourceFd(&front_fd),
5977                    frontend_token,
5978                    Interest::READABLE | Interest::WRITABLE,
5979                )
5980                .expect("register the front socket");
5981            let back_fd = session
5982                .back_socket_mut()
5983                .expect("the dialed Pipe owns a backend socket")
5984                .as_raw_fd();
5985            (front_fd, back_fd)
5986        };
5987        assert!(
5988            epoll_watches(&registry, front_fd) && epoll_watches(&registry, back_fd),
5989            "precondition: both sockets are in the epoll set"
5990        );
5991
5992        // What `Server::shut_down_sessions_by_frontend_tokens` does.
5993        let removed = session
5994            .borrow()
5995            .proxy
5996            .borrow()
5997            .sessions
5998            .borrow_mut()
5999            .slab
6000            .remove(frontend_token.0);
6001        session.borrow_mut().close();
6002        assert!(
6003            epoll_watches(&registry, front_fd),
6004            "TcpSession::close must not deregister a front socket it is about \
6005             to close: the close removes it, the EPOLL_CTL_DEL is a wasted syscall"
6006        );
6007        assert!(
6008            epoll_watches(&registry, back_fd),
6009            "TcpSession::close_backend must not deregister a backend socket it \
6010             is about to close: the close removes it"
6011        );
6012
6013        drop(removed);
6014        drop(session);
6015        drop(fixture);
6016        assert!(
6017            !epoll_watches(&registry, front_fd),
6018            "dropping the session closes the front socket's only descriptor, \
6019             which must take it out of the epoll set"
6020        );
6021        assert!(
6022            !epoll_watches(&registry, back_fd),
6023            "dropping the session closes the backend socket's only descriptor, \
6024             which must take it out of the epoll set"
6025        );
6026    }
6027
6028    /// A session dialed from a state that is not `Pipe` yet, plus everything
6029    /// that must outlive it.
6030    struct DialFixture {
6031        session: Rc<RefCell<TcpSession>>,
6032        proxy_session: Rc<RefCell<dyn ProxySession>>,
6033        frontend_token: Token,
6034        backend_address: SocketAddr,
6035        client: std::net::TcpStream,
6036        _frontend_listener: std::net::TcpListener,
6037        _backend_listener: std::net::TcpListener,
6038    }
6039
6040    /// Build a session for cluster `cluster-dial`, wired to a real bound
6041    /// backend and a real accepted frontend socket, registered in the slab the
6042    /// way `expect_proxy_fixture` does. `build` picks the constructor, hence
6043    /// the starting state; `sni_route` routes `example.com` to the cluster.
6044    fn dial_fixture(
6045        sni_route: bool,
6046        build: impl FnOnce(
6047            Checkout,
6048            Checkout,
6049            Token,
6050            Rc<RefCell<TcpListener>>,
6051            Rc<RefCell<TcpProxy>>,
6052            MioTcpStream,
6053            SocketAddr,
6054        ) -> TcpSession,
6055    ) -> DialFixture {
6056        let ServerParts {
6057            registry,
6058            sessions,
6059            pool,
6060            backends,
6061            ..
6062        } = prebuild_server(16, 16384, false).expect("could not prebuild a test server");
6063
6064        let backend_listener =
6065            std::net::TcpListener::bind("127.0.0.1:0").expect("bind test backend");
6066        let backend_address = backend_listener
6067            .local_addr()
6068            .expect("test backend local addr");
6069        backends.borrow_mut().add_backend(
6070            "cluster-dial",
6071            Backend::new("cluster-dial-1", backend_address, None, None, None),
6072        );
6073
6074        let proxy = Rc::new(RefCell::new(TcpProxy::new(
6075            registry,
6076            sessions.clone(),
6077            pool.clone(),
6078            backends,
6079        )));
6080        let mut bare_listener = test_listener();
6081        if sni_route {
6082            bare_listener
6083                .insert_sni_route("example.com".to_owned(), vec![], "cluster-dial".into())
6084                .expect("insert_sni_route must succeed for a valid test SNI");
6085        }
6086        let listener = Rc::new(RefCell::new(bare_listener));
6087
6088        let frontend_listener =
6089            std::net::TcpListener::bind("127.0.0.1:0").expect("bind test frontend listener");
6090        let client = std::net::TcpStream::connect(
6091            frontend_listener
6092                .local_addr()
6093                .expect("test frontend local addr"),
6094        )
6095        .expect("connect test client");
6096        let (frontend, frontend_peer) = frontend_listener
6097            .accept()
6098            .expect("accept the client connection");
6099        frontend
6100            .set_nonblocking(true)
6101            .expect("frontend socket nonblocking");
6102
6103        let (frontend_buffer, backend_buffer) = {
6104            let mut pool = pool.borrow_mut();
6105            (
6106                pool.checkout().expect("front buffer checkout must succeed"),
6107                pool.checkout().expect("back buffer checkout must succeed"),
6108            )
6109        };
6110
6111        let frontend_token = {
6112            let mut session_manager = sessions.borrow_mut();
6113            let entry = session_manager.slab.vacant_entry();
6114            let token = Token(entry.key());
6115            entry.insert(Rc::new(RefCell::new(crate::server::ListenSession {
6116                protocol: Protocol::TCPListen,
6117            })));
6118            token
6119        };
6120
6121        let session = Rc::new(RefCell::new(build(
6122            backend_buffer,
6123            frontend_buffer,
6124            frontend_token,
6125            listener,
6126            proxy,
6127            MioTcpStream::from_std(frontend),
6128            frontend_peer,
6129        )));
6130        let proxy_session: Rc<RefCell<dyn ProxySession>> = session.clone();
6131        sessions.borrow_mut().slab[frontend_token.0] = proxy_session.clone();
6132
6133        DialFixture {
6134            session,
6135            proxy_session,
6136            frontend_token,
6137            backend_address,
6138            client,
6139            _frontend_listener: frontend_listener,
6140            _backend_listener: backend_listener,
6141        }
6142    }
6143
6144    /// A legacy (non-SNI) session whose cluster uses `proxy_protocol`; its
6145    /// first `ready()` dials the backend while it is still in its
6146    /// PROXY-protocol state.
6147    fn proxy_protocol_session_dialed(proxy_protocol: ProxyProtocolConfig) -> DialFixture {
6148        let fixture = dial_fixture(
6149            false,
6150            |back_buffer, front_buffer, token, listener, proxy, socket, peer| {
6151                TcpSession::new(
6152                    back_buffer,
6153                    None,
6154                    Some("cluster-dial".into()),
6155                    Duration::from_secs(30),
6156                    Duration::from_secs(30),
6157                    Duration::from_secs(30),
6158                    front_buffer,
6159                    token,
6160                    listener,
6161                    Some(proxy_protocol),
6162                    proxy,
6163                    socket,
6164                    peer,
6165                    Duration::from_millis(0),
6166                )
6167            },
6168        );
6169        fixture
6170            .session
6171            .borrow_mut()
6172            .update_readiness(fixture.frontend_token, Ready::WRITABLE);
6173        let closed = fixture
6174            .session
6175            .borrow_mut()
6176            .ready(fixture.proxy_session.clone());
6177        assert!(!closed, "precondition: the dial keeps the session open");
6178        fixture
6179    }
6180
6181    #[test]
6182    fn static_tcp_route_keeps_its_capture_after_cluster_replacement() {
6183        let fixture = dial_fixture(
6184            false,
6185            |back_buffer, front_buffer, token, listener, proxy, socket, peer| {
6186                TcpSession::new(
6187                    back_buffer,
6188                    None,
6189                    Some("cluster-dial".into()),
6190                    Duration::from_secs(30),
6191                    Duration::from_secs(30),
6192                    Duration::from_secs(30),
6193                    front_buffer,
6194                    token,
6195                    listener,
6196                    None,
6197                    proxy,
6198                    socket,
6199                    peer,
6200                    Duration::from_millis(0),
6201                )
6202            },
6203        );
6204        let initial_capture = fixture.session.borrow().cluster_metrics_incarnation;
6205        assert!(
6206            matches!(
6207                initial_capture,
6208                crate::metrics::ClusterMetricsCapture::Active(_)
6209            ),
6210            "precondition: the static route captured an implicit active identity",
6211        );
6212
6213        crate::metrics::METRICS.with(|metrics| {
6214            let mut metrics = metrics.borrow_mut();
6215            metrics.remove_cluster("cluster-dial");
6216            metrics.add_cluster("cluster-dial");
6217            let replacement = metrics
6218                .cluster_incarnation("cluster-dial")
6219                .expect("replacement cluster identity");
6220            assert_ne!(
6221                initial_capture,
6222                crate::metrics::ClusterMetricsCapture::Active(replacement),
6223            );
6224        });
6225        fixture
6226            .session
6227            .borrow_mut()
6228            .update_readiness(fixture.frontend_token, Ready::WRITABLE);
6229        let closed = fixture
6230            .session
6231            .borrow_mut()
6232            .ready(fixture.proxy_session.clone());
6233        assert!(!closed, "the backend dial must keep the session open");
6234        assert_eq!(
6235            fixture.session.borrow().cluster_metrics_incarnation,
6236            initial_capture,
6237            "a retry after replacement must keep the identity captured at static routing time",
6238        );
6239        crate::metrics::METRICS.with(|metrics| metrics.borrow_mut().remove_cluster("cluster-dial"));
6240    }
6241
6242    #[test]
6243    fn sni_tcp_route_keeps_its_capture_after_cluster_replacement() {
6244        use std::io::Write as _;
6245
6246        let mut fixture = dial_fixture(
6247            true,
6248            |back_buffer, front_buffer, token, listener, proxy, socket, peer| {
6249                TcpSession::new_sni_preread(
6250                    back_buffer,
6251                    Duration::from_secs(30),
6252                    Duration::from_secs(30),
6253                    front_buffer,
6254                    token,
6255                    listener,
6256                    proxy,
6257                    socket,
6258                    peer,
6259                    Duration::from_millis(0),
6260                    Duration::from_secs(3),
6261                    16384,
6262                )
6263            },
6264        );
6265        fixture
6266            .client
6267            .write_all(&minimal_client_hello_wire("example.com"))
6268            .expect("write ClientHello");
6269        fixture.client.flush().ok();
6270        for _ in 0..10 {
6271            if fixture.session.borrow().cluster_id.is_some() {
6272                break;
6273            }
6274            let _ = fixture.session.borrow_mut().readable();
6275        }
6276        assert_eq!(
6277            fixture.session.borrow().cluster_id.as_deref(),
6278            Some("cluster-dial")
6279        );
6280        let initial_capture = fixture.session.borrow().cluster_metrics_incarnation;
6281        assert!(
6282            matches!(
6283                initial_capture,
6284                crate::metrics::ClusterMetricsCapture::Active(_)
6285            ),
6286            "precondition: SNI routing captured an implicit active identity",
6287        );
6288
6289        crate::metrics::METRICS.with(|metrics| {
6290            let mut metrics = metrics.borrow_mut();
6291            metrics.remove_cluster("cluster-dial");
6292            metrics.add_cluster("cluster-dial");
6293            let replacement = metrics
6294                .cluster_incarnation("cluster-dial")
6295                .expect("replacement cluster identity");
6296            assert_ne!(
6297                initial_capture,
6298                crate::metrics::ClusterMetricsCapture::Active(replacement),
6299            );
6300        });
6301        let closed = fixture
6302            .session
6303            .borrow_mut()
6304            .ready(fixture.proxy_session.clone());
6305        assert!(
6306            !closed,
6307            "the SNI-routed backend dial must keep the session open"
6308        );
6309        assert_eq!(
6310            fixture.session.borrow().cluster_metrics_incarnation,
6311            initial_capture,
6312            "SNI upgrade and retry after replacement must keep the route-time identity",
6313        );
6314        crate::metrics::METRICS.with(|metrics| metrics.borrow_mut().remove_cluster("cluster-dial"));
6315    }
6316
6317    /// Upgrade a dialed, not-yet-`Pipe` session and assert the `Pipe` reports
6318    /// the backend address the session dialed: the address its access log
6319    /// carries.
6320    fn assert_upgraded_pipe_reports_the_dialed_address(fixture: &DialFixture) {
6321        {
6322            let session = fixture.session.borrow();
6323            assert!(
6324                !matches!(session.state, TcpStateMachine::Pipe(_)),
6325                "precondition: the backend was dialed before the Pipe existed"
6326            );
6327            assert!(
6328                session.backend_token.is_some(),
6329                "precondition: the session dialed its backend"
6330            );
6331        }
6332        let closed = fixture.session.borrow_mut().upgrade();
6333        assert!(!closed, "the upgrade to Pipe must succeed");
6334        let session = fixture.session.borrow();
6335        let TcpStateMachine::Pipe(pipe) = &session.state else {
6336            panic!("the upgrade must install a Pipe");
6337        };
6338        assert_eq!(
6339            pipe.get_backend_address(),
6340            Some(fixture.backend_address),
6341            "the Pipe must report the backend address dialed before the upgrade"
6342        );
6343    }
6344
6345    /// `SendProxyProtocol` dials before it becomes a `Pipe`: `upgrade_send`
6346    /// must hand the dialed address over.
6347    ///
6348    /// To SEE THIS RED: make `TcpSession::set_backend_address` record nothing,
6349    /// or pass `None` to `pipe.set_backend_address` in `upgrade_send`.
6350    #[test]
6351    fn a_send_proxy_protocol_pipe_reports_the_dialed_backend_address() {
6352        let fixture = proxy_protocol_session_dialed(ProxyProtocolConfig::SendHeader);
6353        assert!(
6354            matches!(
6355                fixture.session.borrow().state,
6356                TcpStateMachine::SendProxyProtocol(_)
6357            ),
6358            "precondition: a SendHeader cluster starts in the send state"
6359        );
6360        assert_upgraded_pipe_reports_the_dialed_address(&fixture);
6361    }
6362
6363    /// `RelayProxyProtocol` dials before it becomes a `Pipe`: `upgrade_relay`
6364    /// must hand the dialed address over.
6365    ///
6366    /// To SEE THIS RED: make `TcpSession::set_backend_address` record nothing,
6367    /// or pass `None` to `pipe.set_backend_address` in `upgrade_relay`.
6368    #[test]
6369    fn a_relay_proxy_protocol_pipe_reports_the_dialed_backend_address() {
6370        let fixture = proxy_protocol_session_dialed(ProxyProtocolConfig::RelayHeader);
6371        assert!(
6372            matches!(
6373                fixture.session.borrow().state,
6374                TcpStateMachine::RelayProxyProtocol(_)
6375            ),
6376            "precondition: a RelayHeader cluster starts in the relay state"
6377        );
6378        assert_upgraded_pipe_reports_the_dialed_address(&fixture);
6379    }
6380
6381    /// An SNI-routed session dials from `SniPreread`: `upgrade_sni_preread`
6382    /// builds the `Pipe` through `build_pipe_from_preread`, which must hand
6383    /// the dialed address over.
6384    ///
6385    /// To SEE THIS RED: make `TcpSession::set_backend_address` record nothing,
6386    /// or pass `None` to `pipe.set_backend_address` in
6387    /// `build_pipe_from_preread`.
6388    #[test]
6389    fn an_sni_preread_pipe_reports_the_dialed_backend_address() {
6390        use std::io::Write as _;
6391
6392        let mut fixture = dial_fixture(
6393            true,
6394            |back_buffer, front_buffer, token, listener, proxy, socket, peer| {
6395                TcpSession::new_sni_preread(
6396                    back_buffer,
6397                    Duration::from_secs(30),
6398                    Duration::from_secs(30),
6399                    front_buffer,
6400                    token,
6401                    listener,
6402                    proxy,
6403                    socket,
6404                    peer,
6405                    Duration::from_millis(0),
6406                    Duration::from_secs(3),
6407                    16384,
6408                )
6409            },
6410        );
6411        fixture
6412            .client
6413            .write_all(&minimal_client_hello_wire("example.com"))
6414            .expect("write ClientHello");
6415        fixture.client.flush().ok();
6416        for _ in 0..10 {
6417            if fixture.session.borrow().cluster_id.is_some() {
6418                break;
6419            }
6420            let _ = fixture.session.borrow_mut().readable();
6421        }
6422        assert_eq!(
6423            fixture.session.borrow().cluster_id.as_deref(),
6424            Some("cluster-dial"),
6425            "precondition: the ClientHello routes the session"
6426        );
6427        let closed = fixture
6428            .session
6429            .borrow_mut()
6430            .ready(fixture.proxy_session.clone());
6431        assert!(!closed, "precondition: the dial keeps the session open");
6432        assert!(
6433            matches!(
6434                fixture.session.borrow().state,
6435                TcpStateMachine::SniPreread(_)
6436            ),
6437            "precondition: the routed session dials while still in SniPreread"
6438        );
6439        assert_upgraded_pipe_reports_the_dialed_address(&fixture);
6440    }
6441}
6442
6443/// Coverage for the `RelayProxyProtocol` -> `Pipe` handoff. It lives here
6444/// rather than in `relay.rs`'s own `relay_test` because `into_pipe` demands an
6445/// `Rc<RefCell<TcpListener>>` and every field of that struct — plus its
6446/// constructor — is private to this module, so the relay's test module cannot
6447/// build one. A separate module (rather than the `tests` module above) avoids
6448/// that module's `use std::net::TcpListener` shadowing `super::TcpListener`.
6449#[cfg(test)]
6450mod relay_upgrade_tests {
6451    use std::net::TcpListener as StdTcpListener;
6452
6453    use sozu_command::{config::ListenerBuilder, proto::command::SocketAddress};
6454
6455    use super::*;
6456    use crate::{pool::Pool, testing::provide_port};
6457
6458    fn test_listener() -> TcpListener {
6459        let config = ListenerBuilder::new_tcp(SocketAddress::new_v4(127, 0, 0, 1, provide_port()))
6460            .to_tcp(None)
6461            .expect("could not build a TcpListenerConfig for the test");
6462        TcpListener::new(config, Token(0)).expect("could not build a bare TcpListener for the test")
6463    }
6464
6465    fn connected_pair() -> (std::net::TcpStream, std::net::TcpStream) {
6466        let listener = StdTcpListener::bind("127.0.0.1:0").expect("bind the test listener");
6467        let address = listener
6468            .local_addr()
6469            .expect("the test listener must expose its address");
6470        let local = std::net::TcpStream::connect(address).expect("connect the test pair");
6471        let (peer, _addr) = listener.accept().expect("accept the test pair");
6472        local
6473            .set_nonblocking(true)
6474            .expect("the local end must be non-blocking");
6475        peer.set_nonblocking(true)
6476            .expect("the peer end must be non-blocking");
6477        (local, peer)
6478    }
6479
6480    /// `Pipe::new` ends with `arm_inherited_buffer_writes`, which arms the
6481    /// backend WRITABLE readiness when the inherited `frontend_buffer` is
6482    /// non-empty. `build_pipe_from_preread` carries a comment stating outright
6483    /// that a bare `pipe.backend_readiness.event = ..` pair must NOT be used
6484    /// in its place, because the drain of that buffer must not depend on the
6485    /// restored event carrying WRITABLE itself — and `upgrade_send` re-runs the
6486    /// arm for the same reason (sozu-proxy/sozu#1279's close-before-flush
6487    /// truncation). `RelayProxyProtocol::into_pipe` was the one pipe-entry path
6488    /// still using the bare pair. That was harmless only while the relay's
6489    /// buffer was always empty at upgrade time; now that the relay leaves
6490    /// pipelined client payload in it, this path joins the class those comments
6491    /// describe, and the surviving wake-up must be by construction rather than
6492    /// by the accident that `back_writable` happens to be entered with WRITABLE
6493    /// already set.
6494    ///
6495    /// To SEE THIS RED: in `relay.rs::into_pipe`, replace the
6496    /// `pipe.restore_readiness_events(..)` call with the bare pair
6497    /// `pipe.frontend_readiness.event = self.frontend_readiness.event;` /
6498    /// `pipe.backend_readiness.event = self.backend_readiness.event;`.
6499    #[test]
6500    fn into_pipe_rearms_the_backend_write_for_pipelined_payload() {
6501        let listener = Rc::new(RefCell::new(test_listener()));
6502        let (frontend, _front_peer) = connected_pair();
6503        let (backend, _backend_peer) = connected_pair();
6504
6505        let mut pool = Pool::with_capacity(2, 2, 4096);
6506        let mut frontend_buffer = pool.checkout().expect("frontend buffer");
6507        let backend_buffer = pool.checkout().expect("backend buffer");
6508        // What the relay leaves behind once the header is forwarded: the
6509        // payload the client pipelined into the same read.
6510        let payload = b"pipelined-payload";
6511        frontend_buffer.space()[..payload.len()].copy_from_slice(payload);
6512        frontend_buffer.fill(payload.len());
6513
6514        let mut relay = RelayProxyProtocol::new(
6515            MioTcpStream::from_std(frontend),
6516            Token(0),
6517            Ulid::generate(),
6518            Some(MioTcpStream::from_std(backend)),
6519            frontend_buffer,
6520        );
6521        // `into_pipe` never reads this, but a relay only ever upgrades with a
6522        // fully forwarded header, so leave the state coherent.
6523        relay.header_size = Some(28);
6524        relay.set_back_token(Token(1));
6525        // The case the `build_pipe_from_preread` comment names explicitly: the
6526        // readiness word restored from the previous state need not itself carry
6527        // WRITABLE, and the inherited drain must not depend on it.
6528        relay.backend_readiness.event = Ready::EMPTY;
6529        relay.frontend_readiness.event = Ready::EMPTY;
6530
6531        let pipe = relay.into_pipe(backend_buffer, listener);
6532
6533        assert!(
6534            pipe.backend_readiness.event.is_writable(),
6535            "a non-empty inherited frontend buffer must leave a backend WRITABLE event queued, or the pipelined payload is never flushed"
6536        );
6537        assert!(
6538            pipe.backend_readiness.interest.is_writable(),
6539            "the inherited-write arm must also hold the backend WRITABLE interest"
6540        );
6541    }
6542}