151 lines
8.6 KiB
Markdown
151 lines
8.6 KiB
Markdown
# Browser NAT Traversal
|
|
|
|
Last reviewed: 2026-09-01
|
|
|
|
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.
|
|
|
|
## Browser Prediction Preflight
|
|
|
|
A 2026-09-01 Chrome 151 preflight repeated the combined self-hosted plus
|
|
Cloudflare STUN gathering in six fresh PeerConnections. Each STUN destination
|
|
produced an srflx candidate when tested alone, while every combined run exposed
|
|
only one deduplicated srflx endpoint. The only valid mapping result remained
|
|
`unknown`; individual reachability does not make a single combined candidate
|
|
positive evidence of EIM.
|
|
|
|
One ICE restart exposed a different srflx endpoint in that environment, and a
|
|
synthetic standards-shaped remote srflx candidate using a documentation address
|
|
was accepted by `addIceCandidate()`. The first observation proves only that a
|
|
new mapping can occur: WebRTC specifies new ICE credentials and a new gathering
|
|
phase, not a new socket or mapping on every implementation and network. The
|
|
second proves candidate syntax only; it does not prove that a predicted NAT
|
|
mapping receives an authenticated connectivity check. The probe retained no raw
|
|
address or port.
|
|
|
|
This preflight also leaves the Browser-only mapper without an ordered port
|
|
delta. Candidate events expose response completion, while configured STUN order
|
|
does not specify the NAT allocation order. A disposable namespace matrix then
|
|
provided the controlled gate: its four-by-four baseline matched the expected
|
|
reachability classes, and bounded prediction improved sequential-to-restricted
|
|
connectivity in 14/15 runs. Sequential-to-sequential pairs still depended on
|
|
candidate ordering and remained sensitive to intervening mappings. The result
|
|
supports an opt-in mechanism, not a default or a participant-wide classifier.
|
|
|
|
## Current Decision
|
|
|
|
The flagship STUN configuration remains unchanged. The self-hosted endpoint is
|
|
still the ordinary discovery service; ports 3479 and 3480 are optional
|
|
STUN-only listeners for an explicitly enabled room experiment.
|
|
|
|
The accepted experiment is deliberately connection-local and additive. It
|
|
derives the two auxiliary ports from the existing STUN authority, waits for a
|
|
clear three-point arithmetic `srflx` shape in one ICE generation, and appends a
|
|
small two-sided candidate window from the observed port-sequence endpoint.
|
|
Ordinary candidates trickle immediately, so unavailable auxiliary listeners
|
|
cannot hold back stock ICE. There is no NAT label, hard candidate skip,
|
|
route-controller input, or SFU preference. The Host switch is default off,
|
|
locks while sharing, and applies to Host, Viewer upstream, and Viewer relay P2P
|
|
connections. Its exact scope is recorded in
|
|
[ADR-0009](../adr/0009-connection-local-nat-prediction-experiment.md).
|
|
|
|
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 alter route ordering and
|
|
deadlines.
|
|
|
|
Port prediction remains an experiment rather than a default path. The
|
|
controlled namespace matrix supports the bounded mechanism, but field
|
|
prevalence, direction, and resource impact are not established. The experiment
|
|
therefore stays opt-in and additive until target-network evidence justifies
|
|
enabling it by default.
|
|
|
|
## 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 proves only candidate syntax; it does not prove that a predicted mapping
|
|
receives authenticated ICE checks. The accepted experiment keeps this boundary:
|
|
it adds no protocol fields or controller state and falls back to ordinary ICE
|
|
when its shape test is inconclusive.
|
|
|
|
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/)
|