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

@@ -340,6 +340,17 @@ impl RustProxy {
self.cancel_token.clone(),
);
// Construct H3ProxyService for HTTP/3 request handling
let h3_svc = rustproxy_http::h3_service::H3ProxyService::new(
Arc::new(ArcSwap::from(Arc::clone(&*self.route_table.load()))),
Arc::clone(&self.metrics),
Arc::new(rustproxy_http::connection_pool::ConnectionPool::new()),
Arc::new(rustproxy_http::protocol_cache::ProtocolCache::new()),
rustproxy_passthrough::tls_handler::shared_backend_tls_config(),
std::time::Duration::from_secs(30),
);
udp_mgr.set_h3_service(Arc::new(h3_svc));
for port in &udp_ports {
udp_mgr.add_port_with_tls(*port, quic_tls_config.clone()).await?;
}
@@ -772,13 +783,21 @@ impl RustProxy {
}
}
// Build TLS config for QUIC before taking mutable borrow on udp_mgr
let quic_tls = if new_udp_ports.iter().any(|p| !old_udp_ports.contains(p)) {
let tls_configs = self.current_tls_configs().await;
Self::build_quic_tls_config(&tls_configs)
} else {
None
};
if let Some(ref mut udp_mgr) = self.udp_listener_manager {
udp_mgr.update_routes(Arc::clone(&new_manager));
// Add new UDP ports
// Add new UDP ports (with TLS for QUIC)
for port in &new_udp_ports {
if !old_udp_ports.contains(port) {
udp_mgr.add_port(*port).await?;
udp_mgr.add_port_with_tls(*port, quic_tls.clone()).await?;
}
}
// Remove old UDP ports
@@ -1005,6 +1024,33 @@ impl RustProxy {
Some(Arc::new(tls_config))
}
/// Build the current full TLS config map from all sources (route configs, loaded certs, cert manager).
async fn current_tls_configs(&self) -> HashMap<String, TlsCertConfig> {
let mut configs = Self::extract_tls_configs(&self.options.routes);
// Merge dynamically loaded certs (from loadCertificate IPC)
for (d, c) in &self.loaded_certs {
if !configs.contains_key(d) {
configs.insert(d.clone(), c.clone());
}
}
// Merge certs from cert manager store
if let Some(ref cm_arc) = self.cert_manager {
let cm = cm_arc.lock().await;
for (d, b) in cm.store().iter() {
if !configs.contains_key(d) {
configs.insert(d.clone(), TlsCertConfig {
cert_pem: b.cert_pem.clone(),
key_pem: b.key_pem.clone(),
});
}
}
}
configs
}
/// Set the Unix domain socket path for relaying UDP datagrams to TypeScript datagramHandler callbacks.
pub async fn set_datagram_handler_relay_path(&mut self, path: Option<String>) {
info!("Datagram handler relay path set to: {:?}", path);
@@ -1055,37 +1101,21 @@ impl RustProxy {
key_pem: key_pem.clone(),
});
// Hot-swap TLS config on the listener
if let Some(ref mut listener) = self.listener_manager {
let mut tls_configs = Self::extract_tls_configs(&self.options.routes);
// Hot-swap TLS config on TCP and QUIC listeners
let tls_configs = self.current_tls_configs().await;
// Add the new cert
tls_configs.insert(domain.to_string(), TlsCertConfig {
cert_pem: cert_pem.clone(),
key_pem: key_pem.clone(),
});
// Also include all existing certs from cert manager
if let Some(ref cm_arc) = self.cert_manager {
let cm = cm_arc.lock().await;
for (d, b) in cm.store().iter() {
if !tls_configs.contains_key(d) {
tls_configs.insert(d.clone(), TlsCertConfig {
cert_pem: b.cert_pem.clone(),
key_pem: b.key_pem.clone(),
});
}
}
}
// Merge dynamically loaded certs from previous loadCertificate calls
for (d, c) in &self.loaded_certs {
if !tls_configs.contains_key(d) {
tls_configs.insert(d.clone(), c.clone());
}
}
if let Some(ref listener) = self.listener_manager {
// Build QUIC TLS config before TCP consumes the map
let quic_tls = Self::build_quic_tls_config(&tls_configs);
listener.set_tls_configs(tls_configs);
// Also update QUIC endpoints with the new certs
if let Some(ref udp_mgr) = self.udp_listener_manager {
if let Some(quic_config) = quic_tls {
udp_mgr.update_quic_tls(quic_config);
}
}
}
info!("Certificate loaded and TLS config updated for {}", domain);