Skip to main content

sozu_lib/
lib.rs

1//! ## What this library does
2//!
3//! This library provides tools to build and start HTTP, HTTPS and TCP reverse proxies.
4//!
5//! The proxies handles network polling, HTTP parsing, TLS in a fast single threaded event
6//! loop.
7//!
8//! Each proxy is designed to receive configuration changes at runtime instead of
9//! reloading from a file regularly. The event loop runs in its own thread
10//! and receives commands through a message queue.
11//!
12//! ## Difference with the crate `sozu`
13//!
14//! To create several workers and manage them all at once (which is the most common way to
15//! use Sōzu), the crate `sozu` is more indicated than using the lib directly.
16//!
17//! The crate `sozu` provides a binary called the main process.
18//! The main process uses `sozu_lib` to start and manage workers.
19//! Each worker can handle HTTP, HTTPS and TCP traffic.
20//! The main process receives synchronizes the state of all workers, using UNIX sockets
21//! and custom channels to communicate with them.
22//! The main process itself is is configurable with a file, and has a CLI.
23//!
24//! ## How to use this library directly
25//!
26//! This documentation here explains how to write a binary that will start a single Sōzu
27//! worker and give it orders. The method has two steps:
28//!
29//! 1. Starts a Sōzu worker in a distinct thread
30//! 2. sends instructions to the worker on a UNIX socket via a Sōzu channel
31//!
32//! ### How to start a Sōzu worker
33//!
34//! Before creating an HTTP proxy, we first need to create an HTTP listener.
35//! The listener is an abstraction around a TCP socket provided by the kernel.
36//! We need the `sozu_command_lib` to build a listener.
37//!
38//! ```
39//! use sozu_command_lib::{config::ListenerBuilder, proto::command::SocketAddress};
40//!
41//! let address = SocketAddress::new_v4(127,0,0,1,8080);
42//! let http_listener = ListenerBuilder::new_http(address)
43//!     .to_http(None)
44//!     .expect("Could not create HTTP listener");
45//! ```
46//!
47//! The `http_listener` is of the type `HttpListenerConfig`, that we can be sent to the worker
48//! to start the proxy.
49//!
50//! Then create a pair of channels to communicate with the proxy.
51//! The channel is a wrapper around a unix socket.
52//!
53//! ```ignore
54//! use sozu_command_lib::{
55//!     channel::Channel,
56//!     proto::command::{WorkerRequest, WorkerResponse},
57//! };
58//!
59//! let (mut command_channel, proxy_channel): (
60//!     Channel<WorkerRequest, WorkerResponse>,
61//!     Channel<WorkerResponse, WorkerRequest>,
62//! ) = Channel::generate(1000, 10000).expect("should create a channel");
63//!```
64//!
65//! Here, the `command_channel` end is blocking, it sends `WorkerRequest`s and receives
66//! `WorkerResponses`, while the `proxy_channel` end is non-blocking, and the types are reversed.
67//! Writing the types here isn't even necessary thanks to the compiler,
68//! but it brings the point accross.
69//!
70//! You can now launch the worker in a separate thread, providing the HTTP listener config,
71//! the proxy end of the channel, and your custom number of buffers and their size:
72//!
73//! ```ignore
74//! use std::thread;
75//!
76//! let worker_thread_join_handle = thread::spawn(move || {
77//!     let max_buffers = 500;
78//!     let buffer_size = 16384;
79//!     sozu_lib::http::testing::start_http_worker(http_listener, proxy_channel, max_buffers, buffer_size);
80//! });
81//! ```
82//!
83//! ### Send orders
84//!
85//! Once the thread is launched, the proxy worker will start its event loop and handle
86//! events on the listening interface and port specified when building the HTTP Listener.
87//! Since no frontends or backends were specified for the proxy, it will receive
88//! the connections, parse the requests, then send a default (but configurable)
89//! answer.
90//!
91//! Before defining a frontend and backends, we need to define a cluster, which describes
92//! a routing configuration. A cluster contains:
93//!
94//! - one frontend
95//! - one or several backends
96//! - routing rules
97//!
98//! A cluster is identified by its `cluster_id`, which will be used to define frontends
99//! and backends later on.
100//!
101//! ```
102//! use sozu_command_lib::proto::command::{Cluster, LoadBalancingAlgorithms};
103//!
104//! let cluster = Cluster {
105//!     cluster_id: "my-cluster".to_string(),
106//!     sticky_session: false,
107//!     https_redirect: false,
108//!     load_balancing: LoadBalancingAlgorithms::RoundRobin as i32,
109//!     answer_503: Some("A custom forbidden message".to_string()),
110//!     ..Default::default()
111//! };
112//! ```
113//!
114//! The defaults are sensible, so we could define only the `cluster_id`.
115//!
116//! We can now define a frontend. A frontend is a way to recognize a request and match
117//! it to a `cluster_id`, depending on the hostname and the beginning of the URL path.
118//! The `address` field must match the one of the HTTP listener we defined before:
119//!
120//! ```
121//! use std::collections::BTreeMap;
122//!
123//!  use sozu_command_lib::proto::command::{PathRule, RequestHttpFrontend, RulePosition, SocketAddress};
124//!
125//! let http_front = RequestHttpFrontend {
126//!     cluster_id: Some("my-cluster".to_string()),
127//!     address: SocketAddress::new_v4(127,0,0,1,8080),
128//!     hostname: "example.com".to_string(),
129//!     path: PathRule::prefix(String::from("/")),
130//!     position: RulePosition::Pre.into(),
131//!     tags: BTreeMap::from([
132//!         ("owner".to_owned(), "John".to_owned()),
133//!         ("id".to_owned(), "my-own-http-front".to_owned()),
134//!     ]),
135//!     ..Default::default()
136//! };
137//! ```
138//!
139//! The `tags` are keys and values that will appear in the access logs,
140//! which can come in handy.
141//!
142//! Now let's define a backend.
143//! A backend is an instance of a backend application we want to route traffic to.
144//! The `address` field must match the IP and port of the backend server.
145//!
146//! ```
147//! use sozu_command_lib::proto::command::{AddBackend, LoadBalancingParams, SocketAddress};
148//!
149//! let http_backend = AddBackend {
150//!     cluster_id: "my-cluster".to_string(),
151//!     backend_id: "test-backend".to_string(),
152//!     address: SocketAddress::new_v4(127,0,0,1,8000),
153//!     load_balancing_parameters: Some(LoadBalancingParams::default()),
154//!     ..Default::default()
155//! };
156//! ```
157//!
158//! A cluster can have multiple backend servers, and they can be added or
159//! removed while the proxy is running. If a backend is removed from the configuration
160//! while the proxy is handling a request to that server, it will finish that
161//! request and stop sending new traffic to that server.
162//!
163//!
164//! Now we can use the other end of the channel to send all these requests to the worker,
165//! using the WorkerRequest type:
166//!
167//! ```ignore
168//! use sozu_command_lib::{
169//!     proto::command::{Request, request::RequestType, WorkerRequest},
170//! };
171//!
172//! command_channel
173//!     .write_message(&WorkerRequest {
174//!         id: String::from("add-the-cluster"),
175//!         content: RequestType::AddCluster(cluster).into(),
176//!     })
177//!     .expect("Could not send AddHttpFrontend request");
178//!
179//! command_channel
180//!     .write_message(&WorkerRequest {
181//!         id: String::from("add-the-frontend"),
182//!         content: RequestType::AddHttpFrontend(http_front).into(),
183//!     })
184//!     .expect("Could not send AddHttpFrontend request");
185//!
186//! command_channel
187//!     .write_message(&WorkerRequest {
188//!         id: String::from("add-the-backend"),
189//!         content: RequestType::AddBackend(http_backend).into(),
190//!     })
191//!     .expect("Could not send AddBackend request");
192//!
193//! println!("HTTP -> {:?}", command_channel.read_message());
194//! println!("HTTP -> {:?}", command_channel.read_message());
195//! println!("HTTP -> {:?}", command_channel.read_message());
196//! ```
197//!
198//!
199//! The event loop of the worker will process these instructions and add them to
200//! its state, and the worker will send back an acknowledgement
201//! message.
202//!
203//! Now we can let the worker thread run in the background:
204//!
205//! ```ignore
206//! let _ = worker_thread_join_handle.join();
207//! ```
208//!
209//! Here is the complete example for reference, it matches the `examples/http.rs` example:
210//!
211//! ```
212//! #[macro_use]
213//! extern crate sozu_command_lib;
214//!
215//! use std::{collections::BTreeMap, env, io::stdout, thread};
216//!
217//! use anyhow::Context;
218//! use sozu_command_lib::{
219//!     channel::Channel,
220//!     config::ListenerBuilder,
221//!     logging::setup_default_logging,
222//!     proto::command::{
223//!         request::RequestType, AddBackend, Cluster, LoadBalancingAlgorithms, LoadBalancingParams,
224//!         PathRule, Request, RequestHttpFrontend, RulePosition, SocketAddress,WorkerRequest,
225//!     },
226//! };
227//!
228//! fn main() -> anyhow::Result<()> {
229//!     setup_default_logging(true, "info", "EXAMPLE").with_context(|| "could not setup logging")?;
230//!
231//!     info!("starting up");
232//!
233//!     let http_listener = ListenerBuilder::new_http(SocketAddress::new_v4(127,0,0,1,8080))
234//!         .to_http(None)
235//!         .expect("Could not create HTTP listener");
236//!
237//!     let (mut command_channel, proxy_channel) =
238//!         Channel::generate(1000, 10000).with_context(|| "should create a channel")?;
239//!
240//!     let worker_thread_join_handle = thread::spawn(move || {
241//!         let max_buffers = 500;
242//!         let buffer_size = 16384;
243//!         sozu_lib::http::testing::start_http_worker(http_listener, proxy_channel, max_buffers, buffer_size)
244//!             .expect("The worker could not be started, or shut down");
245//!     });
246//!
247//!     let cluster = Cluster {
248//!         cluster_id: "my-cluster".to_string(),
249//!         sticky_session: false,
250//!         https_redirect: false,
251//!         load_balancing: LoadBalancingAlgorithms::RoundRobin as i32,
252//!         answer_503: Some("A custom forbidden message".to_string()),
253//!         ..Default::default()
254//!     };
255//!
256//!     let http_front = RequestHttpFrontend {
257//!         cluster_id: Some("my-cluster".to_string()),
258//!         address: SocketAddress::new_v4(127,0,0,1,8080),
259//!         hostname: "example.com".to_string(),
260//!         path: PathRule::prefix(String::from("/")),
261//!         position: RulePosition::Pre.into(),
262//!         tags: BTreeMap::from([
263//!             ("owner".to_owned(), "John".to_owned()),
264//!             ("id".to_owned(), "my-own-http-front".to_owned()),
265//!         ]),
266//!         ..Default::default()
267//!     };
268//!     let http_backend = AddBackend {
269//!         cluster_id: "my-cluster".to_string(),
270//!         backend_id: "test-backend".to_string(),
271//!         address: SocketAddress::new_v4(127,0,0,1,8000),
272//!         load_balancing_parameters: Some(LoadBalancingParams::default()),
273//!         ..Default::default()
274//!     };
275//!
276//!     command_channel
277//!         .write_message(&WorkerRequest {
278//!             id: String::from("add-the-cluster"),
279//!             content: RequestType::AddCluster(cluster).into(),
280//!         })
281//!         .expect("Could not send AddHttpFrontend request");
282//!
283//!     command_channel
284//!         .write_message(&WorkerRequest {
285//!             id: String::from("add-the-frontend"),
286//!             content: RequestType::AddHttpFrontend(http_front).into(),
287//!         })
288//!         .expect("Could not send AddHttpFrontend request");
289//!
290//!     command_channel
291//!         .write_message(&WorkerRequest {
292//!             id: String::from("add-the-backend"),
293//!             content: RequestType::AddBackend(http_backend).into(),
294//!         })
295//!         .expect("Could not send AddBackend request");
296//!
297//!     println!("HTTP -> {:?}", command_channel.read_message());
298//!     println!("HTTP -> {:?}", command_channel.read_message());
299//!
300//!     // uncomment to let it run in the background
301//!     // let _ = worker_thread_join_handle.join();
302//!     info!("good bye");
303//!     Ok(())
304//! }
305//! ```
306
307#[macro_use]
308extern crate sozu_command_lib as sozu_command;
309
310#[macro_use]
311pub mod util;
312#[macro_use]
313pub mod metrics;
314
315pub mod backends;
316pub mod crypto;
317pub mod features;
318pub mod health_check;
319pub mod http;
320pub mod load_balancing;
321pub mod pool;
322pub mod protocol;
323pub mod retry;
324pub mod router;
325pub mod socket;
326pub mod timer;
327pub mod tls;
328
329/// Linux zero-copy TCP forwarder. Used by `protocol::pipe::Pipe` when
330/// the listener is `Protocol::TCP` and the `splice` feature is enabled.
331#[cfg(all(target_os = "linux", feature = "splice"))]
332pub(crate) mod splice;
333
334pub mod server;
335pub mod tcp;
336pub mod udp;
337
338pub mod https;
339
340use std::{
341    cell::RefCell,
342    collections::{BTreeMap, HashMap},
343    fmt::{self, Display, Formatter},
344    net::SocketAddr,
345    rc::Rc,
346    str,
347    time::{Duration, Instant, SystemTime},
348};
349
350use backends::BackendError;
351use hex::FromHexError;
352use mio::{Interest, Token, net::TcpStream};
353use protocol::http::{answers::HttpAnswers, answers::TemplateError, parser::Method};
354use router::RouterError;
355use socket::ServerBindError;
356use sozu_command::{
357    AsStr, ObjectKind,
358    logging::{CachedTags, LogContext},
359    proto::command::{
360        Cluster, ForwardedHeaders, ListenerType, RequestHttpFrontend, WorkerRequest, WorkerResponse,
361    },
362    ready::Ready,
363    state::ClusterId,
364};
365use tls::CertificateResolverError;
366
367use crate::{metrics::names, router::RouteResult};
368
369/// Anything that can be registered in mio (subscribe to kernel events)
370#[derive(Debug, Clone, Copy, PartialEq, Eq)]
371pub enum Protocol {
372    HTTP,
373    HTTPS,
374    TCP,
375    UDP,
376    HTTPListen,
377    HTTPSListen,
378    TCPListen,
379    UDPListen,
380    Channel,
381    Metrics,
382    Timer,
383}
384
385/// trait that must be implemented by listeners and client sessions
386pub trait ProxySession {
387    /// indicates the protocol associated with the session
388    ///
389    /// this is used to distinguish sessions from listenrs, channels, metrics
390    /// and timers
391    fn protocol(&self) -> Protocol;
392    /// if a session received an event or can still execute, the event loop will
393    /// call this method. Its result indicates if it can still execute, needs to
394    /// connect to a backend server, close the session
395    fn ready(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed;
396    /// if the event loop got an event for a token associated with the session,
397    /// it will call this method on the session
398    fn update_readiness(&mut self, token: Token, events: Ready);
399    /// close a session, frontend and backend sockets,
400    /// remove the entries from the session manager slab
401    fn close(&mut self);
402    /// if a timeout associated with the session triggers, the event loop will
403    /// call this method with the timeout's token
404    fn timeout(&mut self, t: Token) -> SessionIsToBeClosed;
405    /// last time the session got an event
406    fn last_event(&self) -> Instant;
407    /// display the session's internal state (for debugging purpose)
408    fn print_session(&self);
409    /// get the token associated with the frontend
410    fn frontend_token(&self) -> Token;
411    /// tell the session it has to shut down if possible
412    ///
413    /// if the session handles HTTP requests, it will not close until the response
414    /// is completely sent back to the client
415    fn shutting_down(&mut self) -> SessionIsToBeClosed;
416    /// Best-effort identifier of the cluster currently routed to by this
417    /// session. Returns `None` for `ListenSession` (no per-session
418    /// cluster), and for client sessions before routing has resolved.
419    /// H2 sessions multiplex many streams over one frontend token and may
420    /// touch several clusters; the returned value is whichever cluster
421    /// the session most recently keep-alive'd to. Used for log/metric
422    /// attribution, not for accounting (the tracker keeps the canonical
423    /// per-stream `(cluster, IP)` set).
424    fn cluster_id(&self) -> Option<ClusterId> {
425        None
426    }
427    /// Source address as observed by Sōzu, with proxy-protocol awareness.
428    /// HTTP/HTTPS/TCP client sessions return the parsed PROXY-protocol
429    /// source when present, else `peer_addr`. `ListenSession` returns
430    /// `None`. Used to attribute per-(cluster, source-IP) tracking and
431    /// access logs to the real client behind a layer-4 PROXY frontend.
432    fn session_address(&self) -> Option<SocketAddr> {
433        None
434    }
435    /// Whether the session has work that only a [`Self::ready`] pass can do
436    /// although no socket event will arrive to trigger one.
437    ///
438    /// Asked by `Server::timeout` after a timeout that left the session open.
439    /// A backend whose connect timed out is the case: the timer is the only
440    /// signal a blackholed dial ever produces, and failing it over to another
441    /// backend (dialling, registering the new socket) needs the `ready` path
442    /// (sozu-proxy/sozu#1800).
443    fn needs_ready_pass(&self) -> bool {
444        false
445    }
446}
447
448#[macro_export]
449macro_rules! branch {
450    (if $($value:ident)? == $expected:ident { $($then:tt)* } else { $($else:tt)* }) => {
451        macro_rules! expect {
452            ($expected) => {$($then)*};
453            ($a:ident) => {$($else)*};
454            () => {$($else)*}
455        }
456        expect!($($value)?);
457    };
458    (if $($value:ident)? == $expected:ident { $($then:tt)* } ) => {
459        macro_rules! expect {
460            ($expected) => {$($then)*};
461        }
462        expect!($($value)?);
463    };
464}
465
466#[macro_export]
467macro_rules! fallback {
468    ({} $($default:tt)*) => {
469        $($default)*
470    };
471    ({$($value:tt)+} $($default:tt)*) => {
472        $($value)+
473    };
474}
475
476#[macro_export]
477macro_rules! StateMachineBuilder {
478    (
479        ($d:tt)
480        $(#[$($state_macros:tt)*])*
481        enum $state_name:ident $(impl $trait:ident)?  {
482            $($(#[$($variant_macros:tt)*])*
483            $variant_name:ident($state:ty$(,$($aux:ty),+)?) $(-> $override:expr)?),+ $(,)?
484        }
485    ) => {
486        /// A summary of the last valid State
487        #[derive(Clone, Copy, Debug)]
488        pub enum StateMarker {
489            $($variant_name,)+
490        }
491
492        $(#[$($state_macros)*])*
493        #[allow(clippy::large_enum_variant)]
494        pub enum $state_name {
495            $(
496                $(#[$($variant_macros)*])*
497                $variant_name($state$(,$($aux),+)?),
498            )+
499            /// Informs about upgrade failure, contains a summary the last valid State
500            FailedUpgrade(StateMarker),
501        }
502
503        macro_rules! _fn_impl {
504            ($function:ident(&$d($mut:ident)?, self $d(,$arg_name:ident: $arg_type:ty)*) $d(-> $ret:ty)? $d(| $marker:tt => $fail:expr)?) => {
505                fn $function(&$d($mut)? self $d(,$arg_name: $arg_type)*) $d(-> $ret)? {
506                    match self {
507                        $($state_name::$variant_name(_state, ..) => $crate::fallback!({$($override)?} _state.$function($d($arg_name),*)),)+
508                        $state_name::FailedUpgrade($crate::fallback!({$d($marker)?} _)) => $crate::fallback!({$d($fail)?} unreachable!())
509                    }
510                }
511            };
512        }
513
514        impl $state_name {
515            /// Informs about the last valid State before upgrade failure
516            fn marker(&self) -> StateMarker {
517                match self {
518                    $($state_name::$variant_name(..) => StateMarker::$variant_name,)+
519                    $state_name::FailedUpgrade(marker) => *marker,
520                }
521            }
522            /// Returns wether or not the State is FailedUpgrade
523            fn failed(&self) -> bool {
524                match self {
525                    $state_name::FailedUpgrade(_) => true,
526                    _ => false,
527                }
528            }
529            /// Gives back an owned version of the State,
530            /// leaving a FailedUpgrade in its place.
531            /// The FailedUpgrade retains the marker of the previous State.
532            fn take(&mut self) -> $state_name {
533                let mut owned_state = $state_name::FailedUpgrade(self.marker());
534                std::mem::swap(&mut owned_state, self);
535                owned_state
536            }
537            _fn_impl!{front_socket(&, self) -> &mio::net::TcpStream}
538        }
539
540        $crate::branch!{
541            if $($trait)? == SessionState {
542                impl SessionState for $state_name {
543                    _fn_impl!{ready(&mut, self, session: Rc<RefCell<dyn ProxySession>>, proxy: Rc<RefCell<dyn L7Proxy>>, metrics: &mut SessionMetrics) -> SessionResult}
544                    _fn_impl!{update_readiness(&mut, self, token: Token, events: Ready)}
545                    _fn_impl!{timeout(&mut, self, token: Token, metrics: &mut SessionMetrics) -> StateResult}
546                    _fn_impl!{cancel_timeouts(&mut, self)}
547                    _fn_impl!{print_state(&, self, context: &str) | marker => error!("{} Session(FailedUpgrade({:?}))", context, marker)}
548                    _fn_impl!{close(&mut, self, proxy: Rc<RefCell<dyn L7Proxy>>, metrics: &mut SessionMetrics) | _ => {}}
549                    _fn_impl!{shutting_down(&mut, self) -> SessionIsToBeClosed | _ => true}
550                }
551            } else {}
552        }
553    };
554    ($($tt:tt)+) => {
555        StateMachineBuilder!{($) $($tt)+}
556    }
557}
558
559pub trait ListenerHandler {
560    fn get_addr(&self) -> &SocketAddr;
561
562    fn get_tags(&self, key: &str) -> Option<&CachedTags>;
563
564    fn get_concatenated_tags(&self, key: &str) -> Option<&str> {
565        self.get_tags(key).map(|tags| tags.concatenated.as_str())
566    }
567
568    fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>);
569
570    fn protocol(&self) -> Protocol;
571
572    fn public_address(&self) -> SocketAddr;
573}
574
575#[derive(thiserror::Error, Debug)]
576pub enum FrontendFromRequestError {
577    #[error("Could not parse hostname from '{host}': {error}")]
578    HostParse { host: String, error: String },
579    #[error("invalid remaining chars after hostname. Host: {0}")]
580    InvalidCharsAfterHost(String),
581    #[error("no cluster: {0}")]
582    NoClusterFound(RouterError),
583}
584
585pub trait L7ListenerHandler {
586    fn get_sticky_name(&self) -> &str;
587
588    /// Name of the correlation header Sozu injects into every request and
589    /// response body. Default: `"Sozu-Id"`. Operators can rebrand via the
590    /// `sozu_id_header` listener config knob.
591    fn get_sozu_id_header(&self) -> &str {
592        "Sozu-Id"
593    }
594
595    fn get_connect_timeout(&self) -> u32;
596
597    /// retrieve a frontend by parsing a request's hostname, uri and method
598    fn frontend_from_request(
599        &self,
600        host: &str,
601        uri: &str,
602        method: &Method,
603    ) -> Result<RouteResult, FrontendFromRequestError>;
604
605    /// retrieve the listener's configured HTTP answers (templates)
606    fn get_answers(&self) -> &Rc<RefCell<HttpAnswers>>;
607
608    /// H2 flood detection thresholds from the listener config.
609    /// Returns the default config when the listener does not provide custom values.
610    fn get_h2_flood_config(&self) -> protocol::mux::H2FloodConfig {
611        protocol::mux::H2FloodConfig::default()
612    }
613
614    /// H2 connection tuning from the listener config.
615    /// Returns the default config when the listener does not provide custom values.
616    fn get_h2_connection_config(&self) -> protocol::mux::H2ConnectionConfig {
617        protocol::mux::H2ConnectionConfig::default()
618    }
619
620    /// Whether requests must have their `:authority` / `Host` exact-match
621    /// the TLS SNI negotiated at handshake (CWE-346 / CWE-444).
622    ///
623    /// Defaults to `true` — the safe setting that closes the
624    /// CWE-346 / CWE-444 cross-SNI smuggling vector. Operators can opt
625    /// out per-listener via `HttpsListenerConfig::strict_sni_binding =
626    /// false` when cross-SNI routing is explicitly required. Plaintext
627    /// HTTP listeners return the default value; they never have an SNI
628    /// to compare against, so the routing-layer check short-circuits on
629    /// `tls_server_name: None`.
630    fn get_strict_sni_binding(&self) -> bool {
631        true
632    }
633
634    /// Whether to strip any client-supplied `X-Real-IP` header from
635    /// forwarded requests (anti-spoofing).
636    ///
637    /// Defaults to `false` — preserves the historical pass-through
638    /// behaviour. Operators opt in via
639    /// `HttpListenerConfig::elide_x_real_ip = true` (and the equivalent on
640    /// HTTPS listeners). Independent of [`Self::get_send_x_real_ip`]: the
641    /// two flags can be combined freely (anti-spoof only, send only, both,
642    /// or neither). The elision branch lives in
643    /// `HttpContext::on_request_headers`, so it covers H1 and H2 alike.
644    fn get_elide_x_real_ip(&self) -> bool {
645        false
646    }
647
648    /// Whether to append a proxy-generated `X-Real-IP` header carrying the
649    /// connection peer IP (post-PROXY-v2 unwrap, i.e. the original client
650    /// IP) to every forwarded request.
651    ///
652    /// Defaults to `false` — preserves the historical no-injection
653    /// behaviour. Operators opt in via
654    /// `HttpListenerConfig::send_x_real_ip = true` (and the equivalent on
655    /// HTTPS listeners). Independent of [`Self::get_elide_x_real_ip`]: the
656    /// two flags can be combined freely. The injection branch lives next
657    /// to the existing X-Forwarded-For / Forwarded synthesis in
658    /// `HttpContext::on_request_headers`.
659    fn get_send_x_real_ip(&self) -> bool {
660        false
661    }
662
663    /// Which forwarding header family to add to every forwarded request:
664    /// `X-Forwarded-*`, RFC 7239 `Forwarded`, both, or none.
665    ///
666    /// Defaults to [`ForwardedHeaders::Both`] — the historical behaviour.
667    /// Operators choose another mode via
668    /// `HttpListenerConfig::forwarded_headers` (and the equivalent on HTTPS
669    /// listeners). Independent of [`Self::get_elide_x_real_ip`] and
670    /// [`Self::get_send_x_real_ip`]. The emission and elision branches live
671    /// in `HttpContext::on_request_headers`, so they cover H1 and H2 alike.
672    fn get_forwarded_headers(&self) -> ForwardedHeaders {
673        ForwardedHeaders::Both
674    }
675
676    /// Per-stream idle timeout for H2 connections. An open stream that makes
677    /// no forward progress for this duration is cancelled (RST_STREAM / CANCEL).
678    /// Mitigates slow-multiplex Slowloris where a client keeps connection-level
679    /// activity high (resetting the connection idle timer on every frame) while
680    /// pinning streams for the full nominal connection timeout.
681    ///
682    /// Listeners inherit `max(30s, back_timeout)` when `h2_stream_idle_timeout_seconds`
683    /// is absent so operators who raised the socket-level backend budget do not
684    /// have to duplicate the value here; the 30 s floor preserves the baseline
685    /// slow-multiplex mitigation when `back_timeout` is shorter. Set the knob
686    /// explicitly to cap the per-stream deadline below `back_timeout` (useful
687    /// when under a slow-multiplex attack).
688    fn get_h2_stream_idle_timeout(&self) -> std::time::Duration {
689        std::time::Duration::from_secs(30)
690    }
691
692    /// Wall-clock budget granted to in-flight H2 streams after soft-stop sent
693    /// the initial `GOAWAY(NO_ERROR)`. Once the deadline elapses the mux
694    /// transitions to a forced close (final GOAWAY + session teardown).
695    ///
696    /// Returning `None` disables the forced close entirely — shutdown waits
697    /// for every stream to drain naturally. Returning `Some(d)` enforces the
698    /// budget. Default: `Some(Duration::from_secs(5))` (matches the historic
699    /// hard-coded 5 s deadline). Listeners expose the
700    /// `h2_graceful_shutdown_deadline_seconds` knob; value `0` maps to `None`.
701    fn get_h2_graceful_shutdown_deadline(&self) -> Option<std::time::Duration> {
702        Some(std::time::Duration::from_secs(5))
703    }
704}
705
706#[derive(Clone, Copy, Debug, PartialEq, Eq)]
707pub enum BackendConnectionStatus {
708    NotConnected,
709    Connecting(Instant),
710    Connected,
711}
712
713impl BackendConnectionStatus {
714    pub fn is_connecting(&self) -> bool {
715        matches!(self, BackendConnectionStatus::Connecting(_))
716    }
717}
718
719#[derive(Debug, PartialEq, Eq)]
720pub enum BackendConnectAction {
721    New,
722    Reuse,
723    Replace,
724}
725
726#[derive(thiserror::Error, Debug)]
727pub enum BackendConnectionError {
728    #[error("Not found: {0:?}")]
729    NotFound(ObjectKind),
730    #[error("Too many connections on cluster {0:?}")]
731    MaxConnectionRetries(Option<ClusterId>),
732    #[error("the sessions slab has reached maximum capacity")]
733    MaxSessionsMemory,
734    #[error("error from the backend: {0}")]
735    Backend(BackendError),
736    #[error("failed to retrieve the cluster: {0}")]
737    RetrieveClusterError(RetrieveClusterError),
738    #[error("maximum number of buffers reached")]
739    MaxBuffers,
740    /// Per-(cluster, source-IP) connection limit reached. The protocol
741    /// layer translates this into HTTP 429 Too Many Requests (with an
742    /// optional `Retry-After`) for HTTP/HTTPS sessions, or a graceful TCP
743    /// close for raw TCP. The `cluster_id` is included so log/metric
744    /// pipelines can attribute the rejection.
745    #[error("per-(cluster, source-IP) connection limit reached for cluster {cluster_id:?}")]
746    TooManyConnectionsPerIp { cluster_id: ClusterId },
747    /// A stale-upstream replay could not be carried out. `Stream::retry_buffer`
748    /// holds H1 wire bytes captured under the FIRST attempt's routing
749    /// decision, so the replay is valid only while that decision still holds:
750    /// the cluster must still be known and must still speak H1. When it does
751    /// not, the protocol layer answers 502 Bad Gateway — the same answer the
752    /// stream received before the replay existed (sozu-proxy/sozu#1442) —
753    /// rather than framing raw H1 text as an HTTP/2 DATA payload or
754    /// re-routing a front kawa whose blocks are already drained.
755    #[error("stale-upstream replay refused: {0}")]
756    ReplayRefused(&'static str),
757}
758
759/// Route-extraction failures raised by `kawa_h1::editor::HttpContext` and
760/// consumed by the mux router.
761#[derive(thiserror::Error)]
762pub enum RetrieveClusterError {
763    #[error("No method given")]
764    NoMethod,
765    #[error("No host given")]
766    NoHost,
767    #[error("No path given")]
768    NoPath,
769    #[error("unauthorized route")]
770    UnauthorizedRoute,
771    #[error("{0}")]
772    RetrieveFrontend(FrontendFromRequestError),
773    #[error("HTTPS redirect required")]
774    HttpsRedirect,
775    /// The HTTP `:authority` / `Host` host does not match the TLS SNI that was
776    /// negotiated for this connection, which would cross the TLS trust boundary.
777    /// Maps to HTTP 421 Misdirected Request (RFC 9110 §15.5.20).
778    #[error("TLS SNI does not match HTTP authority: sni_bytes={} authority_bytes={}", .sni.len(), .authority.len())]
779    SniAuthorityMismatch { sni: String, authority: String },
780}
781
782impl fmt::Debug for RetrieveClusterError {
783    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
784        fmt::Display::fmt(self, f)
785    }
786}
787
788/// Used in sessions
789#[derive(Debug, PartialEq, Eq)]
790pub enum AcceptError {
791    IoError,
792    TooManySessions,
793    WouldBlock,
794    RegisterError,
795    WrongSocketAddress,
796    BufferCapacityReached,
797}
798
799/// What a proxy would do with a listening socket handed to its
800/// `activate_listener(address)`.
801///
802/// `Server::notify_activate_listener` asks BEFORE it pulls a descriptor out of
803/// the SCM table, because `Listeners::get_*` removes the entry as it hands it
804/// over while `activate()` returns on its `if self.active` guard without
805/// consuming anything. Taking a descriptor that nothing will consume drops the
806/// owning wrapper on the floor and closes the inherited socket unused — the
807/// accept backlog or receive buffer with it (sozu#1342).
808#[derive(Clone, Copy, Debug, PartialEq, Eq)]
809pub enum InheritedSocketFate {
810    /// A listener exists at the address and is not active: it adopts the
811    /// socket. Take the descriptor.
812    Adopted,
813    /// A listener exists at the address and is already active on a socket of
814    /// its own. `activate()` short-circuits, so nothing consumes this
815    /// descriptor **now**. Close it deliberately.
816    ///
817    /// Not because it could never be adopted: `give_back_listener` clears
818    /// `active` and keeps the listener object, so `DeactivateListener` followed
819    /// by `ActivateListener` — an operator-reachable feature, covered by
820    /// `e2e/src/tests/listener_reactivation_tests.rs` — brings this address back
821    /// to [`Adopted`](Self::Adopted). Closing here is therefore irreversible:
822    /// `scm_listeners` is filled once in `Server::new` and never refilled, so a
823    /// reactivation after this point binds a fresh socket.
824    ///
825    /// It is a deliberate trade, not a claim about the lifetime. The only thing
826    /// this descriptor carries is the connections queued on it at hand-off, and
827    /// their value decays — the peers behind them time out or reset while the
828    /// address is served by the listener's own socket. The cost of keeping it is
829    /// continuous instead: the descriptor is the retiring worker's listening
830    /// socket, still bound with `SO_REUSEPORT` and registered with no event
831    /// loop, so the kernel is expected to keep giving it a share of incoming
832    /// connections that nobody accepts. (That last step follows from
833    /// `SO_REUSEPORT` load-balancing semantics; nothing here measures it.) A
834    /// decaying backlog is not worth an indefinite share of new connections.
835    Refused,
836    /// No listener at the address yet. Keep the descriptor in the table: an
837    /// `AddListener` + `ActivateListener` pair can still arrive and adopt it,
838    /// which is exactly what an upgrade does when the initial state carries the
839    /// listeners inactive and activates them afterwards.
840    Unclaimed,
841}
842
843/// returned by the HTTP, HTTPS and TCP listeners
844#[derive(thiserror::Error)]
845pub enum ListenerError {
846    #[error("failed to handle certificate request, got a resolver error, {0}")]
847    Resolver(CertificateResolverError),
848    #[error("failed to parse pem, {0}")]
849    PemParse(String),
850    #[error(
851        "failed to parse template key_bytes={key_bytes}: {1}",
852        key_bytes = .0.len()
853    )]
854    TemplateParse(String, TemplateError),
855    #[error("failed to build rustls context, {0}")]
856    BuildRustls(String),
857    #[error("failed to configure client certificate authentication (mTLS): {0}")]
858    ClientAuth(String),
859    #[error("could not activate listener with address {address:?}: {error}")]
860    Activation { address: SocketAddr, error: String },
861    #[error("Could not register listener socket: {0}")]
862    SocketRegistration(std::io::Error),
863    #[error("could not add frontend: {0}")]
864    AddFrontend(RouterError),
865    #[error("could not remove frontend: {0}")]
866    RemoveFrontend(RouterError),
867    #[error("invalid value for field '{field}': {reason}")]
868    InvalidValue {
869        field: &'static str,
870        reason: &'static str,
871    },
872    /// `UpdateHttpsListenerConfig.hsts` was present but its `enabled`
873    /// field was unset. Per the partial-update contract, `enabled` is
874    /// the explicit-disambiguator between "explicit disable" (false) and
875    /// "explicit enable" (true); the patch handler refuses an
876    /// `enabled = None` block rather than silently picking one.
877    #[error(
878        "UpdateHttpsListenerConfig.hsts is present but `enabled` is unset; the partial-update \
879         contract requires `enabled` whenever the `hsts` block is present"
880    )]
881    HstsEnabledRequired,
882}
883
884impl fmt::Debug for ListenerError {
885    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
886        fmt::Display::fmt(self, f)
887    }
888}
889
890/// Lift control-plane validation errors into listener-level errors so the
891/// worker can surface the same message without duplicating the match.
892/// Non-`InvalidValue` variants fall back to a generic `InvalidValue` — they
893/// are not expected on the worker's `update_config` path (state lookups
894/// happen on the master) but we avoid panicking if one slips through.
895impl From<sozu_command::state::StateError> for ListenerError {
896    fn from(err: sozu_command::state::StateError) -> Self {
897        match err {
898            sozu_command::state::StateError::InvalidValue { field, reason } => {
899                ListenerError::InvalidValue { field, reason }
900            }
901            _ => ListenerError::InvalidValue {
902                field: "state",
903                reason: "unexpected state error on worker path",
904            },
905        }
906    }
907}
908
909/// Returned by the HTTP, HTTPS and TCP proxies
910#[derive(thiserror::Error, Debug)]
911pub enum ProxyError {
912    #[error("error while soft stopping {proxy_protocol} proxy: {error}")]
913    SoftStop {
914        proxy_protocol: String,
915        error: String,
916    },
917    #[error("error while hard stopping {proxy_protocol} proxy: {error}")]
918    HardStop {
919        proxy_protocol: String,
920        error: String,
921    },
922    /// Names the listener key: the address, plus `%<interface>` for a
923    /// listener bound to one.
924    #[error("found no listener at {0}")]
925    NoListenerFound(sozu_command::listener_key::ListenerKey),
926    #[error("a listener is already present for this token")]
927    ListenerAlreadyPresent,
928    #[error("could not add listener: {0}")]
929    AddListener(ListenerError),
930    #[error("could not add cluster: {0}")]
931    AddCluster(ListenerError),
932    #[error("failed to activate listener with address {address:?}: {listener_error}")]
933    ListenerActivation {
934        address: SocketAddr,
935        listener_error: ListenerError,
936    },
937    #[error("can not add frontend {front:?}: {error}")]
938    WrongInputFrontend {
939        front: Box<RequestHttpFrontend>,
940        error: String,
941    },
942    #[error("could not add frontend: {0}")]
943    AddFrontend(ListenerError),
944    #[error("could not remove frontend: {0}")]
945    RemoveFrontend(ListenerError),
946    #[error("could not add certificate: {0}")]
947    AddCertificate(CertificateResolverError),
948    #[error("could not remove certificate: {0}")]
949    RemoveCertificate(CertificateResolverError),
950    #[error("could not replace certificate: {0}")]
951    ReplaceCertificate(CertificateResolverError),
952    #[error("wrong certificate fingerprint: {0}")]
953    WrongCertificateFingerprint(FromHexError),
954    #[error("this request is not supported by the proxy")]
955    UnsupportedMessage,
956    #[error("failed to acquire the lock, {0}")]
957    Lock(String),
958    #[error("could not bind to socket {0:?}: {1}")]
959    BindToSocket(SocketAddr, ServerBindError),
960    #[error("error registering socket of listener: {0}")]
961    RegisterListener(std::io::Error),
962    #[error("the listener is not activated")]
963    UnactivatedListener,
964    /// HSTS (RFC 6797) was attached to a frontend on a plain-HTTP
965    /// listener. RFC 6797 §7.2 forbids `Strict-Transport-Security` on
966    /// plaintext-HTTP responses; the worker rejects the request rather
967    /// than ship a non-conformant policy. The TOML loader rejects the
968    /// same shape at config-load time
969    /// (`command/src/config.rs::ConfigError::HstsOnPlainHttp`); this
970    /// arm catches the same misconfiguration when the request reaches
971    /// the worker over the IPC channel without going through the TOML
972    /// path (e.g. via `sozu frontend http add`).
973    #[error(
974        "HSTS is only valid on HTTPS frontends; rejecting AddHttpFrontend with hsts.enabled = \
975         true on address {0:?} (RFC 6797 §7.2)"
976    )]
977    HstsOnPlainHttp(SocketAddr),
978    /// A TCP `AddTcpFrontend` would corrupt this listener's SNI/ALPN
979    /// routing invariants (sozu-proxy/sozu#1279): mixing a no-SNI
980    /// catch-all with SNI-scoped routes, an `alpn`-scoped frontend with no
981    /// `sni` to preread against, or an ALPN protocol / catch-all that
982    /// overlaps an existing route on the same `(address, sni)`. TOML
983    /// config-load already rejects the same shapes
984    /// (`command/src/config.rs::ConfigError`), but `AddTcpFrontend` can
985    /// also arrive directly over the command socket or via `LoadState`
986    /// replay of a hand-edited/stale state file, bypassing config.rs
987    /// entirely — see `TcpListener::validate_new_tcp_front`
988    /// (`lib/src/tcp.rs`).
989    #[error("rejected AddTcpFrontend on listener {address:?}: {reason}")]
990    InvalidTcpFrontend { address: SocketAddr, reason: String },
991}
992
993use self::server::ListenToken;
994pub trait ProxyConfiguration {
995    fn notify(&mut self, message: WorkerRequest) -> WorkerResponse;
996    /// Accept one connection on the listener behind `token`, returning the
997    /// socket together with the peer address `accept(2)` reported for it.
998    fn accept(&mut self, token: ListenToken) -> Result<(TcpStream, SocketAddr), AcceptError>;
999    /// Build the session for a socket [`Self::accept`] returned. `peer` is the
1000    /// address `accept(2)` returned with it, so the session never has to ask
1001    /// the kernel again with `getpeername(2)`.
1002    fn create_session(
1003        &mut self,
1004        socket: TcpStream,
1005        peer: SocketAddr,
1006        token: ListenToken,
1007        wait_time: Duration,
1008        proxy: Rc<RefCell<Self>>,
1009        // should we insert the tags here?
1010    ) -> Result<(), AcceptError>;
1011}
1012
1013pub trait L7Proxy {
1014    fn kind(&self) -> ListenerType;
1015
1016    fn register_socket(
1017        &self,
1018        socket: &mut TcpStream,
1019        token: Token,
1020        interest: Interest,
1021    ) -> Result<(), std::io::Error>;
1022
1023    fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token;
1024
1025    /// Remove the session from the session manager slab.
1026    /// Returns true if the session was actually there before deletion
1027    fn remove_session(&self, token: Token) -> bool;
1028
1029    fn clusters(&self) -> &HashMap<ClusterId, Cluster>;
1030
1031    /// Access the worker's [`crate::server::SessionManager`] for per-(cluster, source-IP)
1032    /// connection-limit accounting. The mux uses this to track / untrack
1033    /// stream-granular `(cluster_id, ip)` entries and consult the
1034    /// `cluster_ip_at_limit` gate before each backend connect.
1035    fn sessions(&self) -> Rc<RefCell<crate::server::SessionManager>>;
1036}
1037
1038#[derive(Debug, PartialEq, Eq)]
1039pub enum RequiredEvents {
1040    FrontReadBackNone,
1041    FrontWriteBackNone,
1042    FrontReadWriteBackNone,
1043    FrontNoneBackNone,
1044    FrontReadBackRead,
1045    FrontWriteBackRead,
1046    FrontReadWriteBackRead,
1047    FrontNoneBackRead,
1048    FrontReadBackWrite,
1049    FrontWriteBackWrite,
1050    FrontReadWriteBackWrite,
1051    FrontNoneBackWrite,
1052    FrontReadBackReadWrite,
1053    FrontWriteBackReadWrite,
1054    FrontReadWriteBackReadWrite,
1055    FrontNoneBackReadWrite,
1056}
1057
1058impl RequiredEvents {
1059    pub fn front_readable(&self) -> bool {
1060        matches!(
1061            *self,
1062            RequiredEvents::FrontReadBackNone
1063                | RequiredEvents::FrontReadWriteBackNone
1064                | RequiredEvents::FrontReadBackRead
1065                | RequiredEvents::FrontReadWriteBackRead
1066                | RequiredEvents::FrontReadBackWrite
1067                | RequiredEvents::FrontReadWriteBackWrite
1068                | RequiredEvents::FrontReadBackReadWrite
1069                | RequiredEvents::FrontReadWriteBackReadWrite
1070        )
1071    }
1072
1073    pub fn front_writable(&self) -> bool {
1074        matches!(
1075            *self,
1076            RequiredEvents::FrontWriteBackNone
1077                | RequiredEvents::FrontReadWriteBackNone
1078                | RequiredEvents::FrontWriteBackRead
1079                | RequiredEvents::FrontReadWriteBackRead
1080                | RequiredEvents::FrontWriteBackWrite
1081                | RequiredEvents::FrontReadWriteBackWrite
1082                | RequiredEvents::FrontWriteBackReadWrite
1083                | RequiredEvents::FrontReadWriteBackReadWrite
1084        )
1085    }
1086
1087    pub fn back_readable(&self) -> bool {
1088        matches!(
1089            *self,
1090            RequiredEvents::FrontReadBackRead
1091                | RequiredEvents::FrontWriteBackRead
1092                | RequiredEvents::FrontReadWriteBackRead
1093                | RequiredEvents::FrontNoneBackRead
1094                | RequiredEvents::FrontReadBackReadWrite
1095                | RequiredEvents::FrontWriteBackReadWrite
1096                | RequiredEvents::FrontReadWriteBackReadWrite
1097                | RequiredEvents::FrontNoneBackReadWrite
1098        )
1099    }
1100
1101    pub fn back_writable(&self) -> bool {
1102        matches!(
1103            *self,
1104            RequiredEvents::FrontReadBackWrite
1105                | RequiredEvents::FrontWriteBackWrite
1106                | RequiredEvents::FrontReadWriteBackWrite
1107                | RequiredEvents::FrontNoneBackWrite
1108                | RequiredEvents::FrontReadBackReadWrite
1109                | RequiredEvents::FrontWriteBackReadWrite
1110                | RequiredEvents::FrontReadWriteBackReadWrite
1111                | RequiredEvents::FrontNoneBackReadWrite
1112        )
1113    }
1114}
1115
1116/// Signals transitions between states of a given Protocol
1117#[derive(Debug, PartialEq, Eq)]
1118pub enum StateResult {
1119    /// Signals to the Protocol to close its backend
1120    CloseBackend,
1121    /// Signals to the parent Session to close itself
1122    CloseSession,
1123    /// Signals to the Protocol to connect to backend
1124    ConnectBackend,
1125    /// Signals to the Protocol to continue
1126    Continue,
1127    /// Signals to the parent Session to upgrade to the next Protocol
1128    Upgrade,
1129}
1130
1131/// Signals transitions between states of a given Session
1132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1133pub enum SessionResult {
1134    /// Signals to the Session to close itself
1135    Close,
1136    /// Signals to the Session to continue
1137    Continue,
1138    /// Signals to the Session to upgrade its Protocol
1139    Upgrade,
1140}
1141
1142#[derive(Debug, PartialEq, Eq)]
1143pub enum SocketType {
1144    Listener,
1145    FrontClient,
1146}
1147
1148type SessionIsToBeClosed = bool;
1149
1150#[derive(Clone)]
1151pub struct Readiness {
1152    /// the current readiness
1153    pub event: Ready,
1154    /// the readiness we wish to attain
1155    pub interest: Ready,
1156}
1157
1158impl Display for Readiness {
1159    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1160        let i = &mut [b'-'; 5];
1161        let r = &mut [b'-'; 5];
1162        let mixed = &mut [b'-'; 5];
1163
1164        display_ready(i, self.interest);
1165        display_ready(r, self.event);
1166        display_ready(mixed, self.interest & self.event);
1167
1168        write!(
1169            f,
1170            "I({:?})&R({:?})=M({:?})",
1171            String::from_utf8_lossy(i),
1172            String::from_utf8_lossy(r),
1173            String::from_utf8_lossy(mixed)
1174        )
1175    }
1176}
1177
1178impl Default for Readiness {
1179    fn default() -> Self {
1180        Self::new()
1181    }
1182}
1183
1184impl Readiness {
1185    /// Mask of every bit `Ready` defines (READABLE | WRITABLE | ERROR | HUP |
1186    /// WRITE_CLOSED).
1187    /// Any bit outside this set in `event` or `interest` is a corrupted
1188    /// readiness word — checked by [`Self::check_invariants`]. Not
1189    /// `#[cfg(debug_assertions)]`-gated: it is read from inside `debug_assert!`s
1190    /// whose arguments must still compile in release (HARD RULE 2 / E0425).
1191    const KNOWN_BITS: Ready = Ready(
1192        Ready::READABLE.0
1193            | Ready::WRITABLE.0
1194            | Ready::ERROR.0
1195            | Ready::HUP.0
1196            | Ready::WRITE_CLOSED.0,
1197    );
1198
1199    pub const fn new() -> Readiness {
1200        Readiness {
1201            event: Ready::EMPTY,
1202            interest: Ready::EMPTY,
1203        }
1204    }
1205
1206    /// Cross-field invariant sweep: neither `event` nor `interest` may carry a
1207    /// bit `Ready` does not define. A stray bit would silently widen
1208    /// `filter_interest`'s mask and wake (or starve) a session on a phantom
1209    /// readiness. Cheap enough to call as a postcondition from every mutator.
1210    #[cfg(debug_assertions)]
1211    fn check_invariants(&self) {
1212        debug_assert_eq!(
1213            self.event & Self::KNOWN_BITS,
1214            self.event,
1215            "Readiness.event carries a bit outside READABLE|WRITABLE|ERROR|HUP|WRITE_CLOSED"
1216        );
1217        debug_assert_eq!(
1218            self.interest & Self::KNOWN_BITS,
1219            self.interest,
1220            "Readiness.interest carries a bit outside READABLE|WRITABLE|ERROR|HUP|WRITE_CLOSED"
1221        );
1222    }
1223
1224    pub fn reset(&mut self) {
1225        self.event = Ready::EMPTY;
1226        self.interest = Ready::EMPTY;
1227        // Post-condition: a reset clears *both* words — a half-reset leaves a
1228        // session armed on stale interest after teardown.
1229        debug_assert!(
1230            self.event.is_empty() && self.interest.is_empty(),
1231            "reset must clear both event and interest"
1232        );
1233        #[cfg(debug_assertions)]
1234        self.check_invariants();
1235    }
1236
1237    /// filters the readiness we actually want
1238    pub fn filter_interest(&self) -> Ready {
1239        // Pre-condition: both source words must be well-formed before we mask —
1240        // a stray bit upstream would leak through the intersection and wake (or
1241        // starve) a session on a phantom readiness.
1242        #[cfg(debug_assertions)]
1243        self.check_invariants();
1244        let filtered = self.event & self.interest;
1245        // Post-condition: the result is a subset of the recognized bits and can
1246        // only contain bits the session both saw AND asked for.
1247        debug_assert_eq!(
1248            filtered & Self::KNOWN_BITS,
1249            filtered,
1250            "filter_interest must not yield an unknown bit"
1251        );
1252        debug_assert!(
1253            self.interest.contains(filtered) && self.event.contains(filtered),
1254            "filtered readiness must be present in both interest and event"
1255        );
1256        filtered
1257    }
1258
1259    /// Signal that the socket has buffered data to write (e.g., TLS internal
1260    /// buffers) that won't generate a new epoll WRITABLE event.
1261    pub fn signal_pending_write(&mut self) {
1262        // Snapshot the unrelated bits (everything but WRITABLE) so we can prove
1263        // we flipped exactly the WRITABLE bit. `Ready` exposes no `!`, so mask on
1264        // the public `.0` word.
1265        let other_event_before = Ready(self.event.0 & !Ready::WRITABLE.0);
1266        self.event.insert(Ready::WRITABLE);
1267        debug_assert!(
1268            self.event.is_writable(),
1269            "signal_pending_write must set the WRITABLE event bit"
1270        );
1271        debug_assert_eq!(
1272            Ready(self.event.0 & !Ready::WRITABLE.0),
1273            other_event_before,
1274            "signal_pending_write must touch only the WRITABLE bit"
1275        );
1276        #[cfg(debug_assertions)]
1277        self.check_invariants();
1278    }
1279
1280    /// Signal that the socket has buffered data to read (e.g., TLS plaintext
1281    /// buffer after a 1xx clear) that won't generate a new epoll READABLE event.
1282    pub fn signal_pending_read(&mut self) {
1283        let other_event_before = Ready(self.event.0 & !Ready::READABLE.0);
1284        self.event.insert(Ready::READABLE);
1285        debug_assert!(
1286            self.event.is_readable(),
1287            "signal_pending_read must set the READABLE event bit"
1288        );
1289        debug_assert_eq!(
1290            Ready(self.event.0 & !Ready::READABLE.0),
1291            other_event_before,
1292            "signal_pending_read must touch only the READABLE bit"
1293        );
1294        #[cfg(debug_assertions)]
1295        self.check_invariants();
1296    }
1297
1298    /// Pair `Ready::WRITABLE` insert with `signal_pending_write` — the canonical
1299    /// invariant-15 form for any path that writes bytes to sozu-owned buffers
1300    /// under edge-triggered epoll. See `lib/src/protocol/mux/LIFECYCLE.md`.
1301    #[inline]
1302    pub fn arm_writable(&mut self) {
1303        // Snapshot the non-WRITABLE bits of both words: arm_writable must set the
1304        // WRITABLE bit in *both* interest and event and leave everything else as-is.
1305        let other_interest_before = Ready(self.interest.0 & !Ready::WRITABLE.0);
1306        let other_event_before = Ready(self.event.0 & !Ready::WRITABLE.0);
1307        self.interest.insert(Ready::WRITABLE);
1308        self.signal_pending_write();
1309        debug_assert!(
1310            self.interest.is_writable() && self.event.is_writable(),
1311            "arm_writable must set WRITABLE in both interest and event"
1312        );
1313        debug_assert_eq!(
1314            Ready(self.interest.0 & !Ready::WRITABLE.0),
1315            other_interest_before,
1316            "arm_writable must touch only the WRITABLE interest bit"
1317        );
1318        debug_assert_eq!(
1319            Ready(self.event.0 & !Ready::WRITABLE.0),
1320            other_event_before,
1321            "arm_writable must touch only the WRITABLE event bit"
1322        );
1323        #[cfg(debug_assertions)]
1324        self.check_invariants();
1325    }
1326}
1327
1328#[cfg(test)]
1329mod readiness_tests {
1330    use super::{Readiness, Ready};
1331
1332    #[test]
1333    fn arm_writable_sets_interest_and_event() {
1334        let mut r = Readiness::new();
1335        r.arm_writable();
1336        assert!(r.interest.is_writable());
1337        assert!(r.event.is_writable());
1338    }
1339
1340    #[test]
1341    fn arm_writable_is_idempotent() {
1342        let mut r = Readiness::new();
1343        r.arm_writable();
1344        r.arm_writable();
1345        assert_eq!(r.interest, Ready::WRITABLE);
1346        assert_eq!(r.event, Ready::WRITABLE);
1347    }
1348
1349    /// A trace line tells a half-close from a hang-up: WRITE_CLOSED renders
1350    /// as `C` beside HUP's `H`.
1351    #[test]
1352    fn readiness_rendering_tells_a_hang_up_from_a_half_close() {
1353        assert_eq!(
1354            super::ready_to_string(Ready::READABLE | Ready::HUP),
1355            "R--H-"
1356        );
1357        assert_eq!(
1358            super::ready_to_string(Ready::READABLE | Ready::HUP | Ready::WRITE_CLOSED),
1359            "R--HC"
1360        );
1361        let readiness = Readiness {
1362            event: Ready::HUP | Ready::WRITE_CLOSED,
1363            interest: Ready::READABLE | Ready::HUP,
1364        };
1365        assert_eq!(
1366            readiness.to_string(),
1367            "I(\"R--H-\")&R(\"---HC\")=M(\"---H-\")"
1368        );
1369        assert_eq!(
1370            format!("{readiness:?}"),
1371            "Readiness { interest: R--H-, readiness: ---HC, mixed: ---H- }"
1372        );
1373    }
1374}
1375
1376pub fn display_ready(s: &mut [u8], readiness: Ready) {
1377    if readiness.is_readable() {
1378        s[0] = b'R';
1379    }
1380    if readiness.is_writable() {
1381        s[1] = b'W';
1382    }
1383    if readiness.is_error() {
1384        s[2] = b'E';
1385    }
1386    if readiness.is_hup() {
1387        s[3] = b'H';
1388    }
1389    // `H` alone is a half-close or a hang-up; `C` says the peer can receive
1390    // nothing more (WRITE_CLOSED), which only a hang-up raises.
1391    if readiness.is_write_closed() {
1392        s[4] = b'C';
1393    }
1394}
1395
1396pub fn ready_to_string(readiness: Ready) -> String {
1397    let s = &mut [b'-'; 5];
1398    display_ready(s, readiness);
1399    String::from_utf8(s.to_vec()).unwrap()
1400}
1401
1402impl fmt::Debug for Readiness {
1403    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1404        let i = &mut [b'-'; 5];
1405        let r = &mut [b'-'; 5];
1406        let mixed = &mut [b'-'; 5];
1407
1408        display_ready(i, self.interest);
1409        display_ready(r, self.event);
1410        display_ready(mixed, self.interest & self.event);
1411
1412        write!(
1413            f,
1414            "Readiness {{ interest: {}, readiness: {}, mixed: {} }}",
1415            str::from_utf8(i).unwrap(),
1416            str::from_utf8(r).unwrap(),
1417            str::from_utf8(mixed).unwrap()
1418        )
1419    }
1420}
1421
1422#[derive(Clone, Debug)]
1423pub struct SessionMetrics {
1424    /// date at which we started handling that request
1425    pub start: Option<Instant>,
1426    /// wall-clock timestamp captured alongside `start`, for access-log
1427    /// consumers that need an absolute start time (e.g. OTel span
1428    /// reconstruction) without subtracting a monotonic duration from a
1429    /// wall-clock end time — which mixes two unsynchronised clock sources.
1430    pub start_wall: Option<SystemTime>,
1431    /// time actually spent handling the request
1432    pub service_time: Duration,
1433    /// time spent waiting for its turn
1434    pub wait_time: Duration,
1435    /// bytes received by the frontend
1436    pub bin: usize,
1437    /// bytes sent by the frontend
1438    pub bout: usize,
1439
1440    /// date at which we started working on the request
1441    pub service_start: Option<Instant>,
1442    pub wait_start: Instant,
1443
1444    /// The id of the backend serving this request. An `Rc<str>` so the mux
1445    /// shares the one its `BackendId` carries instead of copying it (#1579).
1446    pub backend_id: Option<Rc<str>>,
1447    pub backend_start: Option<Instant>,
1448    pub backend_connected: Option<Instant>,
1449    /// date at which the backend's response headers finished parsing — the
1450    /// time-to-first-header-byte anchor nginx calls `$upstream_header_time`.
1451    /// Set by the L7 read paths only; raw TCP has no headers and leaves it
1452    /// `None` (see [`SessionMetrics::backend_header_time`]).
1453    pub backend_headers_received: Option<Instant>,
1454    pub backend_stop: Option<Instant>,
1455    pub backend_bin: usize,
1456    pub backend_bout: usize,
1457}
1458
1459impl SessionMetrics {
1460    pub fn new(wait_time: Option<Duration>) -> SessionMetrics {
1461        SessionMetrics {
1462            start: Some(Instant::now()),
1463            start_wall: Some(SystemTime::now()),
1464            service_time: Duration::from_secs(0),
1465            wait_time: wait_time.unwrap_or_else(|| Duration::from_secs(0)),
1466            bin: 0,
1467            bout: 0,
1468            service_start: None,
1469            wait_start: Instant::now(),
1470            backend_id: None,
1471            backend_start: None,
1472            backend_connected: None,
1473            backend_headers_received: None,
1474            backend_stop: None,
1475            backend_bin: 0,
1476            backend_bout: 0,
1477        }
1478    }
1479
1480    pub fn reset(&mut self) {
1481        self.start = None;
1482        self.start_wall = None;
1483        self.service_time = Duration::from_secs(0);
1484        self.wait_time = Duration::from_secs(0);
1485        self.bin = 0;
1486        self.bout = 0;
1487        self.service_start = None;
1488        self.backend_start = None;
1489        self.backend_connected = None;
1490        self.backend_headers_received = None;
1491        self.backend_stop = None;
1492        self.backend_bin = 0;
1493        self.backend_bout = 0;
1494    }
1495
1496    pub fn service_start(&mut self) {
1497        let now = if self.start.is_none() {
1498            self.mark_request_start()
1499        } else {
1500            Instant::now()
1501        };
1502        self.service_start = Some(now);
1503        self.wait_time += now - self.wait_start;
1504    }
1505
1506    pub fn service_stop(&mut self) {
1507        if let Some(start) = self.service_start.take() {
1508            let duration = Instant::now() - start;
1509            self.service_time += duration;
1510        }
1511    }
1512
1513    pub fn wait_start(&mut self) {
1514        self.wait_start = Instant::now();
1515    }
1516
1517    pub fn service_time(&self) -> Duration {
1518        match self.service_start {
1519            Some(start) => {
1520                let last_duration = Instant::now() - start;
1521                self.service_time + last_duration
1522            }
1523            None => self.service_time,
1524        }
1525    }
1526
1527    /// Arm both the monotonic and wall-clock start timestamps together.
1528    /// This must be the single place that sets `start` + `start_wall` outside
1529    /// of `new()`, so the two fields can never desynchronize.
1530    /// Returns the monotonic instant so callers that need it (e.g.
1531    /// `service_start`) can reuse it without a second syscall.
1532    pub fn mark_request_start(&mut self) -> Instant {
1533        let now = Instant::now();
1534        self.start = Some(now);
1535        self.start_wall = Some(SystemTime::now());
1536        now
1537    }
1538
1539    /// time elapsed since the beginning of the session
1540    pub fn request_time(&self) -> Duration {
1541        match self.start {
1542            Some(start) => Instant::now() - start,
1543            None => Duration::from_secs(0),
1544        }
1545    }
1546
1547    /// Wall-clock start time as nanoseconds since the Unix epoch, or `None`
1548    /// if the monotonic start has not been set yet (post-`reset()`, pre-`service_start()`).
1549    pub fn start_wall_ns(&self) -> Option<i128> {
1550        self.start_wall.and_then(|t| {
1551            t.duration_since(SystemTime::UNIX_EPOCH)
1552                .ok()
1553                .map(|d| d.as_nanos() as i128)
1554        })
1555    }
1556
1557    pub fn backend_start(&mut self) {
1558        self.backend_start = Some(Instant::now());
1559    }
1560
1561    pub fn backend_connected(&mut self) {
1562        self.backend_connected = Some(Instant::now());
1563    }
1564
1565    /// The backend's response headers have just finished parsing. Called from
1566    /// the L7 read paths at the header -> body transition, once per response.
1567    ///
1568    /// Overwrites, exactly like [`Self::backend_stop`] and for the same
1569    /// reason: a 1xx informational response is forwarded and the back buffer
1570    /// is then cleared — `ConnectionH1::writable` (`lib/src/protocol/mux/h1.rs`)
1571    /// in its 100-Continue and other-interim (102-199) arms, `ConnectionH2::handle_1xx_reset`
1572    /// (`lib/src/protocol/mux/h2.rs`) — so the final response re-enters that
1573    /// transition from the initial parsing phase. Last write wins, which anchors
1574    /// this on the FINAL response headers — the same response `backend_stop`
1575    /// anchors on, and what nginx reports in `$upstream_header_time`. A
1576    /// set-once guard would anchor the pair on two different responses and
1577    /// floor the metric at ~0ms for every `Expect: 100-continue` client,
1578    /// hiding the upstream think time the metric exists to expose.
1579    pub fn backend_headers_received(&mut self) {
1580        self.backend_headers_received = Some(Instant::now());
1581    }
1582
1583    pub fn backend_stop(&mut self) {
1584        self.backend_stop = Some(Instant::now());
1585    }
1586
1587    pub fn backend_response_time(&self) -> Option<Duration> {
1588        match (self.backend_connected, self.backend_stop) {
1589            (Some(start), Some(end)) => Some(end - start),
1590            (Some(start), None) => Some(Instant::now() - start),
1591            _ => None,
1592        }
1593    }
1594
1595    pub fn backend_connection_time(&self) -> Option<Duration> {
1596        match (self.backend_start, self.backend_connected) {
1597            (Some(start), Some(end)) => Some(end - start),
1598            _ => None,
1599        }
1600    }
1601
1602    /// Time from the established backend connection to the moment its response
1603    /// headers finished parsing — nginx's `$upstream_header_time`, the third
1604    /// of the three upstream timings of sozu-proxy/sozu#426.
1605    ///
1606    /// Deliberately WITHOUT the elapsed-so-far fallback that
1607    /// [`Self::backend_response_time`] carries for its `backend_stop`-is-None
1608    /// case. That fallback serves a live read of a response still streaming;
1609    /// a time-to-first-header-byte has no in-flight meaning, because no header
1610    /// byte has arrived. Returning `Instant::now() - connected` anyway would
1611    /// publish a value that grows with the wait and then freezes wherever the
1612    /// session ended — indistinguishable, on a dashboard, from a slow backend
1613    /// that did answer. `None` is the honest answer, and it is also what keeps
1614    /// raw TCP (`lib/src/tcp.rs`, no headers at all) recording nothing here
1615    /// instead of recording a zero.
1616    pub fn backend_header_time(&self) -> Option<Duration> {
1617        match (self.backend_connected, self.backend_headers_received) {
1618            (Some(connected), Some(headers)) => Some(headers - connected),
1619            _ => None,
1620        }
1621    }
1622
1623    pub fn register_end_of_session(&self, context: &LogContext) {
1624        let request_time = self.request_time();
1625        let service_time = self.service_time();
1626
1627        if let Some(cluster_id) = context.cluster_id {
1628            time!(
1629                names::event_loop::REQUEST_TIME,
1630                cluster_id,
1631                request_time.as_millis()
1632            );
1633            time!(
1634                names::event_loop::SERVICE_TIME,
1635                cluster_id,
1636                service_time.as_millis()
1637            );
1638        }
1639        time!(names::event_loop::REQUEST_TIME, request_time.as_millis());
1640        time!(names::event_loop::SERVICE_TIME, service_time.as_millis());
1641
1642        if let Some(backend_id) = self.backend_id.as_ref()
1643            && let Some(backend_response_time) = self.backend_response_time()
1644        {
1645            record_backend_metrics!(
1646                context.cluster_id.as_str_or("-"),
1647                backend_id,
1648                backend_response_time.as_millis(),
1649                self.backend_connection_time(),
1650                self.backend_header_time(),
1651                self.backend_bin,
1652                self.backend_bout
1653            );
1654        }
1655
1656        incr!(
1657            names::access_logs::COUNT,
1658            context.cluster_id,
1659            context.backend_id
1660        );
1661    }
1662
1663    pub(crate) fn register_end_of_session_for_incarnation(
1664        &self,
1665        context: &LogContext,
1666        incarnation: metrics::ClusterMetricsCapture,
1667    ) {
1668        let request_time = self.request_time();
1669        let service_time = self.service_time();
1670
1671        if let Some(cluster_id) = context.cluster_id {
1672            time!(
1673                names::event_loop::REQUEST_TIME,
1674                cluster_id,
1675                request_time.as_millis(),
1676                incarnation
1677            );
1678            time!(
1679                names::event_loop::SERVICE_TIME,
1680                cluster_id,
1681                service_time.as_millis(),
1682                incarnation
1683            );
1684        }
1685        time!(names::event_loop::REQUEST_TIME, request_time.as_millis());
1686        time!(names::event_loop::SERVICE_TIME, service_time.as_millis());
1687
1688        if let Some(backend_id) = self.backend_id.as_ref()
1689            && let Some(backend_response_time) = self.backend_response_time()
1690        {
1691            record_backend_metrics!(
1692                context.cluster_id.as_str_or("-"),
1693                backend_id,
1694                backend_response_time.as_millis(),
1695                self.backend_connection_time(),
1696                self.backend_header_time(),
1697                self.backend_bin,
1698                self.backend_bout,
1699                incarnation
1700            );
1701        }
1702
1703        incr!(
1704            names::access_logs::COUNT,
1705            context.cluster_id,
1706            context.backend_id,
1707            incarnation
1708        );
1709    }
1710}
1711
1712/// exponentially weighted moving average with high sensibility to latency bursts
1713///
1714/// It reads no clock: construction, [`Self::observe`] and [`Self::get`] each
1715/// take the instant they happen at, so the decay a simulator observes is a
1716/// function of the instants it supplies (#1684).
1717///
1718/// cf Finagle for the original implementation: <https://github.com/twitter/finagle/blob/9cc08d15216497bb03a1cafda96b7266cfbbcff1/finagle-core/src/main/scala/com/twitter/finagle/loadbalancer/PeakEwma.scala>
1719#[derive(Debug, PartialEq, Clone)]
1720pub struct PeakEWMA {
1721    /// decay in nanoseconds
1722    ///
1723    /// higher values will make the EWMA decay slowly to 0
1724    pub decay: f64,
1725    /// estimated RTT in nanoseconds
1726    ///
1727    /// must be set to a high enough default value so that new backends do not
1728    /// get all the traffic right away
1729    pub rtt: f64,
1730    /// last modification
1731    pub last_event: Instant,
1732}
1733
1734impl PeakEWMA {
1735    /// A fresh average created at `now`, which the first decay is measured
1736    /// from. Hardcoded default values for now.
1737    pub fn new(now: Instant) -> Self {
1738        PeakEWMA {
1739            // 1s
1740            decay: 1_000_000_000f64,
1741            // 50ms
1742            rtt: 50_000_000f64,
1743            last_event: now,
1744        }
1745    }
1746
1747    /// Fold an observed `rtt` (in nanoseconds) made at `now` into the average.
1748    ///
1749    /// An instant earlier than the last event decays nothing: the elapsed
1750    /// time saturates at zero.
1751    pub fn observe(&mut self, rtt: f64, now: Instant) {
1752        let dur = now.saturating_duration_since(self.last_event);
1753
1754        // if latency is rising, we will immediately raise the cost
1755        if rtt > self.rtt {
1756            self.rtt = rtt;
1757        } else {
1758            // new_rtt = old_rtt * e^(-elapsed/decay) + observed_rtt * (1 - e^(-elapsed/decay))
1759            let weight = (-(dur.as_nanos() as f64) / self.decay).exp();
1760            self.rtt = self.rtt * weight + rtt * (1.0 - weight);
1761        }
1762
1763        self.last_event = now;
1764    }
1765
1766    /// The cost of a backend with `active_requests` in flight, as seen at
1767    /// `now`.
1768    pub fn get(&mut self, active_requests: usize, now: Instant) -> f64 {
1769        // decay the current value
1770        // (we might not have seen a request in a long time)
1771        self.observe(0.0, now);
1772
1773        (active_requests + 1) as f64 * self.rtt
1774    }
1775}
1776
1777pub mod testing {
1778    pub use std::{cell::RefCell, os::fd::IntoRawFd, rc::Rc};
1779
1780    pub use anyhow::Context;
1781    pub use mio::{Poll, Registry, Token, net::UnixStream};
1782    pub use slab::Slab;
1783    pub use sozu_command::{
1784        proto::command::{
1785            HttpListenerConfig, HttpsListenerConfig, ServerConfig, TcpListenerConfig,
1786        },
1787        scm_socket::{Listeners, ScmSocket},
1788    };
1789
1790    pub use crate::{
1791        Protocol, ProxySession,
1792        backends::BackendMap,
1793        http::HttpProxy,
1794        https::HttpsProxy,
1795        pool::Pool,
1796        server::{ListenSession, ProxyChannel, Server, SessionManager},
1797        tcp::TcpProxy,
1798    };
1799
1800    use std::{
1801        collections::VecDeque,
1802        net::{SocketAddr, UdpSocket},
1803        sync::{Mutex, PoisonError},
1804    };
1805
1806    use socket2::{Domain, Socket, Type};
1807
1808    /// Ports handed to lib tests, below Linux's default ephemeral range
1809    /// (32768–60999) so no `127.0.0.1:0` client socket is auto-bound onto one
1810    /// between allocation and use, and above the fixed 10000-based sequence
1811    /// older revisions of this function used. The e2e suite draws from
1812    /// 20000–65000 through its own registry (`e2e/src/port_registry.rs`),
1813    /// which reserves through [`reserve_port`] too.
1814    const TEST_PORT_FIRST: u16 = 12_000;
1815    const TEST_PORT_LAST: u16 = 19_999;
1816
1817    /// Next candidate for [`provide_port`].
1818    static NEXT_TEST_PORT: Mutex<u16> = Mutex::new(TEST_PORT_FIRST);
1819
1820    /// How many reservations [`reserve_port`] holds at once. Each one is a
1821    /// descriptor: the full e2e suite reserves thousands of ports, which would
1822    /// exceed a default soft `RLIMIT_NOFILE` of 1024 if every reservation
1823    /// lived until the process exits. The oldest reservation is dropped to
1824    /// make room, so a test that releases its listener and binds it again
1825    /// only after this process has reserved this many further ports has lost
1826    /// its reservation in between.
1827    const MAX_HELD_RESERVATIONS: usize = 256;
1828
1829    /// The sockets [`reserve_port`] holds, oldest first.
1830    static PORT_RESERVATIONS: Mutex<VecDeque<Socket>> = Mutex::new(VecDeque::new());
1831
1832    /// Reserve `127.0.0.1:port` against the other processes on this machine
1833    /// that also reserve their ports through this function, until this
1834    /// process has reserved 256 more ports. Returns `false`, reserving
1835    /// nothing, when the port is already in use.
1836    ///
1837    /// A port counter alone is unique within one test binary only: two
1838    /// binaries running concurrently (several worktrees, parallel CI jobs,
1839    /// pre-push hooks) walked the same sequence, and since `server_bind` and
1840    /// `udp_bind` set `SO_REUSEPORT`, both processes bound the same address
1841    /// without error and the kernel split connections and datagrams between
1842    /// them. A port a test releases on purpose, for instance by deactivating
1843    /// a listener before activating it again, could likewise be taken by
1844    /// another process in between.
1845    ///
1846    /// The port is reserved only when an exclusive TCP bind (no
1847    /// `SO_REUSEADDR`, no `SO_REUSEPORT`) and an exclusive UDP bind on it both
1848    /// succeed. The TCP socket is then kept, bound and not listening, with
1849    /// `SO_REUSEADDR` and `SO_REUSEPORT` set so this process's own
1850    /// `server_bind`, `std::net::TcpListener::bind` and their equivalents
1851    /// still succeed on the port. Another process's exclusive TCP bind fails
1852    /// on it, so a process reserving through this function skips it; the
1853    /// reservation never listens, so it accepts no connection and a connect
1854    /// to an otherwise unused reserved port is still refused.
1855    ///
1856    /// Those same socket options mean the reservation does not stop a process
1857    /// that binds without reserving first: a `server_bind` (`SO_REUSEPORT`)
1858    /// from a test binary built before this function existed, or a
1859    /// `std::net::TcpListener::bind` (`SO_REUSEADDR`) while nothing listens on
1860    /// the port, still succeeds on it, including between a deactivate and the
1861    /// re-activate of a test's listener. The guarantee holds only between
1862    /// processes that all reserve their ports through this function, and only
1863    /// while the reservation is held (see `MAX_HELD_RESERVATIONS`). The UDP
1864    /// side is only probed, never held: a bound UDP socket sharing the port
1865    /// through `SO_REUSEPORT` would receive a share of the datagrams.
1866    pub fn reserve_port(port: u16) -> bool {
1867        let address = SocketAddr::from(([127, 0, 0, 1], port));
1868        let reservation = Socket::new(Domain::IPV4, Type::STREAM, None)
1869            .expect("could not create a port reservation socket");
1870        if reservation.bind(&address.into()).is_err() || UdpSocket::bind(address).is_err() {
1871            return false;
1872        }
1873        reservation
1874            .set_reuse_address(true)
1875            .and_then(|()| reservation.set_reuse_port(true))
1876            .expect("could not share the port reservation with this process");
1877        let mut reservations = PORT_RESERVATIONS
1878            .lock()
1879            .unwrap_or_else(PoisonError::into_inner);
1880        if reservations.len() == MAX_HELD_RESERVATIONS {
1881            reservations.pop_front();
1882        }
1883        reservations.push_back(reservation);
1884        true
1885    }
1886
1887    /// Get a localhost port for a test listener, held by [`reserve_port`]:
1888    /// unique across concurrent processes that all allocate their ports
1889    /// through [`reserve_port`], within that function's limits.
1890    pub fn provide_port() -> u16 {
1891        let mut next_port = NEXT_TEST_PORT
1892            .lock()
1893            .unwrap_or_else(PoisonError::into_inner);
1894        for _ in TEST_PORT_FIRST..=TEST_PORT_LAST {
1895            let candidate = *next_port;
1896            *next_port = if candidate == TEST_PORT_LAST {
1897                TEST_PORT_FIRST
1898            } else {
1899                candidate + 1
1900            };
1901            if reserve_port(candidate) {
1902                return candidate;
1903            }
1904        }
1905        panic!("no free localhost port left in {TEST_PORT_FIRST}..={TEST_PORT_LAST}");
1906    }
1907
1908    /// Everything needed to create a Server
1909    pub struct ServerParts {
1910        pub event_loop: Poll,
1911        pub registry: Registry,
1912        pub sessions: Rc<RefCell<SessionManager>>,
1913        pub pool: Rc<RefCell<Pool>>,
1914        pub backends: Rc<RefCell<BackendMap>>,
1915        pub client_scm_socket: ScmSocket,
1916        pub server_scm_socket: ScmSocket,
1917        pub server_config: ServerConfig,
1918    }
1919
1920    /// Setup a standalone server, for testing purposes
1921    pub fn prebuild_server(
1922        max_buffers: usize,
1923        buffer_size: usize,
1924        send_scm: bool,
1925    ) -> anyhow::Result<ServerParts> {
1926        let event_loop = Poll::new().with_context(|| "Failed at creating event loop")?;
1927        let backends = Rc::new(RefCell::new(BackendMap::new()));
1928        let server_config = ServerConfig {
1929            max_connections: max_buffers as u64,
1930            ..Default::default()
1931        };
1932
1933        let pool = Rc::new(RefCell::new(Pool::with_capacity(
1934            1,
1935            max_buffers,
1936            buffer_size,
1937        )));
1938
1939        let mut sessions: Slab<Rc<RefCell<dyn ProxySession>>> = Slab::with_capacity(max_buffers);
1940        {
1941            let entry = sessions.vacant_entry();
1942            info!("taking token {:?} for channel", entry.key());
1943            entry.insert(Rc::new(RefCell::new(ListenSession {
1944                protocol: Protocol::Channel,
1945            })));
1946        }
1947        {
1948            let entry = sessions.vacant_entry();
1949            info!("taking token {:?} for timer", entry.key());
1950            entry.insert(Rc::new(RefCell::new(ListenSession {
1951                protocol: Protocol::Timer,
1952            })));
1953        }
1954        {
1955            let entry = sessions.vacant_entry();
1956            info!("taking token {:?} for metrics", entry.key());
1957            entry.insert(Rc::new(RefCell::new(ListenSession {
1958                protocol: Protocol::Metrics,
1959            })));
1960        }
1961        // Test fixture: both limiters disabled (max_connections_per_ip
1962        // = 0, max_connections_per_subnet = 0) and the subnet prefixes
1963        // at their no-op widths.
1964        let sessions = SessionManager::new(sessions, max_buffers, 0, 0, 0, 32, 128);
1965
1966        let registry = event_loop
1967            .registry()
1968            .try_clone()
1969            .with_context(|| "Failed at creating a registry")?;
1970
1971        let (scm_server, scm_client) =
1972            UnixStream::pair().with_context(|| "Failed at creating scm unix stream")?;
1973        let client_scm_socket = ScmSocket::new(scm_client.into_raw_fd())
1974            .with_context(|| "Failed at creating the scm client socket")?;
1975        let server_scm_socket = ScmSocket::new(scm_server.into_raw_fd())
1976            .with_context(|| "Failed at creating the scm server socket")?;
1977        if send_scm {
1978            client_scm_socket
1979                .send_listeners(&Listeners::default())
1980                .with_context(|| "Failed at sending empty listeners")?;
1981        }
1982
1983        Ok(ServerParts {
1984            event_loop,
1985            registry,
1986            sessions,
1987            pool,
1988            backends,
1989            client_scm_socket,
1990            server_scm_socket,
1991            server_config,
1992        })
1993    }
1994}
1995
1996/// The counting global allocator of this crate's unit-test binary, so a test
1997/// can pin how many heap allocations a hot-path call makes.
1998///
1999/// A binary has exactly one global allocator, so every unit test in
2000/// `sozu-lib` that counts allocations goes through this module: read
2001/// [`allocations`] before and after the code under test and assert on the
2002/// difference. Never declare a second `#[global_allocator]` under
2003/// `#[cfg(test)]`; the test build then fails with "cannot define multiple
2004/// global allocators". [`bytes`] counts the sizes those allocations asked
2005/// for, for the code whose cost is the size of one allocation rather than
2006/// their number.
2007///
2008/// Every method forwards to [`std::alloc::System`] unchanged; the counter is
2009/// thread-local, so the harness's other test threads cannot pollute a
2010/// measurement. Same shape as the allocator `lib/tests/backend_selection.rs`
2011/// installs in its own binary.
2012#[cfg(test)]
2013pub(crate) mod test_allocations {
2014    use std::{
2015        alloc::{GlobalAlloc, Layout, System},
2016        cell::Cell,
2017    };
2018
2019    struct CountingAllocator;
2020
2021    thread_local! {
2022        static ALLOCATIONS: Cell<usize> = const { Cell::new(0) };
2023        static BYTES: Cell<usize> = const { Cell::new(0) };
2024    }
2025
2026    fn record_allocation(size: usize) {
2027        // `try_with`: the slot may already be torn down while a thread exits.
2028        let _ = ALLOCATIONS.try_with(|count| count.set(count.get() + 1));
2029        let _ = BYTES.try_with(|bytes| bytes.set(bytes.get() + size));
2030    }
2031
2032    // SAFETY: every method forwards to `System` unchanged; the only addition
2033    // is a thread-local counter increment, which neither allocates nor unwinds.
2034    unsafe impl GlobalAlloc for CountingAllocator {
2035        unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
2036            record_allocation(layout.size());
2037            unsafe { System.alloc(layout) }
2038        }
2039
2040        unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
2041            record_allocation(layout.size());
2042            unsafe { System.alloc_zeroed(layout) }
2043        }
2044
2045        unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
2046            record_allocation(new_size);
2047            unsafe { System.realloc(ptr, layout, new_size) }
2048        }
2049
2050        unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
2051            unsafe { System.dealloc(ptr, layout) }
2052        }
2053    }
2054
2055    #[global_allocator]
2056    static GLOBAL: CountingAllocator = CountingAllocator;
2057
2058    /// Heap allocations the calling thread has made so far.
2059    pub(crate) fn allocations() -> usize {
2060        ALLOCATIONS.with(Cell::get)
2061    }
2062
2063    /// Bytes the calling thread's allocations have asked for so far: the
2064    /// requested size of each allocation, and the new size of each
2065    /// reallocation.
2066    pub(crate) fn bytes() -> usize {
2067        BYTES.with(Cell::get)
2068    }
2069}
2070
2071#[cfg(test)]
2072pub(crate) fn capture_test_logs(run: impl FnOnce() + Send + 'static) -> String {
2073    capture_test_logs_at_level("info", run)
2074}
2075
2076#[cfg(test)]
2077pub(crate) fn capture_test_logs_at_level(
2078    level: &'static str,
2079    run: impl FnOnce() + Send + 'static,
2080) -> String {
2081    let receiver = std::net::UdpSocket::bind("127.0.0.1:0")
2082        .expect("test log receiver must bind to a loopback port");
2083    let target = format!(
2084        "udp://{}",
2085        receiver
2086            .local_addr()
2087            .expect("test log receiver must have a local address")
2088    );
2089
2090    std::thread::spawn(move || {
2091        sozu_command::logging::Logger::init(
2092            "log-redaction-test".to_owned(),
2093            level,
2094            &target,
2095            false,
2096            None,
2097            None,
2098            None,
2099        )
2100        .expect("test logger must initialize");
2101        run();
2102    })
2103    .join()
2104    .expect("log-producing test thread must not panic");
2105
2106    receiver
2107        .set_nonblocking(true)
2108        .expect("test log receiver must become nonblocking");
2109    let mut output = String::new();
2110    let mut datagram = vec![0; 65_507];
2111    loop {
2112        match receiver.recv(&mut datagram) {
2113            Ok(length) => output.push_str(&String::from_utf8_lossy(&datagram[..length])),
2114            Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => break,
2115            Err(error) => panic!("test log receiver failed: {error}"),
2116        }
2117    }
2118    assert!(!output.is_empty(), "test log capture received no datagrams");
2119    output
2120}
2121
2122#[cfg(test)]
2123mod log_redaction_tests {
2124    use super::*;
2125
2126    #[test]
2127    fn listener_template_parse_error_redacts_custom_answer_key() {
2128        const KEY_SECRET: &str = "CUSTOM_ANSWER_KEY_SECRET_SENTINEL";
2129
2130        let key = format!("{KEY_SECRET}{}", "x".repeat(4096));
2131        let key_len = key.len();
2132        let error = ListenerError::TemplateParse(key, TemplateError::InvalidType);
2133
2134        for (label, output) in [
2135            ("Display", error.to_string()),
2136            ("Debug", format!("{error:?}")),
2137        ] {
2138            assert!(
2139                !output.contains(KEY_SECRET),
2140                "ListenerError {label} leaked custom-answer key {KEY_SECRET}"
2141            );
2142            assert!(
2143                output.contains(&format!("key_bytes={key_len}")),
2144                "ListenerError {label} omitted the bounded key length: {output}"
2145            );
2146            assert!(
2147                output.len() <= 256,
2148                "ListenerError {label} output is not bounded: {} bytes",
2149                output.len()
2150            );
2151        }
2152    }
2153
2154    #[test]
2155    fn sni_authority_mismatch_retains_inputs_but_bounds_textual_formatting() {
2156        const SNI_SECRET: &str = "SNI_MISMATCH_SECRET_SENTINEL";
2157        const AUTHORITY_SECRET: &str = "AUTHORITY_MISMATCH_SECRET_SENTINEL";
2158
2159        let sni = format!("{SNI_SECRET}{}", "x".repeat(4096));
2160        let authority = format!("{AUTHORITY_SECRET}{}", "x".repeat(4096));
2161        let error = RetrieveClusterError::SniAuthorityMismatch {
2162            sni: sni.clone(),
2163            authority: authority.clone(),
2164        };
2165
2166        match &error {
2167            RetrieveClusterError::SniAuthorityMismatch {
2168                sni: retained_sni,
2169                authority: retained_authority,
2170            } => {
2171                assert_eq!(retained_sni, &sni);
2172                assert_eq!(retained_authority, &authority);
2173            }
2174            other => panic!("expected SniAuthorityMismatch, got {other:?}"),
2175        }
2176
2177        for output in [error.to_string(), format!("{error:?}")] {
2178            for secret in [SNI_SECRET, AUTHORITY_SECRET] {
2179                assert!(
2180                    !output.contains(secret),
2181                    "SNI mismatch formatting leaked {secret}: {output}"
2182                );
2183            }
2184            for metadata in [
2185                format!("sni_bytes={}", sni.len()),
2186                format!("authority_bytes={}", authority.len()),
2187            ] {
2188                assert!(
2189                    output.contains(&metadata),
2190                    "SNI mismatch formatting omitted {metadata}: {output}"
2191                );
2192            }
2193            assert!(
2194                output.len() <= 256,
2195                "SNI mismatch formatting is not bounded: {} bytes",
2196                output.len()
2197            );
2198        }
2199    }
2200}
2201
2202#[cfg(test)]
2203mod backend_header_time_tests {
2204    use super::*;
2205
2206    /// A protocol with no response headers at all — raw TCP
2207    /// (`sozu_lib::tcp::TcpSession`, which calls
2208    /// `SessionMetrics::register_end_of_session` like the L7 paths do) — must
2209    /// record NOTHING under `backend_header_time`, not a zero and not an
2210    /// elapsed-so-far value.
2211    ///
2212    /// This is the whole of the `None`-fallback decision, in its only
2213    /// observable shape: `backend_response_time` deliberately answers
2214    /// `Instant::now() - connected` when `backend_stop` is absent, and
2215    /// `backend_header_time` deliberately does not. Without the distinction a
2216    /// TCP session would publish a header time that is really its whole
2217    /// lifetime, under a key whose name promises a time to first header byte.
2218    ///
2219    /// To SEE THIS RED: add `(Some(connected), None) => Some(Instant::now() -
2220    /// connected),` to the match in `SessionMetrics::backend_header_time`,
2221    /// mirroring the arm `SessionMetrics::backend_response_time` carries.
2222    #[test]
2223    fn a_session_that_received_no_response_headers_reports_no_header_time() {
2224        let mut metrics = SessionMetrics::new(None);
2225        metrics.backend_start();
2226        metrics.backend_connected();
2227        metrics.backend_stop();
2228
2229        // Premise: this session is otherwise fully timed, so a `None` below
2230        // cannot come from a missing connection instant.
2231        assert!(
2232            metrics.backend_connection_time().is_some(),
2233            "premise: the connection time must be available"
2234        );
2235        assert!(
2236            metrics.backend_response_time().is_some(),
2237            "premise: the response time must be available"
2238        );
2239
2240        assert_eq!(
2241            metrics.backend_header_time(),
2242            None,
2243            "a session whose backend produced no response headers must report \
2244             no header time"
2245        );
2246    }
2247
2248    /// `SessionMetrics::reset` recycles one `SessionMetrics` across the
2249    /// requests of an H1 keep-alive connection, so a field it forgets is a
2250    /// field the NEXT request inherits. `backend_response_time` was miscounted
2251    /// exactly this way once (commit `5f3371b7`); this pins that the new
2252    /// instant joins the three it sits beside.
2253    ///
2254    /// To SEE THIS RED: drop `self.backend_headers_received = None;` from
2255    /// `SessionMetrics::reset`.
2256    #[test]
2257    fn reset_clears_the_backend_headers_instant_with_its_siblings() {
2258        let mut metrics = SessionMetrics::new(None);
2259        metrics.backend_start();
2260        metrics.backend_connected();
2261        metrics.backend_headers_received();
2262        assert!(
2263            metrics.backend_header_time().is_some(),
2264            "premise: the first request must have a header time to forget"
2265        );
2266
2267        metrics.reset();
2268
2269        assert_eq!(
2270            metrics.backend_headers_received, None,
2271            "reset must clear the headers instant, or the next keep-alive \
2272             request inherits the previous response's headers"
2273        );
2274        // Restaged as a fresh request would: connected now, no headers yet.
2275        metrics.backend_connected();
2276        assert_eq!(
2277            metrics.backend_header_time(),
2278            None,
2279            "a recycled SessionMetrics must not report a header time earned by \
2280             the previous request"
2281        );
2282    }
2283}