概要
- TCP hole punching はNAT越しにPC同士を接続する技術
- 多くの前提条件と複雑なインフラが必要
- 本手法は インフラ不要 でアルゴリズムの動作確認が可能
- タイムスタンプ から全メタデータを導出し同期
- ポート選択・ソケット設定・接続判定までを簡潔に解説
TCP hole punchingの前提と課題
- TCP hole punching はNATルータ配下の2台のPCを直接接続する技術
- 成功には以下の条件が必須
- 互いのWAN IP の事前把握
- 正しい外部ポート番号 の共有
- 完全な同時接続 の実現
- 実運用では、 STUNによるWAN IP取得 や NATタイプ判定・ポート予測、 NTPによる時刻同期、 必要なメタデータ交換チャネル が必要
- これらは 複雑なインフラと実装 を要求し、エラーも多発
インフラ不要でのアルゴリズム検証法
- 単一パラメータ から全メタデータを決定的に導出する方式を採用
- UNIXタイムスタンプ を基準パラメータとして選択
- 両端が通信せずとも合意できる「 バケット」値を算出
- max_clock_error (最大時刻誤差)を考慮
- min_run_window (実行許容時間幅)を設定
- 数式例:
now = timestamp()max_clock_error = 20smin_run_window = 10swindow = (max_clock_error * 2) + 2bucket = int((now - max_clock_error) // window)
- このバケットを用い 時刻ズレにも強い同期 を実現
ポートリストの決定方法
- バケット値 をPRNG(擬似乱数生成器)のシードとして利用
- 乱数で得た値から 共通のポートリスト を生成
- local port = external port となることを期待(equal delta mapping)
- 一部ルータでのみ有効な簡易性重視の仕様
- 具体的手順
large_prime = 2654435761などの素数でバケットを変換stable_boundary = (bucket * large_prime) % 0xFFFFFFFFで範囲を固定- 16個程度のポートを生成、バインド不可なものは除外
- OSの他プロセスとポート衝突の可能性あり
ソケット設定とネットワーク処理
- TCP hole punching には特定のソケットオプションが必須
SO_REUSEADDRおよびSO_REUSEPORTの有効化
- 通常のTCPとは異なり ソケットのクローズ禁止
- クローズでRSTパケットが送信されプロトコル破綻
- OS側のTIME_WAIT等の状態遷移も再利用性を損なう
- ノンブロッキングソケット 推奨
- 非同期(async) はタイミング制御が難しく不適
- select によるポーリングで状態管理
- SYNパケット を0.01秒間隔で連打し、適度な頻度を維持
接続確立後の勝者選定
- 複数ポートで複数接続が成立するため 同一接続の選定 が必要
- WAN IPが大きい方をリーダー、もう一方をフォロワーに決定
- リーダーが任意の1接続で1文字送信し他はクローズ
- フォロワーは select でイベント検知し1バイト受信した接続を採用
- 単一文字 で判定する理由
- TCPはストリーム型なので複数文字だとバッファリングが複雑化
全体の流れと実装例
- 全プロトコルは決定的 であり、宛先IPのみで動作可能
- NTPによる時刻同期 は推奨だが必須ではない
- 別プロセスで実行タイミングを調整すれば、 メタデータ交換不要
- equal delta mapping を採用する一般的な家庭用ルータで動作
- 実装例:
tcp_punch.py 127.0.0.1でローカル動作確認が可能
この手法により、 複雑な外部インフラなしでTCP hole punchingアルゴリズムのテスト が容易に実施可能。 時刻同期と決定的なパラメータ生成 により、最小限の準備でNAT越し接続の動作確認を行える。