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