Skip to main content

sozu_lib/
lib.rs

1//! ## What this library does
2//!
3//! This library provides tools to build and start HTTP, HTTPS and TCP reverse proxies.
4//!
5//! The proxies handles network polling, HTTP parsing, TLS in a fast single threaded event
6//! loop.
7//!
8//! Each proxy is designed to receive configuration changes at runtime instead of
9//! reloading from a file regularly. The event loop runs in its own thread
10//! and receives commands through a message queue.
11//!
12//! ## Difference with the crate `sozu`
13//!
14//! To create several workers and manage them all at once (which is the most common way to
15//! use Sōzu), the crate `sozu` is more indicated than using the lib directly.
16//!
17//! The crate `sozu` provides a binary called the main process.
18//! The main process uses `sozu_lib` to start and manage workers.
19//! Each worker can handle HTTP, HTTPS and TCP traffic.
20//! The main process receives synchronizes the state of all workers, using UNIX sockets
21//! and custom channels to communicate with them.
22//! The main process itself is is configurable with a file, and has a CLI.
23//!
24//! ## How to use this library directly
25//!
26//! This documentation here explains how to write a binary that will start a single Sōzu
27//! worker and give it orders. The method has two steps:
28//!
29//! 1. Starts a Sōzu worker in a distinct thread
30//! 2. sends instructions to the worker on a UNIX socket via a Sōzu channel
31//!
32//! ### How to start a Sōzu worker
33//!
34//! Before creating an HTTP proxy, we first need to create an HTTP listener.
35//! The listener is an abstraction around a TCP socket provided by the kernel.
36//! We need the `sozu_command_lib` to build a listener.
37//!
38//! ```
39//! use sozu_command_lib::{config::ListenerBuilder, proto::command::SocketAddress};
40//!
41//! let address = SocketAddress::new_v4(127,0,0,1,8080);
42//! let http_listener = ListenerBuilder::new_http(address)
43//!     .to_http(None)
44//!     .expect("Could not create HTTP listener");
45//! ```
46//!
47//! The `http_listener` is of the type `HttpListenerConfig`, that we can be sent to the worker
48//! to start the proxy.
49//!
50//! Then create a pair of channels to communicate with the proxy.
51//! The channel is a wrapper around a unix socket.
52//!
53//! ```ignore
54//! use sozu_command_lib::{
55//!     channel::Channel,
56//!     proto::command::{WorkerRequest, WorkerResponse},
57//! };
58//!
59//! let (mut command_channel, proxy_channel): (
60//!     Channel<WorkerRequest, WorkerResponse>,
61//!     Channel<WorkerResponse, WorkerRequest>,
62//! ) = Channel::generate(1000, 10000).expect("should create a channel");
63//!```
64//!
65//! Here, the `command_channel` end is blocking, it sends `WorkerRequest`s and receives
66//! `WorkerResponses`, while the `proxy_channel` end is non-blocking, and the types are reversed.
67//! Writing the types here isn't even necessary thanks to the compiler,
68//! but it brings the point accross.
69//!
70//! You can now launch the worker in a separate thread, providing the HTTP listener config,
71//! the proxy end of the channel, and your custom number of buffers and their size:
72//!
73//! ```ignore
74//! use std::thread;
75//!
76//! let worker_thread_join_handle = thread::spawn(move || {
77//!     let max_buffers = 500;
78//!     let buffer_size = 16384;
79//!     sozu_lib::http::testing::start_http_worker(http_listener, proxy_channel, max_buffers, buffer_size);
80//! });
81//! ```
82//!
83//! ### Send orders
84//!
85//! Once the thread is launched, the proxy worker will start its event loop and handle
86//! events on the listening interface and port specified when building the HTTP Listener.
87//! Since no frontends or backends were specified for the proxy, it will receive
88//! the connections, parse the requests, then send a default (but configurable)
89//! answer.
90//!
91//! Before defining a frontend and backends, we need to define a cluster, which describes
92//! a routing configuration. A cluster contains:
93//!
94//! - one frontend
95//! - one or several backends
96//! - routing rules
97//!
98//! A cluster is identified by its `cluster_id`, which will be used to define frontends
99//! and backends later on.
100//!
101//! ```
102//! use sozu_command_lib::proto::command::{Cluster, LoadBalancingAlgorithms};
103//!
104//! let cluster = Cluster {
105//!     cluster_id: "my-cluster".to_string(),
106//!     sticky_session: false,
107//!     https_redirect: false,
108//!     load_balancing: LoadBalancingAlgorithms::RoundRobin as i32,
109//!     answer_503: Some("A custom forbidden message".to_string()),
110//!     ..Default::default()
111//! };
112//! ```
113//!
114//! The defaults are sensible, so we could define only the `cluster_id`.
115//!
116//! We can now define a frontend. A frontend is a way to recognize a request and match
117//! it to a `cluster_id`, depending on the hostname and the beginning of the URL path.
118//! The `address` field must match the one of the HTTP listener we defined before:
119//!
120//! ```
121//! use std::collections::BTreeMap;
122//!
123//!  use sozu_command_lib::proto::command::{PathRule, RequestHttpFrontend, RulePosition, SocketAddress};
124//!
125//! let http_front = RequestHttpFrontend {
126//!     cluster_id: Some("my-cluster".to_string()),
127//!     address: SocketAddress::new_v4(127,0,0,1,8080),
128//!     hostname: "example.com".to_string(),
129//!     path: PathRule::prefix(String::from("/")),
130//!     position: RulePosition::Pre.into(),
131//!     tags: BTreeMap::from([
132//!         ("owner".to_owned(), "John".to_owned()),
133//!         ("id".to_owned(), "my-own-http-front".to_owned()),
134//!     ]),
135//!     ..Default::default()
136//! };
137//! ```
138//!
139//! The `tags` are keys and values that will appear in the access logs,
140//! which can come in handy.
141//!
142//! Now let's define a backend.
143//! A backend is an instance of a backend application we want to route traffic to.
144//! The `address` field must match the IP and port of the backend server.
145//!
146//! ```
147//! use sozu_command_lib::proto::command::{AddBackend, LoadBalancingParams, SocketAddress};
148//!
149//! let http_backend = AddBackend {
150//!     cluster_id: "my-cluster".to_string(),
151//!     backend_id: "test-backend".to_string(),
152//!     address: SocketAddress::new_v4(127,0,0,1,8000),
153//!     load_balancing_parameters: Some(LoadBalancingParams::default()),
154//!     ..Default::default()
155//! };
156//! ```
157//!
158//! A cluster can have multiple backend servers, and they can be added or
159//! removed while the proxy is running. If a backend is removed from the configuration
160//! while the proxy is handling a request to that server, it will finish that
161//! request and stop sending new traffic to that server.
162//!
163//!
164//! Now we can use the other end of the channel to send all these requests to the worker,
165//! using the WorkerRequest type:
166//!
167//! ```ignore
168//! use sozu_command_lib::{
169//!     proto::command::{Request, request::RequestType, WorkerRequest},
170//! };
171//!
172//! command_channel
173//!     .write_message(&WorkerRequest {
174//!         id: String::from("add-the-cluster"),
175//!         content: RequestType::AddCluster(cluster).into(),
176//!     })
177//!     .expect("Could not send AddHttpFrontend request");
178//!
179//! command_channel
180//!     .write_message(&WorkerRequest {
181//!         id: String::from("add-the-frontend"),
182//!         content: RequestType::AddHttpFrontend(http_front).into(),
183//!     })
184//!     .expect("Could not send AddHttpFrontend request");
185//!
186//! command_channel
187//!     .write_message(&WorkerRequest {
188//!         id: String::from("add-the-backend"),
189//!         content: RequestType::AddBackend(http_backend).into(),
190//!     })
191//!     .expect("Could not send AddBackend request");
192//!
193//! println!("HTTP -> {:?}", command_channel.read_message());
194//! println!("HTTP -> {:?}", command_channel.read_message());
195//! println!("HTTP -> {:?}", command_channel.read_message());
196//! ```
197//!
198//!
199//! The event loop of the worker will process these instructions and add them to
200//! its state, and the worker will send back an acknowledgement
201//! message.
202//!
203//! Now we can let the worker thread run in the background:
204//!
205//! ```ignore
206//! let _ = worker_thread_join_handle.join();
207//! ```
208//!
209//! Here is the complete example for reference, it matches the `examples/http.rs` example:
210//!
211//! ```
212//! #[macro_use]
213//! extern crate sozu_command_lib;
214//!
215//! use std::{collections::BTreeMap, env, io::stdout, thread};
216//!
217//! use anyhow::Context;
218//! use sozu_command_lib::{
219//!     channel::Channel,
220//!     config::ListenerBuilder,
221//!     logging::setup_default_logging,
222//!     proto::command::{
223//!         request::RequestType, AddBackend, Cluster, LoadBalancingAlgorithms, LoadBalancingParams,
224//!         PathRule, Request, RequestHttpFrontend, RulePosition, SocketAddress,WorkerRequest,
225//!     },
226//! };
227//!
228//! fn main() -> anyhow::Result<()> {
229//!     setup_default_logging(true, "info", "EXAMPLE").with_context(|| "could not setup logging")?;
230//!
231//!     info!("starting up");
232//!
233//!     let http_listener = ListenerBuilder::new_http(SocketAddress::new_v4(127,0,0,1,8080))
234//!         .to_http(None)
235//!         .expect("Could not create HTTP listener");
236//!
237//!     let (mut command_channel, proxy_channel) =
238//!         Channel::generate(1000, 10000).with_context(|| "should create a channel")?;
239//!
240//!     let worker_thread_join_handle = thread::spawn(move || {
241//!         let max_buffers = 500;
242//!         let buffer_size = 16384;
243//!         sozu_lib::http::testing::start_http_worker(http_listener, proxy_channel, max_buffers, buffer_size)
244//!             .expect("The worker could not be started, or shut down");
245//!     });
246//!
247//!     let cluster = Cluster {
248//!         cluster_id: "my-cluster".to_string(),
249//!         sticky_session: false,
250//!         https_redirect: false,
251//!         load_balancing: LoadBalancingAlgorithms::RoundRobin as i32,
252//!         answer_503: Some("A custom forbidden message".to_string()),
253//!         ..Default::default()
254//!     };
255//!
256//!     let http_front = RequestHttpFrontend {
257//!         cluster_id: Some("my-cluster".to_string()),
258//!         address: SocketAddress::new_v4(127,0,0,1,8080),
259//!         hostname: "example.com".to_string(),
260//!         path: PathRule::prefix(String::from("/")),
261//!         position: RulePosition::Pre.into(),
262//!         tags: BTreeMap::from([
263//!             ("owner".to_owned(), "John".to_owned()),
264//!             ("id".to_owned(), "my-own-http-front".to_owned()),
265//!         ]),
266//!         ..Default::default()
267//!     };
268//!     let http_backend = AddBackend {
269//!         cluster_id: "my-cluster".to_string(),
270//!         backend_id: "test-backend".to_string(),
271//!         address: SocketAddress::new_v4(127,0,0,1,8000),
272//!         load_balancing_parameters: Some(LoadBalancingParams::default()),
273//!         ..Default::default()
274//!     };
275//!
276//!     command_channel
277//!         .write_message(&WorkerRequest {
278//!             id: String::from("add-the-cluster"),
279//!             content: RequestType::AddCluster(cluster).into(),
280//!         })
281//!         .expect("Could not send AddHttpFrontend request");
282//!
283//!     command_channel
284//!         .write_message(&WorkerRequest {
285//!             id: String::from("add-the-frontend"),
286//!             content: RequestType::AddHttpFrontend(http_front).into(),
287//!         })
288//!         .expect("Could not send AddHttpFrontend request");
289//!
290//!     command_channel
291//!         .write_message(&WorkerRequest {
292//!             id: String::from("add-the-backend"),
293//!             content: RequestType::AddBackend(http_backend).into(),
294//!         })
295//!         .expect("Could not send AddBackend request");
296//!
297//!     println!("HTTP -> {:?}", command_channel.read_message());
298//!     println!("HTTP -> {:?}", command_channel.read_message());
299//!
300//!     // uncomment to let it run in the background
301//!     // let _ = worker_thread_join_handle.join();
302//!     info!("good bye");
303//!     Ok(())
304//! }
305//! ```
306
307#[macro_use]
308extern crate sozu_command_lib as sozu_command;
309
310#[macro_use]
311pub mod util;
312#[macro_use]
313pub mod metrics;
314
315pub mod backends;
316pub mod crypto;
317pub mod features;
318pub mod health_check;
319pub mod http;
320pub mod load_balancing;
321pub mod pool;
322pub mod protocol;
323pub mod retry;
324pub mod router;
325pub mod socket;
326pub mod timer;
327pub mod tls;
328
329/// Linux zero-copy TCP forwarder. Used by `protocol::pipe::Pipe` when
330/// the listener is `Protocol::TCP` and the `splice` feature is enabled.
331#[cfg(all(target_os = "linux", feature = "splice"))]
332pub(crate) mod splice;
333
334pub mod server;
335pub mod tcp;
336pub mod udp;
337
338pub mod https;
339
340use std::{
341    cell::RefCell,
342    collections::{BTreeMap, HashMap},
343    fmt::{self, Display, Formatter},
344    net::SocketAddr,
345    rc::Rc,
346    str,
347    time::{Duration, Instant, SystemTime},
348};
349
350use backends::BackendError;
351use hex::FromHexError;
352use mio::{Interest, Token, net::TcpStream};
353use protocol::http::{answers::HttpAnswers, answers::TemplateError, parser::Method};
354use router::RouterError;
355use socket::ServerBindError;
356use sozu_command::{
357    AsStr, ObjectKind,
358    logging::{CachedTags, LogContext},
359    proto::command::{Cluster, ListenerType, RequestHttpFrontend, WorkerRequest, WorkerResponse},
360    ready::Ready,
361    state::ClusterId,
362};
363use tls::CertificateResolverError;
364
365use crate::{backends::BackendMap, metrics::names, router::RouteResult};
366
367/// Anything that can be registered in mio (subscribe to kernel events)
368#[derive(Debug, Clone, Copy, PartialEq, Eq)]
369pub enum Protocol {
370    HTTP,
371    HTTPS,
372    TCP,
373    UDP,
374    HTTPListen,
375    HTTPSListen,
376    TCPListen,
377    UDPListen,
378    Channel,
379    Metrics,
380    Timer,
381}
382
383/// trait that must be implemented by listeners and client sessions
384pub trait ProxySession {
385    /// indicates the protocol associated with the session
386    ///
387    /// this is used to distinguish sessions from listenrs, channels, metrics
388    /// and timers
389    fn protocol(&self) -> Protocol;
390    /// if a session received an event or can still execute, the event loop will
391    /// call this method. Its result indicates if it can still execute, needs to
392    /// connect to a backend server, close the session
393    fn ready(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed;
394    /// if the event loop got an event for a token associated with the session,
395    /// it will call this method on the session
396    fn update_readiness(&mut self, token: Token, events: Ready);
397    /// close a session, frontend and backend sockets,
398    /// remove the entries from the session manager slab
399    fn close(&mut self);
400    /// if a timeout associated with the session triggers, the event loop will
401    /// call this method with the timeout's token
402    fn timeout(&mut self, t: Token) -> SessionIsToBeClosed;
403    /// last time the session got an event
404    fn last_event(&self) -> Instant;
405    /// display the session's internal state (for debugging purpose)
406    fn print_session(&self);
407    /// get the token associated with the frontend
408    fn frontend_token(&self) -> Token;
409    /// tell the session it has to shut down if possible
410    ///
411    /// if the session handles HTTP requests, it will not close until the response
412    /// is completely sent back to the client
413    fn shutting_down(&mut self) -> SessionIsToBeClosed;
414    /// Best-effort identifier of the cluster currently routed to by this
415    /// session. Returns `None` for `ListenSession` (no per-session
416    /// cluster), and for client sessions before routing has resolved.
417    /// H2 sessions multiplex many streams over one frontend token and may
418    /// touch several clusters; the returned value is whichever cluster
419    /// the session most recently keep-alive'd to. Used for log/metric
420    /// attribution, not for accounting (the tracker keeps the canonical
421    /// per-stream `(cluster, IP)` set).
422    fn cluster_id(&self) -> Option<String> {
423        None
424    }
425    /// Source address as observed by Sōzu, with proxy-protocol awareness.
426    /// HTTP/HTTPS/TCP client sessions return the parsed PROXY-protocol
427    /// source when present, else `peer_addr`. `ListenSession` returns
428    /// `None`. Used to attribute per-(cluster, source-IP) tracking and
429    /// access logs to the real client behind a layer-4 PROXY frontend.
430    fn session_address(&self) -> Option<SocketAddr> {
431        None
432    }
433}
434
435#[macro_export]
436macro_rules! branch {
437    (if $($value:ident)? == $expected:ident { $($then:tt)* } else { $($else:tt)* }) => {
438        macro_rules! expect {
439            ($expected) => {$($then)*};
440            ($a:ident) => {$($else)*};
441            () => {$($else)*}
442        }
443        expect!($($value)?);
444    };
445    (if $($value:ident)? == $expected:ident { $($then:tt)* } ) => {
446        macro_rules! expect {
447            ($expected) => {$($then)*};
448        }
449        expect!($($value)?);
450    };
451}
452
453#[macro_export]
454macro_rules! fallback {
455    ({} $($default:tt)*) => {
456        $($default)*
457    };
458    ({$($value:tt)+} $($default:tt)*) => {
459        $($value)+
460    };
461}
462
463#[macro_export]
464macro_rules! StateMachineBuilder {
465    (
466        ($d:tt)
467        $(#[$($state_macros:tt)*])*
468        enum $state_name:ident $(impl $trait:ident)?  {
469            $($(#[$($variant_macros:tt)*])*
470            $variant_name:ident($state:ty$(,$($aux:ty),+)?) $(-> $override:expr)?),+ $(,)?
471        }
472    ) => {
473        /// A summary of the last valid State
474        #[derive(Clone, Copy, Debug)]
475        pub enum StateMarker {
476            $($variant_name,)+
477        }
478
479        $(#[$($state_macros)*])*
480        #[allow(clippy::large_enum_variant)]
481        pub enum $state_name {
482            $(
483                $(#[$($variant_macros)*])*
484                $variant_name($state$(,$($aux),+)?),
485            )+
486            /// Informs about upgrade failure, contains a summary the last valid State
487            FailedUpgrade(StateMarker),
488        }
489
490        macro_rules! _fn_impl {
491            ($function:ident(&$d($mut:ident)?, self $d(,$arg_name:ident: $arg_type:ty)*) $d(-> $ret:ty)? $d(| $marker:tt => $fail:expr)?) => {
492                fn $function(&$d($mut)? self $d(,$arg_name: $arg_type)*) $d(-> $ret)? {
493                    match self {
494                        $($state_name::$variant_name(_state, ..) => $crate::fallback!({$($override)?} _state.$function($d($arg_name),*)),)+
495                        $state_name::FailedUpgrade($crate::fallback!({$d($marker)?} _)) => $crate::fallback!({$d($fail)?} unreachable!())
496                    }
497                }
498            };
499        }
500
501        impl $state_name {
502            /// Informs about the last valid State before upgrade failure
503            fn marker(&self) -> StateMarker {
504                match self {
505                    $($state_name::$variant_name(..) => StateMarker::$variant_name,)+
506                    $state_name::FailedUpgrade(marker) => *marker,
507                }
508            }
509            /// Returns wether or not the State is FailedUpgrade
510            fn failed(&self) -> bool {
511                match self {
512                    $state_name::FailedUpgrade(_) => true,
513                    _ => false,
514                }
515            }
516            /// Gives back an owned version of the State,
517            /// leaving a FailedUpgrade in its place.
518            /// The FailedUpgrade retains the marker of the previous State.
519            fn take(&mut self) -> $state_name {
520                let mut owned_state = $state_name::FailedUpgrade(self.marker());
521                std::mem::swap(&mut owned_state, self);
522                owned_state
523            }
524            _fn_impl!{front_socket(&, self) -> &mio::net::TcpStream}
525        }
526
527        $crate::branch!{
528            if $($trait)? == SessionState {
529                impl SessionState for $state_name {
530                    _fn_impl!{ready(&mut, self, session: Rc<RefCell<dyn ProxySession>>, proxy: Rc<RefCell<dyn L7Proxy>>, metrics: &mut SessionMetrics) -> SessionResult}
531                    _fn_impl!{update_readiness(&mut, self, token: Token, events: Ready)}
532                    _fn_impl!{timeout(&mut, self, token: Token, metrics: &mut SessionMetrics) -> StateResult}
533                    _fn_impl!{cancel_timeouts(&mut, self)}
534                    _fn_impl!{print_state(&, self, context: &str) | marker => error!("{} Session(FailedUpgrade({:?}))", context, marker)}
535                    _fn_impl!{close(&mut, self, proxy: Rc<RefCell<dyn L7Proxy>>, metrics: &mut SessionMetrics) | _ => {}}
536                    _fn_impl!{shutting_down(&mut, self) -> SessionIsToBeClosed | _ => true}
537                }
538            } else {}
539        }
540    };
541    ($($tt:tt)+) => {
542        StateMachineBuilder!{($) $($tt)+}
543    }
544}
545
546pub trait ListenerHandler {
547    fn get_addr(&self) -> &SocketAddr;
548
549    fn get_tags(&self, key: &str) -> Option<&CachedTags>;
550
551    fn get_concatenated_tags(&self, key: &str) -> Option<&str> {
552        self.get_tags(key).map(|tags| tags.concatenated.as_str())
553    }
554
555    fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>);
556
557    fn protocol(&self) -> Protocol;
558
559    fn public_address(&self) -> SocketAddr;
560}
561
562#[derive(thiserror::Error, Debug)]
563pub enum FrontendFromRequestError {
564    #[error("Could not parse hostname from '{host}': {error}")]
565    HostParse { host: String, error: String },
566    #[error("invalid remaining chars after hostname. Host: {0}")]
567    InvalidCharsAfterHost(String),
568    #[error("no cluster: {0}")]
569    NoClusterFound(RouterError),
570}
571
572pub trait L7ListenerHandler {
573    fn get_sticky_name(&self) -> &str;
574
575    /// Name of the correlation header Sozu injects into every request and
576    /// response body. Default: `"Sozu-Id"`. Operators can rebrand via the
577    /// `sozu_id_header` listener config knob.
578    fn get_sozu_id_header(&self) -> &str {
579        "Sozu-Id"
580    }
581
582    fn get_connect_timeout(&self) -> u32;
583
584    /// retrieve a frontend by parsing a request's hostname, uri and method
585    fn frontend_from_request(
586        &self,
587        host: &str,
588        uri: &str,
589        method: &Method,
590    ) -> Result<RouteResult, FrontendFromRequestError>;
591
592    /// retrieve the listener's configured HTTP answers (templates)
593    fn get_answers(&self) -> &Rc<RefCell<HttpAnswers>>;
594
595    /// H2 flood detection thresholds from the listener config.
596    /// Returns the default config when the listener does not provide custom values.
597    fn get_h2_flood_config(&self) -> protocol::mux::H2FloodConfig {
598        protocol::mux::H2FloodConfig::default()
599    }
600
601    /// H2 connection tuning from the listener config.
602    /// Returns the default config when the listener does not provide custom values.
603    fn get_h2_connection_config(&self) -> protocol::mux::H2ConnectionConfig {
604        protocol::mux::H2ConnectionConfig::default()
605    }
606
607    /// Whether requests must have their `:authority` / `Host` exact-match
608    /// the TLS SNI negotiated at handshake (CWE-346 / CWE-444).
609    ///
610    /// Defaults to `true` — the safe setting that closes the
611    /// CWE-346 / CWE-444 cross-SNI smuggling vector. Operators can opt
612    /// out per-listener via `HttpsListenerConfig::strict_sni_binding =
613    /// false` when cross-SNI routing is explicitly required. Plaintext
614    /// HTTP listeners return the default value; they never have an SNI
615    /// to compare against, so the routing-layer check short-circuits on
616    /// `tls_server_name: None`.
617    fn get_strict_sni_binding(&self) -> bool {
618        true
619    }
620
621    /// Whether to strip any client-supplied `X-Real-IP` header from
622    /// forwarded requests (anti-spoofing).
623    ///
624    /// Defaults to `false` — preserves the historical pass-through
625    /// behaviour. Operators opt in via
626    /// `HttpListenerConfig::elide_x_real_ip = true` (and the equivalent on
627    /// HTTPS listeners). Independent of [`Self::get_send_x_real_ip`]: the
628    /// two flags can be combined freely (anti-spoof only, send only, both,
629    /// or neither). The elision branch lives in
630    /// `HttpContext::on_request_headers`, so it covers H1 and H2 alike.
631    fn get_elide_x_real_ip(&self) -> bool {
632        false
633    }
634
635    /// Whether to append a proxy-generated `X-Real-IP` header carrying the
636    /// connection peer IP (post-PROXY-v2 unwrap, i.e. the original client
637    /// IP) to every forwarded request.
638    ///
639    /// Defaults to `false` — preserves the historical no-injection
640    /// behaviour. Operators opt in via
641    /// `HttpListenerConfig::send_x_real_ip = true` (and the equivalent on
642    /// HTTPS listeners). Independent of [`Self::get_elide_x_real_ip`]: the
643    /// two flags can be combined freely. The injection branch lives next
644    /// to the existing X-Forwarded-For / Forwarded synthesis in
645    /// `HttpContext::on_request_headers`.
646    fn get_send_x_real_ip(&self) -> bool {
647        false
648    }
649
650    /// Per-stream idle timeout for H2 connections. An open stream that makes
651    /// no forward progress for this duration is cancelled (RST_STREAM / CANCEL).
652    /// Mitigates slow-multiplex Slowloris where a client keeps connection-level
653    /// activity high (resetting the connection idle timer on every frame) while
654    /// pinning streams for the full nominal connection timeout.
655    ///
656    /// Listeners inherit `max(30s, back_timeout)` when `h2_stream_idle_timeout_seconds`
657    /// is absent so operators who raised the socket-level backend budget do not
658    /// have to duplicate the value here; the 30 s floor preserves the baseline
659    /// slow-multiplex mitigation when `back_timeout` is shorter. Set the knob
660    /// explicitly to cap the per-stream deadline below `back_timeout` (useful
661    /// when under a slow-multiplex attack).
662    fn get_h2_stream_idle_timeout(&self) -> std::time::Duration {
663        std::time::Duration::from_secs(30)
664    }
665
666    /// Wall-clock budget granted to in-flight H2 streams after soft-stop sent
667    /// the initial `GOAWAY(NO_ERROR)`. Once the deadline elapses the mux
668    /// transitions to a forced close (final GOAWAY + session teardown).
669    ///
670    /// Returning `None` disables the forced close entirely — shutdown waits
671    /// for every stream to drain naturally. Returning `Some(d)` enforces the
672    /// budget. Default: `Some(Duration::from_secs(5))` (matches the historic
673    /// hard-coded 5 s deadline). Listeners expose the
674    /// `h2_graceful_shutdown_deadline_seconds` knob; value `0` maps to `None`.
675    fn get_h2_graceful_shutdown_deadline(&self) -> Option<std::time::Duration> {
676        Some(std::time::Duration::from_secs(5))
677    }
678}
679
680#[derive(Clone, Copy, Debug, PartialEq, Eq)]
681pub enum BackendConnectionStatus {
682    NotConnected,
683    Connecting(Instant),
684    Connected,
685}
686
687impl BackendConnectionStatus {
688    pub fn is_connecting(&self) -> bool {
689        matches!(self, BackendConnectionStatus::Connecting(_))
690    }
691}
692
693#[derive(Debug, PartialEq, Eq)]
694pub enum BackendConnectAction {
695    New,
696    Reuse,
697    Replace,
698}
699
700#[derive(thiserror::Error, Debug)]
701pub enum BackendConnectionError {
702    #[error("Not found: {0:?}")]
703    NotFound(ObjectKind),
704    #[error("Too many connections on cluster {0:?}")]
705    MaxConnectionRetries(Option<String>),
706    #[error("the sessions slab has reached maximum capacity")]
707    MaxSessionsMemory,
708    #[error("error from the backend: {0}")]
709    Backend(BackendError),
710    #[error("failed to retrieve the cluster: {0}")]
711    RetrieveClusterError(RetrieveClusterError),
712    #[error("maximum number of buffers reached")]
713    MaxBuffers,
714    /// Per-(cluster, source-IP) connection limit reached. The protocol
715    /// layer translates this into HTTP 429 Too Many Requests (with an
716    /// optional `Retry-After`) for HTTP/HTTPS sessions, or a graceful TCP
717    /// close for raw TCP. The `cluster_id` is included so log/metric
718    /// pipelines can attribute the rejection.
719    #[error("per-(cluster, source-IP) connection limit reached for cluster {cluster_id:?}")]
720    TooManyConnectionsPerIp { cluster_id: String },
721}
722
723/// used in kawa_h1 module for the Http session state
724#[derive(thiserror::Error)]
725pub enum RetrieveClusterError {
726    #[error("No method given")]
727    NoMethod,
728    #[error("No host given")]
729    NoHost,
730    #[error("No path given")]
731    NoPath,
732    #[error("unauthorized route")]
733    UnauthorizedRoute,
734    #[error("{0}")]
735    RetrieveFrontend(FrontendFromRequestError),
736    #[error("HTTPS redirect required")]
737    HttpsRedirect,
738    /// The HTTP `:authority` / `Host` host does not match the TLS SNI that was
739    /// negotiated for this connection, which would cross the TLS trust boundary.
740    /// Maps to HTTP 421 Misdirected Request (RFC 9110 §15.5.20).
741    #[error("TLS SNI does not match HTTP authority: sni_bytes={} authority_bytes={}", .sni.len(), .authority.len())]
742    SniAuthorityMismatch { sni: String, authority: String },
743}
744
745impl fmt::Debug for RetrieveClusterError {
746    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
747        fmt::Display::fmt(self, f)
748    }
749}
750
751/// Used in sessions
752#[derive(Debug, PartialEq, Eq)]
753pub enum AcceptError {
754    IoError,
755    TooManySessions,
756    WouldBlock,
757    RegisterError,
758    WrongSocketAddress,
759    BufferCapacityReached,
760}
761
762/// returned by the HTTP, HTTPS and TCP listeners
763#[derive(thiserror::Error)]
764pub enum ListenerError {
765    #[error("failed to handle certificate request, got a resolver error, {0}")]
766    Resolver(CertificateResolverError),
767    #[error("failed to parse pem, {0}")]
768    PemParse(String),
769    #[error(
770        "failed to parse template key_bytes={key_bytes}: {1}",
771        key_bytes = .0.len()
772    )]
773    TemplateParse(String, TemplateError),
774    #[error("failed to build rustls context, {0}")]
775    BuildRustls(String),
776    #[error("could not activate listener with address {address:?}: {error}")]
777    Activation { address: SocketAddr, error: String },
778    #[error("Could not register listener socket: {0}")]
779    SocketRegistration(std::io::Error),
780    #[error("could not add frontend: {0}")]
781    AddFrontend(RouterError),
782    #[error("could not remove frontend: {0}")]
783    RemoveFrontend(RouterError),
784    #[error("invalid value for field '{field}': {reason}")]
785    InvalidValue {
786        field: &'static str,
787        reason: &'static str,
788    },
789    /// `UpdateHttpsListenerConfig.hsts` was present but its `enabled`
790    /// field was unset. Per the partial-update contract, `enabled` is
791    /// the explicit-disambiguator between "explicit disable" (false) and
792    /// "explicit enable" (true); the patch handler refuses an
793    /// `enabled = None` block rather than silently picking one.
794    #[error(
795        "UpdateHttpsListenerConfig.hsts is present but `enabled` is unset; the partial-update \
796         contract requires `enabled` whenever the `hsts` block is present"
797    )]
798    HstsEnabledRequired,
799}
800
801impl fmt::Debug for ListenerError {
802    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
803        fmt::Display::fmt(self, f)
804    }
805}
806
807/// Lift control-plane validation errors into listener-level errors so the
808/// worker can surface the same message without duplicating the match.
809/// Non-`InvalidValue` variants fall back to a generic `InvalidValue` — they
810/// are not expected on the worker's `update_config` path (state lookups
811/// happen on the master) but we avoid panicking if one slips through.
812impl From<sozu_command::state::StateError> for ListenerError {
813    fn from(err: sozu_command::state::StateError) -> Self {
814        match err {
815            sozu_command::state::StateError::InvalidValue { field, reason } => {
816                ListenerError::InvalidValue { field, reason }
817            }
818            _ => ListenerError::InvalidValue {
819                field: "state",
820                reason: "unexpected state error on worker path",
821            },
822        }
823    }
824}
825
826/// Returned by the HTTP, HTTPS and TCP proxies
827#[derive(thiserror::Error, Debug)]
828pub enum ProxyError {
829    #[error("error while soft stopping {proxy_protocol} proxy: {error}")]
830    SoftStop {
831        proxy_protocol: String,
832        error: String,
833    },
834    #[error("error while hard stopping {proxy_protocol} proxy: {error}")]
835    HardStop {
836        proxy_protocol: String,
837        error: String,
838    },
839    #[error("found no listener with address {0:?}")]
840    NoListenerFound(SocketAddr),
841    #[error("a listener is already present for this token")]
842    ListenerAlreadyPresent,
843    #[error("could not add listener: {0}")]
844    AddListener(ListenerError),
845    #[error("could not add cluster: {0}")]
846    AddCluster(ListenerError),
847    #[error("failed to activate listener with address {address:?}: {listener_error}")]
848    ListenerActivation {
849        address: SocketAddr,
850        listener_error: ListenerError,
851    },
852    #[error("can not add frontend {front:?}: {error}")]
853    WrongInputFrontend {
854        front: Box<RequestHttpFrontend>,
855        error: String,
856    },
857    #[error("could not add frontend: {0}")]
858    AddFrontend(ListenerError),
859    #[error("could not remove frontend: {0}")]
860    RemoveFrontend(ListenerError),
861    #[error("could not add certificate: {0}")]
862    AddCertificate(CertificateResolverError),
863    #[error("could not remove certificate: {0}")]
864    RemoveCertificate(CertificateResolverError),
865    #[error("could not replace certificate: {0}")]
866    ReplaceCertificate(CertificateResolverError),
867    #[error("wrong certificate fingerprint: {0}")]
868    WrongCertificateFingerprint(FromHexError),
869    #[error("this request is not supported by the proxy")]
870    UnsupportedMessage,
871    #[error("failed to acquire the lock, {0}")]
872    Lock(String),
873    #[error("could not bind to socket {0:?}: {1}")]
874    BindToSocket(SocketAddr, ServerBindError),
875    #[error("error registering socket of listener: {0}")]
876    RegisterListener(std::io::Error),
877    #[error("the listener is not activated")]
878    UnactivatedListener,
879    /// HSTS (RFC 6797) was attached to a frontend on a plain-HTTP
880    /// listener. RFC 6797 §7.2 forbids `Strict-Transport-Security` on
881    /// plaintext-HTTP responses; the worker rejects the request rather
882    /// than ship a non-conformant policy. The TOML loader rejects the
883    /// same shape at config-load time
884    /// (`command/src/config.rs::ConfigError::HstsOnPlainHttp`); this
885    /// arm catches the same misconfiguration when the request reaches
886    /// the worker over the IPC channel without going through the TOML
887    /// path (e.g. via `sozu frontend http add`).
888    #[error(
889        "HSTS is only valid on HTTPS frontends; rejecting AddHttpFrontend with hsts.enabled = \
890         true on address {0:?} (RFC 6797 §7.2)"
891    )]
892    HstsOnPlainHttp(SocketAddr),
893    /// A TCP `AddTcpFrontend` would corrupt this listener's SNI/ALPN
894    /// routing invariants (sozu-proxy/sozu#1279): mixing a no-SNI
895    /// catch-all with SNI-scoped routes, an `alpn`-scoped frontend with no
896    /// `sni` to preread against, or an ALPN protocol / catch-all that
897    /// overlaps an existing route on the same `(address, sni)`. TOML
898    /// config-load already rejects the same shapes
899    /// (`command/src/config.rs::ConfigError`), but `AddTcpFrontend` can
900    /// also arrive directly over the command socket or via `LoadState`
901    /// replay of a hand-edited/stale state file, bypassing config.rs
902    /// entirely — see `TcpListener::validate_new_tcp_front`
903    /// (`lib/src/tcp.rs`).
904    #[error("rejected AddTcpFrontend on listener {address:?}: {reason}")]
905    InvalidTcpFrontend { address: SocketAddr, reason: String },
906}
907
908use self::server::ListenToken;
909pub trait ProxyConfiguration {
910    fn notify(&mut self, message: WorkerRequest) -> WorkerResponse;
911    fn accept(&mut self, token: ListenToken) -> Result<TcpStream, AcceptError>;
912    fn create_session(
913        &mut self,
914        socket: TcpStream,
915        token: ListenToken,
916        wait_time: Duration,
917        proxy: Rc<RefCell<Self>>,
918        // should we insert the tags here?
919    ) -> Result<(), AcceptError>;
920}
921
922pub trait L7Proxy {
923    fn kind(&self) -> ListenerType;
924
925    fn register_socket(
926        &self,
927        socket: &mut TcpStream,
928        token: Token,
929        interest: Interest,
930    ) -> Result<(), std::io::Error>;
931
932    fn deregister_socket(&self, tcp_stream: &mut TcpStream) -> Result<(), std::io::Error>;
933
934    fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token;
935
936    /// Remove the session from the session manager slab.
937    /// Returns true if the session was actually there before deletion
938    fn remove_session(&self, token: Token) -> bool;
939
940    fn backends(&self) -> Rc<RefCell<BackendMap>>;
941
942    fn clusters(&self) -> &HashMap<ClusterId, Cluster>;
943
944    /// Access the worker's [`SessionManager`] for per-(cluster, source-IP)
945    /// connection-limit accounting. The mux uses this to track / untrack
946    /// stream-granular `(cluster_id, ip)` entries and consult the
947    /// `cluster_ip_at_limit` gate before each backend connect.
948    fn sessions(&self) -> Rc<RefCell<crate::server::SessionManager>>;
949}
950
951#[derive(Debug, PartialEq, Eq)]
952pub enum RequiredEvents {
953    FrontReadBackNone,
954    FrontWriteBackNone,
955    FrontReadWriteBackNone,
956    FrontNoneBackNone,
957    FrontReadBackRead,
958    FrontWriteBackRead,
959    FrontReadWriteBackRead,
960    FrontNoneBackRead,
961    FrontReadBackWrite,
962    FrontWriteBackWrite,
963    FrontReadWriteBackWrite,
964    FrontNoneBackWrite,
965    FrontReadBackReadWrite,
966    FrontWriteBackReadWrite,
967    FrontReadWriteBackReadWrite,
968    FrontNoneBackReadWrite,
969}
970
971impl RequiredEvents {
972    pub fn front_readable(&self) -> bool {
973        matches!(
974            *self,
975            RequiredEvents::FrontReadBackNone
976                | RequiredEvents::FrontReadWriteBackNone
977                | RequiredEvents::FrontReadBackRead
978                | RequiredEvents::FrontReadWriteBackRead
979                | RequiredEvents::FrontReadBackWrite
980                | RequiredEvents::FrontReadWriteBackWrite
981                | RequiredEvents::FrontReadBackReadWrite
982                | RequiredEvents::FrontReadWriteBackReadWrite
983        )
984    }
985
986    pub fn front_writable(&self) -> bool {
987        matches!(
988            *self,
989            RequiredEvents::FrontWriteBackNone
990                | RequiredEvents::FrontReadWriteBackNone
991                | RequiredEvents::FrontWriteBackRead
992                | RequiredEvents::FrontReadWriteBackRead
993                | RequiredEvents::FrontWriteBackWrite
994                | RequiredEvents::FrontReadWriteBackWrite
995                | RequiredEvents::FrontWriteBackReadWrite
996                | RequiredEvents::FrontReadWriteBackReadWrite
997        )
998    }
999
1000    pub fn back_readable(&self) -> bool {
1001        matches!(
1002            *self,
1003            RequiredEvents::FrontReadBackRead
1004                | RequiredEvents::FrontWriteBackRead
1005                | RequiredEvents::FrontReadWriteBackRead
1006                | RequiredEvents::FrontNoneBackRead
1007                | RequiredEvents::FrontReadBackReadWrite
1008                | RequiredEvents::FrontWriteBackReadWrite
1009                | RequiredEvents::FrontReadWriteBackReadWrite
1010                | RequiredEvents::FrontNoneBackReadWrite
1011        )
1012    }
1013
1014    pub fn back_writable(&self) -> bool {
1015        matches!(
1016            *self,
1017            RequiredEvents::FrontReadBackWrite
1018                | RequiredEvents::FrontWriteBackWrite
1019                | RequiredEvents::FrontReadWriteBackWrite
1020                | RequiredEvents::FrontNoneBackWrite
1021                | RequiredEvents::FrontReadBackReadWrite
1022                | RequiredEvents::FrontWriteBackReadWrite
1023                | RequiredEvents::FrontReadWriteBackReadWrite
1024                | RequiredEvents::FrontNoneBackReadWrite
1025        )
1026    }
1027}
1028
1029/// Signals transitions between states of a given Protocol
1030#[derive(Debug, PartialEq, Eq)]
1031pub enum StateResult {
1032    /// Signals to the Protocol to close its backend
1033    CloseBackend,
1034    /// Signals to the parent Session to close itself
1035    CloseSession,
1036    /// Signals to the Protocol to connect to backend
1037    ConnectBackend,
1038    /// Signals to the Protocol to continue
1039    Continue,
1040    /// Signals to the parent Session to upgrade to the next Protocol
1041    Upgrade,
1042}
1043
1044/// Signals transitions between states of a given Session
1045#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1046pub enum SessionResult {
1047    /// Signals to the Session to close itself
1048    Close,
1049    /// Signals to the Session to continue
1050    Continue,
1051    /// Signals to the Session to upgrade its Protocol
1052    Upgrade,
1053}
1054
1055#[derive(Debug, PartialEq, Eq)]
1056pub enum SocketType {
1057    Listener,
1058    FrontClient,
1059}
1060
1061type SessionIsToBeClosed = bool;
1062
1063#[derive(Clone)]
1064pub struct Readiness {
1065    /// the current readiness
1066    pub event: Ready,
1067    /// the readiness we wish to attain
1068    pub interest: Ready,
1069}
1070
1071impl Display for Readiness {
1072    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
1073        let i = &mut [b'-'; 4];
1074        let r = &mut [b'-'; 4];
1075        let mixed = &mut [b'-'; 4];
1076
1077        display_ready(i, self.interest);
1078        display_ready(r, self.event);
1079        display_ready(mixed, self.interest & self.event);
1080
1081        write!(
1082            f,
1083            "I({:?})&R({:?})=M({:?})",
1084            String::from_utf8_lossy(i),
1085            String::from_utf8_lossy(r),
1086            String::from_utf8_lossy(mixed)
1087        )
1088    }
1089}
1090
1091impl Default for Readiness {
1092    fn default() -> Self {
1093        Self::new()
1094    }
1095}
1096
1097impl Readiness {
1098    /// Mask of every bit `Ready` defines (READABLE | WRITABLE | ERROR | HUP).
1099    /// Any bit outside this set in `event` or `interest` is a corrupted
1100    /// readiness word — checked by [`Self::check_invariants`]. Not
1101    /// `#[cfg(debug_assertions)]`-gated: it is read from inside `debug_assert!`s
1102    /// whose arguments must still compile in release (HARD RULE 2 / E0425).
1103    const KNOWN_BITS: Ready =
1104        Ready(Ready::READABLE.0 | Ready::WRITABLE.0 | Ready::ERROR.0 | Ready::HUP.0);
1105
1106    pub const fn new() -> Readiness {
1107        Readiness {
1108            event: Ready::EMPTY,
1109            interest: Ready::EMPTY,
1110        }
1111    }
1112
1113    /// Cross-field invariant sweep: neither `event` nor `interest` may carry a
1114    /// bit `Ready` does not define. A stray bit would silently widen
1115    /// `filter_interest`'s mask and wake (or starve) a session on a phantom
1116    /// readiness. Cheap enough to call as a postcondition from every mutator.
1117    #[cfg(debug_assertions)]
1118    fn check_invariants(&self) {
1119        debug_assert_eq!(
1120            self.event & Self::KNOWN_BITS,
1121            self.event,
1122            "Readiness.event carries a bit outside READABLE|WRITABLE|ERROR|HUP"
1123        );
1124        debug_assert_eq!(
1125            self.interest & Self::KNOWN_BITS,
1126            self.interest,
1127            "Readiness.interest carries a bit outside READABLE|WRITABLE|ERROR|HUP"
1128        );
1129    }
1130
1131    pub fn reset(&mut self) {
1132        self.event = Ready::EMPTY;
1133        self.interest = Ready::EMPTY;
1134        // Post-condition: a reset clears *both* words — a half-reset leaves a
1135        // session armed on stale interest after teardown.
1136        debug_assert!(
1137            self.event.is_empty() && self.interest.is_empty(),
1138            "reset must clear both event and interest"
1139        );
1140        #[cfg(debug_assertions)]
1141        self.check_invariants();
1142    }
1143
1144    /// filters the readiness we actually want
1145    pub fn filter_interest(&self) -> Ready {
1146        // Pre-condition: both source words must be well-formed before we mask —
1147        // a stray bit upstream would leak through the intersection and wake (or
1148        // starve) a session on a phantom readiness.
1149        #[cfg(debug_assertions)]
1150        self.check_invariants();
1151        let filtered = self.event & self.interest;
1152        // Post-condition: the result is a subset of the recognized bits and can
1153        // only contain bits the session both saw AND asked for.
1154        debug_assert_eq!(
1155            filtered & Self::KNOWN_BITS,
1156            filtered,
1157            "filter_interest must not yield an unknown bit"
1158        );
1159        debug_assert!(
1160            self.interest.contains(filtered) && self.event.contains(filtered),
1161            "filtered readiness must be present in both interest and event"
1162        );
1163        filtered
1164    }
1165
1166    /// Signal that the socket has buffered data to write (e.g., TLS internal
1167    /// buffers) that won't generate a new epoll WRITABLE event.
1168    pub fn signal_pending_write(&mut self) {
1169        // Snapshot the unrelated bits (everything but WRITABLE) so we can prove
1170        // we flipped exactly the WRITABLE bit. `Ready` exposes no `!`, so mask on
1171        // the public `.0` word.
1172        let other_event_before = Ready(self.event.0 & !Ready::WRITABLE.0);
1173        self.event.insert(Ready::WRITABLE);
1174        debug_assert!(
1175            self.event.is_writable(),
1176            "signal_pending_write must set the WRITABLE event bit"
1177        );
1178        debug_assert_eq!(
1179            Ready(self.event.0 & !Ready::WRITABLE.0),
1180            other_event_before,
1181            "signal_pending_write must touch only the WRITABLE bit"
1182        );
1183        #[cfg(debug_assertions)]
1184        self.check_invariants();
1185    }
1186
1187    /// Signal that the socket has buffered data to read (e.g., TLS plaintext
1188    /// buffer after a 1xx clear) that won't generate a new epoll READABLE event.
1189    pub fn signal_pending_read(&mut self) {
1190        let other_event_before = Ready(self.event.0 & !Ready::READABLE.0);
1191        self.event.insert(Ready::READABLE);
1192        debug_assert!(
1193            self.event.is_readable(),
1194            "signal_pending_read must set the READABLE event bit"
1195        );
1196        debug_assert_eq!(
1197            Ready(self.event.0 & !Ready::READABLE.0),
1198            other_event_before,
1199            "signal_pending_read must touch only the READABLE bit"
1200        );
1201        #[cfg(debug_assertions)]
1202        self.check_invariants();
1203    }
1204
1205    /// Pair `Ready::WRITABLE` insert with `signal_pending_write` — the canonical
1206    /// invariant-15 form for any path that writes bytes to sozu-owned buffers
1207    /// under edge-triggered epoll. See `lib/src/protocol/mux/LIFECYCLE.md`.
1208    #[inline]
1209    pub fn arm_writable(&mut self) {
1210        // Snapshot the non-WRITABLE bits of both words: arm_writable must set the
1211        // WRITABLE bit in *both* interest and event and leave everything else as-is.
1212        let other_interest_before = Ready(self.interest.0 & !Ready::WRITABLE.0);
1213        let other_event_before = Ready(self.event.0 & !Ready::WRITABLE.0);
1214        self.interest.insert(Ready::WRITABLE);
1215        self.signal_pending_write();
1216        debug_assert!(
1217            self.interest.is_writable() && self.event.is_writable(),
1218            "arm_writable must set WRITABLE in both interest and event"
1219        );
1220        debug_assert_eq!(
1221            Ready(self.interest.0 & !Ready::WRITABLE.0),
1222            other_interest_before,
1223            "arm_writable must touch only the WRITABLE interest bit"
1224        );
1225        debug_assert_eq!(
1226            Ready(self.event.0 & !Ready::WRITABLE.0),
1227            other_event_before,
1228            "arm_writable must touch only the WRITABLE event bit"
1229        );
1230        #[cfg(debug_assertions)]
1231        self.check_invariants();
1232    }
1233}
1234
1235#[cfg(test)]
1236mod readiness_tests {
1237    use super::{Readiness, Ready};
1238
1239    #[test]
1240    fn arm_writable_sets_interest_and_event() {
1241        let mut r = Readiness::new();
1242        r.arm_writable();
1243        assert!(r.interest.is_writable());
1244        assert!(r.event.is_writable());
1245    }
1246
1247    #[test]
1248    fn arm_writable_is_idempotent() {
1249        let mut r = Readiness::new();
1250        r.arm_writable();
1251        r.arm_writable();
1252        assert_eq!(r.interest, Ready::WRITABLE);
1253        assert_eq!(r.event, Ready::WRITABLE);
1254    }
1255}
1256
1257pub fn display_ready(s: &mut [u8], readiness: Ready) {
1258    if readiness.is_readable() {
1259        s[0] = b'R';
1260    }
1261    if readiness.is_writable() {
1262        s[1] = b'W';
1263    }
1264    if readiness.is_error() {
1265        s[2] = b'E';
1266    }
1267    if readiness.is_hup() {
1268        s[3] = b'H';
1269    }
1270}
1271
1272pub fn ready_to_string(readiness: Ready) -> String {
1273    let s = &mut [b'-'; 4];
1274    display_ready(s, readiness);
1275    String::from_utf8(s.to_vec()).unwrap()
1276}
1277
1278impl fmt::Debug for Readiness {
1279    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1280        let i = &mut [b'-'; 4];
1281        let r = &mut [b'-'; 4];
1282        let mixed = &mut [b'-'; 4];
1283
1284        display_ready(i, self.interest);
1285        display_ready(r, self.event);
1286        display_ready(mixed, self.interest & self.event);
1287
1288        write!(
1289            f,
1290            "Readiness {{ interest: {}, readiness: {}, mixed: {} }}",
1291            str::from_utf8(i).unwrap(),
1292            str::from_utf8(r).unwrap(),
1293            str::from_utf8(mixed).unwrap()
1294        )
1295    }
1296}
1297
1298#[derive(Clone, Debug)]
1299pub struct SessionMetrics {
1300    /// date at which we started handling that request
1301    pub start: Option<Instant>,
1302    /// wall-clock timestamp captured alongside `start`, for access-log
1303    /// consumers that need an absolute start time (e.g. OTel span
1304    /// reconstruction) without subtracting a monotonic duration from a
1305    /// wall-clock end time — which mixes two unsynchronised clock sources.
1306    pub start_wall: Option<SystemTime>,
1307    /// time actually spent handling the request
1308    pub service_time: Duration,
1309    /// time spent waiting for its turn
1310    pub wait_time: Duration,
1311    /// bytes received by the frontend
1312    pub bin: usize,
1313    /// bytes sent by the frontend
1314    pub bout: usize,
1315
1316    /// date at which we started working on the request
1317    pub service_start: Option<Instant>,
1318    pub wait_start: Instant,
1319
1320    pub backend_id: Option<String>,
1321    pub backend_start: Option<Instant>,
1322    pub backend_connected: Option<Instant>,
1323    pub backend_stop: Option<Instant>,
1324    pub backend_bin: usize,
1325    pub backend_bout: usize,
1326}
1327
1328impl SessionMetrics {
1329    pub fn new(wait_time: Option<Duration>) -> SessionMetrics {
1330        SessionMetrics {
1331            start: Some(Instant::now()),
1332            start_wall: Some(SystemTime::now()),
1333            service_time: Duration::from_secs(0),
1334            wait_time: wait_time.unwrap_or_else(|| Duration::from_secs(0)),
1335            bin: 0,
1336            bout: 0,
1337            service_start: None,
1338            wait_start: Instant::now(),
1339            backend_id: None,
1340            backend_start: None,
1341            backend_connected: None,
1342            backend_stop: None,
1343            backend_bin: 0,
1344            backend_bout: 0,
1345        }
1346    }
1347
1348    pub fn reset(&mut self) {
1349        self.start = None;
1350        self.start_wall = None;
1351        self.service_time = Duration::from_secs(0);
1352        self.wait_time = Duration::from_secs(0);
1353        self.bin = 0;
1354        self.bout = 0;
1355        self.service_start = None;
1356        self.backend_start = None;
1357        self.backend_connected = None;
1358        self.backend_stop = None;
1359        self.backend_bin = 0;
1360        self.backend_bout = 0;
1361    }
1362
1363    pub fn service_start(&mut self) {
1364        let now = if self.start.is_none() {
1365            self.mark_request_start()
1366        } else {
1367            Instant::now()
1368        };
1369        self.service_start = Some(now);
1370        self.wait_time += now - self.wait_start;
1371    }
1372
1373    pub fn service_stop(&mut self) {
1374        if let Some(start) = self.service_start.take() {
1375            let duration = Instant::now() - start;
1376            self.service_time += duration;
1377        }
1378    }
1379
1380    pub fn wait_start(&mut self) {
1381        self.wait_start = Instant::now();
1382    }
1383
1384    pub fn service_time(&self) -> Duration {
1385        match self.service_start {
1386            Some(start) => {
1387                let last_duration = Instant::now() - start;
1388                self.service_time + last_duration
1389            }
1390            None => self.service_time,
1391        }
1392    }
1393
1394    /// Arm both the monotonic and wall-clock start timestamps together.
1395    /// This must be the single place that sets `start` + `start_wall` outside
1396    /// of `new()`, so the two fields can never desynchronize.
1397    /// Returns the monotonic instant so callers that need it (e.g.
1398    /// `service_start`) can reuse it without a second syscall.
1399    pub fn mark_request_start(&mut self) -> Instant {
1400        let now = Instant::now();
1401        self.start = Some(now);
1402        self.start_wall = Some(SystemTime::now());
1403        now
1404    }
1405
1406    /// time elapsed since the beginning of the session
1407    pub fn request_time(&self) -> Duration {
1408        match self.start {
1409            Some(start) => Instant::now() - start,
1410            None => Duration::from_secs(0),
1411        }
1412    }
1413
1414    /// Wall-clock start time as nanoseconds since the Unix epoch, or `None`
1415    /// if the monotonic start has not been set yet (post-`reset()`, pre-`service_start()`).
1416    pub fn start_wall_ns(&self) -> Option<i128> {
1417        self.start_wall.and_then(|t| {
1418            t.duration_since(SystemTime::UNIX_EPOCH)
1419                .ok()
1420                .map(|d| d.as_nanos() as i128)
1421        })
1422    }
1423
1424    pub fn backend_start(&mut self) {
1425        self.backend_start = Some(Instant::now());
1426    }
1427
1428    pub fn backend_connected(&mut self) {
1429        self.backend_connected = Some(Instant::now());
1430    }
1431
1432    pub fn backend_stop(&mut self) {
1433        self.backend_stop = Some(Instant::now());
1434    }
1435
1436    pub fn backend_response_time(&self) -> Option<Duration> {
1437        match (self.backend_connected, self.backend_stop) {
1438            (Some(start), Some(end)) => Some(end - start),
1439            (Some(start), None) => Some(Instant::now() - start),
1440            _ => None,
1441        }
1442    }
1443
1444    pub fn backend_connection_time(&self) -> Option<Duration> {
1445        match (self.backend_start, self.backend_connected) {
1446            (Some(start), Some(end)) => Some(end - start),
1447            _ => None,
1448        }
1449    }
1450
1451    pub fn register_end_of_session(&self, context: &LogContext) {
1452        let request_time = self.request_time();
1453        let service_time = self.service_time();
1454
1455        if let Some(cluster_id) = context.cluster_id {
1456            time!(
1457                names::event_loop::REQUEST_TIME,
1458                cluster_id,
1459                request_time.as_millis()
1460            );
1461            time!(
1462                names::event_loop::SERVICE_TIME,
1463                cluster_id,
1464                service_time.as_millis()
1465            );
1466        }
1467        time!(names::event_loop::REQUEST_TIME, request_time.as_millis());
1468        time!(names::event_loop::SERVICE_TIME, service_time.as_millis());
1469
1470        if let Some(backend_id) = self.backend_id.as_ref()
1471            && let Some(backend_response_time) = self.backend_response_time()
1472        {
1473            record_backend_metrics!(
1474                context.cluster_id.as_str_or("-"),
1475                backend_id,
1476                backend_response_time.as_millis(),
1477                self.backend_connection_time(),
1478                self.backend_bin,
1479                self.backend_bout
1480            );
1481        }
1482
1483        incr!(
1484            names::access_logs::COUNT,
1485            context.cluster_id,
1486            context.backend_id
1487        );
1488    }
1489}
1490
1491/// exponentially weighted moving average with high sensibility to latency bursts
1492///
1493/// cf Finagle for the original implementation: <https://github.com/twitter/finagle/blob/9cc08d15216497bb03a1cafda96b7266cfbbcff1/finagle-core/src/main/scala/com/twitter/finagle/loadbalancer/PeakEwma.scala>
1494#[derive(Debug, PartialEq, Clone)]
1495pub struct PeakEWMA {
1496    /// decay in nanoseconds
1497    ///
1498    /// higher values will make the EWMA decay slowly to 0
1499    pub decay: f64,
1500    /// estimated RTT in nanoseconds
1501    ///
1502    /// must be set to a high enough default value so that new backends do not
1503    /// get all the traffic right away
1504    pub rtt: f64,
1505    /// last modification
1506    pub last_event: Instant,
1507}
1508
1509impl Default for PeakEWMA {
1510    fn default() -> Self {
1511        Self::new()
1512    }
1513}
1514
1515impl PeakEWMA {
1516    // hardcoded default values for now
1517    pub fn new() -> Self {
1518        PeakEWMA {
1519            // 1s
1520            decay: 1_000_000_000f64,
1521            // 50ms
1522            rtt: 50_000_000f64,
1523            last_event: Instant::now(),
1524        }
1525    }
1526
1527    pub fn observe(&mut self, rtt: f64) {
1528        let now = Instant::now();
1529        let dur = now - self.last_event;
1530
1531        // if latency is rising, we will immediately raise the cost
1532        if rtt > self.rtt {
1533            self.rtt = rtt;
1534        } else {
1535            // new_rtt = old_rtt * e^(-elapsed/decay) + observed_rtt * (1 - e^(-elapsed/decay))
1536            let weight = (-(dur.as_nanos() as f64) / self.decay).exp();
1537            self.rtt = self.rtt * weight + rtt * (1.0 - weight);
1538        }
1539
1540        self.last_event = now;
1541    }
1542
1543    pub fn get(&mut self, active_requests: usize) -> f64 {
1544        // decay the current value
1545        // (we might not have seen a request in a long time)
1546        self.observe(0.0);
1547
1548        (active_requests + 1) as f64 * self.rtt
1549    }
1550}
1551
1552pub mod testing {
1553    pub use std::{cell::RefCell, os::fd::IntoRawFd, rc::Rc};
1554
1555    pub use anyhow::Context;
1556    pub use mio::{Poll, Registry, Token, net::UnixStream};
1557    pub use slab::Slab;
1558    pub use sozu_command::{
1559        proto::command::{
1560            HttpListenerConfig, HttpsListenerConfig, ServerConfig, TcpListenerConfig,
1561        },
1562        scm_socket::{Listeners, ScmSocket},
1563    };
1564
1565    pub use crate::{
1566        Protocol, ProxySession,
1567        backends::BackendMap,
1568        http::HttpProxy,
1569        https::HttpsProxy,
1570        pool::Pool,
1571        server::{ListenSession, ProxyChannel, Server, SessionManager},
1572        tcp::TcpProxy,
1573    };
1574
1575    use std::sync::atomic::{AtomicU16, Ordering};
1576
1577    /// Port counter for sozu listener addresses in lib tests.
1578    /// Starts at 10000 to avoid collision with:
1579    /// - Privileged ports (<1024)
1580    /// - e2e suite (starts at 2000)
1581    /// - Ephemeral port range (typically 32768+)
1582    static PORT_PROVIDER: AtomicU16 = AtomicU16::new(10000);
1583
1584    /// Get a unique port for a sozu listener address.
1585    /// Each call returns a different port, safe for parallel test execution.
1586    pub fn provide_port() -> u16 {
1587        PORT_PROVIDER.fetch_add(1, Ordering::SeqCst)
1588    }
1589
1590    /// Everything needed to create a Server
1591    pub struct ServerParts {
1592        pub event_loop: Poll,
1593        pub registry: Registry,
1594        pub sessions: Rc<RefCell<SessionManager>>,
1595        pub pool: Rc<RefCell<Pool>>,
1596        pub backends: Rc<RefCell<BackendMap>>,
1597        pub client_scm_socket: ScmSocket,
1598        pub server_scm_socket: ScmSocket,
1599        pub server_config: ServerConfig,
1600    }
1601
1602    /// Setup a standalone server, for testing purposes
1603    pub fn prebuild_server(
1604        max_buffers: usize,
1605        buffer_size: usize,
1606        send_scm: bool,
1607    ) -> anyhow::Result<ServerParts> {
1608        let event_loop = Poll::new().with_context(|| "Failed at creating event loop")?;
1609        let backends = Rc::new(RefCell::new(BackendMap::new()));
1610        let server_config = ServerConfig {
1611            max_connections: max_buffers as u64,
1612            ..Default::default()
1613        };
1614
1615        let pool = Rc::new(RefCell::new(Pool::with_capacity(
1616            1,
1617            max_buffers,
1618            buffer_size,
1619        )));
1620
1621        let mut sessions: Slab<Rc<RefCell<dyn ProxySession>>> = Slab::with_capacity(max_buffers);
1622        {
1623            let entry = sessions.vacant_entry();
1624            info!("taking token {:?} for channel", entry.key());
1625            entry.insert(Rc::new(RefCell::new(ListenSession {
1626                protocol: Protocol::Channel,
1627            })));
1628        }
1629        {
1630            let entry = sessions.vacant_entry();
1631            info!("taking token {:?} for timer", entry.key());
1632            entry.insert(Rc::new(RefCell::new(ListenSession {
1633                protocol: Protocol::Timer,
1634            })));
1635        }
1636        {
1637            let entry = sessions.vacant_entry();
1638            info!("taking token {:?} for metrics", entry.key());
1639            entry.insert(Rc::new(RefCell::new(ListenSession {
1640                protocol: Protocol::Metrics,
1641            })));
1642        }
1643        // Test fixture: feature disabled (max_connections_per_ip = 0).
1644        let sessions = SessionManager::new(sessions, max_buffers, 0, 0);
1645
1646        let registry = event_loop
1647            .registry()
1648            .try_clone()
1649            .with_context(|| "Failed at creating a registry")?;
1650
1651        let (scm_server, scm_client) =
1652            UnixStream::pair().with_context(|| "Failed at creating scm unix stream")?;
1653        let client_scm_socket = ScmSocket::new(scm_client.into_raw_fd())
1654            .with_context(|| "Failed at creating the scm client socket")?;
1655        let server_scm_socket = ScmSocket::new(scm_server.into_raw_fd())
1656            .with_context(|| "Failed at creating the scm server socket")?;
1657        if send_scm {
1658            client_scm_socket
1659                .send_listeners(&Listeners::default())
1660                .with_context(|| "Failed at sending empty listeners")?;
1661        }
1662
1663        Ok(ServerParts {
1664            event_loop,
1665            registry,
1666            sessions,
1667            pool,
1668            backends,
1669            client_scm_socket,
1670            server_scm_socket,
1671            server_config,
1672        })
1673    }
1674}
1675
1676#[cfg(test)]
1677pub(crate) fn capture_test_logs(run: impl FnOnce() + Send + 'static) -> String {
1678    capture_test_logs_at_level("info", run)
1679}
1680
1681#[cfg(test)]
1682pub(crate) fn capture_test_logs_at_level(
1683    level: &'static str,
1684    run: impl FnOnce() + Send + 'static,
1685) -> String {
1686    let receiver = std::net::UdpSocket::bind("127.0.0.1:0")
1687        .expect("test log receiver must bind to a loopback port");
1688    let target = format!(
1689        "udp://{}",
1690        receiver
1691            .local_addr()
1692            .expect("test log receiver must have a local address")
1693    );
1694
1695    std::thread::spawn(move || {
1696        sozu_command::logging::Logger::init(
1697            "log-redaction-test".to_owned(),
1698            level,
1699            &target,
1700            false,
1701            None,
1702            None,
1703            None,
1704        )
1705        .expect("test logger must initialize");
1706        run();
1707    })
1708    .join()
1709    .expect("log-producing test thread must not panic");
1710
1711    receiver
1712        .set_nonblocking(true)
1713        .expect("test log receiver must become nonblocking");
1714    let mut output = String::new();
1715    let mut datagram = vec![0; 65_507];
1716    loop {
1717        match receiver.recv(&mut datagram) {
1718            Ok(length) => output.push_str(&String::from_utf8_lossy(&datagram[..length])),
1719            Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => break,
1720            Err(error) => panic!("test log receiver failed: {error}"),
1721        }
1722    }
1723    assert!(!output.is_empty(), "test log capture received no datagrams");
1724    output
1725}
1726
1727#[cfg(test)]
1728mod log_redaction_tests {
1729    use super::*;
1730
1731    #[test]
1732    fn listener_template_parse_error_redacts_custom_answer_key() {
1733        const KEY_SECRET: &str = "CUSTOM_ANSWER_KEY_SECRET_SENTINEL";
1734
1735        let key = format!("{KEY_SECRET}{}", "x".repeat(4096));
1736        let key_len = key.len();
1737        let error = ListenerError::TemplateParse(key, TemplateError::InvalidType);
1738
1739        for (label, output) in [
1740            ("Display", error.to_string()),
1741            ("Debug", format!("{error:?}")),
1742        ] {
1743            assert!(
1744                !output.contains(KEY_SECRET),
1745                "ListenerError {label} leaked custom-answer key {KEY_SECRET}"
1746            );
1747            assert!(
1748                output.contains(&format!("key_bytes={key_len}")),
1749                "ListenerError {label} omitted the bounded key length: {output}"
1750            );
1751            assert!(
1752                output.len() <= 256,
1753                "ListenerError {label} output is not bounded: {} bytes",
1754                output.len()
1755            );
1756        }
1757    }
1758
1759    #[test]
1760    fn sni_authority_mismatch_retains_inputs_but_bounds_textual_formatting() {
1761        const SNI_SECRET: &str = "SNI_MISMATCH_SECRET_SENTINEL";
1762        const AUTHORITY_SECRET: &str = "AUTHORITY_MISMATCH_SECRET_SENTINEL";
1763
1764        let sni = format!("{SNI_SECRET}{}", "x".repeat(4096));
1765        let authority = format!("{AUTHORITY_SECRET}{}", "x".repeat(4096));
1766        let error = RetrieveClusterError::SniAuthorityMismatch {
1767            sni: sni.clone(),
1768            authority: authority.clone(),
1769        };
1770
1771        match &error {
1772            RetrieveClusterError::SniAuthorityMismatch {
1773                sni: retained_sni,
1774                authority: retained_authority,
1775            } => {
1776                assert_eq!(retained_sni, &sni);
1777                assert_eq!(retained_authority, &authority);
1778            }
1779            other => panic!("expected SniAuthorityMismatch, got {other:?}"),
1780        }
1781
1782        for output in [error.to_string(), format!("{error:?}")] {
1783            for secret in [SNI_SECRET, AUTHORITY_SECRET] {
1784                assert!(
1785                    !output.contains(secret),
1786                    "SNI mismatch formatting leaked {secret}: {output}"
1787                );
1788            }
1789            for metadata in [
1790                format!("sni_bytes={}", sni.len()),
1791                format!("authority_bytes={}", authority.len()),
1792            ] {
1793                assert!(
1794                    output.contains(&metadata),
1795                    "SNI mismatch formatting omitted {metadata}: {output}"
1796                );
1797            }
1798            assert!(
1799                output.len() <= 256,
1800                "SNI mismatch formatting is not bounded: {} bytes",
1801                output.len()
1802            );
1803        }
1804    }
1805}