最も洗練されたTCPホールパンチングアルゴリズム
概要
- 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越し接続の動作確認を行える。