From 1a2d7529db729614cf3e1c21ec13d95bfa6e857f Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 20 Mar 2026 02:53:41 +0000 Subject: [PATCH] fix(rustproxy-http): enable TLS connections for HTTP/3 upstream requests when backend re-encryption or TLS is configured --- changelog.md | 7 +++ rust/crates/rustproxy-http/src/h3_service.rs | 52 ++++++++++++++------ ts/00_commitinfo_data.ts | 2 +- 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/changelog.md b/changelog.md index 6892c39..f471037 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-03-20 - 25.17.2 - fix(rustproxy-http) +enable TLS connections for HTTP/3 upstream requests when backend re-encryption or TLS is configured + +- Pass backend TLS client configuration into the HTTP/3 request handler. +- Detect TLS-required upstream targets using route and target TLS settings before connecting. +- Build backend request URIs with the correct http or https scheme to match the upstream connection. + ## 2026-03-20 - 25.17.1 - fix(rustproxy-routing) allow QUIC UDP TLS connections without SNI to match domain-restricted routes diff --git a/rust/crates/rustproxy-http/src/h3_service.rs b/rust/crates/rustproxy-http/src/h3_service.rs index 8a940ac..27d60a7 100644 --- a/rust/crates/rustproxy-http/src/h3_service.rs +++ b/rust/crates/rustproxy-http/src/h3_service.rs @@ -36,7 +36,6 @@ pub struct H3ProxyService { protocol_cache: Arc, #[allow(dead_code)] upstream_selector: UpstreamSelector, - #[allow(dead_code)] backend_tls_config: Arc, connect_timeout: Duration, } @@ -98,12 +97,14 @@ impl H3ProxyService { let rm = self.route_manager.load(); let pool = Arc::clone(&self.connection_pool); let metrics = Arc::clone(&self.metrics); + let backend_tls = Arc::clone(&self.backend_tls_config); let connect_timeout = self.connect_timeout; let client_ip = client_ip.clone(); tokio::spawn(async move { if let Err(e) = handle_h3_request( - request, stream, port, &client_ip, &rm, &pool, &metrics, connect_timeout, + request, stream, port, &client_ip, &rm, &pool, &metrics, + &backend_tls, connect_timeout, ).await { debug!("HTTP/3 request error from {}: {}", client_ip, e); } @@ -133,6 +134,7 @@ async fn handle_h3_request( route_manager: &RouteManager, _connection_pool: &ConnectionPool, metrics: &MetricsCollector, + backend_tls_config: &Arc, connect_timeout: Duration, ) -> anyhow::Result<()> { let method = request.method().clone(); @@ -173,7 +175,15 @@ async fn handle_h3_request( let backend_port = target.port.resolve(port); let backend_addr = format!("{}:{}", backend_host, backend_port); - // Connect to backend via TCP HTTP/1.1 with timeout + // Determine if backend requires TLS (same logic as proxy_service.rs) + let mut use_tls = target.tls.is_some(); + if let Some(ref tls) = route.action.tls { + if tls.mode == rustproxy_config::TlsMode::TerminateAndReencrypt { + use_tls = true; + } + } + + // Connect to backend via TCP with timeout let tcp_stream = tokio::time::timeout( connect_timeout, tokio::net::TcpStream::connect(&backend_addr), @@ -183,15 +193,27 @@ async fn handle_h3_request( let _ = tcp_stream.set_nodelay(true); - let io = hyper_util::rt::TokioIo::new(tcp_stream); - let (mut sender, conn) = hyper::client::conn::http1::handshake(io).await - .map_err(|e| anyhow::anyhow!("Backend handshake failed: {}", e))?; - - tokio::spawn(async move { - if let Err(e) = conn.await { - debug!("Backend connection closed: {}", e); - } - }); + // Branch: wrap in TLS if backend requires it, then HTTP/1.1 handshake. + // hyper's SendRequest is NOT generic over the IO type, so both branches + // produce the same type and can be unified. + let mut sender = if use_tls { + let connector = tokio_rustls::TlsConnector::from(Arc::clone(backend_tls_config)); + let server_name = rustls::pki_types::ServerName::try_from(backend_host.to_string()) + .map_err(|e| anyhow::anyhow!("Invalid backend SNI '{}': {}", backend_host, e))?; + let tls_stream = connector.connect(server_name, tcp_stream).await + .map_err(|e| anyhow::anyhow!("Backend TLS handshake to {} failed: {}", backend_addr, e))?; + let io = hyper_util::rt::TokioIo::new(tls_stream); + let (sender, conn) = hyper::client::conn::http1::handshake(io).await + .map_err(|e| anyhow::anyhow!("Backend handshake failed: {}", e))?; + tokio::spawn(async move { let _ = conn.await; }); + sender + } else { + let io = hyper_util::rt::TokioIo::new(tcp_stream); + let (sender, conn) = hyper::client::conn::http1::handshake(io).await + .map_err(|e| anyhow::anyhow!("Backend handshake failed: {}", e))?; + tokio::spawn(async move { let _ = conn.await; }); + sender + }; // Stream request body from H3 client to backend via an mpsc channel. // This avoids buffering the entire request body in memory. @@ -214,7 +236,7 @@ async fn handle_h3_request( // Create a body that polls from the mpsc receiver let body = H3RequestBody { receiver: body_rx }; - let backend_req = build_backend_request(&method, &backend_addr, &path, &host, &request, body)?; + let backend_req = build_backend_request(&method, &backend_addr, &path, &host, &request, body, use_tls)?; let response = sender.send_request(backend_req).await .map_err(|e| anyhow::anyhow!("Backend request failed: {}", e))?; @@ -294,10 +316,12 @@ fn build_backend_request( host: &str, original_request: &hyper::Request<()>, body: B, + use_tls: bool, ) -> anyhow::Result> { + let scheme = if use_tls { "https" } else { "http" }; let mut req = hyper::Request::builder() .method(method) - .uri(format!("http://{}{}", backend_addr, path)) + .uri(format!("{}://{}{}", scheme, backend_addr, path)) .header("host", host); // Forward non-pseudo headers diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index a62d387..8234427 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '25.17.1', + version: '25.17.2', description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' }