このページの目次

ポートとNAT透過

ポートはトランスポート層の「マルチプレクサ」であり、NAT はそれをインターネット最大のハックに変えました。STUN/TURN/ICE という一連のNAT透過ツールの本質は、外部マッピングと内部アドレスの間に予測可能なパスを確立することです。

概要

NAT透過は、現在のインターネットアーキテクチャにおける最大の妥協点です。IPv4アドレスの枯渇により、数十億台のデバイスがパブリックIPを共有せざるを得ず、「直接接続」が例外となりました。NATの背後では、2つのデバイス間は直接通信できません(リレーまたはホールパンチングを使用する場合を除く)。STUNはデバイスがパブリックマッピングを特定するのを助け、TURNは透過できない場合のリレーを提供し、ICEは最適なパス(直接接続 > ホールパンチング > リレー)を自動選択します。Tailscaleはこれらの技術をユーザーが意識しないメッシュVPNとしてパッケージ化しています。

エフェメラルポート

TCP/UDP接続において、クライアント側が自動的に選択するポートは、カーネルによって管理される範囲内にあります:

cat /proc/sys/net/ipv4/ip_local_port_range  # デフォルト: 32768 60999

枯渇リスク:高同時接続の短い接続(各接続が1つのエフェメラルポートを消費し、TIME_WAIT期間中60秒間保持)→ ポートが枯渇 → connect()EADDRNOTAVAIL を返す

緩和策:範囲の拡大 + tw_reuse

NAT分類 (RFC 4787)

NATのマッピング動作が、透過の難易度を決定します:

NATマッピング動作の分類: Full ConeからSymmetricへ、透過難易度が上昇 Full Cone(エンドポイント非依存マッピング, EIM) 内 (IP₁,Port₁) → 外 (IP_ext,Port_ext)、固定マッピング 外部ホストがPort_extを知っていれば、誰でもパケットを送信可能 最も透過しやすい アドレス依存マッピング 内 (IP₁,Port₁) → 外 (IP_ext,Port_ext_X) [外部IP_X宛て] 同じ内部エンドポイントでも、宛先IPが異なるごとに異なる外部ポートを使用 通信済みのホストのみが応答可能 Symmetric(アドレス・ポート依存マッピング, APDM) 内 (IP₁,Port₁) → 外 (IP_ext,Port_ext_X) [(外部IP_X, Port_X) 宛て] 外部ポートは予測不可能で、宛先ごとに異なるポートを使用 最も透過が困難 上から下へ、外部マッピングの予測可能性が低下していく —— これがSTUNホールパンチングが 「常に成功する」状態から「ほぼ不可能」な状態への境界線です。

NATフィルタリング動作(ファイアウォール側):マッピングと同様に、エンドポイント非依存が最も緩く、アドレス・ポート依存が最も厳格です。

NATが「エンドポイント非依存マッピング + アドレス依存フィルタリング」(一般的な家庭用ルーターで最も多い)の場合、STUNは有効です。Symmetric NAT(多くのモバイルネットワーク)の場合、TURNまたはポート予測が必要です。

STUN (RFC 8489)

STUN: まずパブリックマッピングを検出し、その後ピアと直接接続を試みる ① パブリックマッピングを検出(Binding Request) Client → STUN Server Binding Request · ポート 3478/5349(TLS) Serverが送信元を記録 src_ip:port (NAT外部アドレス) Response: XOR-MAPPED-ADDRESS=1.2.3.4:56789 ② ピアに通知し、直接接続を試みる クライアントがパブリックマッピングを取得 「公网から見ると、私は 1.2.3.4:56789 です」 ピアにそのアドレスを通知 ピアが 1.2.3.4:56789 へ直接UDP送信 Full Cone / 通信済みのAddress-Dependent → パケットが受信可能 STUNはアドレスの発見のみを担当します。実際に透過できるかどうかは、相手側のNATのフィルタリングポリシーにかかっています。

TURN (RFC 8656)

STUNはSymmetric NATでは機能しません — 外部ポートが予測不可能だからです。TURNはリレーとして機能します:

TURN: 透過できない場合はリレーを使用し、常に通信可能だが代償を伴う Peer A 送信元 TURN Server ポート 50000 を割り当て (Allocate) Peer B 受信側 ① Allocate/Send ② Data Indication

③ 逆方向も同様: Peer B → TURN Server → Peer A

リレー遅延 = N × (A↔TURN 遅延 + TURN↔B 遅延) 帯域幅: TURNサーバーが2倍の負荷(A→TURN + TURN→B)を処理 — 常に通信可能だが、遅延と帯域幅の代償を払う必要がある。

ICE (RFC 8445): 候補の収集と接続チェック

ICE: 候補の収集で優先度を決定し、接続チェックでパスを選択

候補の収集 Candidate Gathering (type_pref に基づく優先度) priority = 2²⁴×type_pref + 2⁸×local_pref + (256−component_id)

host 192.168.1.10:50000 · ローカルLAN · type_pref=126 (最高)

srflx 1.2.3.4:56789 · STUNマッピング · type_pref=100

relay 5.5.5.5:60000 · TURNリレー · type_pref=0 (最低、フォールバックのみ)

接続チェック: 各候補ペアに対してSTUN Binding Requestを送信

ペアごとに送信し、利用可能な最高優先度のペアを選択 host ↔ host 直接接続 NATを経由しない srflx ↔ srflx のみ接続可能 パブリックSTUNパス それ以外の場合 TURNリレー (フォールバック) Trickle ICE: 全候補の収集を待たず、収集しながら接続チェックを行い、接続確立時間を短縮する。

TailscaleのNAT透過戦略

Tailscale NAT透過: 優先度に従って段階的に試行し、失敗した場合のみ降格 ① 同じLAN内 → host-to-host 直接接続 (UDP または tailscaled経由のTCP) 異なるLANの場合、次の段階へ ② 片方がパブリックIP + Full Cone NAT → STUN → 直接UDP 両方がSymmetric NATの場合、次の段階へ ③ 両方がSymmetric NAT → ポート予測 (誕生日攻撃) 予測に失敗した場合、次の段階へ ④ 上記すべてが失敗 → DERPリレー (独自構築のリレー)

③ 詳細: ポート割り当てモードがホールパンチングの成功率を決定する

増分割り当て (N, N+1, N+2…) 予測可能 → ホールパンチング可能 ランダム割り当て 予測不可能 → ホールパンチング不可能 両方が同時に予測されたポートへUDPを送信 → 命中したペアでチャネル確立

参考

  • 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