From e7680ff47e09dfbf1ef3e42d3ffc35a1bd301092 Mon Sep 17 00:00:00 2001 From: cfal Date: Sat, 31 Oct 2026 13:12:43 +0000 Subject: [PATCH] fix(xhttp): suppress inherited SNI inside the carrier Retain the hop identity for certificate verification while restoring the absent default wire SNI for inner protocols. Extend the TLS identity matrix through the XHTTP builder to cover omitted, null, and explicit SNI. --- src/tcp/tcp_client_handler_factory.rs | 2 +- src/tls_client_handler.rs | 35 +++++++++++++++++++-------- src/xhttp/connector.rs | 3 ++- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/tcp/tcp_client_handler_factory.rs b/src/tcp/tcp_client_handler_factory.rs index 01afe3cd..d94837f8 100644 --- a/src/tcp/tcp_client_handler_factory.rs +++ b/src/tcp/tcp_client_handler_factory.rs @@ -63,7 +63,7 @@ pub(crate) fn tls_verification_name( rustls::pki_types::ServerName::try_from(identity).map_err(std::io::Error::other) } -fn create_with_hop_context( +pub(crate) fn create_with_hop_context( client_proxy_config: ClientProxyConfig, hop_location: &NetLocation, default_sni_hostname: Option, diff --git a/src/tls_client_handler.rs b/src/tls_client_handler.rs index 274d0e40..3d101ff9 100644 --- a/src/tls_client_handler.rs +++ b/src/tls_client_handler.rs @@ -167,15 +167,20 @@ mod tests { #[tokio::test] async fn verification_uses_hop_identity_even_without_wire_sni() { - check_hop_identity(false).await; + check_hop_identity(false, false).await; } #[tokio::test] async fn udp_verification_uses_hop_identity_even_without_wire_sni() { - check_hop_identity(true).await; + check_hop_identity(true, false).await; } - async fn check_hop_identity(udp: bool) { + #[tokio::test] + async fn xhttp_inner_tls_keeps_hop_identity_without_default_sni() { + check_hop_identity(false, true).await; + } + + async fn check_hop_identity(udp: bool, xhttp: bool) { use crate::address::NetLocation; use crate::async_stream::{AsyncFlushMessage, AsyncReadMessage, AsyncWriteMessage}; use crate::tcp::tcp_client_handler_factory::create_tcp_client_handler; @@ -208,7 +213,7 @@ mod tests { "localhost:443", "", "localhost", - if nested { None } else { Some("localhost") }, + if nested || xhttp { None } else { Some("localhost") }, ), ("localhost:443", "sni_hostname: null", "localhost", None), ("127.0.0.1:443", "", "127.0.0.1", None), @@ -269,12 +274,22 @@ mod tests { yaml }; let handler = crate::rustls_config_util::with_test_roots(roots, || { - create_tcp_client_handler( - serde_yaml::from_str(&yaml).unwrap(), - &hop, - Arc::new(crate::resolver::NativeResolver::new()), - ) - .unwrap() + let protocol: crate::config::ClientProxyConfig = + serde_yaml::from_str(&yaml).unwrap(); + let resolver = Arc::new(crate::resolver::NativeResolver::new()); + if xhttp { + let config = serde_json::from_value(serde_json::json!({ + "address": hop.to_string(), + "protocol": {"type": "xhttp", "protocol": protocol} + })) + .unwrap(); + crate::xhttp::connector::build(&config, resolver) + .unwrap() + .unwrap() + .1 + } else { + create_tcp_client_handler(protocol, &hop, resolver).unwrap() + } }); let (io, peer) = tokio::io::duplex(65536); let server_task = tokio::spawn(async move { diff --git a/src/xhttp/connector.rs b/src/xhttp/connector.rs index cc21e038..c993ad96 100644 --- a/src/xhttp/connector.rs +++ b/src/xhttp/connector.rs @@ -264,9 +264,10 @@ pub(crate) fn build_with_limits( "XHTTP split modes require at least two request lanes", )); } - let inner = crate::tcp::tcp_client_handler_factory::create_tcp_client_handler( + let inner = crate::tcp::tcp_client_handler_factory::create_with_hop_context( *xhttp.protocol.clone(), &config.address, + None, resolver, )?; let client = Client::new(