docs: document browser NAT traversal boundary (#346)
Record the verified Browser ICE observability limits and keep predictive candidates behind a controlled experiment gate.
This commit is contained in:
1 parent
ce4e931100
commit
6daf288af8
2 files changed
+119
No files matched your search
@@ -0,0 +1,114 @@
|
||||
# Browser NAT Traversal
|
||||
|
||||
Last reviewed: 2026-08-31
|
||||
|
||||
This document owns evidence for improving direct Browser ICE without adding a
|
||||
new relay or a custom transport. The current product contract remains standard
|
||||
WebRTC ICE with STUN discovery and bounded SFU fallback.
|
||||
|
||||
## Confirmed Boundaries
|
||||
|
||||
- STUN discovers mapped addresses for ICE; it does not carry media.
|
||||
- Chromium's shared UDP port sends to multiple configured STUN servers. Its
|
||||
tests retain two srflx candidates when the servers report different mapped
|
||||
addresses and deduplicate the result when they report the same address.
|
||||
- A STUN operator observes the client's source address, source port, request
|
||||
time and protocol metadata. It never receives the DTLS-SRTP media path. An
|
||||
external STUN endpoint is therefore a metadata dependency, not a media relay.
|
||||
- The current `STUN_URLS` wire already accepts a bounded list. Adding an
|
||||
auxiliary STUN destination needs no client compatibility surface, but it has
|
||||
no product value until a valid Browser observation or prediction consumer
|
||||
exists.
|
||||
|
||||
On 2026-08-31, Chrome 151 on Windows gathered a host plus IPv4 srflx candidate
|
||||
through the self-hosted STUN endpoint and independently through Cloudflare's
|
||||
official STUN endpoint while the machine used TUN and fake-IP DNS. With both
|
||||
configured, Chromium emitted one deduplicated srflx candidate. No raw address or
|
||||
port was retained. This proves endpoint reachability and candidate
|
||||
deduplication; it does not classify the NAT or prove another direct path.
|
||||
|
||||
## Current Decision
|
||||
|
||||
Do not change the flagship STUN configuration in this phase. A second endpoint
|
||||
does not itself traverse a hard NAT and does not justify a permanent external
|
||||
metadata dependency. The self-hosted STUN endpoint remains the production
|
||||
discovery service.
|
||||
|
||||
The Browser-only alternatives do not yet have an accepted implementation:
|
||||
|
||||
- WebRTC ICE already exchanges observed candidates and performs coordinated
|
||||
connectivity checks. libp2p's mature hole-punching specification explicitly
|
||||
keeps private Browser peers on WebRTC because Browser code cannot own or
|
||||
reuse the underlying UDP, QUIC or TCP socket.
|
||||
- Tailscale compares mappings observed by different STUN destinations, but
|
||||
keeps `MappingVariesByDestIP` as a tri-state network report. It retains an
|
||||
additional mapped endpoint only after repeated observation; it does not turn
|
||||
one NAT label into hard connection rejection or a predicted port.
|
||||
- A second STUN destination supplies an observation, not a new path through an
|
||||
endpoint-dependent NAT. Deploying it without a consumer adds metadata
|
||||
exposure and candidate traffic without proving more direct connections.
|
||||
|
||||
The Browser API is also weaker than the raw-socket netcheck model:
|
||||
|
||||
- Different srflx endpoints prove only that the current Browser socket's mapping
|
||||
varied by destination. An identical endpoint is deduplicated before the
|
||||
application sees it, so one emitted candidate cannot distinguish stable
|
||||
mapping from a second STUN destination that did not answer.
|
||||
- Candidate events expose response completion order, not a specified STUN send
|
||||
or NAT allocation order. Sorting by configured URL or event time cannot turn
|
||||
observed ports into a standards-backed sequential delta.
|
||||
- ICE restart creates a new generation and may use a new socket. Network
|
||||
interface, VPN/TUN route and CGNAT allocation traffic can all change between
|
||||
generations.
|
||||
|
||||
Consequently Browser observations can support a positive `varies` diagnostic,
|
||||
but cannot produce the proposed `eim | edm-sequential | edm-random` participant
|
||||
fact. They cannot safely remove a Peer candidate or save its five-second window.
|
||||
|
||||
Port prediction remains the only reachable Browser experiment that might add
|
||||
direct paths beyond stock ICE. It requires a controlled sequential/random NAT
|
||||
matrix and two real Browser ICE agents before any product design. No such
|
||||
controlled Browser matrix is currently available, and the production server is
|
||||
not an acceptable network-emulation lab. No candidate injection ships without
|
||||
that gate.
|
||||
|
||||
## Rejected Product Inference
|
||||
|
||||
Multiple STUN destinations can expose whether one current socket received the
|
||||
same or different mappings. They do not establish a stable participant-wide NAT
|
||||
type:
|
||||
|
||||
- RFC 5780 behavior discovery requires carefully controlled alternate address
|
||||
tests, warns that adding destinations can change observed behavior, and says
|
||||
results can vary by source port or become more restrictive later.
|
||||
- Mapping behavior does not reveal filtering behavior. A Browser cannot infer
|
||||
that a Peer pair is impossible from mapping samples alone.
|
||||
- A global `eim | edm` fact would cross connection generations, interfaces,
|
||||
VPN/TUN routes and physical network changes that own different sockets.
|
||||
|
||||
Therefore NAT samples must not skip a P2P candidate, alter the committed graph,
|
||||
or become a persistent participant capability. Successful `addIceCandidate()`
|
||||
parsing would prove only candidate syntax; it would not prove that a predicted
|
||||
mapping receives authenticated ICE checks. Port prediction and synthetic
|
||||
candidate injection remain experiments until controlled Browser and target-
|
||||
network measurements prove both benefit and bounded false rejection. They do
|
||||
not enter the current protocol or controller.
|
||||
|
||||
QUIC on UDP 443 does not change this Browser boundary. WebRTC's ICE agent owns
|
||||
its UDP sockets and candidate checks; the Web application cannot replace that
|
||||
transport with a custom QUIC hole-punching socket. LiveKit may use its own
|
||||
supported WebRTC transport configuration, but that is the bounded SFU path, not
|
||||
a new Peer route.
|
||||
|
||||
## Primary Sources
|
||||
|
||||
- [RFC 8445: Interactive Connectivity Establishment](https://datatracker.ietf.org/doc/html/rfc8445)
|
||||
- [RFC 4787: UDP NAT behavioral requirements](https://datatracker.ietf.org/doc/html/rfc4787)
|
||||
- [RFC 5780: NAT Behavior Discovery Using STUN](https://datatracker.ietf.org/doc/html/rfc5780)
|
||||
- [IETF symmetric NAT prediction draft](https://datatracker.ietf.org/doc/html/draft-takeda-symmetric-nat-traversal-00)
|
||||
- [W3C WebRTC candidate model](https://www.w3.org/TR/webrtc/)
|
||||
- [Chromium shared-socket STUN tests](https://webrtc.googlesource.com/src/+/refs/heads/main/p2p/base/stun_port_unittest.cc)
|
||||
- [Tailscale netcheck mapping observations](https://github.com/tailscale/tailscale/blob/main/net/netcheck/netcheck.go)
|
||||
- [libp2p Browser and DCUtR hole-punching boundary](https://github.com/libp2p/specs/blob/master/connections/hole-punching.md)
|
||||
- [coturn listener and auxiliary endpoint reference](https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf)
|
||||
- [Cloudflare Realtime STUN service](https://developers.cloudflare.com/realtime/turn/)
|
||||
@@ -54,6 +54,11 @@ until their stated boundary is met.
|
||||
Browser interoperability are proved.
|
||||
12. **Platform output.** Revisit AirPlay/Cast only when a target Browser and
|
||||
physical receiver prove the live `MediaStream` contract.
|
||||
13. **NAT inference and predictive candidates.** Keep multi-STUN observations
|
||||
diagnostic until controlled Browser and target-network evidence proves a
|
||||
connection-scoped classifier with bounded false rejection. Do not hard-skip
|
||||
Peer candidates from a participant-wide NAT label or inject predicted ports
|
||||
into production ICE without a new accepted decision and a measured gate.
|
||||
|
||||
## Decision Needed
|
||||
|
||||
|
||||
Reference in new issue
Block a user