On this page

Ports and NAT Traversal

Ports are the transport layer’s "multiplexer," and NAT turned it into the internet’s biggest hack. The essence of NAT traversal tools like STUN/TURN/ICE is: establishing a predictable path between the external mapping and the internal address.

Overview

NAT traversal is the biggest compromise in the current internet architecture — IPv4 address exhaustion forces billions of devices to share public IPs, making "direct connections" the exception. Behind NAT, two devices cannot communicate directly (unless via relay or hole punching). STUN helps devices discover their public mapping, TURN provides relaying when traversal fails, and ICE automatically selects the optimal path (direct > hole punching > relay). Tailscale encapsulates these technologies into a user-transparent mesh VPN.

Ephemeral Ports

The port automatically selected by the client in TCP/UDP connections, with a range managed by the kernel:

cat /proc/sys/net/ipv4/ip_local_port_range  # Default: 32768 60999

Exhaustion risk: High-concurrency short-lived connections (each connection consumes one ephemeral port + 60s during TIME_WAIT) → ports run out → connect() returns EADDRNOTAVAIL

Mitigation: Increase range + tw_reuse

NAT Classification (RFC 4787)

NAT mapping behavior determines traversal difficulty:

NAT Mapping Behavior Classification: From Full Cone to Symmetric, Traversal Difficulty Increases Full Cone (Endpoint-Independent Mapping, EIM) Internal (IP₁,Port₁) → External (IP_ext,Port_ext), fixed mapping Any external host knowing Port_ext can send packets in Easiest to traverse Address-Dependent Mapping Internal (IP₁,Port₁) → External (IP_ext,Port_ext_X) for External IP_X Same internal endpoint to different target IPs → different external ports Only previously communicated hosts can reply Symmetric (Address and Port-Dependent Mapping, APDM) Internal (IP₁,Port₁) → External (IP_ext,Port_ext_X) for (External IP_X,Port_X) External port is unpredictable; each target port is different Hardest to traverse From top to bottom, the predictability of external mappings decreases — this is exactly the dividing line where STUN hole punching goes from "always successful" to "almost impossible."

NAT filtering behavior (firewall side): Similar to mapping; Endpoint-Independent → most permissive; Address+Port-Dependent → most strict.

If the NAT is Endpoint-Independent Mapping + Address-Dependent Filtering (most common home router), then STUN is effective. If Symmetric (most mobile networks), TURN or port prediction is required.

STUN (RFC 8489)

STUN: First discover public mapping, then let Peer connect directly ① Discover Public Mapping (Binding Request) Client → STUN Server Binding Request · Port 3478/5349 (TLS) Server records source src_ip:port (NAT external address) Response: XOR-MAPPED-ADDRESS=1.2.3.4:56789 ② Inform Peer, attempt direct connection Client obtains public mapping "From the public network, I am 1.2.3.4:56789" Tell Peer this address Peer sends UDP directly to 1.2.3.4:56789 Full Cone / Address-Dependent (previously communicated) → Packets can get in STUN only discovers addresses; whether traversal actually succeeds depends on the peer's NAT filtering policy.

TURN (RFC 8656)

STUN is ineffective for Symmetric NAT — external ports are unpredictable. TURN acts as a relay:

TURN: If traversal fails, relay; always works but at a cost Peer A Initiator TURN Server Allocate port 50000 (Allocate) Peer B Receiver ① Allocate/Send ② Data Indication ③ Reverse is similar: Peer B → TURN Server → Peer A Relay latency = N × (A↔TURN latency + TURN↔B latency) Bandwidth: TURN server handles double traffic (A→TURN + TURN→B) — always works, but at the cost of latency and bandwidth.

ICE (RFC 8445): Candidate Gathering and Connectivity Checks

ICE: Candidate Gathering sets priority, Connectivity Checks select path Candidate Gathering (priority determined by type_pref) priority = 2²⁴×type_pref + 2⁸×local_pref + (256−component_id) host 192.168.1.10:50000 · Local LAN · type_pref=126 (highest) srflx 1.2.3.4:56789 · STUN mapping · type_pref=100 relay 5.5.5.5:60000 · TURN relay · type_pref=0 (lowest, fallback only) Connectivity Checks: Send STUN Binding Request for each candidate pair Send sequentially, select the available highest-priority pair host ↔ host direct connection Does not pass through NAT Only srflx ↔ srflx works Public STUN path Otherwise TURN relay (fallback) Trickle ICE: Do not wait for all candidates to be collected; perform connectivity checks as candidates are gathered to reduce setup time.

Tailscale's NAT Traversal Strategy

Tailscale NAT Traversal: Try step-by-step by priority, degrade only on failure ① Same LAN → host-to-host direct connection (UDP or TCP via tailscaled) Different LANs, proceed to next level ② One side has public IP + Full Cone NAT → STUN → direct UDP Both sides have Symmetric NAT, proceed to next level ③ Both sides have Symmetric NAT → port prediction (birthday attack) Prediction fails, proceed to next level ④ All above fail → DERP relay (self-hosted relay) ③ Details: Port allocation pattern determines hole punching success rate Incremental Allocation (N, N+1, N+2…) Predictable → Hole punching possible Random Allocation Unpredictable → Hole punching impossible Both sides simultaneously send UDP to predicted ports → The matched pair establishes a channel

References

  • RFC: 4787, 8489, 8656, 8445, 6887
  • tailscale blog: "How NAT traversal works"

Keywords: ephemeral ports, NAT, Full Cone, Symmetric NAT, STUN, TURN, ICE, hole punching, DERP