3. NAT越えの技術
==============================================================
独自マークアップの定義
[*image*] : 画像の希望
[*table*] : 表の希望('|'が列区切り)
[*footnote*] : 脚注
[*refer*] : 別章への参照(岩田さんの記事に依存)
[*backnum*] : 過去の号に記事があれば参照を追加してほしい箇所
==============================================================
構成
- P2PとNAT越えの技術
- NATの動作
- TCPによるNAT越え技術
- UDPによるNAT越え技術
- NATの今後
==============================================================
* P2PとNAT越えの技術
NAT の持つ非対称性は、対称的なノードが協調動作を行うP2Pと相性が悪い。NATの問題点はいくつかあるが、この章では、NATルータの外部ノードから NATルータの内部のノードにセッションを開始できない問題に話を限定する。この問題を解決するNAT越えの技術を紹介する。
* NATの動作
NAT越えの技術を理解するには、NATの動作を理解する必要がある([*footnote*]本文書は、ポート変換を含むNAT(いわゆるNAPT)に話を限定する)。
本章の説明は、TCP/IPパケットのヘッダがいつどこで書き変わるかと、どこがどんな状態を持つか、の2つに視点を絞る([*footnote*]慣習に従い、TCP/IPと書いた時は、意味的にUDPを暗黙に含んでいると考えて欲しい)。
前章([*refer*]P2P探索技術)から続けて読んでいる人は、本章で見ているネットワーク層が前章と全く異なることに注意して欲しい。前章の焦点はアプリケーション層で、本章はTCP/IP層の話だ。アドレスや経路と言った同じ用語がでてきても、意味は異なる。特に断らない限り、パケット、アドレス、ルータ、転送、経路等の用語は、全てIP層の用語を表すとする。
対照および表記に慣れるために、NATルータが無いホスト間のIPパケットのやりとりを見てみる([*image*]pic1)。双方グローバルIPを持つホストa(Ha)とホストb(Hb)の間のパケットのやりとりだ。
TCP/IP のパケットを、IPヘッダのfromアドレスとtoアドレス、TCPヘッダのfromポートとtoポートの4つの値だけで表している ([*footnote*]TCP/IPの教科書では、fromとtoの代わりにsourceとdestinationがよく使われる。意味は同じなので適宜読み替えて欲しい)。
気をつけておきたい点がふたつある。
ひとつは、パケットがルータ間を経由する間、IPヘッダとTCPヘッダの4 つの値は書き変わらない点だ([*footnote*]ルータが書き換えるのはひとつ下の層、リンク層(Ethernetなど)のヘッダの値だ)。これは、TCP/IPヘッダの書き換えに注目している限り、パケットがルータを経由することに特別の注意を払う必要が無いことを意味する。このため、今後の図では、ルータを経由する部分を雲の形で表すことにする。
もうひとつは、HaからHbの間に、何か目に見えない通り道のようなものを想定してはいけない点だ。言葉を変えると、それぞれのルータは、特定のパケットの経由を記憶したりはしない。HaとHbの間にTCPのセッションを張る場合を考えてみる。HaとHbの間のルータ上をパケットが行き来する。しかし、このセッション状態を保持しているのはHaとHbのみで、途中のルータでは無い。ルータがやることは、IPヘッダのtoアドレスを見て、個々のパケットを転送することだけだ。NATの特異性を考える上で、ルータが個々のパケットに関する状態を持たない点は重要なので、喚起しておく。
NATルータが現われることでどう変わるか見てみよう。
HaがNATルータ内部のプライベートIPのホストだとする([*image*]pic2)。
Ha とHbの間にTCPのセッションを張る場合を考えてみる。後述するように、TCPセッションを開始できるのは、Haからのみだ。NATルータは、Haから出たパケットのfromアドレスとfromポートを書き換える。fromアドレスはNATルータの外部(グローバル)のIPアドレスになる ([*footnote*]以後、NATルータのIPアドレスと言った場合、暗黙にNATルータの外部のグローバルIPアドレスを指す)。fromポートは、NATルータが任意の空いているポートを選んで書き換える。こうして、Hbには、NATルータが出したように見えるパケットが届く。
Haと Hbの間にTCPセッションを確立するためには、この後、Hbから出るパケットがHaに届く必要がある。NATルータの仕事は、Hbから届くパケットの toアドレスとtoポートをHa宛に書き換えることだ。これは、最初のfrom部分の書き換えとは事情が異なる。なぜなら、NATルータは状態を持つ必要があるからだ。NATルータが持つべき状態をマッピングテーブルと呼ぶ。最小限のマッピングテーブルは、HaのIPアドレス、Haのポート番号、from アドレスの書き換えの時に選んだ外側ポート番号(NATルータ)の3つだ。この3つ組を記憶しておくと、NATルータは正しくアドレス変換をできる。 NATルータの内側に複数のホストがあっても問題は無い。
* TCPによるNAT越え技術
Hbから、NATルータの内側のHaにTCPセッションを開始できるか考えてみよう。
Hb は、HaのIPアドレスとポート番号、Haの外側のNATルータのIPアドレスを知っているとする([*footnote*]もちろん、これらの情報を知る手段は自明では無い。探索技術の領域なので、方法は省略する)。Hbの出すべきパケットのtoアドレスとtoポートは何になるだろう。toアドレスは NATルータのアドレスにするしかない。HaのIPアドレスはプライベートIPなので、インターネットのルータを経由して相手に届くことは無いからだ。 toポートはどうだろう。他に選択肢が無いので、Haのポート番号にしてみる。このパケットはNATルータまでは届く。しかし、NATルータのマッピングテーブルにエントリが無いので、Haに届くことは無い。ここまでは当然の話だろう。
マッピングテーブルをあらかじめ設定しておくことで、Hbから HaにTCPセッションを開始できそうに思うかもしれない。この勘は正しい。NATルータの外側のポート番号を決めておき、マッピングテーブルにあらかじめ設定しておく。例えば、ポート番号2000を選んだとする。マッピングテーブルに、HaのIPアドレス、Haのポート番号(任意)、NATルータのポート番号2000の3つ組を設定する。HbがHaとTCPセッションを開始するには、toアドレスをNATのIPアドレス、toポートを2000にすれば良い。このパケットはNATルータを経由して、Haまで届く。このように、マッピングテーブルをあらかじめ設定しておくことは、NATルータの静的ポートフォワーディングと呼ばれる。
残念ながら、静的ポートフォワーディングをP2Pアプリに利用できる環境は限定される。多人数でNATルータを使っている場合は難しいだろう。NATルータのマッピングテーブルを設定するのは一般にNATルータの管理者の仕事だ。NATルータの内側のホストの数が多い場合、誰かがP2Pアプリを動かすたびに、NATルータの設定を行うのは非現実的だ。静的ポートフォワーディングをP2Pアプリに利用できるのは、NATルータの管理者と内部のホスト(PC)の利用者が同じ、家のネットワーク環境ぐらいだろう。
これに対する解決策として、 UPnP(Universal Plug and Play)を使ってNATルータのマッピングテーブルを書き換える方法がある。一般に、UPnP NAT Traversalと呼ばれる技術だ。UPnPは、ネットワーク上(主にLAN環境)でデバイスを発見して制御するための通信プロトコルだ。内部ホスト HaがUPnPのコントローラとなり、内部からNATルータのマッピングテーブルを書き換えることができる。動作には、NATルータがUPnPに対応する必要がある。HbからHaにセッションを開始する動作原理は静的ポートフォワーディングと同じだ。
ポートフォワーディング以外の方法も紹介しよう。Napster以降、多くのP2Pアプリで利用されている技術に、Connection reversal(逆向き接続のセッション開始)と呼ばれる技術がある。HbからHaにTCPセッションを開始できないことは既に述べた。逆に、Haから HbへのTCPセッションの開始は普通にできることを既に述べた。これを利用して、アプリケーション層でHbからHaにセッションを開始したい場合に、 HaからHbにTCPセッションを開始させる方法がConnection reversalだ。一度、TCPセッションを開始できれば、パケットは双方向にやりとりできる。アプリケーション層から見れば、TCPセッションをどちらが開始したかは重要では無い。
ここまで、HaがNATルータの内部で、HbがグローバルIPを持ったホストを前提に話してきた。ここで、Hbが(別の)NATルータ内部のプライベートIPの場合を考えよう([*image*] pic3)。この場合、上に挙げた方法は全て行き詰まる。
Ha とHbの双方が(それぞれ別の)NATルータ内部の場合、グローバルIPの中継ノードを介する方法が考えられる。これはもちろん動作する。またどんな環境でも動作する、唯一の現実的な解でもある。中継ノードの利用は、NAT越えの技術として、特別目新しいものではないので、ここでは説明を省略する。
* UDPによるNAT越え技術
NATルータの内部のホスト間でUDPパケットを双方向に投げる技術が、UDP hole punchingとして知られている。
UDP hole punchingのスペック例には、STUN(rfc3489)やTeredo(IETF draft)がある([*footnote*]STUNはSimple Traversal of UDP Through NATの略)。
雰囲気をつかむために、UDP hole punchingの基本アイディアを説明してみる。
図pic2 で、HaからHbにパケットを投げると、NATルータのマッピングテーブルに状態ができることは既に説明した。マッピングテーブルには、HaのIPアドレス、Haのポート番号、NATルータの外側ポート番号の3つ組が記憶された。(NATルータが任意に選択した)外側ポート番号を"NAT-P1"と呼ぶことにする。ここで、NATルータの外部に、Hbとは別のホストHcを想定してみる。Hcから出たパケットのtoアドレスがNATルータのIPアドレス、 toポートが"NAT-P1"の場合、このパケットはどうなるだろう。今、NATルータは3つ組(Ha-IP, Ha-Port, NAT-Port)しか状態を持っていない。このため、パケットは、Haに到達できてしまう([*image*]pic4)。
パケットがTCPのパケットであれば、NATを通過しても、Haはパケットを破棄する。なぜなら、TCPのセッションは、ローカルのIPアドレスとポート、リモートのアドレスとポートの4つ組でセッションを確立しているからだ。このため、HaはHcからのTCPパケットを処理しない。しかし、パケットがUDPのパケットの場合は事情が異なる。UDPにはこのようなセッション状態が無い。このため、HaはHcからのUDPパケットを処理できる(fromアドレスが異なるので、 HbからのUDPパケットとHcからのUDPパケットは区別できる)。
グローバルIPのHcから、NATルータ内部のHaにUDPパケットを投げることが可能なことが分かった。
次に、Hcが別のNATルータの内部にいる場合を考えてみる。同じような仕組み(詳細は後述)で、HaがHcにUDPパケットを投げることが可能になる。これで、双方がNATルータの内部にいるホスト間で、双方向にUDPパケットを投げることが可能になった。
UDP は信頼性を保証しないので、これだけでは、アプリケーション層が求めるセッション的な仕組みは無い。つまり、アプリケーション層から書き込んだデータは、相手ホストに届く保証も無いし、到達する順序の保証も無い。これらのセッション管理はUDPの上に作りこむ必要がある。これはUDP hole punchingの範囲外なので、本記事では省略する([*footnote*]最低限のセッション管理に必要なのは、パケットの到達確認と受信データの順序の保証だろう。再送制御や輻輳制御もあると望ましいが、やりすぎると、UDP上でTCPの再発明をする羽目になる)。
Hbと通信しただけで、突然、見知らぬHcからのパケットを通すのは、単なるセキュリティホールでは無いかと思うかもしれない。世の中のNATルータが、全てこのような動作をするわけではない。素直に考えれば、マッピングテーブルにHbの情報を持つべきであることが分かる。
マッピングテーブルのタイプで、NATルータは表のように分類できる。
[*table*] NATルータの分類
|名称|マッピングテーブルのタイプ(pic2でHbにパケットを送った後)|
|full cone NAT|Ha-IP, Ha-Port, NAT-Port|
|Restricted Cone NAT|Ha-IP, Ha-Port, NAT-Port, Hb-IP|
|Port-Restricted Cone NAT|Ha-IP, Ha-Port, NAT-Port, Hb-IP, Hb-Port|
|symmetric NAT|Ha-IP, Ha-Port, NAT-Port(*共用しない), Hb-IP, Hb-Port|
symmetric NATは、NAT-Portを共用しない点がCone NATと異なる。
Haから、同じfromアドレスとfromポートを使い、HbとHcそれぞれにパケットを送った後のマッピングテーブルは次のようになる。
[*table*] Ha側のNATルータのマッピングテーブルの例
|Ha-IP, Ha-Port, NAT-Port[1], Hb-IP, Hb-Port|
|Ha-IP, Ha-Port, NAT-Port[2], Hc-IP, Hc-Port|
(Port-Restricted)Cone NATではNAT-Port[1]とNAT-Port[2]に同じ値を使う。一方、symmetric NATは異なる値を使う。後述するように、UDP hole punchingは、このCone NATの性質を利用する。このため、symmetric NATでは動作しない([*footnote*]symmetric NATで動作させるために、いくつかトリッキーな方法は提案されている。しかし、現実的では無いようだ)。
STUNを例に、UDP hole punchingの具体的な動作を簡単に説明する。NATルータは、Port-Restricted Cone NATタイプを仮定する。STUNは、ユーザ認証やNATルータのタイプの判別方法を規定しているが、本記事はそこを省略して、NATルータを越えてアプリケーション層のセッションを確立できる部分に話を限定する。
STUNはグローバルIPを持ったSTUNサーバを前提とする。
異なるNATルータの内部にある、2つのホストHaとHbを考え、HaとHbの間にアプリケーション層のセッションを確立してみる。
Ha からSTUNサーバにUDPパケットを投げると、STUNサーバは受信パケットのfromアドレスとfromポートをUDPパケットのペイロードに書いて、レスポンスを返す([*image*]pic5)。Haはこのパケットを受け取ると、NATルータの外部アドレスとポートの組が分かる。Haで動作するP2Pアプリは、この情報をローカルノードのアドレスとして扱い、P2Pアプリの層でこのアドレスを外部ノードに伝える([*refer*]P2P探索技術)。
Hbも同様のやりとりをSTUNサーバと行う([*footnote*]STUNが規定しているのはここまでで、これ以降はSTUNの応用例になる)。
ふたつのNATルータの、マッピングテーブルを表に示す。
[*table*]
NAT-aのマッピングテーブル
|Ha-IP, Ha-Port, NAT-a-Port, STUN-IP, STUN-Port|
NAT-bのマッピングテーブル
|Hb-IP, Hb-Port, NAT-b-Port, STUN-IP, STUN-Port|
Ha側のNATルータのIPアドレスをNAT-a-IP、Hb側のNATルータのIPアドレスをNAT-b-IPと表記する。
HaとHbは、P2Pアプリケーション層で、相手のNATルータの外部アドレスとSTUNサーバに対して使われた外部ポート番号を知ったとする。つまり、HaはNAT-b-IPとNAT-b-Portを知り、HbはNAT-a-IPとNAT-a-Portを知ったとする。
Ha から、toアドレスがNAT-b-IP、toポートがNAT-b-Portのパケットを投げる([*image*]pic6)。このパケットはNAT-b で破棄される。なぜなら、NAT-bルータはPort-Restricted Cone NATタイプであり、NAT-b-Port宛のパケットは、fromアドレスがSTUN-IP、fromポートがSTUN-Portであることを求めるからだ。しかし、この時、NAT-aルータのマッピングテーブルには、図pic6のようにエントリが追加される。1行目は、STUNサーバ相手のエントリを残している(もう使わない)。2行目はNAT-bルータ宛に投げたパケットで追加された行だ。ふたつの行の3列目、NAT-aルータの外側のポート番号が同じであることに注意しよう。symmetric NATの場合、このポート番号は一致しない。
次に、Hbから、toアドレスがNAT-a- IP、toポートがNAT-a-Portのパケットを投げる([*image*]pic7)。このパケットは、NAT-aルータのマッピングテーブルに合致するので、Haに到達する。この時、NAT-bのマッピングテーブルに、図のようにエントリが追加される。このエントリは、逆に、HaからHbまでのパケットを通過させることが分かるだろう。
これでHaとHbが、双方向にUDPパケットを投げられるようになった。既に述べたように、これだけでは、アプリケーション層から見てセッションを確立したとは言いがたい。UDP上でのセッション的な仕組みの構築は、実装の問題なので、説明は省略する。
以上が、UDP hole punchingの動作の概要だ。
* NATの今後
NAT の存在は複雑な状況だ。本誌の読者であれば、NAT不要論を聞いたことがあるかもしれない。一方、アドレス変換機能によるアドレス空間の拡張は正しい、と主張する向きもある。NAT自体がIPv4の延命技術なのに、更にNATを延命させるようなNAT越え技術とはけしからん、と思う人もいるだろう。この辺りは、各人が議論をして、考えを巡らせてもらいたいと思う。([*footnote*]筆者自身はNAT不要論者だ。本記事は主観にとらわれず、技術の解説に徹したことを記しておきたい。)