NATを越えろ

自宅のデスク周りを充実させまくったら、部屋から完全に出不精になってしまいました。

ビデオ会議をする機会が大分増えてきて、今後もビデオチャットで仕事をすることや、プライベートでも活用の幅がふえてくるのでは?ということでせっかくなのでWebRTC※1でビデオチャットを実装した際にNATについて色々学びがあったのでここでアウトプット。

※1. Web Real-Time Communicationの略でブラウザで気軽にP2P通信ができるようにしたもの(オープン標準技術)

以下WebRTCのプロトコルスタック
主に赤枠の部分についての話

図:オライリー

そもそもNATってなんやねん

NAT(Network Address Translation)とは、コンピュータネットワークにおいて、主にグローバルIPアドレスをプライベートIPアドレスに、またはその逆の変換を行う技術のことである。

by wikipedia

つまりプライベートアドレスをグローバルアドレスに変換してインターネット上のアドレスとやりとり出来るようになる

ローカルネットワークとグローバルネットワークを行き来するための仕組み

静的・動的?

静的NAT

グローバルアドレスとプライベートアドレスを1対1の固定ペアとして変換を行う

動的NAT

外部への通信が発生したときに、プール内のグローバルアドレスを使用して変換を行う

NAPT(動的)

NAPT(Network Address Port Translation)とは、アドレスに加えてポート番号も変換する技術。
グローバルアドレスがひとつしかない場合でも、変換時に動的に割り当てた送り元Portを元にどの端末の通信かの関連付けを行うことができる。

家や会社で複数の端末が一つのグローバルIPで同時にインターネットができるのはこの仕組みのおかげ

送り元IP:送り元ポート、送り先IP:送り先ポートの組で接続ごとに管理している

NATタイプ

一口にNATと言っても挙動には個性があって、大きく分けると2系統ある。

  • Cone系: 接続先が変わっても同じ変換(同じ外向きIP:ポート)を使い回すタイプ
  • Symmetric: 接続先ごとに別の変換を割り当てるタイプ

さらにCone系は「外から来たパケットをどこまで通すか」の厳しさで3段階に分かれるので、合計4タイプ。緩い順に見ていく。

Full cone NAT(フルコーンNAT)

一度どこかへ送信して変換エントリさえ作られれば、送信したことがない相手からでも受信可能
※UPnPのポートマッピングはほぼこれに該当する

Address-Restricted cone NAT(制限付きコーンNAT)

一度送信した相手のアドレスから(NATデバイス上に送信先アドレスのエントリが存在する場合)であれば、ポートを問わず受信可能

Port-Restricted cone NAT(ポート制限付きコーンNAT)

一度送信した相手のアドレスとPortの両方が一致する場合(NATデバイス上に送信先アドレスとPortのエントリが存在する)のみ受信可能

Symmetric NAT(対称型NAT)

対称型NATは最も制限の厳しいもので、宛先アドレスとポートを用いて接続先を区別し、接続先ごとに別のアドレス変換(ポート)を割り当てる。
そのため後述のSTUNで調べたアドレスが他の相手との通信には使い回せない

Nat traversal(NAT越えの手法)

なんでこんなにタイプ分けの話をしたかというと、P2Pは「相手のNATの外から中へパケットを届ける」ことをやらないといけなくて、さっきの「外から来たパケットをどこまで通すか」という性格の違いが、そのままNATの越えやすさになるから。緩いFull coneなら穴を開けやすく、一番厳しいSymmetricになると後述のSTUNで調べたアドレスすら使い回せない。

とはいえ人様のPCに外部から接続なんて、冷静に考えたら厳しいのは当たり前ではあるのですが(; ・`д・´)

NATを越えるための手法はいくつかあって、ルーターで対応してるもの(UPnP)や、connection reversalなどがありますが、
WebRTCではSTUNという仕様で自分の変換後のアドレスを調べ、それを使って行うUDP hole punchingについてちょっと触れてみた。

UDP hole punching

上記Cone型による接続を行う際にファイヤーウォールとかどうやって突破すんねんって疑問が沸くかと思いますが、
それをうまいことするのがこのUDP hole punching。

上記の仕組みを使うことで、NAT外サーバーを経由してアドレス交換(STUN SERVERで自分の変換後のアドレスを確認し、SIGNALING SERVER経由で相手と交換)を行い、NAT内ホスト同士でも直接通信を開始できるようにしている。

で、うちはどのConeやねん

図だけだとほんまかいなという感じなので、うちのNATを実際に調べてみた。

まず、別々のSTUNサーバー2つ(GoogleとCloudflare)に同じソケットからBinding Request(「私のIPとポートをおしえて」)を投げてみると、どちらからも同じ203.0.113.1:23081が返ってきた。接続先が変わっても同じ変換を使い回している=うちはCone系。

203.0.113.1は文書の例示用に予約されているダミーのIPアドレス(RFC 5737)です。実際のグローバルIPをこれに置き換えていて、以降のログやスクショも同様です

で、同じCone系でも「外からのパケットをどこまで通すか」の厳しさは概念として3段階に分かれるのだった。

  • Full cone: 一度穴が開けば誰からでも届く
  • Address-Restricted: 送ったことのあるIPからなら届く
  • Port-Restricted: 送ったことのあるIP:ポートの組からしか届かない

どれなのかをちゃんと判定する仕組み(STUNのCHANGE-REQUEST属性で「返事を別のIP・ポートから送って」とお願いして届くか見る)もあるにはあるが、対応している公開サーバーが少ないのもあって今回は割愛。

というのも、3つをよく見ると「自分が送った相手のIP:ポートからの返事は通す」だけはどれも共通している。つまり両側がお互いに向かって一発ずつ撃ち合えば、どのCone同士でも両側の条件が満たされて穴が開くはず。相手がSymmetric(そもそもSTUNで調べたアドレスが使い回せない)でない限り、判定するまでもなく通せてしまうのがUDP hole punchingの賢いところ。

ほんまに通るんかい

というわけで仕上げに、hole punching本番。相手はモバイル回線にテザリングで繋いだWindows(=別のNATの向こう側)を用意した。

手順は雑で、両方でSTUNに自分の外向きアドレスを聞く→チャットで教え合う(シグナリングサーバーの役を人力でやる)→お互い相手のIP:ポートへUDPを撃ち合うだけ。スクリプトはこちら

1
2
3
4
5
6
7
8
$ node nat-punch.js
[LOCAL] ポート58396のUDPソケットで開始
相手のアドレスを入力(例 203.0.113.1:23081)> [STUN] あなたの外向きアドレス: 203.0.113.1:39464
↑これを相手に伝えてください(相手のアドレスも聞いておく)
198.51.100.7:44308
[PUNCH] 198.51.100.7:443080.5秒間隔で撃ち込みます...
[RECV] 198.51.100.7:44308 からACKが届いた = こちら→相手も通ってる
*** 双方向で貫通。hole punching成功です ***

通りました!(相手側のIPも例示用のダミーに置き換えてます)

これもtcpdumpでキャプチャした。最初のPUNCHを送った0.12秒後に、相手からのパケット(ACK)が届いているのが写っている。穴が開いた瞬間。

一度も直接会話したことがなかった2台が、お互いに一発撃った瞬間に繋がった。フィルタの条件「送った相手のIP:ポートからなら通す」を、両側が同時に満たしに行くのがhole punchingということ。

ちなみに今回チャットで人力コピペしたアドレス交換は、実際のサービスではシグナリングサーバーが全自動でやっている。お互いアプリを開いた時点で自分からサーバーに繋いでおき(自分から外へ繋ぐ分にはNATに邪魔されない)、サーバーはその接続越しに連絡先を取り次ぐだけの電話交換手。交換が済めば、映像や音声は今回と同じくサーバーを経由せずP2Pで直接流れる。

おまけ:Cone系かどうかって誰が決めてるの?

ところで、NATタイプの話で「接続先が変わっても同じ変換を使い回す優しいCone系」と「接続先ごとに変換を変える厳しいSymmetric」という性格の違いが出てきた。あれって誰が決めているのか?

決めているのはルーター。ルーターはネットの出入り口に立っている門番さんで、外から来たパケットをどこまで通すかはこの門番さんの方針次第。法律(規格)で決まっているわけではない。

家のルーターは「ゲームやビデオ通話が繋がらなくなると困るから、あまり厳しくしないでね」という業界のお約束(RFC 4787)もあって、優しめのCone系が多数派。逆に会社の門番(ファイアウォールやキャリアグレードNAT)は、セキュリティや経費の都合で「さっき話した相手以外は全員お断り!」なSymmetricだったりする。

同じPCなのに、家ではP2Pが繋がって会社では繋がらないことがあるのは、PCのせいではなく門番さんの性格の違いというわけ。

用語集(ざっくり)

  • NAT / NAPT: プライベートアドレスとグローバルアドレスを変換する仕組み。NAPTはポート番号まで変換して1つのグローバルIPをみんなで共有するやつで、家のルーターはだいたいこれ
  • グローバルIP / プライベートIP: インターネットで通用する住所 / LANの中でだけ通用する住所
  • 変換エントリ: 「中の192.168.x.x:ポート ⇔ 外の203.0.113.1:ポート」という対応表の1行。門番さんの通行記録
  • Cone系NAT: 接続先が変わっても同じ変換を使い回すタイプ。外からのパケットをどこまで通すかで、Full cone / Address-Restricted / Port-Restrictedの3段階に分かれる
  • Symmetric NAT: 接続先ごとに別の変換を割り当てる一番厳しいタイプ。STUNで調べたアドレスが他の相手に使い回せない
  • STUN: 「あなたは外からこう見えてますよ」と教えてくれる鏡のようなサーバー。教えてくれるだけで、データの中継はしない
  • Binding Request: STUNサーバーへの質問パケット。「私のIPとポート番号をおしえてください」
  • シグナリング: お互いのアドレスなどの接続情報を交換すること。専用サーバーを立てるのが普通だが、今回はチャットのコピペで人力代行した
  • UDP hole punching: 両側から同時に撃ち合うことで、お互いの門番に「この相手は許可済み」と認識させて穴を開ける技。この記事の主役
  • TURN: hole punchingが通用しない相手(Symmetric同士など)のときに、全データを代わりに運んでくれる中継サーバー。帯域を食うので最終手段の保険。今回は出番なし
  • ICE: 直接接続・STUN経由・TURN中継の候補を全部並行で試して、繋がったものを使うWebRTCの総当たり係。この記事で人力でやったことを全自動でやってくれる
  • RFC 4787: 「変換は宛先非依存にすべし(=Cone系にしてね)」という業界のお約束。家庭用ルーターにCone系が多い理由

NATってなんやねん、から始まって、STUNに自分の姿を聞いて、両側から撃ち合って、無事にNATを越えられました。ビデオ会議の裏でも毎回この穴あけが行われていると思うと、なんだか愛着が湧いてきます。それでは安心して引きこもります(*´з`)