NAT越え:NATの分類からホールパンチングの完全理解まで
トップページで紹介したSTUN/TURN/ホールパンチングを、NATの型分類、パケット単位のシーケンス、Symmetric NAT対策、TCPでの応用まで徹底的に掘り下げます。P2P実装で最も泥臭く、最も重要な領域です。
- 登録: 双方がSへ送信(NATにS宛てマッピング生成)
- Sが互いのパブリック/プライベート端点を通知
- 同時送信: 初弾は破棄されるが自NATに穴が開く
- 双方の穴が開通 → 直接通信確立(S不要に)
UDPホールパンチングの4段階。鍵は「自分から送ることで自分のNATに相手宛ての通り道を作る」こと。
NATの4分類:越えやすさは型で決まる
NATは「内側から出た通信のマッピング(内部IP:ポート ⇔ 外部IP:ポート)をどう作り、外からのパケットをどこまで許すか」で古典的に4種類へ分類されます(RFC 3489)。現代のRFC 4787ではマッピング挙動とフィルタリング挙動を分けて記述しますが、直感的な理解にはこの4分類が今も有用です。
- Full Cone NAT: 内側の1ソケットに1つの外部ポートを固定で割り当て、一度マッピングができれば誰からのパケットでも内側へ通す。最も越えやすい。
- Restricted Cone NAT: マッピングは固定だが、過去に内側から送信したことのある宛先IPからのパケットのみ通す。
- Port Restricted Cone NAT: さらに厳しく、宛先IP+ポートまで一致した相手からのみ通す。ここまでは標準的なホールパンチングで越えられる。
- Symmetric NAT: 宛先ごとに異なる外部ポートを割り当てる。STUNサーバーに対して見えたポートと、相手ピアとの通信で使われるポートが別物になるため、教科書どおりのホールパンチングが原理的に成立しない。企業ネットワークやモバイル網(CGNAT)に多い。
UDPホールパンチング:パケット単位で追う完全シーケンス
ホールパンチングの本質は、「NATは、内側から送ったパケットと同じ経路の返信を、一定時間だけ通す」という性質の利用です。双方が「先に自分から送っておく」ことで、互いのNATに相手宛ての通り道(マッピング=穴)を先回りで作ります。手順を1ステップずつ追いましょう。登場人物は、NAT配下のピアA・ピアB、そして両者が最初に接続しているランデブーサーバー(仲介役。シグナリングサーバーとも呼ばれる)です。
- 登録: AとBはそれぞれランデブーサーバーSへUDPパケットを送る。この時点で各NATに「S宛ての通信」のマッピングができ、Sは各ピアのパブリックエンドポイント(NATの外側から見えるIP:ポート)を観測する。
- エンドポイント交換: SはAに「Bのパブリックエンドポイントとプライベートエンドポイント」を、Bに「Aの同情報」を伝える。プライベート側も交換するのが重要(理由は後述)。
- 同時送信(パンチ): AはBのエンドポイントへ、BはAのエンドポイントへ、ほぼ同時にUDPパケットを送り始める。最初の数発は相手のNATに「未知の送信元」として破棄されるが、この送信自体が自分のNATに相手宛てのマッピング(穴)を作る。
- 開通: 双方の穴が開いた後に届いたパケットは、それぞれのNATにとって「内側から送った通信への返信」に見えるため通過する。以後は直接の双方向UDP通信が確立し、ランデブーサーバーは不要になる(切断してよい)。
なぜプライベートエンドポイントも試すのか? AとBが同じNATの内側(同一家庭・同一オフィス)にいる場合、パブリックエンドポイント同士の通信はNATのヘアピニング対応に依存し、失敗することが多いからです。プライベートアドレスへの直接送信も同時に試み、先に疎通した方を採用するのが実装の定石です(ICEが全候補ペアを試すのはこの一般化と言えます)。
また「同時」は厳密な同時刻である必要はありません。必要なのは双方が相手からの最初のパケット到達より前に、自分からの送信を済ませていることです。実装では数十ミリ秒間隔のリトライを数秒間続けるのが普通で、片方の初弾が捨てられても後続が穴を通ります。
NATタイプ別・成功可否マトリクス
標準的なUDPホールパンチング(ポート予測なし)の成否は、両端のNATタイプの組み合わせでほぼ決まります。
| A \ B | Full Cone | Restricted | Port Restricted | Symmetric |
|---|---|---|---|---|
| Full Cone | ○ | ○ | ○ | ○ |
| Restricted | ○ | ○ | ○ | ○ |
| Port Restricted | ○ | ○ | ○ | × |
| Symmetric | ○ | ○ | × | × |
Symmetric側は相手との通信に新しい外部ポートを使うため、相手が「ポートまで一致」を要求するPort Restricted以上だと弾かれてしまうため、×になります。相手がFull Cone/Restricted(ポートを見ない)なら、Symmetricが混ざっていても通ります。つまり「Symmetric × Port Restricted」と「Symmetric × Symmetric」だけが標準手法の圏外です。
Symmetric NATを越える:ポート予測とバースデーパラドックス
- ポート予測: 多くのSymmetric NATはポートを完全ランダムではなく逐次的(+1ずつ等)に割り当てます。STUNへ2回問い合わせて割り当ての規則性を推定できれば、「次に使われるはずのポート」を狙い撃ちできます。
- 多ポート同時試行(バースデーパラドックス): 予測が効かないランダム割り当てでも、確率に頼って成功させる方法があります。Symmetric側が数百の送信元ソケットから穴を開け、相手側が数百のポートへ試行すると、「誕生日のパラドックス」により衝突(一致)の確率が急上昇します。65,535ポート空間でも、双方が約400ポートずつ試せば成功率は9割を超えます。TailscaleなどのメッシュVPNが実際に採用しているテクニックです。
- それでも駄目なら: CGNATの多段構成や完全ランダム割り当てには勝てないことがあります。その場合は潔くTURNリレーへフォールバックします。設計上の要諦は「フォールバックまで含めて数秒以内に自動で決着させる」ことです。
TCPホールパンチング:同時オープンという裏技
TCPでも原理的にはホールパンチングが可能です。鍵はTCP状態機械に古くから定義されている同時オープン(simultaneous open)です。双方がほぼ同時にSYNを送り合うと、SYN+ACKの交換を経ずに両者のSYN同士で接続が成立するという、通常はまず起きない遷移です。双方が同じローカルポートから(SO_REUSEADDRを使い)相手へconnectを繰り返すことで、UDPと同様に穴を開けられます。
ただし成功率はUDPよりかなり低くなります。理由は(1)NATがTCPの状態(SYNの向き)まで追跡していて「外から来た先行SYN」をRSTで拒否する製品が多いこと、(2)双方のSYNのタイミング窓がUDPより厳しいこと、(3)OSのTCPスタック依存の挙動差が大きいこと、です。実務では「UDPで穴を開けてその上に信頼性層(QUICやSCTP)を載せる」方が一般的で、WebRTCのデータチャネルもまさにこの構成(UDP上のSCTP)です。
keep-aliveとマッピング寿命:開けた穴は放っておくと塞がる
NATのマッピングにはタイムアウトがあります。UDPは無通信30秒〜数分で消える機器が多く(RFC 4787は最低2分を推奨、実機は30秒前後も珍しくない)、TCPは確立後なら2時間以上が推奨ですが、これも機器次第です。したがってP2P接続を維持するには、無通信でも定期的にkeep-aliveパケットを送り続ける必要があります。実装では15〜25秒間隔の小さなUDPパケットが定番です。モバイル端末ではkeep-aliveの頻度が電池消費と直結するため、「タイムアウトを実測して間隔を適応させる」高度な実装もあります。穴が塞がってしまった場合の再パンチ(re-punch)手順を用意しておくことも、堅牢なP2Pアプリの必須要件です。
実世界での使われ方
- WebRTC / ICE: ホールパンチングは、ICEの「接続性チェック」として標準化された形で毎日数十億回実行されています。候補収集(ホスト/STUN反射/TURNリレー)→SDP交換→全候補ペアの優先度順チェック→最良経路選定、という流れの中で、STUNバインディング要求の送り合いがそのままパンチとして機能します。
- オンラインゲーム: コンソールゲームのマッチメイキングは、マッチングサーバーがランデブーサーバーを兼ね、対戦者同士をホールパンチングで直結させるのが定番構成です。NATタイプ判定(いわゆる「NATタイプA/B/C」表示)はこの成功可否の事前診断です。
- メッシュVPN(Tailscale等): 各ノードが調整サーバーで鍵とエンドポイントを交換し、WireGuardトンネルをホールパンチングで直結します。Symmetric NAT相手にはポート予測とバースデーパラドックス試行を行い、それでも無理な相手にはDERPと呼ばれる中継サーバー(TURN相当)で必ず通す、という多段戦略の好例です。
ICE:候補収集から接続確立まで
- 候補収集: 各ピアがローカルアドレス(ホスト候補)、STUNで得た外部アドレス(サーバー反射候補)、TURNの中継アドレス(リレー候補)を集める。
- 候補交換: シグナリングチャネル(WebRTCならWebSocketサーバー経由が典型)でSDPに載せて交換する。
- 接続性チェック: 候補同士の全ペアに優先度を付け、STUNバインディング要求を送り合って疎通を確認する。この相互チェックがそのままホールパンチングとして機能する。
- 選定: 疎通したペアのうち最も優先度の高い経路(直結 > 反射 > リレー)を選び、以後の通信に使う。ネットワーク変化時は再チェック(ICE restart)。
現実の落とし穴
モバイル回線では通信事業者側のNAT(CGNAT)が重なり、家庭のルーターと二重NATになることが珍しくありません。UDPをブロックするファイアウォール環境ではTCP経由やTURN over TLS(443番)へのフォールバックが必要です。根本的な解決策はIPv6の普及であり、端末同士がグローバルアドレスを持てばNAT越えという問題自体が消滅します。ただし当面は、ICEの泥臭い世界が続きます。アドホック網のルーティングやチャーン測定など、関連する基礎研究は参考文献へ。