Skip to main content

sozu_lib/
https.rs

1//! HTTPS proxy entry point.
2//!
3//! Owns the TLS listener config (rustls), the ALPN-driven post-handshake
4//! mux dispatch (`h2` → `ConnectionH2`, `http/1.1` → `ConnectionH1`,
5//! neither → reject + `https.alpn.rejected.{unsupported,http11_disabled}`
6//! metrics), the SNI binding policy (`strict_sni_binding`), and the
7//! listener-update surface called from the command socket. Front-end H2
8//! is gated by ALPN here; `cluster.http2` is a backend-capability hint.
9//! Frontend rustls handshake I/O lives in `lib/src/protocol/rustls.rs`;
10//! certificate resolution lives in `lib/src/tls.rs`.
11
12use std::{
13    cell::RefCell,
14    collections::{BTreeMap, HashMap, hash_map::Entry},
15    io::ErrorKind,
16    net::{Shutdown, SocketAddr as StdSocketAddr},
17    os::unix::io::AsRawFd,
18    rc::{Rc, Weak},
19    str::{from_utf8, from_utf8_unchecked},
20    sync::Arc,
21    time::{Duration, Instant},
22};
23
24use mio::{
25    Interest, Registry, Token,
26    net::{TcpListener as MioTcpListener, TcpStream as MioTcpStream},
27    unix::SourceFd,
28};
29use rustls::{
30    CipherSuite, ProtocolVersion, ServerConfig as RustlsServerConfig, ServerConnection,
31    SupportedCipherSuite, crypto::CryptoProvider,
32};
33use rusty_ulid::Ulid;
34use sozu_command::{
35    certificate::Fingerprint,
36    config::{DEFAULT_ALPN_PROTOCOLS, DEFAULT_CIPHER_LIST},
37    proto::command::{
38        AddCertificate, CertificateSummary, CertificatesByAddress, Cluster, HttpsListenerConfig,
39        ListOfCertificatesByAddress, ListenerType, RemoveCertificate, RemoveListener,
40        ReplaceCertificate, RequestHttpFrontend, ResponseContent, TlsVersion,
41        UpdateHttpsListenerConfig, WorkerRequest, WorkerResponse, request::RequestType,
42        response_content::ContentType,
43    },
44    ready::Ready,
45    response::HttpFrontend,
46    state::{
47        ClusterId, validate_alpn_protocols, validate_h2_flood_knobs_https, validate_sozu_id_header,
48    },
49};
50
51use crate::metrics::names;
52use crate::{
53    AcceptError, CachedTags, FrontendFromRequestError, L7ListenerHandler, L7Proxy, ListenerError,
54    ListenerHandler, Protocol, ProxyConfiguration, ProxyError, ProxySession, SessionIsToBeClosed,
55    SessionMetrics, SessionResult, StateMachineBuilder, StateResult,
56    backends::BackendMap,
57    crypto::{cipher_suite_by_name, default_provider, kx_group_by_name},
58    pool::Pool,
59    protocol::{
60        Pipe, SessionState,
61        http::answers::HttpAnswers,
62        http::parser::{Method, hostname_and_port},
63        mux::{self, Mux, MuxTls},
64        proxy_protocol::expect::ExpectProxyProtocol,
65        rustls::TlsHandshake,
66    },
67    router::{RouteResult, Router},
68    server::{ListenToken, SessionManager},
69    socket::{FrontRustls, server_bind},
70    timer::TimeoutContainer,
71    tls::MutexCertificateResolver,
72    util::UnwrapLog,
73};
74
75StateMachineBuilder! {
76    /// The various Stages of an HTTPS connection:
77    ///
78    /// - optional (ExpectProxyProtocol)
79    /// - TLS handshake
80    /// - HTTP or HTTP2 (via Mux)
81    /// - WebSocket (passthrough), only from HTTP/1.1
82    enum HttpsStateMachine impl SessionState {
83        Expect(ExpectProxyProtocol<MioTcpStream>, ServerConnection),
84        Handshake(TlsHandshake),
85        Mux(MuxTls),
86        WebSocket(Pipe<FrontRustls, HttpsListener>),
87    }
88}
89
90enum AlpnProtocol {
91    H2,
92    Http11,
93}
94
95/// Module-level prefix for log lines emitted from this file when no session
96/// is in scope. Produces a bold bright-white `HTTPS` label in colored mode.
97/// Used by [`HttpsProxy`] / [`HttpsListener`] callbacks (`notify`,
98/// `add_cluster`, `add_*_frontend`, `accept`, `soft_stop`, `hard_stop`)
99/// which own a token map keyed by listener and have no `frontend_token` of
100/// their own.
101macro_rules! log_module_context {
102    () => {{
103        let (open, reset, _, _, _) = sozu_command::logging::ansi_palette();
104        format!("{open}HTTPS{reset}\t >>>", open = open, reset = reset)
105    }};
106}
107
108/// Per-session prefix for log lines emitted with an [`HttpsSession`] in
109/// scope. Renders the canonical `\tHTTPS\tSession(...)\t >>>` envelope from
110/// the session's `frontend_token` and `peer_address`. Operators can grep-
111/// correlate against the token id (and the peer address when present)
112/// across log lines for the same TLS connection.
113macro_rules! log_context {
114    ($self:expr) => {{
115        let (open, reset, grey, gray, white) = sozu_command::logging::ansi_palette();
116        format!(
117            "{open}HTTPS{reset}\t{grey}Session{reset}({gray}frontend{reset}={white}{frontend}{reset}, {gray}peer{reset}={white}{peer}{reset})\t >>>",
118            open = open,
119            reset = reset,
120            grey = grey,
121            gray = gray,
122            white = white,
123            frontend = $self.frontend_token.0,
124            peer = $self.peer_address.map(|a| a.to_string()).unwrap_or_else(|| "<none>".to_string()),
125        )
126    }};
127}
128
129pub struct HttpsSession {
130    configured_backend_timeout: Duration,
131    configured_connect_timeout: Duration,
132    configured_frontend_timeout: Duration,
133    frontend_token: Token,
134    has_been_closed: bool,
135    last_event: Instant,
136    listener: Rc<RefCell<HttpsListener>>,
137    metrics: SessionMetrics,
138    peer_address: Option<StdSocketAddr>,
139    pool: Weak<RefCell<Pool>>,
140    proxy: Rc<RefCell<HttpsProxy>>,
141    public_address: StdSocketAddr,
142    state: HttpsStateMachine,
143}
144
145impl HttpsSession {
146    #[allow(clippy::too_many_arguments)]
147    pub fn new(
148        configured_backend_timeout: Duration,
149        configured_connect_timeout: Duration,
150        configured_frontend_timeout: Duration,
151        configured_request_timeout: Duration,
152        expect_proxy: bool,
153        listener: Rc<RefCell<HttpsListener>>,
154        pool: Weak<RefCell<Pool>>,
155        proxy: Rc<RefCell<HttpsProxy>>,
156        public_address: StdSocketAddr,
157        rustls_details: ServerConnection,
158        sock: MioTcpStream,
159        token: Token,
160        wait_time: Duration,
161    ) -> HttpsSession {
162        let peer_address = if expect_proxy {
163            // Will be defined later once the expect proxy header has been received and parsed
164            None
165        } else {
166            sock.peer_addr().ok()
167        };
168
169        let request_id = Ulid::generate();
170        let container_frontend_timeout = TimeoutContainer::new(configured_request_timeout, token);
171
172        let state = if expect_proxy {
173            trace!("{} starting in expect proxy state", log_module_context!());
174            gauge_add!(names::protocol::PROXY_EXPECT, 1);
175            HttpsStateMachine::Expect(
176                ExpectProxyProtocol::new(container_frontend_timeout, sock, token, request_id),
177                rustls_details,
178            )
179        } else {
180            gauge_add!(names::protocol::TLS_HANDSHAKE, 1);
181            HttpsStateMachine::Handshake(TlsHandshake::new(
182                container_frontend_timeout,
183                rustls_details,
184                sock,
185                token,
186                request_id,
187                peer_address,
188            ))
189        };
190
191        let metrics = SessionMetrics::new(Some(wait_time));
192        HttpsSession {
193            configured_backend_timeout,
194            configured_connect_timeout,
195            configured_frontend_timeout,
196            frontend_token: token,
197            has_been_closed: false,
198            last_event: Instant::now(),
199            listener,
200            metrics,
201            peer_address,
202            pool,
203            proxy,
204            public_address,
205            state,
206        }
207    }
208
209    pub fn upgrade(&mut self) -> SessionIsToBeClosed {
210        debug!("{} upgrade", log_context!(self));
211        let new_state = match self.state.take() {
212            HttpsStateMachine::Expect(expect, ssl) => self.upgrade_expect(expect, ssl),
213            HttpsStateMachine::Handshake(handshake) => self.upgrade_handshake(handshake),
214            HttpsStateMachine::Mux(mux) => self.upgrade_mux(mux),
215            HttpsStateMachine::WebSocket(wss) => self.upgrade_websocket(wss),
216            HttpsStateMachine::FailedUpgrade(_) => {
217                // Reaching this arm means a prior upgrade already returned
218                // `None` and the session should have been closed. Fall back
219                // to closing cleanly instead of panicking the worker.
220                error!(
221                    "{} upgrade called on FailedUpgrade state; closing session",
222                    log_context!(self)
223                );
224                None
225            }
226        };
227
228        match new_state {
229            Some(state) => {
230                self.state = state;
231                false
232            }
233            // The state stays FailedUpgrade, but the Session should be closed right after
234            None => true,
235        }
236    }
237
238    fn upgrade_expect(
239        &mut self,
240        mut expect: ExpectProxyProtocol<MioTcpStream>,
241        ssl: ServerConnection,
242    ) -> Option<HttpsStateMachine> {
243        if let Some(ref addresses) = expect.addresses {
244            if let (Some(public_address), Some(session_address)) =
245                (addresses.destination(), addresses.source())
246            {
247                self.public_address = public_address;
248                self.peer_address = Some(session_address);
249
250                let ExpectProxyProtocol {
251                    container_frontend_timeout,
252                    frontend,
253                    frontend_readiness: readiness,
254                    request_id,
255                    ..
256                } = expect;
257
258                let mut handshake = TlsHandshake::new(
259                    container_frontend_timeout,
260                    ssl,
261                    frontend,
262                    self.frontend_token,
263                    request_id,
264                    self.peer_address,
265                );
266                // Transfer both interest and event from the proxy protocol state,
267                // so the event loop properly monitors the socket after the transition.
268                handshake.frontend_readiness = readiness;
269                handshake.frontend_readiness.event.insert(Ready::READABLE);
270
271                gauge_add!(names::protocol::PROXY_EXPECT, -1);
272                gauge_add!(names::protocol::TLS_HANDSHAKE, 1);
273                return Some(HttpsStateMachine::Handshake(handshake));
274            }
275        }
276
277        // currently, only happens in expect proxy protocol with AF_UNSPEC address
278        if !expect.container_frontend_timeout.cancel() {
279            error!(
280                "{} failed to cancel request timeout on expect upgrade phase for 'expect proxy protocol with AF_UNSPEC address'",
281                log_context!(self)
282            );
283        }
284
285        None
286    }
287
288    fn upgrade_handshake(&mut self, handshake: TlsHandshake) -> Option<HttpsStateMachine> {
289        // Capture the SNI as an owned, already-lowercased String so it outlives
290        // the `handshake.session` move below. Lowercasing here once avoids
291        // doing it on every route decision (RFC 9110 §4.2.3 says hostnames are
292        // case-insensitive); no port is ever part of an SNI value (RFC 6066
293        // §3 — `HostName` is a dns_name, no port).
294        // RFC 1034 §3.1 absolute-form: `example.com.` and `example.com`
295        // are the same host. rustls hands us the wire-form SNI verbatim;
296        // strip a single trailing dot so a legitimate client emitting
297        // absolute-form SNI does not get its
298        // `host` / `:authority` rejected by `authority_matches_sni` for a
299        // length mismatch. Empty / no-SNI is unaffected.
300        let sni_owned: Option<String> = handshake
301            .session
302            .server_name()
303            .map(|s| s.to_ascii_lowercase())
304            .map(|mut s| {
305                if s.ends_with('.') {
306                    s.pop();
307                }
308                s
309            });
310        let alpn = handshake.session.alpn_protocol();
311        let alpn = alpn.and_then(|alpn| from_utf8(alpn).ok());
312        debug!(
313            "{} successful TLS handshake with, received: {:?} {:?}",
314            log_context!(self),
315            sni_owned,
316            alpn
317        );
318
319        // Reject clients that fail to negotiate `h2` when the listener is
320        // configured as H2-only: silently falling back to HTTP/1.1 would let a
321        // downgrade-capable peer bypass H2-specific protections advertised
322        // for this listener (Pass 5 Medium #4 of the security audit).
323        let disable_http11 = self.listener.borrow().is_http11_disabled();
324        // Pair the parsed AlpnProtocol with the on-the-wire label so the
325        // access log can record it as a `&'static str` without re-stringifying
326        // the protocol enum on every request. Unknown ALPN values still bail
327        // out below — only successful negotiations propagate to the log.
328        let (alpn, alpn_label): (AlpnProtocol, Option<&'static str>) = match alpn {
329            Some("http/1.1") => {
330                if disable_http11 {
331                    incr!(names::https::ALPN_REJECTED_HTTP11_DISABLED);
332                    warn!(
333                        "{} rejecting TLS connection: listener is H2-only but client negotiated http/1.1",
334                        log_context!(self)
335                    );
336                    return None;
337                }
338                (AlpnProtocol::Http11, Some("http/1.1"))
339            }
340            Some("h2") => (AlpnProtocol::H2, Some("h2")),
341            Some(other) => {
342                // This branch was not metered, so any operator dashboard
343                // graphing `https.alpn.rejected.*`
344                // missed unknown-protocol refusals (e.g. an `h3` mistake
345                // bleeding through some misconfiguration). Add a dedicated
346                // counter so the SOC's "ALPN refusal" ratebar matches the
347                // sum of the labelled buckets.
348                incr!(names::https::ALPN_REJECTED_UNSUPPORTED);
349                error!(
350                    "{} unsupported ALPN protocol: {}",
351                    log_context!(self),
352                    other
353                );
354                return None;
355            }
356            // Some clients don't fill in the ALPN protocol. By default we
357            // downgrade to HTTP/1.1 to preserve compatibility; on an H2-only
358            // listener we instead drop the connection.
359            None => {
360                if disable_http11 {
361                    incr!(names::https::ALPN_REJECTED_HTTP11_DISABLED);
362                    warn!(
363                        "{} rejecting TLS connection: listener is H2-only but client did not negotiate ALPN",
364                        log_context!(self)
365                    );
366                    return None;
367                }
368                (AlpnProtocol::Http11, None)
369            }
370        };
371
372        // Capture the negotiated TLS metadata as `&'static str` labels for the
373        // access log alongside the existing metric counters. Both calls are
374        // single rustls accessors — duplicating them keeps the metric path
375        // unchanged and avoids mutating-after-move on `handshake.session`.
376        let tls_version_label = handshake
377            .session
378            .protocol_version()
379            .and_then(rustls_version_label);
380        let tls_cipher_label = handshake
381            .session
382            .negotiated_cipher_suite()
383            .and_then(rustls_ciphersuite_label);
384        if let Some(version) = handshake.session.protocol_version() {
385            incr!(rustls_version_str(version));
386        };
387        if let Some(cipher) = handshake.session.negotiated_cipher_suite() {
388            incr!(rustls_ciphersuite_str(cipher));
389        };
390
391        gauge_add!(names::protocol::TLS_HANDSHAKE, -1);
392
393        let session_ulid = rusty_ulid::Ulid::generate();
394        let front_stream = FrontRustls {
395            stream: handshake.stream,
396            session: handshake.session,
397            peer_disconnected: false,
398            peer_reset: false,
399            session_ulid,
400        };
401        let router = mux::Router::new(
402            self.configured_backend_timeout,
403            self.configured_connect_timeout,
404        );
405        let mut context = mux::Context::new(
406            session_ulid,
407            self.pool.clone(),
408            self.listener.clone(),
409            self.peer_address,
410            self.public_address,
411        );
412        // Snapshot the SAN set of the certificate this handshake actually
413        // served. Frozen at handshake to match browser behaviour (Firefox
414        // and Chrome cache the validated cert per connection — RFC 7540
415        // §9.1.1 / RFC 9113 §9.1.1) and so the H2 router can accept
416        // coalesced streams whose `:authority` is covered by any SAN
417        // (RFC 6125 §6.4.3 wildcards).
418        //
419        // # Known race window (accepted risk)
420        //
421        // This is a SECOND lookup, separate from rustls's `resolve()`
422        // callback. Between rustls's `resolve()` (during ClientHello
423        // processing) and this block (post-`Finished`), the mio loop may
424        // dispatch the command-channel token — handlers there call
425        // `add_certificate` / `remove_certificate`, which mutate the same
426        // resolver trie. The single-threaded-worker invariant prevents
427        // simultaneous mutation, but not interleaving between mio
428        // iterations.
429        //
430        // Realistic threat model: internal misuse — a tenant operator
431        // with config-IPC privilege races a `remove_certificate(A)` plus
432        // `add_certificate(B covering same SNI)` inside the handshake
433        // window. The snapshot below then reflects B instead of A. The
434        // attacker already holds the trust boundary they would need to
435        // mint a malicious cert outright (config IPC == resolver write
436        // privilege), so the race grants no privilege the attacker did
437        // not already have. Closing it structurally would require either
438        // (a) rustls API support to recover the served cert chain
439        // post-handshake (`server_cert_chain` is `pub(crate)` in rustls
440        // 0.23.x) or (b) threading per-session state from `resolve()` to
441        // here through a side-channel that handles out-of-order handshake
442        // completion — both deferred. Keep the second lookup; document
443        // the window honestly.
444        //
445        // Cases handled:
446        //   * SNI absent → `None`; routing falls back to the legacy
447        //     `authority_matches_sni` predicate (no SNI ⇒ predicate no-ops).
448        //   * SNI present and the resolver returned a SAN-bearing cert →
449        //     `Some(snapshot)` (lowercase + trailing-dot strip + dedup).
450        //     Routing accepts `:authority` covered by the SAN set with
451        //     RFC 6125 §6.4.3 wildcard handling — this is the H2
452        //     connection-coalescing fix (RFC 7540 §9.1.1 / RFC 9113
453        //     §9.1.1, Firefox + Chrome semantics).
454        //   * SNI present but no matching cert (rustls served the default
455        //     cert) → `None`. The legacy exact-match fallback applies:
456        //     accept iff `:authority == SNI`, identical to the pre-fix
457        //     behaviour. Returning `Some(empty)` here would block every
458        //     authority — including configurations where the operator
459        //     intentionally keeps a frontend reachable on a different cert
460        //     (test fixtures, dev setups, misconfigured listeners). The
461        //     real defence stays on the client: a browser will refuse the
462        //     default cert when SNI doesn't validate against it; a
463        //     deliberate insecure client choosing to ignore that is
464        //     responsible for its own behaviour and is not a trust-boundary
465        //     concern for the proxy.
466        let tls_cert_names: Option<Arc<Vec<String>>> = match sni_owned.as_deref() {
467            Some(sni) => self
468                .listener
469                .borrow()
470                .resolver()
471                .names_for_sni(sni.as_bytes())
472                .and_then(|names| {
473                    let mut snapshot: Vec<String> = names
474                        .into_iter()
475                        .map(|mut name| {
476                            name.make_ascii_lowercase();
477                            if name.ends_with('.') {
478                                name.pop();
479                            }
480                            name
481                        })
482                        .collect();
483                    snapshot.sort();
484                    snapshot.dedup();
485                    if snapshot.is_empty() {
486                        None
487                    } else {
488                        Some(Arc::new(snapshot))
489                    }
490                }),
491            None => None,
492        };
493        // Bind the TLS SNI to this session so the routing layer can reject any
494        // H2 stream whose `:authority` crosses the TLS trust boundary (see
495        // `route_from_request`).
496        context.tls_server_name = sni_owned;
497        context.tls_cert_names = tls_cert_names;
498        // Stamp the connection-scoped TLS metadata so every per-stream
499        // HttpContext created by `Context::create_stream` inherits it for
500        // the access log without re-querying rustls.
501        context.tls_version = tls_version_label;
502        context.tls_cipher = tls_cipher_label;
503        context.tls_alpn = alpn_label;
504        let mut frontend = match alpn {
505            AlpnProtocol::Http11 => {
506                incr!(names::http::ALPN_HTTP11);
507                context.create_stream(handshake.request_id, 1 << 16)?;
508                mux::Connection::new_h1_server(
509                    session_ulid,
510                    front_stream,
511                    handshake.container_frontend_timeout,
512                )
513            }
514            AlpnProtocol::H2 => {
515                incr!(names::http::ALPN_H2);
516                let flood_config = self.listener.borrow().get_h2_flood_config();
517                let connection_config = self.listener.borrow().get_h2_connection_config();
518                let stream_idle_timeout = self.listener.borrow().get_h2_stream_idle_timeout();
519                let graceful_shutdown_deadline =
520                    self.listener.borrow().get_h2_graceful_shutdown_deadline();
521                mux::Connection::new_h2_server(
522                    session_ulid,
523                    front_stream,
524                    self.pool.clone(),
525                    handshake.container_frontend_timeout,
526                    flood_config,
527                    connection_config,
528                    stream_idle_timeout,
529                    graceful_shutdown_deadline,
530                )?
531            }
532        };
533        // Ensure the upgraded connection can both read and write immediately.
534        // With TLS 1.3 + NewSessionTicket, the upgrade may happen from writable()
535        // where READABLE is no longer in the event (consumed by the prior readable()
536        // call). The HTTP/2 preface may already be in rustls's plaintext buffer
537        // (not on the TCP socket), so no new READABLE event from epoll will arrive.
538        // Without WRITABLE in the event, the H2 state machine cannot transition from
539        // reading the preface to writing SETTINGS, causing a deadlock with clients
540        // (like hyper) that wait for the server's SETTINGS before proceeding.
541        frontend
542            .readiness_mut()
543            .event
544            .insert(Ready::READABLE | Ready::WRITABLE);
545
546        gauge_add!(names::protocol::HTTPS, 1);
547        Some(HttpsStateMachine::Mux(Mux {
548            configured_frontend_timeout: self.configured_frontend_timeout,
549            frontend_token: self.frontend_token,
550            frontend,
551            context,
552            router,
553            session_ulid,
554        }))
555    }
556
557    fn upgrade_mux(&self, mut mux: MuxTls) -> Option<HttpsStateMachine> {
558        debug!("{} mux switching to wss", log_context!(self));
559        let Some(stream) = mux.context.streams.pop() else {
560            error!(
561                "{} upgrade_mux: no stream attached to the TLS mux session, closing",
562                log_context!(self)
563            );
564            return None;
565        };
566        // http.active_requests was already decremented by generate_access_log()
567        // in h1.rs before MuxResult::Upgrade was returned to us.
568
569        let (frontend_readiness, frontend_socket, mut container_frontend_timeout) =
570            match mux.frontend {
571                mux::Connection::H1(mux::ConnectionH1 {
572                    readiness,
573                    socket,
574                    timeout_container,
575                    ..
576                }) => (readiness, socket, timeout_container),
577                mux::Connection::H2(_) => {
578                    error!(
579                        "{} only h1<->h1 connections can upgrade to websocket",
580                        log_context!(self)
581                    );
582                    return None;
583                }
584            };
585
586        let mux::StreamState::Linked(back_token) = stream.state else {
587            error!(
588                "{} upgrading stream should be linked to a backend",
589                log_context!(self)
590            );
591            return None;
592        };
593        let Some(backend) = mux.router.backends.remove(&back_token) else {
594            error!(
595                "{} upgrade_mux: backend for token {:?} is missing (already disconnected?), closing",
596                log_context!(self),
597                back_token
598            );
599            return None;
600        };
601        let (cluster_id, backend, backend_readiness, backend_socket, mut container_backend_timeout) =
602            match backend {
603                mux::Connection::H1(mux::ConnectionH1 {
604                    position:
605                        mux::Position::Client(cluster_id, backend, mux::BackendStatus::Connected),
606                    readiness,
607                    socket,
608                    timeout_container,
609                    ..
610                }) => (cluster_id, backend, readiness, socket, timeout_container),
611                mux::Connection::H1(_) => {
612                    error!(
613                        "{} the backend disconnected just after upgrade, abort",
614                        log_context!(self)
615                    );
616                    return None;
617                }
618                mux::Connection::H2(_) => {
619                    error!(
620                        "{} only h1<->h1 connections can upgrade to websocket",
621                        log_context!(self)
622                    );
623                    return None;
624                }
625            };
626
627        let ws_context = stream.context.websocket_context();
628
629        container_frontend_timeout.reset();
630        container_backend_timeout.reset();
631
632        let backend_id = backend.borrow().backend_id.clone();
633        // Unwrap the `SessionTcpStream` that the mux put around every backend
634        // TCP socket — `Pipe::backend_socket` is typed `Option<TcpStream>`.
635        let backend_socket = backend_socket.stream;
636        let mut pipe = Pipe::new(
637            stream.back.storage.buffer,
638            Some(backend_id),
639            Some(backend_socket),
640            Some(backend),
641            Some(container_backend_timeout),
642            Some(container_frontend_timeout),
643            Some(cluster_id),
644            stream.front.storage.buffer,
645            self.frontend_token,
646            frontend_socket,
647            self.listener.clone(),
648            Protocol::HTTPS,
649            stream.context.session_id,
650            stream.context.id,
651            stream.context.session_address,
652            ws_context,
653        );
654
655        pipe.frontend_readiness.event = frontend_readiness.event;
656        pipe.backend_readiness.event = backend_readiness.event;
657        pipe.set_back_token(back_token);
658        // Carry the connection-scoped TLS metadata captured at handshake time
659        // into the post-upgrade WSS pipe so its access log records the same
660        // version/cipher/sni/alpn the H1 request log already emitted. `clone`
661        // on the SNI is the only heap touch — the other three are
662        // `&'static str` borrows into the rustls label tables.
663        pipe.set_tls_metadata(
664            stream.context.tls_version,
665            stream.context.tls_cipher,
666            stream.context.tls_server_name.clone(),
667            stream.context.tls_alpn,
668        );
669
670        // http.active_requests was already decremented by generate_access_log()
671        // in h1.rs when the 101 response was written (before MuxResult::Upgrade).
672        gauge_add!(names::protocol::HTTPS, -1);
673        gauge_add!(names::protocol::WSS, 1);
674        gauge_add!(names::websocket::ACTIVE_REQUESTS, 1);
675        Some(HttpsStateMachine::WebSocket(pipe))
676    }
677
678    fn upgrade_websocket(
679        &self,
680        wss: Pipe<FrontRustls, HttpsListener>,
681    ) -> Option<HttpsStateMachine> {
682        // what do we do here?
683        error!(
684            "{} upgrade called on WSS, this should not happen",
685            log_context!(self)
686        );
687        Some(HttpsStateMachine::WebSocket(wss))
688    }
689}
690
691impl ProxySession for HttpsSession {
692    fn close(&mut self) {
693        if self.has_been_closed {
694            return;
695        }
696
697        trace!("{} closing HTTPS session", log_context!(self));
698        self.metrics.service_stop();
699
700        // Restore gauges
701        match self.state.marker() {
702            StateMarker::Expect => gauge_add!(names::protocol::PROXY_EXPECT, -1),
703            StateMarker::Handshake => gauge_add!(names::protocol::TLS_HANDSHAKE, -1),
704            StateMarker::Mux => gauge_add!(names::protocol::HTTPS, -1),
705            StateMarker::WebSocket => {
706                gauge_add!(names::protocol::WSS, -1);
707                gauge_add!(names::websocket::ACTIVE_REQUESTS, -1);
708            }
709        }
710
711        if self.state.failed() {
712            match self.state.marker() {
713                StateMarker::Expect => incr!(names::https::UPGRADE_EXPECT_FAILED),
714                StateMarker::Handshake => incr!(names::https::UPGRADE_HANDSHAKE_FAILED),
715                StateMarker::Mux => incr!(names::https::UPGRADE_MUX_FAILED),
716                StateMarker::WebSocket => incr!(names::https::UPGRADE_WSS_FAILED),
717            }
718            // FailedUpgrade means the socket was consumed by a failed upgrade
719            // attempt, so we can only close the state (no-op) and remove the
720            // session — cancel_timeouts / front_socket are unreachable.
721            self.state.close(self.proxy.clone(), &mut self.metrics);
722            self.proxy.borrow().remove_session(self.frontend_token);
723            self.has_been_closed = true;
724            return;
725        }
726
727        self.state.cancel_timeouts();
728        // defer backend closing to the state
729        // in case of https it should also send a close notify on the client before the socket is closed below
730        self.state.close(self.proxy.clone(), &mut self.metrics);
731
732        // Shut down the write side only. shutdown(Both) includes SHUT_RD which
733        // discards unread data in the receive buffer (e.g. client's GOAWAY, ACKs).
734        // On Linux, close() after SHUT_RD with discarded receive data sends TCP RST
735        // instead of FIN, destroying any data still in the send buffer — including
736        // TLS records that the drain loop just flushed. Using SHUT_WR only sends
737        // FIN after all send buffer data is delivered, preserving the response.
738        let front_socket = self.state.front_socket();
739        if let Err(e) = front_socket.shutdown(Shutdown::Write) {
740            // error 107 NotConnected can happen when was never fully connected, or was already disconnected due to error
741            if e.kind() != ErrorKind::NotConnected {
742                error!(
743                    "{} error shutting down front socket({:?}): {:?}",
744                    log_context!(self),
745                    front_socket,
746                    e
747                );
748            }
749        }
750
751        // deregister the frontend and remove it
752        let proxy = self.proxy.borrow();
753        let fd = front_socket.as_raw_fd();
754        if let Err(e) = proxy.registry.deregister(&mut SourceFd(&fd)) {
755            error!(
756                "{} error deregistering front socket({:?}) while closing HTTPS session: {:?}",
757                log_context!(self),
758                fd,
759                e
760            );
761        }
762        proxy.remove_session(self.frontend_token);
763
764        self.has_been_closed = true;
765    }
766
767    fn timeout(&mut self, token: Token) -> SessionIsToBeClosed {
768        let session_result = self.state.timeout(token, &mut self.metrics);
769        if session_result == StateResult::CloseSession {
770            debug!(
771                "{} HTTPS timeout requested close: token={:?}, marker={:?}",
772                log_context!(self),
773                token,
774                self.state.marker()
775            );
776        }
777        session_result == StateResult::CloseSession
778    }
779
780    fn protocol(&self) -> Protocol {
781        Protocol::HTTPS
782    }
783
784    fn update_readiness(&mut self, token: Token, events: Ready) {
785        trace!(
786            "{} token {:?} got event {}",
787            log_context!(self),
788            token,
789            super::ready_to_string(events)
790        );
791        self.last_event = Instant::now();
792        self.metrics.wait_start();
793        self.state.update_readiness(token, events);
794    }
795
796    fn ready(&mut self, session: Rc<RefCell<dyn ProxySession>>) -> SessionIsToBeClosed {
797        self.metrics.service_start();
798
799        let session_result =
800            self.state
801                .ready(session.clone(), self.proxy.clone(), &mut self.metrics);
802
803        let to_be_closed = match session_result {
804            SessionResult::Close => true,
805            SessionResult::Continue => false,
806            SessionResult::Upgrade => match self.upgrade() {
807                false => self.ready(session),
808                true => true,
809            },
810        };
811        if to_be_closed {
812            debug!(
813                "{} HTTPS ready requested close: marker={:?}",
814                log_context!(self),
815                self.state.marker()
816            );
817        }
818
819        self.metrics.service_stop();
820        to_be_closed
821    }
822
823    fn shutting_down(&mut self) -> SessionIsToBeClosed {
824        self.state.shutting_down()
825    }
826
827    fn last_event(&self) -> Instant {
828        self.last_event
829    }
830
831    fn print_session(&self) {
832        self.state.print_state("HTTPS");
833        error!("{} Metrics: {:?}", log_context!(self), self.metrics);
834    }
835
836    fn frontend_token(&self) -> Token {
837        self.frontend_token
838    }
839}
840
841pub type HostName = String;
842pub type PathBegin = String;
843
844pub struct HttpsListener {
845    active: bool,
846    address: StdSocketAddr,
847    answers: Rc<RefCell<HttpAnswers>>,
848    config: HttpsListenerConfig,
849    fronts: Router,
850    listener: Option<MioTcpListener>,
851    resolver: Arc<MutexCertificateResolver>,
852    rustls_details: Arc<RustlsServerConfig>,
853    tags: BTreeMap<String, CachedTags>,
854    token: Token,
855}
856
857impl ListenerHandler for HttpsListener {
858    fn get_addr(&self) -> &StdSocketAddr {
859        &self.address
860    }
861
862    fn get_tags(&self, key: &str) -> Option<&CachedTags> {
863        self.tags.get(key)
864    }
865
866    fn set_tags(&mut self, key: String, tags: Option<BTreeMap<String, String>>) {
867        match tags {
868            Some(tags) => self.tags.insert(key, CachedTags::new(tags)),
869            None => self.tags.remove(&key),
870        };
871    }
872
873    fn protocol(&self) -> Protocol {
874        Protocol::HTTPS
875    }
876
877    fn public_address(&self) -> StdSocketAddr {
878        self.config
879            .public_address
880            .map(|addr| addr.into())
881            .unwrap_or(self.address)
882    }
883}
884
885impl L7ListenerHandler for HttpsListener {
886    fn get_sticky_name(&self) -> &str {
887        &self.config.sticky_name
888    }
889
890    fn get_sozu_id_header(&self) -> &str {
891        self.config
892            .sozu_id_header
893            .as_deref()
894            .filter(|s| !s.is_empty())
895            .unwrap_or("Sozu-Id")
896    }
897
898    fn get_connect_timeout(&self) -> u32 {
899        self.config.connect_timeout
900    }
901
902    fn frontend_from_request(
903        &self,
904        host: &str,
905        uri: &str,
906        method: &Method,
907    ) -> Result<RouteResult, FrontendFromRequestError> {
908        let start = Instant::now();
909        let (remaining_input, (hostname, _)) = match hostname_and_port(host.as_bytes()) {
910            Ok(tuple) => tuple,
911            Err(parse_error) => {
912                // parse_error contains a slice of given_host, which should NOT escape this scope
913                return Err(FrontendFromRequestError::HostParse {
914                    host: host.to_owned(),
915                    error: parse_error.to_string(),
916                });
917            }
918        };
919
920        if remaining_input != &b""[..] {
921            return Err(FrontendFromRequestError::InvalidCharsAfterHost(
922                host.to_owned(),
923            ));
924        }
925
926        // it is alright to call from_utf8_unchecked,
927        // we already verified that there are only ascii
928        // chars in there
929        // SAFETY: `hostname` was just produced by `hostname_and_port` (see
930        // `lib/src/protocol/kawa_h1/parser.rs:133`), which only accepts
931        // bytes matching `is_hostname_char` (alphanumeric, `-`, `.`, plus
932        // `_` under the tolerant-http1-parser feature). All accepted
933        // bytes are ASCII (≤ 0x7F), so the slice is valid single-byte UTF-8.
934        let host = unsafe { from_utf8_unchecked(hostname) };
935
936        let route = self.fronts.lookup(host, uri, method).map_err(|e| {
937            incr!(names::http::FAILED_BACKEND_MATCHING);
938            FrontendFromRequestError::NoClusterFound(e)
939        })?;
940
941        let now = Instant::now();
942
943        if let Some(cluster) = route.cluster_id.as_deref() {
944            time!(
945                names::event_loop::FRONTEND_MATCHING_TIME,
946                cluster,
947                (now - start).as_millis()
948            );
949        }
950
951        Ok(route)
952    }
953
954    fn get_answers(&self) -> &Rc<RefCell<HttpAnswers>> {
955        &self.answers
956    }
957
958    fn get_h2_flood_config(&self) -> crate::protocol::mux::H2FloodConfig {
959        let defaults = crate::protocol::mux::H2FloodConfig::default();
960        crate::protocol::mux::H2FloodConfig {
961            max_rst_stream_per_window: self
962                .config
963                .h2_max_rst_stream_per_window
964                .unwrap_or(defaults.max_rst_stream_per_window),
965            max_ping_per_window: self
966                .config
967                .h2_max_ping_per_window
968                .unwrap_or(defaults.max_ping_per_window),
969            max_settings_per_window: self
970                .config
971                .h2_max_settings_per_window
972                .unwrap_or(defaults.max_settings_per_window),
973            max_empty_data_per_window: self
974                .config
975                .h2_max_empty_data_per_window
976                .unwrap_or(defaults.max_empty_data_per_window),
977            max_window_update_stream0_per_window: self
978                .config
979                .h2_max_window_update_stream0_per_window
980                .unwrap_or(defaults.max_window_update_stream0_per_window),
981            max_continuation_frames: self
982                .config
983                .h2_max_continuation_frames
984                .unwrap_or(defaults.max_continuation_frames),
985            max_glitch_count: self
986                .config
987                .h2_max_glitch_count
988                .unwrap_or(defaults.max_glitch_count),
989            max_rst_stream_lifetime: self
990                .config
991                .h2_max_rst_stream_lifetime
992                .unwrap_or(defaults.max_rst_stream_lifetime),
993            max_rst_stream_abusive_lifetime: self
994                .config
995                .h2_max_rst_stream_abusive_lifetime
996                .unwrap_or(defaults.max_rst_stream_abusive_lifetime),
997            max_rst_stream_emitted_lifetime: self
998                .config
999                .h2_max_rst_stream_emitted_lifetime
1000                .unwrap_or(defaults.max_rst_stream_emitted_lifetime),
1001            max_header_list_size: self
1002                .config
1003                .h2_max_header_list_size
1004                .unwrap_or(defaults.max_header_list_size),
1005            max_header_table_size: self
1006                .config
1007                .h2_max_header_table_size
1008                .unwrap_or(defaults.max_header_table_size),
1009        }
1010    }
1011
1012    fn get_h2_connection_config(&self) -> crate::protocol::mux::H2ConnectionConfig {
1013        crate::protocol::mux::H2ConnectionConfig::from_optional(
1014            self.config.h2_initial_connection_window,
1015            self.config.h2_max_concurrent_streams,
1016            self.config.h2_stream_shrink_ratio,
1017        )
1018    }
1019
1020    fn get_strict_sni_binding(&self) -> bool {
1021        // SNI↔:authority binding is enforced by default (closes
1022        // CWE-346 / CWE-444); this listener knob preserves that
1023        // behavior by default and lets operators opt out when cross-SNI
1024        // routing is intentional.
1025        //
1026        // Note: `strict_sni_binding = false` theoretically allows an
1027        // attacker to present many distinct SNIs on the same TCP
1028        // connection. rustls 0.23 **bans TLS renegotiation outright** (see
1029        // `rustls::server::ClientHello` which is consumed during the initial
1030        // handshake only), so a single TCP connection gets exactly one SNI
1031        // for its lifetime — the cross-SNI-flood vector is not reachable in
1032        // practice. Kept documented here so a future rustls upgrade that
1033        // reintroduces renegotiation (vanishingly unlikely) surfaces the
1034        // assumption during review.
1035        self.config.strict_sni_binding.unwrap_or(true)
1036    }
1037
1038    fn get_elide_x_real_ip(&self) -> bool {
1039        self.config.elide_x_real_ip.unwrap_or(false)
1040    }
1041
1042    fn get_send_x_real_ip(&self) -> bool {
1043        self.config.send_x_real_ip.unwrap_or(false)
1044    }
1045
1046    fn get_h2_stream_idle_timeout(&self) -> std::time::Duration {
1047        // Inherit `back_timeout` when the knob is unset so listeners tuned for
1048        // long-running backends do not cancel streams at the 30 s security
1049        // floor. The `max(30, …)` keeps the baseline slow-multiplex mitigation
1050        // when `back_timeout` is shorter than 30 s. Explicit values (including
1051        // ones below 30 s) win — operators under a slow-multiplex attack can
1052        // lower the per-stream deadline to cap buffer pinning.
1053        let seconds = self
1054            .config
1055            .h2_stream_idle_timeout_seconds
1056            .map(|s| u64::from(s.max(1)))
1057            .unwrap_or_else(|| u64::from(self.config.back_timeout).max(30));
1058        std::time::Duration::from_secs(seconds)
1059    }
1060
1061    fn get_h2_graceful_shutdown_deadline(&self) -> Option<std::time::Duration> {
1062        match self.config.h2_graceful_shutdown_deadline_seconds {
1063            None => Some(std::time::Duration::from_secs(5)),
1064            Some(0) => None,
1065            Some(s) => Some(std::time::Duration::from_secs(u64::from(s))),
1066        }
1067    }
1068}
1069
1070impl HttpsListener {
1071    /// Whether this listener rejects clients that do not negotiate `h2`
1072    /// via TLS ALPN (including those that omit ALPN). Reads the
1073    /// `disable_http11` knob; defaults to `false` to preserve the
1074    /// historical behavior where a missing ALPN silently downgrades
1075    /// to HTTP/1.1.
1076    pub fn is_http11_disabled(&self) -> bool {
1077        self.config.disable_http11.unwrap_or(false)
1078    }
1079
1080    /// Borrow the listener's certificate resolver. Used by the TLS handshake
1081    /// path to snapshot the SAN set of the certificate Sōzu serves for a
1082    /// given SNI, so the H2 router can accept connection coalescing
1083    /// (RFC 7540 §9.1.1 / RFC 9113 §9.1.1) on every authority covered by
1084    /// that cert (RFC 6125 §6.4.3 wildcard handling).
1085    pub fn resolver(&self) -> &Arc<MutexCertificateResolver> {
1086        &self.resolver
1087    }
1088
1089    pub fn try_new(
1090        config: HttpsListenerConfig,
1091        token: Token,
1092    ) -> Result<HttpsListener, ListenerError> {
1093        let resolver = Arc::new(MutexCertificateResolver::default());
1094
1095        let server_config = Arc::new(Self::create_rustls_context(&config, resolver.to_owned())?);
1096
1097        let answers = {
1098            // Reconcile the legacy `http_answers` per-status fields with
1099            // the new template map: the new map wins on collision, the
1100            // legacy fields fill in any status the operator hasn't yet
1101            // migrated.
1102            let mut answers_map = config.answers.clone();
1103            if let Some(ref legacy) = config.http_answers {
1104                crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1105            }
1106            HttpAnswers::new(&answers_map)
1107                .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?
1108        };
1109
1110        Ok(HttpsListener {
1111            listener: None,
1112            address: config.address.into(),
1113            resolver,
1114            rustls_details: server_config,
1115            active: false,
1116            fronts: Router::new(),
1117            answers: Rc::new(RefCell::new(answers)),
1118            config,
1119            token,
1120            tags: BTreeMap::new(),
1121        })
1122    }
1123
1124    pub fn activate(
1125        &mut self,
1126        registry: &Registry,
1127        tcp_listener: Option<MioTcpListener>,
1128    ) -> Result<Token, ListenerError> {
1129        if self.active {
1130            return Ok(self.token);
1131        }
1132        let address: StdSocketAddr = self.config.address.into();
1133
1134        let mut listener = match tcp_listener {
1135            Some(tcp_listener) => tcp_listener,
1136            None => {
1137                server_bind(address).map_err(|server_bind_error| ListenerError::Activation {
1138                    address,
1139                    error: server_bind_error.to_string(),
1140                })?
1141            }
1142        };
1143
1144        registry
1145            .register(&mut listener, self.token, Interest::READABLE)
1146            .map_err(ListenerError::SocketRegistration)?;
1147
1148        self.listener = Some(listener);
1149        self.active = true;
1150        Ok(self.token)
1151    }
1152
1153    pub fn create_rustls_context(
1154        config: &HttpsListenerConfig,
1155        resolver: Arc<MutexCertificateResolver>,
1156    ) -> Result<RustlsServerConfig, ListenerError> {
1157        let cipher_names = if config.cipher_list.is_empty() {
1158            DEFAULT_CIPHER_LIST.to_vec()
1159        } else {
1160            config
1161                .cipher_list
1162                .iter()
1163                .map(|s| s.as_str())
1164                .collect::<Vec<_>>()
1165        };
1166
1167        let ciphers = cipher_names
1168            .into_iter()
1169            .filter_map(|cipher| {
1170                cipher_suite_by_name(cipher).or_else(|| {
1171                    error!(
1172                        "{} unknown or unsupported cipher: {:?}",
1173                        log_module_context!(),
1174                        cipher
1175                    );
1176                    None
1177                })
1178            })
1179            .collect::<Vec<_>>();
1180
1181        let versions = config
1182            .versions
1183            .iter()
1184            .filter_map(|version| match TlsVersion::try_from(*version) {
1185                Ok(TlsVersion::TlsV12) => Some(&rustls::version::TLS12),
1186                Ok(TlsVersion::TlsV13) => Some(&rustls::version::TLS13),
1187                Ok(other_version) => {
1188                    error!(
1189                        "{} unsupported TLS version {:?}",
1190                        log_module_context!(),
1191                        other_version
1192                    );
1193                    None
1194                }
1195                Err(_) => {
1196                    error!("{} unsupported TLS version", log_module_context!());
1197                    None
1198                }
1199            })
1200            .collect::<Vec<_>>();
1201
1202        let kx_groups = if config.groups_list.is_empty() {
1203            default_provider().kx_groups
1204        } else {
1205            config
1206                .groups_list
1207                .iter()
1208                .filter_map(|group| match kx_group_by_name(group) {
1209                    Some(kx) => Some(kx),
1210                    None => {
1211                        debug!("key exchange group {:?} not supported by the compiled crypto provider, skipping", group);
1212                        None
1213                    }
1214                })
1215                .collect::<Vec<_>>()
1216        };
1217
1218        let provider = CryptoProvider {
1219            cipher_suites: ciphers,
1220            kx_groups,
1221            ..default_provider()
1222        };
1223
1224        let mut server_config = RustlsServerConfig::builder_with_provider(provider.into())
1225            .with_protocol_versions(&versions[..])
1226            .map_err(|err| ListenerError::BuildRustls(err.to_string()))?
1227            .with_no_client_auth()
1228            .with_cert_resolver(resolver);
1229        server_config.send_tls13_tickets = config.send_tls13_tickets as usize;
1230
1231        server_config.alpn_protocols = if config.alpn_protocols.is_empty() {
1232            DEFAULT_ALPN_PROTOCOLS
1233                .iter()
1234                .map(|p| p.as_bytes().to_vec())
1235                .collect()
1236        } else {
1237            config
1238                .alpn_protocols
1239                .iter()
1240                .map(|p| p.as_bytes().to_vec())
1241                .collect()
1242        };
1243
1244        Ok(server_config)
1245    }
1246
1247    /// Apply a partial-update patch to this listener's live configuration.
1248    ///
1249    /// Fields absent in the patch (i.e. `None`) are preserved unchanged.
1250    /// If `alpn_protocols` is present the rustls `ServerConfig` is rebuilt —
1251    /// in-flight handshakes keep the old Arc; new ones see the new one.
1252    /// If `http_answers` is present only the listener-default templates are
1253    /// replaced; per-cluster overrides in `cluster_custom_answers` are kept.
1254    pub fn update_config(
1255        &mut self,
1256        patch: &UpdateHttpsListenerConfig,
1257    ) -> Result<(), ListenerError> {
1258        // Defense-in-depth validation: main-process ConfigState::dispatch
1259        // validates before scatter, but a raw protobuf client or state replay
1260        // may reach the worker without that check. `StateError` lifts into
1261        // `ListenerError` via `From` so `?` suffices.
1262        validate_h2_flood_knobs_https(patch)?;
1263        if let Some(ref alpn) = patch.alpn_protocols {
1264            validate_alpn_protocols(&alpn.values)?;
1265        }
1266        if let Some(ref hdr) = patch.sozu_id_header {
1267            validate_sozu_id_header(hdr)?;
1268        }
1269
1270        // --- simple field patches ---
1271        if let Some(v) = patch.public_address {
1272            self.config.public_address = Some(v);
1273        }
1274        if let Some(v) = patch.expect_proxy {
1275            self.config.expect_proxy = v;
1276        }
1277        if let Some(ref v) = patch.sticky_name {
1278            self.config.sticky_name = v.to_owned();
1279        }
1280        if let Some(v) = patch.front_timeout {
1281            self.config.front_timeout = v;
1282        }
1283        if let Some(v) = patch.back_timeout {
1284            self.config.back_timeout = v;
1285        }
1286        if let Some(v) = patch.connect_timeout {
1287            self.config.connect_timeout = v;
1288        }
1289        if let Some(v) = patch.request_timeout {
1290            self.config.request_timeout = v;
1291        }
1292        if let Some(v) = patch.strict_sni_binding {
1293            self.config.strict_sni_binding = Some(v);
1294        }
1295        if let Some(v) = patch.disable_http11 {
1296            self.config.disable_http11 = Some(v);
1297        }
1298        if let Some(ref v) = patch.sozu_id_header {
1299            self.config.sozu_id_header = Some(v.to_owned());
1300        }
1301        if let Some(v) = patch.elide_x_real_ip {
1302            self.config.elide_x_real_ip = Some(v);
1303        }
1304        if let Some(v) = patch.send_x_real_ip {
1305            self.config.send_x_real_ip = Some(v);
1306        }
1307
1308        // --- H2 flood knobs ---
1309        if let Some(v) = patch.h2_max_rst_stream_per_window {
1310            self.config.h2_max_rst_stream_per_window = Some(v);
1311        }
1312        if let Some(v) = patch.h2_max_ping_per_window {
1313            self.config.h2_max_ping_per_window = Some(v);
1314        }
1315        if let Some(v) = patch.h2_max_settings_per_window {
1316            self.config.h2_max_settings_per_window = Some(v);
1317        }
1318        if let Some(v) = patch.h2_max_empty_data_per_window {
1319            self.config.h2_max_empty_data_per_window = Some(v);
1320        }
1321        if let Some(v) = patch.h2_max_continuation_frames {
1322            self.config.h2_max_continuation_frames = Some(v);
1323        }
1324        if let Some(v) = patch.h2_max_glitch_count {
1325            self.config.h2_max_glitch_count = Some(v);
1326        }
1327        if let Some(v) = patch.h2_initial_connection_window {
1328            self.config.h2_initial_connection_window = Some(v);
1329        }
1330        if let Some(v) = patch.h2_max_concurrent_streams {
1331            self.config.h2_max_concurrent_streams = Some(v);
1332        }
1333        if let Some(v) = patch.h2_stream_shrink_ratio {
1334            self.config.h2_stream_shrink_ratio = Some(v);
1335        }
1336        if let Some(v) = patch.h2_max_rst_stream_lifetime {
1337            self.config.h2_max_rst_stream_lifetime = Some(v);
1338        }
1339        if let Some(v) = patch.h2_max_rst_stream_abusive_lifetime {
1340            self.config.h2_max_rst_stream_abusive_lifetime = Some(v);
1341        }
1342        if let Some(v) = patch.h2_max_rst_stream_emitted_lifetime {
1343            self.config.h2_max_rst_stream_emitted_lifetime = Some(v);
1344        }
1345        if let Some(v) = patch.h2_max_header_list_size {
1346            self.config.h2_max_header_list_size = Some(v);
1347        }
1348        if let Some(v) = patch.h2_max_header_table_size {
1349            self.config.h2_max_header_table_size = Some(v);
1350        }
1351        if let Some(v) = patch.h2_stream_idle_timeout_seconds {
1352            self.config.h2_stream_idle_timeout_seconds = Some(v);
1353        }
1354        if let Some(v) = patch.h2_graceful_shutdown_deadline_seconds {
1355            self.config.h2_graceful_shutdown_deadline_seconds = Some(v);
1356        }
1357        if let Some(v) = patch.h2_max_window_update_stream0_per_window {
1358            self.config.h2_max_window_update_stream0_per_window = Some(v);
1359        }
1360
1361        // --- ALPN rebuild (may force a rustls ServerConfig rebuild) ---
1362        //
1363        // Transactional: build the candidate rustls context first using a
1364        // **cloned** config that carries the new ALPN. Only if the build
1365        // succeeds do we commit `self.config.alpn_protocols` and swap the
1366        // Arc. This ensures a rustls failure (crypto provider transient,
1367        // resolver error, etc.) leaves the listener observably unchanged —
1368        // the master-side state would still diverge from the worker-side
1369        // refusal, but the worker itself stays consistent.
1370        if let Some(ref alpn_wrapper) = patch.alpn_protocols {
1371            let mut candidate = self.config.clone();
1372            candidate.alpn_protocols = alpn_wrapper.values.clone();
1373            let new_rustls = Arc::new(Self::create_rustls_context(
1374                &candidate,
1375                self.resolver.clone(),
1376            )?);
1377            // Build succeeded — commit.
1378            self.config.alpn_protocols = alpn_wrapper.values.clone();
1379            self.rustls_details = new_rustls;
1380        }
1381
1382        // HTTP answers: merge legacy `http_answers` and the new `answers`
1383        // map on top of the existing config, then rebuild the listener-level
1384        // template registry. Per-cluster overrides in
1385        // `HttpAnswers::cluster_answers` are preserved across the rebuild.
1386        let answers_changed = patch.http_answers.is_some() || !patch.answers.is_empty();
1387        if answers_changed {
1388            if let Some(ref new_answers) = patch.http_answers {
1389                crate::sozu_command::state::merge_custom_http_answers(
1390                    &mut self.config.http_answers,
1391                    new_answers,
1392                );
1393            }
1394            for (code, body) in &patch.answers {
1395                if !body.is_empty() {
1396                    self.config.answers.insert(code.clone(), body.clone());
1397                }
1398            }
1399
1400            let mut answers_map = self.config.answers.clone();
1401            if let Some(ref legacy) = self.config.http_answers {
1402                crate::protocol::http::answers::merge_legacy_into_map(&mut answers_map, legacy);
1403            }
1404            let mut rebuilt = HttpAnswers::new(&answers_map)
1405                .map_err(|(name, error)| ListenerError::TemplateParse(name, error))?;
1406            let preserved = std::mem::take(&mut self.answers.borrow_mut().cluster_answers);
1407            rebuilt.cluster_answers = preserved;
1408            *self.answers.borrow_mut() = rebuilt;
1409        }
1410
1411        // HSTS: full-object replacement when present in the patch. Absent
1412        // patch field preserves current value (matches the rest of this
1413        // partial-update handler). When `enabled` is missing on a present
1414        // HSTS block, refuse the patch — `enabled` is the explicit
1415        // disambiguator between "disable" and "enable" semantics, and the
1416        // operator must signal one or the other on every update.
1417        //
1418        // Inheriting frontends are refreshed in place via
1419        // `Router::refresh_inheriting_hsts`: every frontend whose HSTS
1420        // came from the previous listener default
1421        // (`Frontend.inherits_listener_hsts == true`) gets its
1422        // `headers_response` re-materialised against the new value.
1423        // Explicit per-frontend overrides
1424        // (`inherits_listener_hsts == false`) are untouched. The
1425        // `http.hsts.listener_default_patched` counter still fires so
1426        // dashboards can correlate patches with the new
1427        // `http.hsts.frontend_refreshed` counter (sum of refreshed
1428        // frontends from this patch).
1429        if let Some(new_hsts) = patch.hsts {
1430            if new_hsts.enabled.is_none() {
1431                return Err(ListenerError::HstsEnabledRequired);
1432            }
1433            self.config.hsts = Some(new_hsts);
1434            let refreshed = self
1435                .fronts
1436                .refresh_inheriting_hsts(self.config.hsts.as_ref());
1437            for _ in 0..refreshed {
1438                crate::incr!(names::http::HSTS_FRONTEND_REFRESHED);
1439            }
1440            info!(
1441                "{} HTTPS listener {:?} HSTS default patched; refreshed {} inheriting \
1442                 frontend(s). Explicit per-frontend overrides untouched.",
1443                log_module_context!(),
1444                self.config.address,
1445                refreshed,
1446            );
1447            crate::incr!(names::http::HSTS_LISTENER_DEFAULT_PATCHED);
1448        }
1449
1450        Ok(())
1451    }
1452
1453    pub fn add_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
1454        self.add_https_front_with_hsts_origin(tls_front, crate::router::HstsOrigin::Explicit)
1455    }
1456
1457    /// Variant of [`Self::add_https_front`] that records the origin of
1458    /// `tls_front.hsts` so listener-default patches can reflow inheriting
1459    /// frontends without disturbing explicit per-frontend overrides. The
1460    /// caller passes [`HstsOrigin::InheritedFromListenerDefault`] when
1461    /// the value was filled in from `self.config.hsts` rather than from
1462    /// the operator's per-frontend configuration.
1463    pub fn add_https_front_with_hsts_origin(
1464        &mut self,
1465        tls_front: HttpFrontend,
1466        hsts_origin: crate::router::HstsOrigin,
1467    ) -> Result<(), ListenerError> {
1468        self.fronts
1469            .add_http_front_with_hsts_origin(&tls_front, hsts_origin)
1470            .map_err(ListenerError::AddFrontend)
1471    }
1472
1473    pub fn remove_https_front(&mut self, tls_front: HttpFrontend) -> Result<(), ListenerError> {
1474        debug!(
1475            "{} removing tls_front {:?}",
1476            log_module_context!(),
1477            tls_front
1478        );
1479        self.fronts
1480            .remove_http_front(&tls_front)
1481            .map_err(ListenerError::RemoveFrontend)
1482    }
1483
1484    fn accept(&mut self) -> Result<MioTcpStream, AcceptError> {
1485        if let Some(ref sock) = self.listener {
1486            sock.accept()
1487                .map_err(|e| match e.kind() {
1488                    ErrorKind::WouldBlock => AcceptError::WouldBlock,
1489                    _ => {
1490                        error!("{} accept() IO error: {:?}", log_module_context!(), e);
1491                        AcceptError::IoError
1492                    }
1493                })
1494                .map(|(sock, _)| sock)
1495        } else {
1496            error!(
1497                "{} cannot accept connections, no listening socket available",
1498                log_module_context!()
1499            );
1500            Err(AcceptError::IoError)
1501        }
1502    }
1503}
1504
1505pub struct HttpsProxy {
1506    listeners: HashMap<Token, Rc<RefCell<HttpsListener>>>,
1507    clusters: HashMap<ClusterId, Cluster>,
1508    backends: Rc<RefCell<BackendMap>>,
1509    pool: Rc<RefCell<Pool>>,
1510    registry: Registry,
1511    sessions: Rc<RefCell<SessionManager>>,
1512}
1513
1514impl HttpsProxy {
1515    pub fn new(
1516        registry: Registry,
1517        sessions: Rc<RefCell<SessionManager>>,
1518        pool: Rc<RefCell<Pool>>,
1519        backends: Rc<RefCell<BackendMap>>,
1520    ) -> HttpsProxy {
1521        HttpsProxy {
1522            listeners: HashMap::new(),
1523            clusters: HashMap::new(),
1524            backends,
1525            pool,
1526            registry,
1527            sessions,
1528        }
1529    }
1530
1531    pub fn add_listener(
1532        &mut self,
1533        config: HttpsListenerConfig,
1534        token: Token,
1535    ) -> Result<Token, ProxyError> {
1536        match self.listeners.entry(token) {
1537            Entry::Vacant(entry) => {
1538                let https_listener =
1539                    HttpsListener::try_new(config, token).map_err(ProxyError::AddListener)?;
1540                entry.insert(Rc::new(RefCell::new(https_listener)));
1541                Ok(token)
1542            }
1543            _ => Err(ProxyError::ListenerAlreadyPresent),
1544        }
1545    }
1546
1547    pub fn remove_listener(
1548        &mut self,
1549        remove: RemoveListener,
1550    ) -> Result<Option<ResponseContent>, ProxyError> {
1551        let len = self.listeners.len();
1552
1553        let remove_address = remove.address.into();
1554        self.listeners
1555            .retain(|_, listener| listener.borrow().address != remove_address);
1556
1557        if !self.listeners.len() < len {
1558            info!(
1559                "{} no HTTPS listener to remove at address {}",
1560                log_module_context!(),
1561                remove_address
1562            )
1563        }
1564        Ok(None)
1565    }
1566
1567    pub fn soft_stop(&mut self) -> Result<(), ProxyError> {
1568        let listeners: HashMap<_, _> = self.listeners.drain().collect();
1569        let mut socket_errors = vec![];
1570        for (_, l) in listeners.iter() {
1571            if let Some(mut sock) = l.borrow_mut().listener.take() {
1572                debug!("{} deregistering socket {:?}", log_module_context!(), sock);
1573                if let Err(e) = self.registry.deregister(&mut sock) {
1574                    let error = format!("socket {sock:?}: {e:?}");
1575                    socket_errors.push(error);
1576                }
1577            }
1578        }
1579
1580        if !socket_errors.is_empty() {
1581            return Err(ProxyError::SoftStop {
1582                proxy_protocol: "HTTPS".to_string(),
1583                error: format!("Error deregistering listen sockets: {socket_errors:?}"),
1584            });
1585        }
1586
1587        Ok(())
1588    }
1589
1590    pub fn hard_stop(&mut self) -> Result<(), ProxyError> {
1591        let mut listeners: HashMap<_, _> = self.listeners.drain().collect();
1592        let mut socket_errors = vec![];
1593        for (_, l) in listeners.drain() {
1594            if let Some(mut sock) = l.borrow_mut().listener.take() {
1595                debug!("{} deregistering socket {:?}", log_module_context!(), sock);
1596                if let Err(e) = self.registry.deregister(&mut sock) {
1597                    let error = format!("socket {sock:?}: {e:?}");
1598                    socket_errors.push(error);
1599                }
1600            }
1601        }
1602
1603        if !socket_errors.is_empty() {
1604            return Err(ProxyError::HardStop {
1605                proxy_protocol: "HTTPS".to_string(),
1606                error: format!("Error deregistering listen sockets: {socket_errors:?}"),
1607            });
1608        }
1609
1610        Ok(())
1611    }
1612
1613    pub fn query_all_certificates(&mut self) -> Result<Option<ResponseContent>, ProxyError> {
1614        let certificates = self
1615            .listeners
1616            .values()
1617            .map(|listener| {
1618                let owned = listener.borrow();
1619                let resolver = unwrap_msg!(owned.resolver.0.lock());
1620                let certificate_summaries = resolver
1621                    .domains
1622                    .to_hashmap()
1623                    .drain()
1624                    .map(|(k, fingerprint)| CertificateSummary {
1625                        domain: String::from_utf8(k).unwrap(),
1626                        fingerprint: fingerprint.to_string(),
1627                    })
1628                    .collect();
1629
1630                CertificatesByAddress {
1631                    address: owned.address.into(),
1632                    certificate_summaries,
1633                }
1634            })
1635            .collect();
1636
1637        info!(
1638            "{} got Certificates::All query, answering with {:?}",
1639            log_module_context!(),
1640            certificates
1641        );
1642
1643        Ok(Some(
1644            ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
1645        ))
1646    }
1647
1648    pub fn query_certificate_for_domain(
1649        &mut self,
1650        domain: String,
1651    ) -> Result<Option<ResponseContent>, ProxyError> {
1652        let certificates = self
1653            .listeners
1654            .values()
1655            .map(|listener| {
1656                let owned = listener.borrow();
1657                let resolver = unwrap_msg!(owned.resolver.0.lock());
1658                let mut certificate_summaries = vec![];
1659
1660                if let Some((k, fingerprint)) = resolver.domain_lookup(domain.as_bytes(), true) {
1661                    certificate_summaries.push(CertificateSummary {
1662                        domain: String::from_utf8(k.to_vec()).unwrap(),
1663                        fingerprint: fingerprint.to_string(),
1664                    });
1665                }
1666                CertificatesByAddress {
1667                    address: owned.address.into(),
1668                    certificate_summaries,
1669                }
1670            })
1671            .collect();
1672
1673        info!(
1674            "{} got Certificates::Domain({}) query, answering with {:?}",
1675            log_module_context!(),
1676            domain,
1677            certificates
1678        );
1679
1680        Ok(Some(
1681            ContentType::CertificatesByAddress(ListOfCertificatesByAddress { certificates }).into(),
1682        ))
1683    }
1684
1685    pub fn activate_listener(
1686        &mut self,
1687        addr: &StdSocketAddr,
1688        tcp_listener: Option<MioTcpListener>,
1689    ) -> Result<Token, ProxyError> {
1690        let listener = self
1691            .listeners
1692            .values()
1693            .find(|listener| listener.borrow().address == *addr)
1694            .ok_or(ProxyError::NoListenerFound(addr.to_owned()))?;
1695
1696        listener
1697            .borrow_mut()
1698            .activate(&self.registry, tcp_listener)
1699            .map_err(|listener_error| ProxyError::ListenerActivation {
1700                address: *addr,
1701                listener_error,
1702            })
1703    }
1704
1705    pub fn give_back_listeners(&mut self) -> Vec<(StdSocketAddr, MioTcpListener)> {
1706        self.listeners
1707            .values()
1708            .filter_map(|listener| {
1709                let mut owned = listener.borrow_mut();
1710                if let Some(listener) = owned.listener.take() {
1711                    // Reset `active` so a subsequent `activate()` re-binds
1712                    // instead of short-circuiting on the stale flag.
1713                    owned.active = false;
1714                    return Some((owned.address, listener));
1715                }
1716
1717                None
1718            })
1719            .collect()
1720    }
1721
1722    pub fn give_back_listener(
1723        &mut self,
1724        address: StdSocketAddr,
1725    ) -> Result<(Token, MioTcpListener), ProxyError> {
1726        let listener = self
1727            .listeners
1728            .values()
1729            .find(|listener| listener.borrow().address == address)
1730            .ok_or(ProxyError::NoListenerFound(address))?;
1731
1732        let mut owned = listener.borrow_mut();
1733
1734        let taken_listener = owned
1735            .listener
1736            .take()
1737            .ok_or(ProxyError::UnactivatedListener)?;
1738
1739        // Reset `active` so a subsequent `activate()` re-binds instead of
1740        // short-circuiting on the stale flag.
1741        owned.active = false;
1742
1743        Ok((owned.token, taken_listener))
1744    }
1745
1746    /// Apply a partial-update patch to the identified HTTPS listener.
1747    pub fn update_listener(&mut self, patch: UpdateHttpsListenerConfig) -> Result<(), ProxyError> {
1748        let address: std::net::SocketAddr = patch.address.into();
1749        let listener = self
1750            .listeners
1751            .values()
1752            .find(|l| l.borrow().address == address)
1753            .ok_or(ProxyError::NoListenerFound(address))?;
1754        listener
1755            .borrow_mut()
1756            .update_config(&patch)
1757            .map_err(|listener_error| ProxyError::ListenerActivation {
1758                address,
1759                listener_error,
1760            })
1761    }
1762
1763    pub fn add_cluster(
1764        &mut self,
1765        mut cluster: Cluster,
1766    ) -> Result<Option<ResponseContent>, ProxyError> {
1767        let mut cluster_overrides = cluster.answers.clone();
1768        if let Some(answer_503) = cluster.answer_503.take() {
1769            cluster_overrides
1770                .entry("503".to_owned())
1771                .or_insert(answer_503);
1772        }
1773        if !cluster_overrides.is_empty() {
1774            for listener in self.listeners.values() {
1775                listener
1776                    .borrow()
1777                    .answers
1778                    .borrow_mut()
1779                    .add_cluster_answers(&cluster.cluster_id, &cluster_overrides)
1780                    .map_err(|(status, error)| {
1781                        ProxyError::AddCluster(ListenerError::TemplateParse(status, error))
1782                    })?;
1783            }
1784        }
1785        self.clusters.insert(cluster.cluster_id.clone(), cluster);
1786        Ok(None)
1787    }
1788
1789    pub fn remove_cluster(
1790        &mut self,
1791        cluster_id: &str,
1792    ) -> Result<Option<ResponseContent>, ProxyError> {
1793        self.clusters.remove(cluster_id);
1794        for listener in self.listeners.values() {
1795            listener
1796                .borrow()
1797                .answers
1798                .borrow_mut()
1799                .remove_cluster_answers(cluster_id);
1800        }
1801
1802        Ok(None)
1803    }
1804
1805    pub fn add_https_frontend(
1806        &mut self,
1807        front: RequestHttpFrontend,
1808    ) -> Result<Option<ResponseContent>, ProxyError> {
1809        let mut front = front.clone().to_frontend().map_err(|request_error| {
1810            ProxyError::WrongInputFrontend {
1811                front: Box::new(front),
1812                error: request_error.to_string(),
1813            }
1814        })?;
1815
1816        let mut listener = self
1817            .listeners
1818            .values()
1819            .find(|l| l.borrow().address == front.address)
1820            .ok_or(ProxyError::NoListenerFound(front.address))?
1821            .borrow_mut();
1822
1823        // ── HSTS listener-default → frontend inheritance ─────────────────
1824        // When the frontend declares no `hsts` block, fall back to the
1825        // listener default so the operator can opt into HSTS once at the
1826        // listener and have every HTTPS frontend inherit it.
1827        // `enabled = Some(false)` on the frontend is the explicit-disable
1828        // signal: it stays as-is and suppresses the inherited default.
1829        //
1830        // The `hsts_origin` flag is passed through to the router so the
1831        // resulting `Frontend` carries the inheritance bit; a later
1832        // `UpdateHttpsListenerConfig.hsts` patch will then refresh this
1833        // entry via `Router::refresh_inheriting_hsts` without disturbing
1834        // explicit per-frontend overrides.
1835        let hsts_origin = if front.hsts.is_none() && listener.config.hsts.is_some() {
1836            front.hsts = listener.config.hsts;
1837            crate::router::HstsOrigin::InheritedFromListenerDefault
1838        } else {
1839            crate::router::HstsOrigin::Explicit
1840        };
1841
1842        listener.set_tags(front.hostname.to_owned(), front.tags.to_owned());
1843        listener
1844            .add_https_front_with_hsts_origin(front, hsts_origin)
1845            .map_err(ProxyError::AddFrontend)?;
1846        Ok(None)
1847    }
1848
1849    pub fn remove_https_frontend(
1850        &mut self,
1851        front: RequestHttpFrontend,
1852    ) -> Result<Option<ResponseContent>, ProxyError> {
1853        let front = front.clone().to_frontend().map_err(|request_error| {
1854            ProxyError::WrongInputFrontend {
1855                front: Box::new(front),
1856                error: request_error.to_string(),
1857            }
1858        })?;
1859
1860        let mut listener = self
1861            .listeners
1862            .values()
1863            .find(|l| l.borrow().address == front.address)
1864            .ok_or(ProxyError::NoListenerFound(front.address))?
1865            .borrow_mut();
1866
1867        let hostname = front.hostname.to_owned();
1868
1869        listener
1870            .remove_https_front(front)
1871            .map_err(ProxyError::RemoveFrontend)?;
1872
1873        if !listener.fronts.has_hostname(&hostname) {
1874            listener.set_tags(hostname, None);
1875        }
1876        Ok(None)
1877    }
1878
1879    pub fn add_certificate(
1880        &mut self,
1881        add_certificate: AddCertificate,
1882    ) -> Result<Option<ResponseContent>, ProxyError> {
1883        let address = add_certificate.address.into();
1884
1885        let listener = self
1886            .listeners
1887            .values()
1888            .find(|l| l.borrow().address == address)
1889            .ok_or(ProxyError::NoListenerFound(address))?
1890            .borrow_mut();
1891
1892        let mut resolver = listener
1893            .resolver
1894            .0
1895            .lock()
1896            .map_err(|e| ProxyError::Lock(e.to_string()))?;
1897
1898        resolver
1899            .add_certificate(&add_certificate)
1900            .map_err(ProxyError::AddCertificate)?;
1901
1902        Ok(None)
1903    }
1904
1905    //FIXME: should return an error if certificate still has fronts referencing it
1906    pub fn remove_certificate(
1907        &mut self,
1908        remove_certificate: RemoveCertificate,
1909    ) -> Result<Option<ResponseContent>, ProxyError> {
1910        let address = remove_certificate.address.into();
1911
1912        let fingerprint = Fingerprint(
1913            hex::decode(&remove_certificate.fingerprint)
1914                .map_err(ProxyError::WrongCertificateFingerprint)?,
1915        );
1916
1917        let listener = self
1918            .listeners
1919            .values()
1920            .find(|l| l.borrow().address == address)
1921            .ok_or(ProxyError::NoListenerFound(address))?
1922            .borrow_mut();
1923
1924        let mut resolver = listener
1925            .resolver
1926            .0
1927            .lock()
1928            .map_err(|e| ProxyError::Lock(e.to_string()))?;
1929
1930        resolver
1931            .remove_certificate(&fingerprint)
1932            .map_err(ProxyError::RemoveCertificate)?;
1933
1934        Ok(None)
1935    }
1936
1937    //FIXME: should return an error if certificate still has fronts referencing it
1938    pub fn replace_certificate(
1939        &mut self,
1940        replace_certificate: ReplaceCertificate,
1941    ) -> Result<Option<ResponseContent>, ProxyError> {
1942        let address = replace_certificate.address.into();
1943
1944        let listener = self
1945            .listeners
1946            .values()
1947            .find(|l| l.borrow().address == address)
1948            .ok_or(ProxyError::NoListenerFound(address))?
1949            .borrow_mut();
1950
1951        let mut resolver = listener
1952            .resolver
1953            .0
1954            .lock()
1955            .map_err(|e| ProxyError::Lock(e.to_string()))?;
1956
1957        resolver
1958            .replace_certificate(&replace_certificate)
1959            .map_err(ProxyError::ReplaceCertificate)?;
1960
1961        Ok(None)
1962    }
1963}
1964
1965impl ProxyConfiguration for HttpsProxy {
1966    fn accept(&mut self, token: ListenToken) -> Result<MioTcpStream, AcceptError> {
1967        match self.listeners.get(&Token(token.0)) {
1968            Some(listener) => listener.borrow_mut().accept(),
1969            None => Err(AcceptError::IoError),
1970        }
1971    }
1972
1973    fn create_session(
1974        &mut self,
1975        mut frontend_sock: MioTcpStream,
1976        token: ListenToken,
1977        wait_time: Duration,
1978        proxy: Rc<RefCell<Self>>,
1979    ) -> Result<(), AcceptError> {
1980        let listener = self
1981            .listeners
1982            .get(&Token(token.0))
1983            .ok_or(AcceptError::IoError)?;
1984        if let Err(e) = frontend_sock.set_nodelay(true) {
1985            error!(
1986                "{} error setting nodelay on front socket({:?}): {:?}",
1987                log_module_context!(),
1988                frontend_sock,
1989                e
1990            );
1991        }
1992
1993        let owned = listener.borrow();
1994        let rustls_details = ServerConnection::new(owned.rustls_details.clone()).map_err(|e| {
1995            error!(
1996                "{} failed to create server session: {:?}",
1997                log_module_context!(),
1998                e
1999            );
2000            AcceptError::IoError
2001        })?;
2002
2003        let mut session_manager = self.sessions.borrow_mut();
2004        let entry = session_manager.slab.vacant_entry();
2005        let session_token = Token(entry.key());
2006
2007        self.registry
2008            .register(
2009                &mut frontend_sock,
2010                session_token,
2011                Interest::READABLE | Interest::WRITABLE,
2012            )
2013            .map_err(|register_error| {
2014                error!(
2015                    "{} error registering front socket({:?}): {:?}",
2016                    log_module_context!(),
2017                    frontend_sock,
2018                    register_error
2019                );
2020                AcceptError::RegisterError
2021            })?;
2022
2023        let public_address: StdSocketAddr = match owned.config.public_address {
2024            Some(pub_addr) => pub_addr.into(),
2025            None => owned.config.address.into(),
2026        };
2027
2028        let session = Rc::new(RefCell::new(HttpsSession::new(
2029            Duration::from_secs(owned.config.back_timeout as u64),
2030            Duration::from_secs(owned.config.connect_timeout as u64),
2031            Duration::from_secs(owned.config.front_timeout as u64),
2032            Duration::from_secs(owned.config.request_timeout as u64),
2033            owned.config.expect_proxy,
2034            listener.clone(),
2035            Rc::downgrade(&self.pool),
2036            proxy,
2037            public_address,
2038            rustls_details,
2039            frontend_sock,
2040            session_token,
2041            wait_time,
2042        )));
2043        entry.insert(session);
2044
2045        Ok(())
2046    }
2047
2048    fn notify(&mut self, request: WorkerRequest) -> WorkerResponse {
2049        let request_id = request.id.clone();
2050
2051        let request_type = match request.content.request_type {
2052            Some(t) => t,
2053            None => return WorkerResponse::error(request_id, "Empty request"),
2054        };
2055
2056        let content_result = match request_type {
2057            RequestType::AddCluster(cluster) => {
2058                debug!(
2059                    "{} {} add cluster {:?}",
2060                    log_module_context!(),
2061                    request_id,
2062                    cluster
2063                );
2064                self.add_cluster(cluster)
2065            }
2066            RequestType::RemoveCluster(cluster_id) => {
2067                debug!(
2068                    "{} {} remove cluster {:?}",
2069                    log_module_context!(),
2070                    request_id,
2071                    cluster_id
2072                );
2073                self.remove_cluster(&cluster_id)
2074            }
2075            RequestType::AddHttpsFrontend(front) => {
2076                debug!(
2077                    "{} {} add https front {:?}",
2078                    log_module_context!(),
2079                    request_id,
2080                    front
2081                );
2082                self.add_https_frontend(front)
2083            }
2084            RequestType::RemoveHttpsFrontend(front) => {
2085                debug!(
2086                    "{} {} remove https front {:?}",
2087                    log_module_context!(),
2088                    request_id,
2089                    front
2090                );
2091                self.remove_https_frontend(front)
2092            }
2093            RequestType::AddCertificate(add_certificate) => {
2094                debug!(
2095                    "{} {} add certificate: {:?}",
2096                    log_module_context!(),
2097                    request_id,
2098                    add_certificate
2099                );
2100                self.add_certificate(add_certificate)
2101            }
2102            RequestType::RemoveCertificate(remove_certificate) => {
2103                debug!(
2104                    "{} {} remove certificate: {:?}",
2105                    log_module_context!(),
2106                    request_id,
2107                    remove_certificate
2108                );
2109                self.remove_certificate(remove_certificate)
2110            }
2111            RequestType::ReplaceCertificate(replace_certificate) => {
2112                debug!(
2113                    "{} {} replace certificate: {:?}",
2114                    log_module_context!(),
2115                    request_id,
2116                    replace_certificate
2117                );
2118                self.replace_certificate(replace_certificate)
2119            }
2120            RequestType::RemoveListener(remove) => {
2121                debug!(
2122                    "{} removing HTTPS listener at address {:?}",
2123                    log_module_context!(),
2124                    remove.address
2125                );
2126                self.remove_listener(remove)
2127            }
2128            RequestType::SoftStop(_) => {
2129                debug!(
2130                    "{} {} processing soft shutdown",
2131                    log_module_context!(),
2132                    request_id
2133                );
2134                match self.soft_stop() {
2135                    Ok(_) => {
2136                        info!(
2137                            "{} {} soft stop successful",
2138                            log_module_context!(),
2139                            request_id
2140                        );
2141                        return WorkerResponse::processing(request.id);
2142                    }
2143                    Err(e) => Err(e),
2144                }
2145            }
2146            RequestType::HardStop(_) => {
2147                debug!(
2148                    "{} {} processing hard shutdown",
2149                    log_module_context!(),
2150                    request_id
2151                );
2152                match self.hard_stop() {
2153                    Ok(_) => {
2154                        debug!(
2155                            "{} {} hard stop successful",
2156                            log_module_context!(),
2157                            request_id
2158                        );
2159                        return WorkerResponse::processing(request.id);
2160                    }
2161                    Err(e) => Err(e),
2162                }
2163            }
2164            RequestType::Status(_) => {
2165                debug!("{} {} status", log_module_context!(), request_id);
2166                Ok(None)
2167            }
2168            RequestType::QueryCertificatesFromWorkers(filters) => {
2169                if let Some(domain) = filters.domain {
2170                    debug!(
2171                        "{} {} query certificate for domain {}",
2172                        log_module_context!(),
2173                        request_id,
2174                        domain
2175                    );
2176                    self.query_certificate_for_domain(domain)
2177                } else {
2178                    debug!(
2179                        "{} {} query all certificates",
2180                        log_module_context!(),
2181                        request_id
2182                    );
2183                    self.query_all_certificates()
2184                }
2185            }
2186            other_request => {
2187                debug!(
2188                    "{} {} unsupported message for HTTPS proxy, ignoring {:?}",
2189                    log_module_context!(),
2190                    request.id,
2191                    other_request
2192                );
2193                Err(ProxyError::UnsupportedMessage)
2194            }
2195        };
2196
2197        match content_result {
2198            Ok(content) => {
2199                debug!("{} {} successful", log_module_context!(), request_id);
2200                match content {
2201                    Some(content) => WorkerResponse::ok_with_content(request_id, content),
2202                    None => WorkerResponse::ok(request_id),
2203                }
2204            }
2205            Err(proxy_error) => {
2206                debug!(
2207                    "{} {} unsuccessful: {}",
2208                    log_module_context!(),
2209                    request_id,
2210                    proxy_error
2211                );
2212                WorkerResponse::error(request_id, proxy_error)
2213            }
2214        }
2215    }
2216}
2217impl L7Proxy for HttpsProxy {
2218    fn kind(&self) -> ListenerType {
2219        ListenerType::Https
2220    }
2221
2222    fn register_socket(
2223        &self,
2224        socket: &mut MioTcpStream,
2225        token: Token,
2226        interest: Interest,
2227    ) -> Result<(), std::io::Error> {
2228        self.registry.register(socket, token, interest)
2229    }
2230
2231    fn deregister_socket(&self, tcp_stream: &mut MioTcpStream) -> Result<(), std::io::Error> {
2232        self.registry.deregister(tcp_stream)
2233    }
2234
2235    fn add_session(&self, session: Rc<RefCell<dyn ProxySession>>) -> Token {
2236        let mut session_manager = self.sessions.borrow_mut();
2237        let entry = session_manager.slab.vacant_entry();
2238        let token = Token(entry.key());
2239        let _entry = entry.insert(session);
2240        token
2241    }
2242
2243    fn remove_session(&self, token: Token) -> bool {
2244        let mut sessions = self.sessions.borrow_mut();
2245        // Mirror of HttpProxy::remove_session — drain the per-(cluster,
2246        // source-IP) accounting before the slab slot is reused.
2247        sessions.untrack_all_cluster_ip(token);
2248        sessions.slab.try_remove(token.0).is_some()
2249    }
2250
2251    fn backends(&self) -> Rc<RefCell<BackendMap>> {
2252        self.backends.clone()
2253    }
2254
2255    fn clusters(&self) -> &HashMap<ClusterId, Cluster> {
2256        &self.clusters
2257    }
2258
2259    fn sessions(&self) -> Rc<RefCell<SessionManager>> {
2260        self.sessions.clone()
2261    }
2262}
2263
2264/// Used for metrics keeping
2265fn rustls_version_str(version: ProtocolVersion) -> &'static str {
2266    match version {
2267        ProtocolVersion::SSLv2 => "tls.version.SSLv2",
2268        ProtocolVersion::SSLv3 => "tls.version.SSLv3",
2269        ProtocolVersion::TLSv1_0 => "tls.version.TLSv1_0",
2270        ProtocolVersion::TLSv1_1 => "tls.version.TLSv1_1",
2271        ProtocolVersion::TLSv1_2 => "tls.version.TLSv1_2",
2272        ProtocolVersion::TLSv1_3 => "tls.version.TLSv1_3",
2273        ProtocolVersion::DTLSv1_0 => "tls.version.DTLSv1_0",
2274        ProtocolVersion::DTLSv1_2 => "tls.version.DTLSv1_2",
2275        ProtocolVersion::DTLSv1_3 => "tls.version.DTLSv1_3",
2276        ProtocolVersion::Unknown(_) => "tls.version.Unknown",
2277        _ => "tls.version.unimplemented",
2278    }
2279}
2280
2281/// Short label suitable for access logs (e.g. `"TLSv1.3"`).
2282///
2283/// Distinct from [`rustls_version_str`] which prefixes with `tls.version.`
2284/// for metric ingestion. Returns `None` for variants Sōzu does not know how
2285/// to label, so the access log records `tls_version` as absent rather than
2286/// emitting a misleading `"unimplemented"` literal.
2287pub(crate) fn rustls_version_label(version: ProtocolVersion) -> Option<&'static str> {
2288    match version {
2289        ProtocolVersion::SSLv2 => Some("SSLv2"),
2290        ProtocolVersion::SSLv3 => Some("SSLv3"),
2291        ProtocolVersion::TLSv1_0 => Some("TLSv1.0"),
2292        ProtocolVersion::TLSv1_1 => Some("TLSv1.1"),
2293        ProtocolVersion::TLSv1_2 => Some("TLSv1.2"),
2294        ProtocolVersion::TLSv1_3 => Some("TLSv1.3"),
2295        ProtocolVersion::DTLSv1_0 => Some("DTLSv1.0"),
2296        ProtocolVersion::DTLSv1_2 => Some("DTLSv1.2"),
2297        ProtocolVersion::DTLSv1_3 => Some("DTLSv1.3"),
2298        _ => None,
2299    }
2300}
2301
2302/// Used for metrics keeping
2303fn rustls_ciphersuite_str(cipher: SupportedCipherSuite) -> &'static str {
2304    match cipher.suite() {
2305        CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
2306            "tls.cipher.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
2307        }
2308        CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
2309            "tls.cipher.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
2310        }
2311        CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
2312            "tls.cipher.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
2313        }
2314        CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
2315            "tls.cipher.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
2316        }
2317        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
2318            "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
2319        }
2320        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
2321            "tls.cipher.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
2322        }
2323        CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => "tls.cipher.TLS13_CHACHA20_POLY1305_SHA256",
2324        CipherSuite::TLS13_AES_256_GCM_SHA384 => "tls.cipher.TLS13_AES_256_GCM_SHA384",
2325        CipherSuite::TLS13_AES_128_GCM_SHA256 => "tls.cipher.TLS13_AES_128_GCM_SHA256",
2326        _ => "tls.cipher.Unsupported",
2327    }
2328}
2329
2330/// Short label suitable for access logs (e.g. `"TLS_AES_128_GCM_SHA256"`).
2331///
2332/// Distinct from [`rustls_ciphersuite_str`] which prefixes with `tls.cipher.`
2333/// for metric ingestion. Returns `None` for cipher suites Sōzu does not know
2334/// how to label, so the access log records `tls_cipher` as absent rather
2335/// than emitting a misleading `"Unsupported"` literal.
2336pub(crate) fn rustls_ciphersuite_label(cipher: SupportedCipherSuite) -> Option<&'static str> {
2337    match cipher.suite() {
2338        CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 => {
2339            Some("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256")
2340        }
2341        CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 => {
2342            Some("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256")
2343        }
2344        CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 => {
2345            Some("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256")
2346        }
2347        CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 => {
2348            Some("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384")
2349        }
2350        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 => {
2351            Some("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256")
2352        }
2353        CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 => {
2354            Some("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384")
2355        }
2356        CipherSuite::TLS13_CHACHA20_POLY1305_SHA256 => Some("TLS13_CHACHA20_POLY1305_SHA256"),
2357        CipherSuite::TLS13_AES_256_GCM_SHA384 => Some("TLS13_AES_256_GCM_SHA384"),
2358        CipherSuite::TLS13_AES_128_GCM_SHA256 => Some("TLS13_AES_128_GCM_SHA256"),
2359        _ => None,
2360    }
2361}
2362
2363pub mod testing {
2364    use crate::testing::*;
2365
2366    /// this function is not used, but is available for example and testing purposes
2367    pub fn start_https_worker(
2368        config: HttpsListenerConfig,
2369        channel: ProxyChannel,
2370        max_buffers: usize,
2371        buffer_size: usize,
2372    ) -> anyhow::Result<()> {
2373        let address = config.address.into();
2374
2375        let ServerParts {
2376            event_loop,
2377            registry,
2378            sessions,
2379            pool,
2380            backends,
2381            client_scm_socket: _,
2382            server_scm_socket,
2383            server_config,
2384        } = prebuild_server(max_buffers, buffer_size, true)?;
2385
2386        let token = {
2387            let mut sessions = sessions.borrow_mut();
2388            let entry = sessions.slab.vacant_entry();
2389            let key = entry.key();
2390            let _ = entry.insert(Rc::new(RefCell::new(ListenSession {
2391                protocol: Protocol::HTTPSListen,
2392            })));
2393            Token(key)
2394        };
2395
2396        let mut proxy = HttpsProxy::new(registry, sessions.clone(), pool.clone(), backends.clone());
2397        proxy
2398            .add_listener(config, token)
2399            .with_context(|| "Failed at creating adding the listener")?;
2400        proxy
2401            .activate_listener(&address, None)
2402            .with_context(|| "Failed at creating activating the listener")?;
2403
2404        let mut server = Server::new(
2405            event_loop,
2406            channel,
2407            server_scm_socket,
2408            sessions,
2409            pool,
2410            backends,
2411            None,
2412            Some(proxy),
2413            None,
2414            server_config,
2415            None,
2416            false,
2417        )
2418        .with_context(|| "Failed at creating server")?;
2419
2420        debug!("{} starting event loop", log_module_context!());
2421        server.run();
2422        debug!("{} ending event loop", log_module_context!());
2423        Ok(())
2424    }
2425}
2426
2427#[cfg(test)]
2428mod tests {
2429    use std::sync::Arc;
2430
2431    use sozu_command::{config::ListenerBuilder, proto::command::SocketAddress};
2432
2433    use super::*;
2434    use crate::router::{MethodRule, PathRule, Route, Router, pattern_trie::TrieNode};
2435
2436    /*
2437    #[test]
2438    #[cfg(target_pointer_width = "64")]
2439    fn size_test() {
2440      assert_size!(ExpectProxyProtocol<mio::net::TcpStream>, 520);
2441      assert_size!(TlsHandshake, 240);
2442      assert_size!(Http<SslStream<mio::net::TcpStream>>, 1232);
2443      assert_size!(Pipe<SslStream<mio::net::TcpStream>>, 272);
2444      assert_size!(State, 1240);
2445      // fails depending on the platform?
2446      assert_size!(Session, 1672);
2447
2448      assert_size!(SslStream<mio::net::TcpStream>, 16);
2449      assert_size!(Ssl, 8);
2450    }
2451    */
2452
2453    #[test]
2454    fn frontend_from_request_test() {
2455        let cluster_id1 = "cluster_1".to_owned();
2456        let cluster_id2 = "cluster_2".to_owned();
2457        let cluster_id3 = "cluster_3".to_owned();
2458        let uri1 = "/".to_owned();
2459        let uri2 = "/yolo".to_owned();
2460        let uri3 = "/yolo/swag".to_owned();
2461
2462        let mut fronts = Router::new();
2463        assert!(fronts.add_tree_rule(
2464            "lolcatho.st".as_bytes(),
2465            &PathRule::Prefix(uri1),
2466            &MethodRule::new(None),
2467            &Route::ClusterId(cluster_id1.clone())
2468        ));
2469        assert!(fronts.add_tree_rule(
2470            "lolcatho.st".as_bytes(),
2471            &PathRule::Prefix(uri2),
2472            &MethodRule::new(None),
2473            &Route::ClusterId(cluster_id2)
2474        ));
2475        assert!(fronts.add_tree_rule(
2476            "lolcatho.st".as_bytes(),
2477            &PathRule::Prefix(uri3),
2478            &MethodRule::new(None),
2479            &Route::ClusterId(cluster_id3)
2480        ));
2481        assert!(fronts.add_tree_rule(
2482            "other.domain".as_bytes(),
2483            &PathRule::Prefix("test".to_string()),
2484            &MethodRule::new(None),
2485            &Route::ClusterId(cluster_id1)
2486        ));
2487
2488        let address = SocketAddress::new_v4(127, 0, 0, 1, 1032);
2489        let resolver = Arc::new(MutexCertificateResolver::default());
2490
2491        let crypto_provider = Arc::new(default_provider());
2492
2493        let server_config = RustlsServerConfig::builder_with_provider(crypto_provider)
2494            .with_protocol_versions(&[&rustls::version::TLS12, &rustls::version::TLS13])
2495            .expect("could not create rustls config server")
2496            .with_no_client_auth()
2497            .with_cert_resolver(resolver.clone());
2498
2499        let rustls_details = Arc::new(server_config);
2500
2501        let default_config = ListenerBuilder::new_https(address)
2502            .to_tls(None)
2503            .expect("Could not create default HTTPS listener config");
2504
2505        println!("it doesn't even matter");
2506
2507        let listener = HttpsListener {
2508            listener: None,
2509            address: address.into(),
2510            fronts,
2511            rustls_details,
2512            resolver,
2513            answers: Rc::new(RefCell::new(
2514                HttpAnswers::new(&std::collections::BTreeMap::new()).unwrap(),
2515            )),
2516            config: default_config,
2517            token: Token(0),
2518            active: true,
2519            tags: BTreeMap::new(),
2520        };
2521
2522        println!("TEST {}", line!());
2523        let frontend1 = listener.frontend_from_request("lolcatho.st", "/", &Method::Get);
2524        assert_eq!(
2525            frontend1
2526                .expect("should find a frontend")
2527                .cluster_id
2528                .as_deref(),
2529            Some("cluster_1")
2530        );
2531        println!("TEST {}", line!());
2532        let frontend2 = listener.frontend_from_request("lolcatho.st", "/test", &Method::Get);
2533        assert_eq!(
2534            frontend2
2535                .expect("should find a frontend")
2536                .cluster_id
2537                .as_deref(),
2538            Some("cluster_1")
2539        );
2540        println!("TEST {}", line!());
2541        let frontend3 = listener.frontend_from_request("lolcatho.st", "/yolo/test", &Method::Get);
2542        assert_eq!(
2543            frontend3
2544                .expect("should find a frontend")
2545                .cluster_id
2546                .as_deref(),
2547            Some("cluster_2")
2548        );
2549        println!("TEST {}", line!());
2550        let frontend4 = listener.frontend_from_request("lolcatho.st", "/yolo/swag", &Method::Get);
2551        assert_eq!(
2552            frontend4
2553                .expect("should find a frontend")
2554                .cluster_id
2555                .as_deref(),
2556            Some("cluster_3")
2557        );
2558        println!("TEST {}", line!());
2559        let frontend5 = listener.frontend_from_request("domain", "/", &Method::Get);
2560        assert!(frontend5.is_err());
2561        // assert!(false);
2562    }
2563
2564    #[test]
2565    fn wildcard_certificate_names() {
2566        let mut trie = TrieNode::root();
2567
2568        trie.domain_insert("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8);
2569        trie.domain_insert("*.clever-cloud.com".as_bytes().to_vec(), 2u8);
2570        trie.domain_insert("services.clever-cloud.com".as_bytes().to_vec(), 0u8);
2571        trie.domain_insert(
2572            "abprefix.services.clever-cloud.com".as_bytes().to_vec(),
2573            3u8,
2574        );
2575        trie.domain_insert(
2576            "cdprefix.services.clever-cloud.com".as_bytes().to_vec(),
2577            4u8,
2578        );
2579
2580        let res = trie.domain_lookup(b"test.services.clever-cloud.com", true);
2581        println!("query result: {res:?}");
2582
2583        assert_eq!(
2584            trie.domain_lookup(b"pgstudio.services.clever-cloud.com", true),
2585            Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
2586        );
2587        assert_eq!(
2588            trie.domain_lookup(b"test-prefix.services.clever-cloud.com", true),
2589            Some(&("*.services.clever-cloud.com".as_bytes().to_vec(), 1u8))
2590        );
2591    }
2592
2593    #[test]
2594    fn wildcard_with_subdomains() {
2595        let mut trie = TrieNode::root();
2596
2597        trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
2598        trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
2599
2600        let res = trie.domain_lookup(b"sub.test.example.com", true);
2601        println!("query result: {res:?}");
2602
2603        assert_eq!(
2604            trie.domain_lookup(b"sub.test.example.com", true),
2605            Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
2606        );
2607        assert_eq!(
2608            trie.domain_lookup(b"hello.sub.test.example.com", true),
2609            Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
2610        );
2611
2612        // now try in a different order
2613        let mut trie = TrieNode::root();
2614
2615        trie.domain_insert("hello.sub.test.example.com".as_bytes().to_vec(), 2u8);
2616        trie.domain_insert("*.test.example.com".as_bytes().to_vec(), 1u8);
2617
2618        let res = trie.domain_lookup(b"sub.test.example.com", true);
2619        println!("query result: {res:?}");
2620
2621        assert_eq!(
2622            trie.domain_lookup(b"sub.test.example.com", true),
2623            Some(&("*.test.example.com".as_bytes().to_vec(), 1u8))
2624        );
2625        assert_eq!(
2626            trie.domain_lookup(b"hello.sub.test.example.com", true),
2627            Some(&("hello.sub.test.example.com".as_bytes().to_vec(), 2u8))
2628        );
2629    }
2630
2631    #[test]
2632    fn h2_stream_idle_timeout_inherits_back_timeout() {
2633        use std::time::Duration;
2634
2635        let address = SocketAddress::new_v4(127, 0, 0, 1, 1041);
2636        let build = |back_timeout: u32, explicit: Option<u32>| -> HttpsListener {
2637            let mut cfg = ListenerBuilder::new_https(address)
2638                .to_tls(None)
2639                .expect("default HTTPS listener config");
2640            cfg.back_timeout = back_timeout;
2641            cfg.h2_stream_idle_timeout_seconds = explicit;
2642            HttpsListener::try_new(cfg, Token(0)).expect("build listener")
2643        };
2644
2645        // Knob unset: inherit back_timeout when it exceeds the 30s floor.
2646        assert_eq!(
2647            build(180, None).get_h2_stream_idle_timeout(),
2648            Duration::from_secs(180)
2649        );
2650
2651        // Knob unset, back_timeout below floor: stay at 30s.
2652        assert_eq!(
2653            build(5, None).get_h2_stream_idle_timeout(),
2654            Duration::from_secs(30)
2655        );
2656
2657        // Explicit values win in both directions.
2658        assert_eq!(
2659            build(180, Some(10)).get_h2_stream_idle_timeout(),
2660            Duration::from_secs(10)
2661        );
2662        assert_eq!(
2663            build(5, Some(600)).get_h2_stream_idle_timeout(),
2664            Duration::from_secs(600)
2665        );
2666
2667        // `Some(0)` is clamped to 1s.
2668        assert_eq!(
2669            build(180, Some(0)).get_h2_stream_idle_timeout(),
2670            Duration::from_secs(1)
2671        );
2672    }
2673}