feat(quic,http3): add HTTP/3 proxy handling and hot-reload QUIC TLS configuration

This commit is contained in:
2026-03-19 20:27:57 +00:00
parent 9e1103e7a7
commit af970c447e
10 changed files with 1911 additions and 1780 deletions

View File

@@ -1,8 +1,11 @@
//! Bounded, TTL-based protocol detection cache for HTTP/2 auto-detection.
//! Bounded, TTL-based protocol detection cache for backend protocol auto-detection.
//!
//! Caches the ALPN-negotiated protocol (H1 or H2) per backend endpoint and requested
//! Caches the detected protocol (H1, H2, or H3) per backend endpoint and requested
//! domain (host:port + requested_host). This prevents cache oscillation when multiple
//! frontend domains share the same backend but differ in HTTP/2 support.
//! frontend domains share the same backend but differ in protocol support.
//!
//! H3 detection uses the browser model: Alt-Svc headers from H1/H2 responses are
//! parsed and cached, including the advertised H3 port (which may differ from TCP).
use std::sync::Arc;
use std::time::{Duration, Instant};
@@ -29,6 +32,14 @@ pub enum DetectedProtocol {
H3,
}
/// Result of a protocol cache lookup.
#[derive(Debug, Clone, Copy)]
pub struct CachedProtocol {
pub protocol: DetectedProtocol,
/// For H3: the port advertised by Alt-Svc (may differ from TCP port).
pub h3_port: Option<u16>,
}
/// Key for the protocol cache: (host, port, requested_host).
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct ProtocolCacheKey {
@@ -43,6 +54,8 @@ pub struct ProtocolCacheKey {
struct CachedEntry {
protocol: DetectedProtocol,
detected_at: Instant,
/// For H3: the port advertised by Alt-Svc (may differ from TCP port).
h3_port: Option<u16>,
}
/// Bounded, TTL-based protocol detection cache.
@@ -75,11 +88,14 @@ impl ProtocolCache {
/// Look up the cached protocol for a backend endpoint.
/// Returns `None` if not cached or expired (caller should probe via ALPN).
pub fn get(&self, key: &ProtocolCacheKey) -> Option<DetectedProtocol> {
pub fn get(&self, key: &ProtocolCacheKey) -> Option<CachedProtocol> {
let entry = self.cache.get(key)?;
if entry.detected_at.elapsed() < PROTOCOL_CACHE_TTL {
debug!("Protocol cache hit: {:?} for {}:{} (requested: {:?})", entry.protocol, key.host, key.port, key.requested_host);
Some(entry.protocol)
Some(CachedProtocol {
protocol: entry.protocol,
h3_port: entry.h3_port,
})
} else {
// Expired — remove and return None to trigger re-probe
drop(entry); // release DashMap ref before remove
@@ -91,6 +107,16 @@ impl ProtocolCache {
/// Insert a detected protocol into the cache.
/// If the cache is at capacity, evict the oldest entry first.
pub fn insert(&self, key: ProtocolCacheKey, protocol: DetectedProtocol) {
self.insert_with_h3_port(key, protocol, None);
}
/// Insert an H3 detection result with the Alt-Svc advertised port.
pub fn insert_h3(&self, key: ProtocolCacheKey, h3_port: u16) {
self.insert_with_h3_port(key, DetectedProtocol::H3, Some(h3_port));
}
/// Insert a protocol detection result with an optional H3 port.
fn insert_with_h3_port(&self, key: ProtocolCacheKey, protocol: DetectedProtocol, h3_port: Option<u16>) {
if self.cache.len() >= PROTOCOL_CACHE_MAX_ENTRIES && !self.cache.contains_key(&key) {
// Evict the oldest entry to stay within bounds
let oldest = self.cache.iter()
@@ -103,6 +129,7 @@ impl ProtocolCache {
self.cache.insert(key, CachedEntry {
protocol,
detected_at: Instant::now(),
h3_port,
});
}