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