---
title: ポートとNAT透過
url: https://doc.liz6.com/ja/networking/03-l4-transport-layer/03-ports-and-nat-traversal
locale: ja
area: networking
tags:
- networking
- l4-transport-layer
date: 2026-06-30
modified: 2026-07-16
description: ポートはトランスポート層の「マルチプレクサ」であり、NAT はそれをインターネット最大のハックに変えました。STUN/TURN/ICE という一連のNAT透過ツールの本質は、外部マッピングと内部アドレスの間に予測可能なパスを確立することです。
---

# ポートとNAT透過

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

## 概要

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

## エフェメラルポート

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

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

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

緩和策：範囲の拡大 + `tw_reuse`

## NAT分類 (RFC 4787)

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

<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マッピング動作の分類: Full ConeからSymmetricへ、透過難易度が上昇">
  <rect width="720" height="400" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">NATマッピング動作の分類: Full ConeからSymmetricへ、透過難易度が上昇</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(エンドポイント非依存マッピング, EIM)</text>
  <text x="56" y="92" font-size="11" fill="#15803d">内 (IP₁,Port₁) → 外 (IP_ext,Port_ext)、固定マッピング</text>
  <text x="56" y="108" font-size="11" fill="#15803d">外部ホストがPort_extを知っていれば、誰でもパケットを送信可能</text>
  <text x="664" y="122" text-anchor="end" font-size="12.5" font-weight="700" fill="#166534">最も透過しやすい</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">アドレス依存マッピング</text>
  <text x="56" y="188" font-size="11" fill="#c2410c">内 (IP₁,Port₁) → 外 (IP_ext,Port_ext_X) [外部IP_X宛て]</text>
  <text x="56" y="204" font-size="11" fill="#c2410c">同じ内部エンドポイントでも、宛先IPが異なるごとに異なる外部ポートを使用</text>
  <text x="664" y="218" text-anchor="end" font-size="12.5" font-weight="700" fill="#9a3412">通信済みのホストのみが応答可能</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(アドレス・ポート依存マッピング, APDM)</text>
  <text x="56" y="284" font-size="11" fill="#334155">内 (IP₁,Port₁) → 外 (IP_ext,Port_ext_X) [(外部IP_X, Port_X) 宛て]</text>
  <text x="56" y="300" font-size="11" fill="#334155">外部ポートは予測不可能で、宛先ごとに異なるポートを使用</text>
  <text x="664" y="314" text-anchor="end" font-size="12.5" font-weight="700" fill="#dc2626">最も透過が困難</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">上から下へ、外部マッピングの予測可能性が低下していく —— これがSTUNホールパンチングが</text>
  <text x="56" y="374" font-size="12.5" fill="#3730a3">「常に成功する」状態から「ほぼ不可能」な状態への境界線です。</text>
</svg>

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

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

## 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フロー: まずパブリックマッピングを検出し、その後ピアと直接接続を試みる">
  <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: まずパブリックマッピングを検出し、その後ピアと直接接続を試みる</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">① パブリックマッピングを検出(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 · ポート 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が送信元を記録</text>
  <text x="66" y="176" font-size="11" fill="#4f46e5">src_ip:port (NAT外部アドレス)</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">② ピアに通知し、直接接続を試みる</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">クライアントがパブリックマッピングを取得</text>
  <text x="406" y="126" font-size="11" fill="#0f766e">「公网から見ると、私は 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">ピアにそのアドレスを通知</text>
  <text x="406" y="176" font-size="11" fill="#0f766e">ピアが 1.2.3.4:56789 へ直接UDP送信</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</text>
  <text x="530" y="227" text-anchor="middle" font-size="11" font-weight="600" fill="#115e59">→ パケットが受信可能</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はアドレスの発見のみを担当します。実際に透過できるかどうかは、相手側のNATのフィルタリングポリシーにかかっています。</text>
</svg>

## TURN (RFC 8656)

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

<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リレーフロー: 透過できない場合はリレーを使用し、常に通信可能だが代償を伴う">
  <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: 透過できない場合はリレーを使用し、常に通信可能だが代償を伴う</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">送信元</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">ポート 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">受信側</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">③ 逆方向も同様: 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">リレー遅延 = N × (A↔TURN 遅延 + TURN↔B 遅延)</text>
  <text x="76" y="248" font-size="12.5" fill="#9a3412">帯域幅: TURNサーバーが2倍の負荷(A→TURN + TURN→B)を処理 — 常に通信可能だが、遅延と帯域幅の代償を払う必要がある。</text>
</svg>

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

<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: 候補の収集で優先度を決定し、接続チェックでパスを選択">
  <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: 候補の収集で優先度を決定し、接続チェックでパスを選択</text>

  <text x="40" y="50" font-size="12.5" font-weight="700" fill="#1f2933">候補の収集 Candidate Gathering (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 · ローカルLAN · type_pref=126 (最高)</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マッピング · 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リレー · type_pref=0 (最低、フォールバックのみ)</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">接続チェック: 各候補ペアに対してSTUN Binding Requestを送信</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">ペアごとに送信し、利用可能な最高優先度のペアを選択</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 直接接続</text>
  <text x="140" y="322" text-anchor="middle" font-size="11" fill="#15803d">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">srflx ↔ srflx のみ接続可能</text>
  <text x="360" y="322" text-anchor="middle" font-size="11" fill="#0f766e">パブリックSTUNパス</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">それ以外の場合</text>
  <text x="580" y="322" text-anchor="middle" font-size="11" fill="#c2410c">TURNリレー (フォールバック)</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: 全候補の収集を待たず、収集しながら接続チェックを行い、接続確立時間を短縮する。</text>
</svg>

## TailscaleのNAT透過戦略

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

  <text x="40" y="282" font-size="12.5" font-weight="700" fill="#1f2933">③ 詳細: ポート割り当てモードがホールパンチングの成功率を決定する</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">増分割り当て (N, N+1, N+2…)</text>
  <text x="190" y="336" text-anchor="middle" font-size="11" fill="#15803d">予測可能 → ホールパンチング可能</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">ランダム割り当て</text>
  <text x="530" y="336" text-anchor="middle" font-size="11" fill="#334155">予測不可能 → ホールパンチング不可能</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">両方が同時に予測されたポートへUDPを送信 → 命中したペアでチャネル確立</text>
</svg>

## 参考

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