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