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