NAT Traversalの仕組み (2020)
(tailscale.com)- NAT Traversalは、NATとファイアウォールの背後にある機器同士がUDPパケットを直接交換できるようにし、Tailscaleが中央ハブなしでWireGuardトンネルを接続できるようにする基盤技術である
- 中核となる前提は、プロトコルがUDPベースであり、NAT探索用パケットと実際の通信パケットを同じネットワークソケットで送受信できることである
- ステートフルファイアウォールは、先に外へ出たUDPパケットに対応する応答だけを許可するため、ピアが互いの
ip:portを把握してほぼ同時にパケットを送れば、ファイアウォールの状態を開けることができる - NATは送信元IPとポートを書き換えるため、STUN、ポートマッピング、NAT64処理、誕生日のパラドックスに基づくポート探索、リレーといった補完技法が必要になる
- ICEは利用可能な候補経路を同時に試して最適な経路を選び、TailscaleはDERPリレーで即座に接続した後、より良い直接経路が見つかれば透過的に切り替える
NAT Traversalの基本条件
- 目標は、2台の機器の間に双方向のUDPパケットフローを作り、その上でWireGuard、QUIC、WebRTCのようなプロトコルを動作させることである
- 直接実装するには2つの条件が重要である
- プロトコルはUDPベースでなければならない
- TCPでも可能ではあるが複雑さが増し、実装方法によってはカーネルの修正が必要になることがある
- ストリーム指向の接続が必要なら、UDP上で動作するQUICを検討できる
- プログラムがパケットを送受信するネットワークソケットを直接制御できなければならない
- NAT Traversalでは本来のプロトコル以外の追加パケットを送る必要があるため、既存のネットワークライブラリに単純に付け足すのは難しい
- NAT Traversalロジックと本来のプロトコルが同じソケットを共有しながら並行動作する構成が有用である
- プロトコルはUDPベースでなければならない
- ソケットへの直接アクセスが難しければ、ローカルプロキシを置くこともできる
- 元のプロトコルはプロキシと通信する
- プロキシはNAT Traversalとピアへのパケットリレーを担当する
ステートフルファイアウォールの通過
- ステートフルファイアウォールは、過去に見たパケットを記憶して新しいパケットを許可するかどうかを決める
- 例として、Windows Defender firewall、Ubuntu
ufw、BSDpf、macOSのpf、AWS Security Groupsのようなものがある - 一般的な設定は、すべてのoutbound接続を許可し、すべてのinbound接続を遮断する方式である
- 例として、Windows Defender firewall、Ubuntu
- UDPではルールは単純である
- ファイアウォールが
2.2.2.2:1234から5.5.5.5:5678へ出ていくUDPパケットを見た場合、逆向きの5.5.5.5:5678から2.2.2.2:1234へ入ってくるパケットを許可する - 一部の緩いファイアウォールは、一度通信したローカルポートに対してどこからでも入ってくるトラフィックを許可することがあるが、そうした例はますます少なくなっている
- ファイアウォールが
- サーバーとクライアントの構成では、ファイアウォールの背後にある機器が先に接続を開始すればよいため、問題は小さい
- VPNでは、ファイアウォールのないハブとファイアウォールの背後にあるスポークが接続されるhub-and-spoke構成になる
- 2つのクライアントが直接通信しようとすると、双方のファイアウォールが互いを遮る状況が生じる
- どちらも先に外へ出なければ応答を受け取れないが、相手側も同じ条件にある
- ユーザーがポート開放を手動設定する方式は不便で、Tailscaleのようなメッシュネットワークでは拡張性が低い
- 空港やカフェのルーターのように、ユーザーが制御できないファイアウォールも多い
- 解決の要点は、UDPファイアウォールのルールが実際の応答関係を検証せず、IPとポートの組み合わせだけを見ることである
- 両側のピアが相手の
ip:portを事前に把握し、同時にUDPパケットを送れば、最初のいくつかのパケットは遮断されてもファイアウォール状態は開く - その後に相手が送ったパケットは、応答のように見えて通過する
- 両側のピアが相手の
- この方式にはサイドチャネルが必要である
- 両端点がほぼ同時に通信を試みる必要がある
- 遅延が数秒あってもよく、数千バイトだけ伝送できれば十分な通信経路で足りる
- WebRTCはシグナリングチャネルを必要とし、Tailscaleはcoordination serverとDERPサーバーをサイドチャネルとして使う
- ファイアウォール状態は永続的ではない
- UDPセッションタイムアウトの一般的な値は30秒である
- 接続を維持するには定期的にパケットを送るか、必要なときにout-of-band方式で接続を再開しなければならない
- ステートフルファイアウォールが何層あっても、outboundを許可しているなら同時送信方式で通過できる
NATが問題をさらに難しくする仕組み
- NAT(Network Address Translator)はステートフルファイアウォールのように動作しつつ、パケットのIPアドレスやポートまで書き換える
- NAT Traversalで問題になるのは主に**Source NAT(SNAT)**である
- SNATは、複数の機器がより少ない数のIPアドレス、たいていは1つのグローバルIPv4アドレスを共有できるようにする
- DNATも存在するが、ここで扱うNAT Traversalの問題とは関係が薄い
- たとえばノートPCが
192.168.0.20:1234からインターネット上のサーバー7.7.7.7:5678へUDPパケットを送ると、ホームルーターはグローバルIPの空きポートである2.2.2.2:4242を選ぶ- ルーターは
192.168.0.20:1234と2.2.2.2:4242が同一であるというNAT mappingを作る - 以後、外へ出るパケットは
2.2.2.2:4242から来たかのように書き換えられる - 入ってくる応答は再び
192.168.0.20:1234へ書き換えられる
- ルーターは
- 企業ネットワークでも同じ原理が適用される
- NAT層が高可用性や容量のために複数の機器で構成されることがあり、複数のグローバルIPを持つことがある点が異なる
STUNとNATマッピングの発見
- NATの背後にいるピアは、相手から見える自分のグローバル
ip:portを知ることができず、NATマッピングは通常、インターネット向けのトラフィックが発生してはじめて作られる - STUNは、NATの背後にいるクライアントがインターネット上でどのように見えているかを調べるためのプロトコルである
- クライアントはSTUNサーバーに「私のendpointはあなたからどう見えるか」と問い合わせる
- STUNサーバーは、UDPパケットが来たグローバル
ip:portを応答する
- STUNが教えたグローバル
ip:portをピアと共有すれば、ファイアウォール通過で使った同時送信の手法を適用できる - NAT Traversalロジックと実際の通信プロトコルが同じソケットを使わなければならない理由もここにある
- ソケットごとにNAT機器上で異なるマッピングが作られる
- 実際の通信に使うソケットではない別ソケットでSTUNを行っても、役に立たない
ip:portしか得られない
- STUNだけですべてのNATを解決できるわけではない
- 大半のホームルーターでは機能する可能性がある
- 一部の企業向けNATゲートウェイでは失敗することがある
- STUNで見える
2.2.2.2:4242が、インターネット全体で同じ意味を持つという前提は常に成り立つわけではない
易しいNATと難しいNAT
- NAT機器は、宛先に応じてマッピングを変えることもあれば、宛先に関係なく同じマッピングを維持することもある
- RFC 4787では、宛先に関係なくマッピングが維持される易しい形を**Endpoint-Independent Mapping(EIM)**と呼ぶ
- 宛先に応じてマッピングが変わる難しい形は**Endpoint-Dependent Mapping(EDM)**である
- 宛先IPだけを基準に変わる場合もあれば、宛先IPとポートの組み合わせを基準に変わる場合もある
- NAT Traversalの観点では、どちらも望ましくない
- 以前の用語であるFull Cone、Restricted Cone、Port-Restricted Cone、Symmetric NATは、NATマッピングの挙動とファイアウォールの挙動を混ぜて表現している
- 実用的な実装では、「Symmetricかそれ以外か」あるいはEIMとEDMの区別のほうが重要である
- 同時送信の手法は、さまざまな種類のファイアウォールを通過できる
- 実環境では、IPとポートに依存するファイアウォールが圧倒的に多い
- しかし、経路上のどこかにhard NATが1つでもあると、STUNと同時送信だけでは問題が生じる
直接接続に失敗した場合のリレー
- 直接接続は、あらゆる手法を使っても失敗することがある
- NATが厄介だったり、UC Berkeley guest Wi-Fi のように DNS を除く outbound UDP を遮断するネットワークでは、NAT の手法では解決できない
- この場合、両者からアクセス可能な リレー を介してパケットをやり取りできる
- 直接接続ほど良くはないが、リレーが経路に十分近く、帯域幅が十分であれば、接続品質の低下はそれほど大きくない場合がある
- 遅延が増えたり帯域幅が減ったりしても、まったく接続できないよりはましである
- 従来のリレープロトコルは TURN である
- クライアントは TURN サーバーに認証する
- TURN サーバーはリレー用の
ip:portを割り当てる - ピアはその
ip:portで通信する
- Tailscale は TURN の代わりに DERP(Detoured Encrypted Routing Protocol) を作った
- DERP は HTTP 上で動作する
- 厳格な outbound ルールがあるネットワークで有用である
- 宛先の公開鍵を基準に暗号化されたペイロードをリレーする
- DERP は 2 つの役割を担う
- NAT Traversal に失敗した際のデータリレー
- NAT Traversal を支援するサイドチャネル
- STUN、同時送信、リレーまで実装すれば、90% 以上は直接接続が可能であり、リレーが常に何らかの形の接続性を保証できると推定している
Hard NAT のための追加手法
- hard NAT では、容易な側のピアは難しい側の NAT がどのポートを開けたか分からない
- STUN により IP はおおむね正しいと仮定できる
- 分からないのはポートであり、取り得る値は 65,535 個である
- 単純にすべてのポートをなめると、100 パケット/秒基準で最悪の場合およそ 10 分かかり、ポートスキャンのように見える
- 誕生日のパラドックス を使うと探索コストを下げられる
- hard NAT 側で 256 個のソケットにより 256 個のポートを開き、容易な NAT 側でランダムな宛先ポートを探索する
- 256 個のポートが開いていると仮定した場合の成功確率は次のとおり
- 174 回のランダム探索: 50%
- 256 回のランダム探索: 64%
- 1024 回のランダム探索: 98%
- 2048 回のランダム探索: 99.9%
- 100 ポート/秒なら半分は 2 秒以内に通過し、20 秒ほどで全空間の 4% 未満しか探索していなくてもほぼ成功する
- 両側とも hard NAT であれば、はるかに難しい
- 今度は
{source port, destination port}の組が一致しなければならない - 同じ条件では 20 秒後の成功確率は 0.01% である
- 99.9% の成功確率には両側がそれぞれ 170,000 個の probe を送る必要があり、100 パケット/秒基準で 28 分かかる
- 今度は
- この方法は、ホーム-オフィス、ホーム-クラウド、一部のオフィス-クラウドやクラウド-クラウドのシナリオで接続性を改善できる
- ホームルーターは容易な NAT である傾向があり、hard NAT はオフィスルーターやクラウド NAT ゲートウェイである傾向がある
ポートマッピングプロトコル
- NAT に直接「この WAN ポートをこの LAN
ip:portに転送してほしい」と要求するプロトコルがある - 代表的な 3 つは次のとおり
- ローカルのデフォルトゲートウェイに対して UPnP IGD、NAT-PMP、PCP を試し、応答があればグローバルポートマッピングを要求できる
- 成功すれば STUN のようにグローバル
ip:portを知れるだけでなく、NAT がそのポートに対してより寛容に動作するようにもできる - どこから来たパケットでも、マッピングされたポートに到達すれば内部デバイスへ転送される
- 成功すれば STUN のようにグローバル
- これらのプロトコルに依存することはできない
- 機器に実装されていない場合がある
- デフォルトで無効になっている場合がある
- ポリシー上無効化されている場合がある
- UPnP の過去の脆弱性のため、ポリシー上無効にされることがある
- 一部の機器では 1 つの「UPnP」チェックボックスで UPnP、NAT-PMP、PCP をまとめて無効化することもある
- 利用できるなら、データ経路上の NAT が 1 つ事実上消えることになり、接続が容易になる
Double NAT と CGNAT
- 1 台のデバイスの手前に NAT が 2 段ある double NAT では、外側、つまりインターネット直前の NAT の動作が最も重要である
- 複数段のステートフルファイアウォールと同様に、追加の NAT 層はたいてい見えない
- 既存の手法は NAT 層の数に関係なく動作できる
- double NAT が大きく壊すのはポートマッピングプロトコルである
- ポートマッピングはクライアントに最も近い NAT 層に作用する
- しかし、リモートピアが通過しなければならないのは最も外側の NAT である
- 結果として得られる
ip:portは中間ネットワークのアドレスなので、リモートピアは到達できない
- double NAT は、明示的な NAT Traversal を行わない大半の一般的なアプリケーションには見えない
- しかし、多くのゲームのマルチプレイを悪化させる可能性があり、IPv6 を取り除いて NAT なし接続という選択肢を減らすことがある
- CGNAT(Carrier-Grade NAT) は、ISP が IPv4 アドレス不足を解決するために SNAT をもう 1 段適用する構成である
- ホームルーターがデバイスを中間 IP に SNAT する
- ISP ネットワーク内の第 2 の NAT 層が、中間 IP をより少ないグローバル IP にマッピングする
- CGNAT では、ユーザーは ISP の NAT をリセットできない
- 以前は上級ユーザーがホームルーターのポートフォワーディングで問題を回避できたが、CGNAT ではその方法が封じられる
- CGNAT も基本的には double NAT なので、既存手法のほとんどは引き続き動作する
- ポートマッピングプロトコルだけは例外的に制約がある
Hairpinning の問題
- 同じ CGNAT の背後にありながら、異なるホーム NAT の背後にいる 2 つのピアは特別な問題に直面する
- STUN サーバーはインターネットの外側から見たグローバル
ip:portを教える - しかし、2 つのピアが実際に必要としているのは CGNAT 内部の中間ネットワークで通用する
ip:portである
- STUN サーバーはインターネットの外側から見たグローバル
- ホーム NAT のどちらか一方でもポートマッピングプロトコルをサポートしていれば、接続は容易になる可能性がある
- double NAT のため、ポートマッピングプロトコルが中間ネットワークの
ip:portを教えてくれることが、むしろ役に立つ
- double NAT のため、ポートマッピングプロトコルが中間ネットワークの
- ポートマッピングが使えない場合は hairpinning が必要になる
- たとえば peer A が、STUN で得た peer B の
2.2.2.2:5678にパケットを送る - CGNAT はこのパケットを外のインターネットへ送らず、内部で peer B の NAT マッピングへ折り返して送らなければならない
- たとえば peer A が、STUN で得た peer B の
- 多くの NAT は hairpinning をサポートしていない
- 内部ネットワークから非内部 IP へ向かうパケットは常にインターネットへ出ていくと仮定する機器がある
- こうした仮定はルーティング用シリコンに焼き付いており、新しいハードウェアなしでは修正できない可能性がある
- CGNAT が絡むと、hairpinning は接続性にとって重要になる
- hairpinning とポートマッピングの両方が失敗した場合は、リレーを使う必要がある
IPv6とNAT64
- IPv6のみの世界であれば、NATの問題ははるかに単純になる
- すべてのデバイスが、NATなしで到達可能なアドレスを持てる
- ただしステートフルファイアウォールは依然として残るため、ファイアウォール越えとサイドチャネルは必要
- outbound UDPを遮断するネットワーク向けに、HTTPのようなプロトコルを使うフォールバックリレーも依然として有用
- IPv6だけではまだ不十分
- 世界の大半はIPv4で、IPv6は約33%
- IPv6の展開は一様ではないため、ピアの組み合わせによって100% IPv6にも0% IPv6にもなり得る
- 無条件の接続を目指すなら、IPv4+NATの処理を引き続き行う必要がある
- IPv6とIPv4の共存は、NAT64という追加のケースを生む
- NAT44はIPv4を別のIPv4に変換する
- NAT64は内部のIPv6を外部のIPv4に変換する
- DNS64と組み合わせると、端末にはIPv6-onlyネットワークのように見せつつ、IPv4インターネットへのアクセスを提供できる
- DNS名だけを使うアプリケーションは、NAT64をほとんど意識せずに済む
- しかしNAT Traversalは具体的なIPとポートを直接扱うため、別途処理が必要
- デバイスが**CLAT(Customer-side translator)**をサポートしていれば、OSが直接IPv4接続を持っているように見せ、背後でNAT64を処理する
- CLATはモバイルデバイスでは一般的
- デスクトップ、ノートPC、サーバーではまれ
- CLATがなければ、NAT64+DNS64を直接検出する必要がある
ipv4only.arpa.にDNSリクエストを送る- この名前は既知の固定IPv4アドレスにのみ解決される
- IPv6アドレスが返ってきたら、それはDNS64が変換したものなので、NAT64 prefixを特定できる
- その後IPv4アドレスと通信するには、
{NAT64 prefix + IPv4 address}宛てにIPv6パケットを送ればよい- NAT64経由でSTUNを実行して公開
ip:portを見つければ、再び通常のNAT Traversalの問題に戻る
- NAT64経由でSTUNを実行して公開
ICEで候補経路を統合する
- どの手法を使うべきかを事前に正確に分類する方式は、スケーラビリティが低い
- ネットワークエンジニアやNAT機器の実装者が多様な動作を作り出すため
- ICE(Interactive Connectivity Establishment) の核心は、可能なものをすべて同時に試し、動作するものの中から最良の経路を選ぶアルゴリズムにある
- 通信開始時に、ローカルソケットに対する候補endpointの一覧を集める
- IPv6
ip:ports - IPv4 LAN
ip:ports - STUNで発見したIPv4 WAN
ip:ports - NAT64 translator経由で発見したIPv4 WAN
ip:ports - ポートマッピングプロトコルで割り当てられたIPv4 WAN
ip:port - 静的に設定されたポートフォワーディングのような、運用者提供のendpoint
- IPv6
- その後、サイドチャネルを通じて候補一覧を交換し、相手が提示したすべてのendpointにprobeパケットを送る
- probeパケットはファイアウォールとNATを開くためのパケットとして機能する
- 同時にping/pong形式の疎通確認の役割も果たす
- 一定時間後、動作が確認された候補経路の中から、ヒューリスティック上もっとも良い経路を選ぶ
- ICEは通常、LAN > WAN > WAN+NATのような事前スコアを使う
- Tailscaleはv0.100.0から、ハードコードされた優先順位の代わりにRTTを使用している
- Tailscaleは接続を厳密なprobe段階と通信段階に分けない
- すべての接続は、あらかじめDERPが選択された状態で開始する
- ユーザーはフォールバック経路で即座に接続を利用できる
- 経路探索は並行して走り、数秒後により良い経路が見つかれば透過的にアップグレードされる
運用中の経路維持とセキュリティ
- 非対称経路に注意する必要がある
- ICEは、両側のピアが同じネットワーク経路を選び、双方向のパケットフローが維持されるように努める
- 同等の手順を実装していない場合でも、使用中のすべての経路に双方向トラフィックが存在しなければならない
- 定期的なping/pong probeだけでも、これを維持できる
- 現在選択されている経路が失敗することもある
- NATのメンテナンスによって状態が消えるケースがその一例
- 可能なすべての経路を継続的にprobeし、ウォームなフォールバックを維持できる
- ただしdowngradeはまれなので、最終手段のリレーに落としてから経路探索を再開するほうが効率的な場合もある
- 上位プロトコルが独自のセキュリティを提供するという前提が重要
- QUICはTLS証明書を使う
- WireGuardは独自の公開鍵を使う
- 動的に経路を切り替えると、IPベースのセキュリティは意味を失う
- 少なくともend-to-end認証が必要
- 上位層にend-to-endセキュリティがあれば、ping/pong probeがスプーフィング可能でも、最悪の場合でも攻撃者がトラフィックを自分経由に誘導できる程度にとどまる
- それでも、経路探索パケットも認証・暗号化したほうがよい
堅牢なNAT Traversalの構成要素
- 堅牢なNAT Traversalには、次の要素が必要
- UDPベースで拡張するプロトコル
- プログラム内から直接アクセス可能なソケット
- ピアと通信するためのサイドチャネル
- いくつかのSTUNサーバー
- 任意だが強く推奨されるフォールバックリレーネットワーク
- 実行手順は次のとおり
- 直接接続されたインターフェース上で、ソケットのすべての
ip:portsを列挙する - STUNサーバーに問い合わせて、WAN
ip:portsとNATの難易度を調べる - ポートマッピングプロトコルで追加のWAN
ip:portsを見つける - NAT64があればこれを検出し、その経路でもWAN
ip:portを見つける - サイドチャネルで、すべての
ip:portsと暗号鍵をピアと交換する - 高速な接続確立のため、最初にフォールバックリレーで通信してもよい
- 相手のすべての
ip:portsをprobeし、必要ならhard NATを通過するために誕生日パラドックスベースの探索を行う - 現在の経路より良い接続経路が見つかれば、透過的にアップグレードする
- アクティブな経路が停止したら、必要に応じてdowngradeして接続性を維持する
- すべての通信はend-to-endで暗号化・認証されていなければならない
- 直接接続されたインターフェース上で、ソケットのすべての
1件のコメント
Hacker News のコメント
素晴らしい記事。一般に TCP ベースのホールパンチングは UDP より難しいので避けるべき、という暗黙知のようなものがあるが、実際にはすでに複雑な UDP のフローに比べて、追加の複雑さはそれほど大きくないように見える。
記事でも TCP の NAT 越えは可能だが複雑さが増し、深く踏み込むとカーネルの修正が必要になるかもしれないと認めている。ただし、生の UDP パケットで接続を開始する部分を、TCP SYN パケットと **同時オープン(simultaneous open)**のサポートに置き換えればよいと思う。
特に UC Berkeley のゲスト Wi‑Fi のように、DNS 以外のすべての UDP 送信をブロックするネットワークがあることを考えると、TCP ホールパンチングを「UDP より難しい」という理由だけで雑に片づけてしまうのは惜しい。ほぼ同じくらい実現可能で、追加の複雑さも限定的だと思う。
https://ttcplinux.sourceforge.net/documents/one/tcpstate/tcp...
したがって、TCP で同時オープンを行う追加の複雑さはかなり小さい。核心的な難所は公開マッピングを伝達し、「同時」のパンチング/オープンを調整することだが、これは通常 UDP でも必要になる。
TCP でさらに一つ複雑な点は、偽の TCP SYN パケットを作るよりも、実際に
connect()呼び出しを行う必要があることだ。一部のファイアウォールはシーケンス番号を見るためである。ただし TCP ホールパンチングは UDP パケットよりも SYN floodにはるかに似て見えることがあり、一部のネットワークでは成功率が下がる可能性がある。実際には、まだフィルタリングはあまり見ていない。
TCP ホールパンチングはかなり面白い。複数回の NTP 測定でシステムクロックが NTP からどれだけずれているかという「クロックオフセット」を計算し、開始側が NTP 基準の将来の待ち合わせ時刻を決める方式で実装している。思ったより正確で、同じインターフェース上のソケット同士でも TCP ホールパンチングは動作する。
こうした奇妙なローカルベースのパンチングモードをサポートした理由は、ホスト内部のパンチングがその程度の効率で成功するなら、LAN やインターネットでも十分速い可能性が高いからだ。コードは Python で、最初の試みはかなり衝撃的だった。TCP ホールパンチングはタイミングに敏感なので、Python で昔ながらの直接的なソケット管理、スレッディング、C のソケット経験をもとに作った粗いイベントループを使うと失敗した。
そのコードを動かすには、Python プロセスの優先度を上げ、他のプロセスがパンチング試行の間に遅延を作れないようにする必要があった。非効率な実装では、それほど時間に敏感になる。現在の実装は、それぞれがイベントループを持つプロセスプールを使い、時間的に分散したタスクリストを作成したうえで、各タスクが同じソケットを再利用して接続を開く方式だ。主要なオペレーティングシステムでテストした後、Python ではこのアプローチが最善だと判断した。
TCP と UDP のホールパンチングの難易度が似ていることには同意する。どちらも最も難しい部分は NAT 予測の段階だ。まだ対称型 NAT 回避コードは使っていないが、統合するか新しいプラグインにする方法が見え始めている。
ルーターの状態テーブルは非常に小さく、一部のパンチング手法はかなり攻撃的だ。例えば対称型 NAT 回避を試みるアルゴリズムのように、数百個の TCP 接続を開くと、ルーターをサービス拒否状態にしてしまう可能性もある。
UDP は状態管理の最適化のおかげで、パンチングによってルーター全体が停止する可能性はより低いかもしれない。ただし、これは推測だ。
効果は興味深いほど良いものの、これを本番の企業ネットワークに入れようという話になると、なんとなく不安になる
従来のNATとファイアウォールを迂回し、代わりにソフトウェアACLひとつだけに依存する感じで危険に見える。たとえばAWSのテスト環境に放置されたVMにTailscaleがあり、攻撃者がそこへアクセスできると、社内ネットワークのノートPCに至るまで、カーネルを通過した後はユーザー空間のTailscale ACLコードだけが許可/拒否を決める経路ができるように見える
許可されていない誰かがその地点まで入り込んでも、気づけるのか分からない
NATと、それに関連する大半のステートフルフィルタを突破するのは非常に簡単。実際の企業の本番環境向けに販売する製品としてこうしたものを実装したことがあるが、魔法ではなく実務者にはよく知られた技術
本物のパケットフィルタリング、つまりファイアウォールが欲しいなら、NATとは別のファイアウォールインスタンスを配置し、適切なルールを置く必要がある。ただしそれも主にトラフィック量を減らす助けになるだけで、ファイアウォール自体の実質的なセキュリティ上の利点は今では小さい。攻撃の大半はHTTP/HTTPSやPOP/IMAPのような上位レイヤー経由で入ってくるため
実際にはステートフルファイアウォールがほとんどの仕事をしているのに、NATが手柄を持っていくようなもの。Tailscaleはファイアウォールをなくすのではなく、正しいACLベースのはるかに包括的な設定を提供する
ただしTailscaleのACLツールには大きく改善の余地がある点は認める
その影響でWindowsのネットワークスタックもかなり変わり、より複雑になった。WireGuardがLinuxに入って以降、誰もがどこかでVPSにつなぐVPNをひとつくらい持っている。自分が何を知らないのかを知らないという点のせいで、実際の状況は思ったより悪い可能性が高い
ファイアウォールは別の概念。ただし接続性とセキュリティを絡めて言うなら、インターネットセキュリティがずっと宛先ポートを基準にパケットを止める方式に頼ってきたことは悲しく、不安でもある
正しいことより簡単なことをしながらも「プロフェッショナルなソリューション」という名前が付くのが現実
それでも内部の何かが先に外部へ話しかける必要があるため、実際のファイアウォールは送信接続と受信接続の両方を許可リストで管理すべき
つまり境界防御に依存しているなら、誰かがいずれ自分の組織版の「蛍光ベスト」が何なのかを見つけ出すのは時間の問題
アプリケーション層ですでに暗号化している機器のために、接続の暗号化がないTailscale風の代替があるとよい。インターネットのほぼ全体がそう動いているように、常に下位レイヤーまで暗号化する必要はない
低消費電力の機器、たとえばTailscale風のトンネルを動かすIoT機器では、計算コストが特に大きい
GREトンネルは存在し、実際によく使われているが、UDPホールパンチングが処理されないためハブ・アンド・スポーク構成が必要になる。GRE、つまり
ip fouではピア間メッシュを作れない身元確認のための暗号化ハンドシェイクの後に、UDPホールパンチングと暗号化されていないGREトンネルを提供するライブラリがあるのか気になる
より汎用的な接続性に合わせたものが欲しいなら、libp2pが求めているものに近いかもしれない
https://datatracker.ietf.org/doc/html/rfc8445
https://github.com/pion/webrtc
https://github.com/algesten/str0m
https://libp2p.io
Pythonで書いた。ただし大半のネットワークコードのように、デフォルトインターフェースの使用を前提にはしていない。望む任意のインターフェースでサービスを動かせるようにして、より多様で有用なものを作れるようにしたかった
ほとんどは標準ライブラリモジュールベース。C拡張はクロスプラットフォームのパッケージをよく壊すので嫌い
ピア同士が相手の使う
ip:portを事前に知る必要があり、それを同期するために調整サーバーを作ったという部分を見ると、SIPが名前どおりの働きをしてくれればよかったのにと思うSIPはSession Initiation Protocol、つまりVPNのような任意のセッションも開始できるはずの名前だが、実際には複雑すぎる混沌で、受け入れる価値が小さくなってしまった。元々はP2P RTPストリームを確立するための通信用サイドチャネルとして作られたのだと思う
HTTPのようだが状態があり、双方向で、フェデレーション型で、UDP上でも動作する
baresipがSIPをやるためだけに実装している量を見ると、TLS over UDPまで含めて膨大。しかも水増しではなく、それらの機能は実際に必要
2020年の記事。以前の議論は次のとおり
2022年: https://news.ycombinator.com/item?id=30707711
2020年: https://news.ycombinator.com/item?id=24241105
https://news.ycombinator.com/item?id=30707711
https://news.ycombinator.com/item?id=24241105
How NAT traversal works (2020) - https://news.ycombinator.com/item?id=36969018 - 2023年8月、コメント106件
How NAT traversal works (2020) - https://news.ycombinator.com/item?id=30707711 - 2022年3月、コメント37件
How NAT Traversal Works - https://news.ycombinator.com/item?id=24241105 - 2020年8月、コメント28件
ちなみにリンクがクリックできなかった理由は、2文字以上インデントした行がコードとしてフォーマットされるため: https://news.ycombinator.com/formatdoc
NAT越えを説明するとき、人に送っていた記事がまさにこの記事だった
P2Pアプリを作るとき、私たちはこの方式に頼り続けることになるのかもしれない。IPv6は十分な勢いを得られず、NATとSNIルーティングが大半の人にとって大半の問題を解決してくれるため
ISP側にも、その状況を変えさせるインセンティブはあまりない
インターネット全体でNAT越えを扱った記事の中でも、最も詳細な部類に入ると思う。ただしデルタ挙動に関する情報が抜けている
複雑な話ではなく、一部のNATが連続する外部ポートを割り当てる際に、観測可能なパターンを持つという意味。最も一般的なパターンは送信元ポートを保持することで、以前のマッピングから1ずつ増やすようなパターンもあり得る
理論的には非常に良い記事だが、ソフトウェアエンジニアが実際にどの程度活用できるかは気になる。多くを説明しているが、アルゴリズムを書けるほど細かくはないかもしれない。たとえば、この記事だけを読んでNATタイプをテストするアルゴリズムを書いたり、自分のホールパンチングコードを調整したりできるかは分からない
個人的には、このような長い記事より単純な表のほうが有用だった論文も見たことがある。それでも良い出発点にはなり得る
記事の最後の節は特に重要。モバイルシステムで使われる対称型NATを回避できる可能性がある。最近のNAT越え研究も似た手法を使っており、ほぼ100%に近い成功率を主張している
昔を思い出させる興味深い記事。2010年にこの方式を使う忘却型P2Pメッシュネットワークを作った
当時、人々は私たちが考えていたほどセキュリティを気にしていなかったし、今でもまだ十分には気にしていない。デバイスは増え、価値も大きくなったが、依然としてかなり安全ではない
ハードウェアの信頼の基点、認証・認可のための安全な信頼チェーン、最小限の一時的権限を備えた本当に安全なエンドポイントはいまだに難しく、家庭内ネットワーク、企業ネットワーク、大規模本番データセンターネットワークでもネットワーク境界セキュリティ劇場は続いている
これらがセキュリティ侵害の主な根本原因に見えない唯一の理由は、もっと簡単な攻撃経路がまだいくらでもあるから
少し話題から外れるが、数週間前、この分野をまったく知らない状態で少し読んでみた
受けた印象では、IPv6がこれらすべてをなくし、NAT越えも不要にするように思えた。だとすると、なぜIPv6はもっと広く使われておらず、ホームネットワークとTailscale VPNで始めるにはどうすればよいのか気になる
ビジネス上のインセンティブも不足している
IPv6の代わりにこういうものが登場したという事実自体が、十分に良いハックの力をよく示している