---
title: Ports and NAT Traversal
url: https://doc.liz6.com/en/networking/03-l4-transport-layer/03-ports-and-nat-traversal
locale: en
area: networking
tags:
- networking
- l4-transport-layer
date: 2026-06-30
modified: 2026-07-16
description: '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.'
---

# 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:

```bash
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:

<svg viewBox="0 0 720 400" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="NAT Mapping Behavior Classification: From Full Cone to Symmetric, Traversal Difficulty Increases">
  <rect width="720" height="400" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">NAT Mapping Behavior Classification: From Full Cone to Symmetric, Traversal Difficulty Increases</text>

  <rect x="40" y="48" width="640" height="84" rx="8" fill="#dcfce7" stroke="#4ade80"/>
  <text x="56" y="72" font-size="13.5" font-weight="700" fill="#166534">Full Cone (Endpoint-Independent Mapping, EIM)</text>
  <text x="56" y="92" font-size="11" fill="#15803d">Internal (IP₁,Port₁) → External (IP_ext,Port_ext), fixed mapping</text>
  <text x="56" y="108" font-size="11" fill="#15803d">Any external host knowing Port_ext can send packets in</text>
  <text x="664" y="122" text-anchor="end" font-size="12.5" font-weight="700" fill="#166534">Easiest to traverse</text>

  <rect x="40" y="144" width="640" height="84" rx="8" fill="#ffedd5" stroke="#f97316"/>
  <text x="56" y="168" font-size="13.5" font-weight="700" fill="#9a3412">Address-Dependent Mapping</text>
  <text x="56" y="188" font-size="11" fill="#c2410c">Internal (IP₁,Port₁) → External (IP_ext,Port_ext_X) for External IP_X</text>
  <text x="56" y="204" font-size="11" fill="#c2410c">Same internal endpoint to different target IPs → different external ports</text>
  <text x="664" y="218" text-anchor="end" font-size="12.5" font-weight="700" fill="#9a3412">Only previously communicated hosts can reply</text>

  <rect x="40" y="240" width="640" height="84" rx="8" fill="#ffffff" stroke="#ef4444" stroke-width="2"/>
  <text x="56" y="264" font-size="13.5" font-weight="700" fill="#dc2626">Symmetric (Address and Port-Dependent Mapping, APDM)</text>
  <text x="56" y="284" font-size="11" fill="#334155">Internal (IP₁,Port₁) → External (IP_ext,Port_ext_X) for (External IP_X,Port_X)</text>
  <text x="56" y="300" font-size="11" fill="#334155">External port is unpredictable; each target port is different</text>
  <text x="664" y="314" text-anchor="end" font-size="12.5" font-weight="700" fill="#dc2626">Hardest to traverse</text>

  <rect x="40" y="336" width="640" height="50" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="56" y="356" font-size="12.5" fill="#3730a3">From top to bottom, the predictability of external mappings decreases — this is exactly the dividing line where STUN hole punching goes from</text>
  <text x="56" y="374" font-size="12.5" fill="#3730a3">"always successful" to "almost impossible."</text>
</svg>

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)

<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="STUN Process: First discover public mapping, then let Peer connect directly">
  <defs><marker id="stunAh" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="300" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">STUN: First discover public mapping, then let Peer connect directly</text>

  <rect x="40" y="50" width="300" height="28" rx="6" fill="#4f46e5"/>
  <text x="190" y="69" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">① Discover Public Mapping (Binding Request)</text>
  <rect x="52" y="90" width="276" height="44" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="109" font-size="12" font-weight="700" fill="#3730a3">Client → STUN Server</text>
  <text x="66" y="126" font-size="11" fill="#4f46e5">Binding Request · Port 3478/5349 (TLS)</text>
  <rect x="52" y="140" width="276" height="44" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="66" y="159" font-size="12" font-weight="700" fill="#3730a3">Server records source</text>
  <text x="66" y="176" font-size="11" fill="#4f46e5">src_ip:port (NAT external address)</text>
  <rect x="52" y="196" width="276" height="38" rx="6" fill="#e0e7ff"/>
  <text x="190" y="220" text-anchor="middle" font-size="11.5" font-weight="600" fill="#3730a3">Response: XOR-MAPPED-ADDRESS=1.2.3.4:56789</text>

  <rect x="380" y="50" width="300" height="28" rx="6" fill="#0d9488"/>
  <text x="530" y="69" text-anchor="middle" font-size="13" font-weight="700" fill="#ffffff">② Inform Peer, attempt direct connection</text>
  <rect x="392" y="90" width="276" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="109" font-size="12" font-weight="700" fill="#115e59">Client obtains public mapping</text>
  <text x="406" y="126" font-size="11" fill="#0f766e">"From the public network, I am 1.2.3.4:56789"</text>
  <rect x="392" y="140" width="276" height="44" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="406" y="159" font-size="12" font-weight="700" fill="#115e59">Tell Peer this address</text>
  <text x="406" y="176" font-size="11" fill="#0f766e">Peer sends UDP directly to 1.2.3.4:56789</text>
  <rect x="392" y="196" width="276" height="38" rx="6" fill="#ccfbf1"/>
  <text x="530" y="213" text-anchor="middle" font-size="11" font-weight="600" fill="#115e59">Full Cone / Address-Dependent (previously communicated)</text>
  <text x="530" y="227" text-anchor="middle" font-size="11" font-weight="600" fill="#115e59">→ Packets can get in</text>

  <line x1="330" y1="215" x2="388" y2="215" stroke="#475569" stroke-width="1.8" marker-end="url(#stunAh)"/>
  <text x="360" y="262" text-anchor="middle" font-size="12" fill="#64748b">STUN only discovers addresses; whether traversal actually succeeds depends on the peer's NAT filtering policy.</text>
</svg>

## TURN (RFC 8656)

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

<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="TURN Relay Process: If traversal fails, relay; always works but at a cost">
  <defs><marker id="turnAh" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="300" fill="#ffffff"/>
  <text x="360" y="30" text-anchor="middle" font-size="18" font-weight="700" fill="#1f2933">TURN: If traversal fails, relay; always works but at a cost</text>

  <rect x="40" y="90" width="150" height="56" rx="6" fill="#e2e8f0"/>
  <text x="115" y="113" text-anchor="middle" font-size="13" font-weight="700" fill="#334155">Peer A</text>
  <text x="115" y="131" text-anchor="middle" font-size="11" fill="#64748b">Initiator</text>

  <rect x="230" y="90" width="220" height="56" rx="6" fill="#eef2ff" stroke="#818cf8"/>
  <text x="340" y="113" text-anchor="middle" font-size="13" font-weight="700" fill="#3730a3">TURN Server</text>
  <text x="340" y="131" text-anchor="middle" font-size="11" fill="#4338ca">Allocate port 50000 (Allocate)</text>

  <rect x="490" y="90" width="190" height="56" rx="6" fill="#e2e8f0"/>
  <text x="585" y="113" text-anchor="middle" font-size="13" font-weight="700" fill="#334155">Peer B</text>
  <text x="585" y="131" text-anchor="middle" font-size="11" fill="#64748b">Receiver</text>

  <line x1="190" y1="118" x2="226" y2="118" stroke="#475569" stroke-width="1.6" marker-end="url(#turnAh)"/>
  <text x="208" y="86" text-anchor="middle" font-size="9.5" fill="#475569">① Allocate/Send</text>
  <line x1="450" y1="118" x2="486" y2="118" stroke="#475569" stroke-width="1.6" marker-end="url(#turnAh)"/>
  <text x="468" y="86" text-anchor="middle" font-size="9.5" fill="#475569">② Data Indication</text>

  <text x="360" y="176" text-anchor="middle" font-size="11" fill="#64748b">③ Reverse is similar: Peer B → TURN Server → Peer A</text>

  <rect x="60" y="200" width="600" height="68" rx="8" fill="#ffedd5" stroke="#f97316"/>
  <text x="76" y="226" font-size="12.5" fill="#9a3412">Relay latency = N × (A↔TURN latency + TURN↔B latency)</text>
  <text x="76" y="248" font-size="12.5" fill="#9a3412">Bandwidth: TURN server handles double traffic (A→TURN + TURN→B) — always works, but at the cost of latency and bandwidth.</text>
</svg>

## ICE (RFC 8445): Candidate Gathering and Connectivity Checks

<svg viewBox="0 0 720 410" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="ICE: Candidate Gathering sets priority, Connectivity Checks select path">
  <defs><marker id="iceAh" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="410" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">ICE: Candidate Gathering sets priority, Connectivity Checks select path</text>

  <text x="40" y="50" font-size="12.5" font-weight="700" fill="#1f2933">Candidate Gathering (priority determined by type_pref)</text>
  <text x="40" y="64" font-size="10.5" fill="#94a3b8">priority = 2²⁴×type_pref + 2⁸×local_pref + (256−component_id)</text>

  <text x="40" y="94" font-size="12" font-weight="700" fill="#166534">host</text>
  <rect x="150" y="76" width="300" height="26" rx="3" fill="#22c55e"/>
  <text x="160" y="94" font-size="11" fill="#ffffff">192.168.1.10:50000 · Local LAN · type_pref=126 (highest)</text>

  <text x="40" y="128" font-size="12" font-weight="700" fill="#3730a3">srflx</text>
  <rect x="150" y="110" width="230" height="26" rx="3" fill="#4f46e5"/>
  <text x="160" y="128" font-size="11" fill="#ffffff">1.2.3.4:56789 · STUN mapping · type_pref=100</text>

  <text x="40" y="162" font-size="12" font-weight="700" fill="#9a3412">relay</text>
  <rect x="150" y="144" width="70" height="26" rx="3" fill="#f97316"/>
  <text x="230" y="162" font-size="11" fill="#c2410c">5.5.5.5:60000 · TURN relay · type_pref=0 (lowest, fallback only)</text>

  <line x1="40" y1="190" x2="680" y2="190" stroke="#e2e8f0" stroke-width="1"/>

  <text x="40" y="210" font-size="12.5" font-weight="700" fill="#1f2933">Connectivity Checks: Send STUN Binding Request for each candidate pair</text>

  <rect x="260" y="222" width="200" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="246" text-anchor="middle" font-size="11.5" font-weight="700" fill="#3730a3">Send sequentially, select the available highest-priority pair</text>

  <line x1="360" y1="262" x2="140" y2="278" stroke="#475569" stroke-width="1.6" marker-end="url(#iceAh)"/>
  <line x1="360" y1="262" x2="360" y2="278" stroke="#475569" stroke-width="1.6" marker-end="url(#iceAh)"/>
  <line x1="360" y1="262" x2="580" y2="278" stroke="#475569" stroke-width="1.6" marker-end="url(#iceAh)"/>

  <rect x="40" y="280" width="200" height="56" rx="8" fill="#dcfce7" stroke="#4ade80"/>
  <text x="140" y="302" text-anchor="middle" font-size="12" font-weight="700" fill="#166534">host ↔ host direct connection</text>
  <text x="140" y="322" text-anchor="middle" font-size="11" fill="#15803d">Does not pass through NAT</text>

  <rect x="260" y="280" width="200" height="56" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="360" y="302" text-anchor="middle" font-size="12" font-weight="700" fill="#115e59">Only srflx ↔ srflx works</text>
  <text x="360" y="322" text-anchor="middle" font-size="11" fill="#0f766e">Public STUN path</text>

  <rect x="480" y="280" width="200" height="56" rx="8" fill="#ffedd5" stroke="#f97316"/>
  <text x="580" y="302" text-anchor="middle" font-size="12" font-weight="700" fill="#9a3412">Otherwise</text>
  <text x="580" y="322" text-anchor="middle" font-size="11" fill="#c2410c">TURN relay (fallback)</text>

  <rect x="40" y="356" width="640" height="40" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="56" y="380" font-size="12.5" fill="#3730a3">Trickle ICE: Do not wait for all candidates to be collected; perform connectivity checks as candidates are gathered to reduce setup time.</text>
</svg>

## Tailscale's NAT Traversal Strategy

<svg viewBox="0 0 720 420" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Tailscale NAT Traversal: Try step-by-step by priority, degrade only on failure">
  <defs><marker id="tsAh" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="420" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Tailscale NAT Traversal: Try step-by-step by priority, degrade only on failure</text>

  <rect x="40" y="48" width="640" height="40" rx="6" fill="#dcfce7" stroke="#4ade80"/>
  <text x="56" y="73" font-size="12.5" font-weight="700" fill="#166534">① Same LAN → host-to-host direct connection (UDP or TCP via tailscaled)</text>

  <line x1="60" y1="90" x2="60" y2="104" stroke="#475569" stroke-width="1.6" marker-end="url(#tsAh)"/>
  <text x="76" y="101" font-size="10" fill="#64748b">Different LANs, proceed to next level</text>

  <rect x="40" y="106" width="640" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="56" y="131" font-size="12.5" font-weight="700" fill="#3730a3">② One side has public IP + Full Cone NAT → STUN → direct UDP</text>

  <line x1="60" y1="148" x2="60" y2="162" stroke="#475569" stroke-width="1.6" marker-end="url(#tsAh)"/>
  <text x="76" y="159" font-size="10" fill="#64748b">Both sides have Symmetric NAT, proceed to next level</text>

  <rect x="40" y="164" width="640" height="40" rx="6" fill="#ffedd5" stroke="#f97316"/>
  <text x="56" y="189" font-size="12.5" font-weight="700" fill="#9a3412">③ Both sides have Symmetric NAT → port prediction (birthday attack)</text>

  <line x1="60" y1="206" x2="60" y2="220" stroke="#475569" stroke-width="1.6" marker-end="url(#tsAh)"/>
  <text x="76" y="217" font-size="10" fill="#64748b">Prediction fails, proceed to next level</text>

  <rect x="40" y="222" width="640" height="40" rx="6" fill="#e2e8f0" stroke="#cbd5e1"/>
  <text x="56" y="247" font-size="12.5" font-weight="700" fill="#334155">④ All above fail → DERP relay (self-hosted relay)</text>

  <text x="40" y="282" font-size="12.5" font-weight="700" fill="#1f2933">③ Details: Port allocation pattern determines hole punching success rate</text>

  <rect x="40" y="294" width="300" height="56" rx="8" fill="#dcfce7" stroke="#4ade80"/>
  <text x="190" y="316" text-anchor="middle" font-size="12" font-weight="700" fill="#166534">Incremental Allocation (N, N+1, N+2…)</text>
  <text x="190" y="336" text-anchor="middle" font-size="11" fill="#15803d">Predictable → Hole punching possible</text>

  <rect x="380" y="294" width="300" height="56" rx="8" fill="#ffffff" stroke="#ef4444" stroke-width="2"/>
  <text x="530" y="316" text-anchor="middle" font-size="12" font-weight="700" fill="#dc2626">Random Allocation</text>
  <text x="530" y="336" text-anchor="middle" font-size="11" fill="#334155">Unpredictable → Hole punching impossible</text>

  <line x1="220" y1="350" x2="330" y2="368" stroke="#475569" stroke-width="1.6" marker-end="url(#tsAh)"/>
  <line x1="500" y1="350" x2="390" y2="368" stroke="#475569" stroke-width="1.6" marker-end="url(#tsAh)"/>

  <rect x="200" y="372" width="320" height="40" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="360" y="396" text-anchor="middle" font-size="11" font-weight="700" fill="#3730a3">Both sides simultaneously send UDP to predicted ports → The matched pair establishes a channel</text>
</svg>

## 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*
