Expand description
L4 TCP/UDP proxying with SNI routing reuse (DW-103, DP-01 / #234: UDP datagram forwarding).
L4 proxying is a LISTENER TYPE, not a route action: a protocol: tcp
(or protocol: udp) listener accepts raw L4 connections and splices
them byte-for-byte to an upstream endpoint, never running the HTTP
pipeline. This is the same byte-splice model the DW-008 TLS
passthrough path uses – and when sni_routing is true, the TCP
dispatcher reuses the EXACT SNI extraction from passthrough
(security::tls::sni_from_client_hello + the peek loop) to select
the upstream from the listener’s tls.sni_routes.
§TCP dispatcher
L4Dispatcher (built from L4ProxyConfig) accepts a TCP
connection and:
- If
sni_routingis true, peeks the TLS ClientHello SNI (reusingsecurity::tls::handle_passthrough’s peek +sni_from_client_helloextraction – the same bounded reassembly, the same 64 KiB budget, the same 10s peek timeout). The SNI is matched against the listener’stls.sni_routesto select the upstream; the configuredupstreamis the fallback for no-SNI / unmatched names (absent = close). - If
sni_routingis false, the configuredupstreamreceives every connection. - The selected upstream’s endpoint is picked through the CURRENT generation’s balancers (no hash key – a byte splice has no client-IP semantics), so L4 picks follow config reloads.
- The client and upstream connections are spliced with
tokio::io::copy_bidirectionaluntil either side closes (the same tunnel the passthrough path and the 101 upgrade path use). An optional idle timeout closes the splice when neither side sends data for the configured duration.
Peeking (never reading) keeps the ClientHello bytes available for the upstream once splicing starts: the entire hello is still in the socket buffer and is replayed to the upstream by the splice.
§UDP dispatcher (DP-01, #234)
UdpDispatcher implements UDP datagram forwarding with per-client
session tracking. Each unique client SocketAddr gets a dedicated
upstream UdpSocket; datagrams from the client are forwarded to the
upstream, and responses from the upstream are forwarded back to the
client. Sessions expire after a configurable idle timeout (default
30s) to avoid leaking NAT state. The dispatcher uses a bounded
session table (max_sessions, default 4096) to prevent resource
exhaustion under adversarial traffic.
§Availability
The entire module is compiled into the OSS build. The config
schema (ListenerProtocol::Tcp/Udp + L4Config) is always
present so configs round-trip; L4 proxying is always available.
Structs§
- L4Dispatcher
- TCP L4 dispatcher: accept one TCP connection, optionally peek SNI, select the upstream, and splice byte-for-byte.
- L4Proxy
Config - Compiled L4 proxying configuration (from
L4Config). - UdpDispatcher
- UDP L4 dispatcher (DP-01, #234): per-client session tracking with datagram forwarding and idle-session eviction.
Enums§
- L4Dispatch
Action - The outcome of an L4 TCP dispatch: which upstream endpoint the connection was spliced to (for metrics/logging), or that it was closed (no upstream, no endpoint, or SNI routing with no match).
- L4Error
- L4 proxying error. Kept simple: the dispatcher logs and closes on any error – there is no client-facing HTTP envelope at L4.