From 247653c9d07b1758828a54997fa18ef3c7db5787 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Fri, 20 Mar 2026 02:35:22 +0000 Subject: [PATCH] fix(rustproxy-routing): allow QUIC UDP TLS connections without SNI to match domain-restricted routes --- changelog.md | 6 ++ .../rustproxy-routing/src/route_manager.rs | 62 +++++++++++++++++-- ts/00_commitinfo_data.ts | 2 +- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 14d21e7..6892c39 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 2026-03-20 - 25.17.1 - fix(rustproxy-routing) +allow QUIC UDP TLS connections without SNI to match domain-restricted routes + +- Exempts UDP transport from the no-SNI rejection logic because QUIC encrypts the TLS ClientHello and SNI is unavailable at accept time +- Adds regression tests to confirm QUIC route matching succeeds without SNI while TCP TLS without SNI remains rejected + ## 2026-03-19 - 25.17.0 - feat(rustproxy-passthrough) add PROXY protocol v2 client IP handling for UDP and QUIC listeners diff --git a/rust/crates/rustproxy-routing/src/route_manager.rs b/rust/crates/rustproxy-routing/src/route_manager.rs index 99bb97d..33cbb61 100644 --- a/rust/crates/rustproxy-routing/src/route_manager.rs +++ b/rust/crates/rustproxy-routing/src/route_manager.rs @@ -122,10 +122,16 @@ impl RouteManager { // This prevents session-ticket resumption from misrouting when clients // omit SNI (RFC 8446 recommends but doesn't mandate SNI on resumption). // Wildcard-only routes (domains: ["*"]) still match since they accept all. - let patterns = domains.to_vec(); - let is_wildcard_only = patterns.iter().all(|d| *d == "*"); - if !is_wildcard_only { - return false; + // + // Exception: QUIC (UDP transport) encrypts the TLS ClientHello, so SNI + // is unavailable at accept time. Domain verification happens per-request + // in H3ProxyService via the :authority header. + if ctx.transport != Some(TransportProtocol::Udp) { + let patterns = domains.to_vec(); + let is_wildcard_only = patterns.iter().all(|d| *d == "*"); + if !is_wildcard_only { + return false; + } } } } @@ -997,4 +1003,52 @@ mod tests { let result = manager.find_route(&udp_ctx).unwrap(); assert_eq!(result.route.name.as_deref(), Some("udp-route")); } + + #[test] + fn test_quic_tls_no_sni_matches_domain_restricted_route() { + // QUIC accept-level matching: is_tls=true, domain=None, transport=Udp. + // Should match because QUIC encrypts the ClientHello — SNI is unavailable + // at accept time but verified per-request in H3ProxyService. + let mut route = make_route(443, Some("example.com"), 0); + route.route_match.transport = Some(TransportProtocol::Udp); + let routes = vec![route]; + let manager = RouteManager::new(routes); + + let ctx = MatchContext { + port: 443, + domain: None, + path: None, + client_ip: None, + tls_version: None, + headers: None, + is_tls: true, + protocol: Some("quic"), + transport: Some(TransportProtocol::Udp), + }; + + assert!(manager.find_route(&ctx).is_some(), + "QUIC (UDP) with is_tls=true and domain=None should match domain-restricted routes"); + } + + #[test] + fn test_tcp_tls_no_sni_still_rejects_domain_restricted_route() { + // TCP TLS without SNI must still be rejected (no QUIC exemption). + let routes = vec![make_route(443, Some("example.com"), 0)]; + let manager = RouteManager::new(routes); + + let ctx = MatchContext { + port: 443, + domain: None, + path: None, + client_ip: None, + tls_version: None, + headers: None, + is_tls: true, + protocol: None, + transport: None, // TCP (default) + }; + + assert!(manager.find_route(&ctx).is_none(), + "TCP TLS without SNI should NOT match domain-restricted routes"); + } } diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 9deab00..a62d387 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.0', + version: '25.17.1', 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.' }