NATタイプ、ポートセービングIPマスカレード、UDPホールパンチング、STUN

概要

この記事では、NAT(NAPT)を行う機器の動作タイプの分類、およびそれと密接に関連する話題として、ポートの枯渇を防止するためのいわゆる「ポートセービングIPマスカレード」の手法とUDPホールパンチングについて解説します。

NAT(NAPT)(あるいはIPマスカレード)の概念については、ここでは基礎的な解説はしないので他サイト等を参考にしてください。基本的には、「ルーターだけがインターネットと直接接続し、ルーターの内側にある各機器を代表して通信を行う」「そのためには内側の各機器のアドレス/ポートとルーター自身のアドレス/ポートとの間で書き換えが必要」というイメージがあれば問題ありません。

この記事で扱うようなNATの動作は基本的にポートに関連するものであり、ポートを使用するプロトコルTCPUDP)以外にはあまり影響はありません。

RFC 4787による分類

NATタイプの分類にはいくつかの方式がありますが、この記事では現代の標準的な分類方法を示しているRFC 4787という文書に従います。

RFC 4787では、NAT機器の動作における「Mapping」「Filtering」という要素がそれぞれ「Endpoint Independent」「Address Dependent」「Address and Port Dependent」のどれに分類されるか、という基準でNAT機器を分類します。基本的にはMappingのほうが重要で、Mappingがどれであるかによって大きく3種類、その中でFilteringによる違いもある、という感じになります。

まずはMappingについて見ていきます。

Endpoint Independent Mapping

例えば、ルーターの内側に192.168.1.1と192.168.1.2という2台のPCがあって、ルーターグローバルIPは1.1.1.1としましょう。これらのPCの様々なポート、つまり192.168.1.1:40000とか192.168.1.2:50000とかからインターネットの様々なサイト(google.comとかyahoo.comとか)との間で通信が行われる状況をイメージしてください。

このとき、「NATの内側の機器の(Address, Port)の組」によってのみルーターの使用ポートを変化させるというのがEndpoint IndependentなMappingです。Endpointとはインターネット側の通信相手(行き先)のことです。つまり、LAN側が192.168.1.1:40000であれば、相手がgoogle.comだろうとyahoo.comだろうと関係なく常に1.1.1.1:44444を使用する、というような動作をするのがEndpoint Independent Mappingです。

逆に、LAN側が192.168.1.1:50000とか192.168.1.2:40000とかであれば、それぞれまた別のポート(たとえばそれぞれ44445番と44446番とか)を使用することになります。なぜなら、例えば192.168.1.1:40000と192.168.1.1:50000にどちらも44444番を割り当ててしまうと、例えばgoogle.comから1.1.1.1:44444に何らかの通信(内側からの通信への応答含む)が来たとなった場合に192.168.1.1:40000と192.168.1.1:50000のどちらに転送すればいいかわからないからです。外側から内側への通信の転送先が一意に定まるためには何が必要かを考えるのは以降でも重要なポイントです(外側のアドレスはNATによる書き換えが起こらないので気にする必要はありません)。というわけで結局、Endpoint Independent Mappingでは「NATの内側の機器の(Address, Port)の組」とルーターの使用ポートが(多対一ではなく)一対一に対応するということになります。

"Endpoint Independent Mapping"だと長いので、"EIM"のように略されることもあります。これは他のものについても同じです(APDF=Address and Port Dependent Filteringなど)。EIM-NATやEIM NATなどと呼ぶこともあります。このような呼び方は英語圏でも通じるようです。

RFC 4787より古いRFC 3489という文書では、EIM NATのことをcone NATと呼んでおり、この呼び方も(RFC 4787では非推奨とされているものの)現在でも広く使われています。coneというのは円錐という意味で(トウモロコシ=cornではない)、ルーターの一つのポートから全てのEndpointへと放射状にルートが伸びている様子を円錐に見立てたものです。

Address Dependent Mapping

ADMでは、ルーターのポートを変える基準に「EndpointのAddress」が加わります。つまり、「NATの内側の機器の(Address, Port)の組」および相手のアドレスによってルーターの使用ポートを変化させる、ということです。例えば、192.168.1.1:40000とgoogle.comの間での通信には(google.comのポートが何であっても)44444番を使用するが、192.168.1.1:40000とyahoo.comの間での通信には別のポート、例えば44445番を使用する、といった形になります。

これについても先ほどと同様に外側から内側への転送先が一意に定まるための条件を考えてみます。ADMではEIMと違って「ルーターの各ポートに関して、内側のそれぞれの(Address, Port)がどの相手と通信するためにそれを使用しているのかを特定できる」という特徴があります。つまりは「外側アドレスが異なっていれば、内側にある複数の(Address, Port)がルーターのポートを共有しても問題はない」ということになります。若干わかりづらいですが、ここは後述の「ポートセービングIPマスカレード」を理解するにあたって重要な部分です。

例えば、192.168.1.1:40000がgoogle.comと通信するために1.1.1.1:44444を割り当てたとしたら、44444番を経由して192.168.1.1:40000と通信している相手はgoogle.comだけです。だったら、この44444番ポートを「192.168.1.1:50000yahoo.comの通信」にも使う、となったとしても、google.comから44444番に来た通信は192.168.1.1:40000、yahoo.comから44444番に来た通信は192.168.1.1:50000、という風に一意に転送先を決めることができます。

というか、「転送先が一意に定まるか」を基準に考えると、「内側の(Address Port)が同じでも行き先のアドレスが違えば割り当てポートも変える」という条件は別に必要ではなく、「そのポートを使って同じ行き先と通信している他の(Address, Port)がまだ存在しないようなポートを使用する」というルールだけあればいいはずです。つまり、ポートごとに、「そのポートを使用する【内側機器の(Address, Port)、行き先のAddress】の組のリスト」を持っておいて、そのリスト内で「行き先のAddress」が一意になるようにしておけば、転送先は一意に定まるはずです。例えば、44444番ポートに関して、「192.168.1.1:40000google.comとの通信」「192.168.1.1:50000yahoo.comとの通信」「192.168.1.1:40000microsoft.comとの通信」に使用する、というようなリストがあったとすれば、「192.168.1.1:40000」がgoogle.comとの通信にもyahoo.comとの通信にも44444番が使用されることになりますが、行き先アドレスには重複がないので外側からの通信を一意に転送できます。このやり方はEIMでもADMでも(もちろんAPDMでも)ありません。(ただ、ADMの定義に当てはまらないというだけで、「内側ポートと行き先アドレスに従ったマッピングでNATが管理されている」ということは言えます)

念のためですが、RFC 4787には、「(内側の(Address, Port)と)行き先アドレスが同じ場合に、またそのときに限り、同じポートを使用する」ということがはっきり書いてあります。しかし実際には、この「そのときに限り」は「転送先を一意に定める」ためには必要でもなければ(上記の通り)十分でもない(そもそも内側の(Address, Port)が異なる場合への言及がないから)、ということになります。

ちなみに、ADMの動作をする機器は現実にはあまりないようです。

Address and Port Dependent Mapping

最後に、Address and Port DependentなMappingでは、ポートを変える基準としてEndpointのAddressだけではなくPortも使用します。つまり、192.168.1.1:40000とgoogle.com:80の間での通信には44444番を使用するが、192.168.1.1:40000とgoogle.com:8080の間での通信には44445番を使用する、といった形になります。これについても先ほどと同様に、「行き先アドレスとポート(の組)が異なっていれば、内側にある複数の(Address, Port)がルーターのポートを共有しても問題はない」ということになります。例えば、「192.168.1.1:40000からgoogle.com:80への通信」と「192.168.1.1:50000からgoogle.com:8080への通信」にどちらも44444番を使用したとしても、80番と8080番のどちらから来たかによって40000と50000を決めることができるということです。通信経路を一意に決められるかだけを考えるなら別に行き先が違うからといって別のポートを使う必要は無いというのも先ほどと同じです。

APDM NATは、RFC 3489ではSymmetric NATと呼ばれていました。実はADM NATもSymmetric NATに含まれるようですが、現実に例が少ないようなので気にしなくていいでしょう。

この"symmetric"(対称的)というのは、おそらくルーターのポートを中心として内側と外側が対称に見えるかということだと思います。つまり、cone NATではルーターの1つのポートが内側に関しては1つのポートだけ、外側に関しては全ての通信先とつながっている状態でしたが、symmetric NATでは、内側のポート1つにつき1つ(つまり同数)の通信先とつながっている状態になります。

Connection Dependent Mapping

ポートを使用する主なプロトコルUDPTCPですが、接続の概念が無く生のIP通信に近いUDPに比べると、TCPでは接続の開始・終了の手続きが明確に定まっているので、これに基づいたMappingを考えることもできます。TCPに関するNATの挙動について記述されているRFC 5382 - NAT Behavioral Requirements for TCP 日本語訳によれば、同じ内側(Address, Port)からの発信であっても、新規の接続であれば別の外側ポートを割り当てるNATがあり、これをConnection Dependent Mappingと呼ぶようです。

これはSymmetric NATよりもさらに細かいMappingということになりますが、現実的にはSymmetric NATとしてまとめて扱っておけば問題ないでしょう。

Filteringの違い - EIM NATの場合

次にFilteringについて説明します。

例として、EIM NATの場合について考えてみます。EIMでは内側の機器の(Address, Port)とルーターの使用ポートが1対1に対応します。いま、例えば、192.168.1.1:40000がgoogle.comにアクセスしようとしたため、ルーターが「192.168.1.1:40000には44444番を使う」というようにマッピングしたとしましょう。この状態では、もちろんgoogle.comから1.1.1.1:44444への正当な通信(応答)は通ります。しかし、例えばyahoo.comのような別のところから通信が来たらどうでしょうか?

これを決めるのがFilteringです。Endpoint Independent Filtering (EIF)は、有効な(既にマッピングで使われている)ポートに来た通信はどんなものであっても全て通します。つまり上記のyahoo.comの通信も192.168.1.1:40000に転送されます(ただしもちろん192.168.1.1:40000を使っているアプリケーションがそれを無視・拒絶する可能性はある)。これに対して、Address Dependent Filtering (ADF)は、google.comからの通信のみを内側に通します。さらに、Address and Port Dependent Filtering (APDF)は、通信先のポートも条件に加えます。つまり、内側からgoogle.com:80へと通信を行っていた場合でも、google.com:8080から来た通信は内側には通しません。

EIM NATはcone NATと呼ばれると前述しましたが、EIM/EIFなNATはfull cone NAT(フルコーンNAT)、EIM/ADFなNATは(address-)restricted cone NAT(制限コーンNAT)、EIM/APDFなNATはport-restricted cone NAT(ポート制限コーンNAT) とそれぞれ呼ばれます。

Filteringの違い - 他のMappingの場合

他のMappingについても考え方は同様です。

ポートの共有を行わない場合は、それぞれの(使用中の)ルーターのポートに対応する内側の(Address, Port)が一意に定まるので、EIMと同様にEIF/ADF/APDFの区別が考えられます。

ポートの共有がある場合は少し注意が必要です。異なる宛先アドレスに関してポートを共有する(=ポートのマッピングに宛先アドレスが使用されている)場合は、未知の宛先アドレスから通信が来たとしてもそもそも対応する内側ポートが存在しない(どの(Address,Port)に転送すべきか決定できない)ので、Filtering以前の問題です(実際の動作としては、無視・拒否されることになるはず)。ただ、「既知のAddressの未知のPort」から通信が来たという場合は、それを(そのAddressに対してMappingされている)ポートに通すかどうかという選択肢があるので、それによってADF(通す)とAPDF(通さない)とに分類することはできます。

さらに、宛先アドレスまたはポートが異なればポートが共有されるかもしれない、という仕様であれば、既知の外側(Address, Port)からの通信でないと転送先を決められないので、事実上はAPDFのみということになります。

ポート共有が無かったとしても、例えばADM/APDMでEIFを採用する場合、特定のホストにしか使用しないつもりだったポートに未知の別のホストから通信が来たらその未知のホストとの通信にも使用することになります。内側から別のホストに接続する場合は別のポートを割り当てるのに外側から来た場合は同じポートを使ってもよい(=EIM的)というのも変な話ではあります。実際にこのような動作をする機器は知る限りではありません。

従って、事実上は、「ADMならADF/APDF、APDMならAPDF」という関係が成り立っている可能性が高いではないかと思います。

これは、先ほどのような「ADMやAPDMの定義には当てはまらないが、アドレスあるいはアドレスとポートに従ったマッピングを保持する」ようなNATについても同じです。つまり、「宛先リモートアドレスが違う場合にはポートを共有したい」ならADF(またはAPDF)になるし、「リモートポートだけが違う場合でもポートを共有したい」ならAPDFになります。

Connection Dependent Filtering

接続の概念があるTCPにおいては接続にもとづいたマッピング(Connection-Dependent Mapping)も考えられるということを前述しましたが、Filteringに関しても同様に(既知のアドレス・ポートから来ていたとしても)外部からの接続要求(SYN)は内側に転送しないという動作が考えられ、RFC 5382において「Connection-Dependent Filtering」と呼ばれています。実際、このような動作をするNATシステムはそれなりにありそうです。

NATの種類まとめ

今までの主なNATの分類・呼称をまとめると以下の表のようになります(適宜用語を略している部分があります)。

Address&PortAddressIndependentMappingFiltering(ポート共有無し)(ポート共有無し)(Full Cone)Independent(ポート共有無し)-(Address Restricted Cone)Address--(Port Restricted Cone)Address&Port

この表に含まれないNATの動作はいくらでも考えられます。

「Connection Dependent Mapping (or Filtering)」もその一例です。後述の「ポートの共有を行うようなNATでEIMでもADMでもAPDMでもないもの」や、あるいは「基本的にEIMだが、気が向いたら内側の(Address, Port)が同じでも別のポートを使ってみる」といったような振る舞いをするNATも理論上は可能です。そのようなNATはRFC 4787の用語でいえば非決定的NAT(Non-deterministic NAT)ということになると思います。

また、Mapping/Filteringの動作はプロトコルごとに独立に決めることができます。例えばUDPがEIM/EIF、TCPがAPDM/APDFといったような動作が可能です。UDPTCPの動作は互いに全く干渉しないのが普通だと思います。理論上は、例えばTCPでもUDPでも必ず同じポートを利用する(いわば"Protocol Independent Mapping")ようなものも考えられますが、聞いたことはありません。

その他、奇妙な動作をするNAT

Cloud NAT の Endpoint-independent Mapping とは? | by Seiji Ariga | google-cloud-jp | Mediumこれによると、Google Cloud NATはEIM/EIFのくせに通信先を覚えておいて無理やりポート共有をするという実装になっています。そのせいで、同じ外側ポートを共有している片方のポートが既に通信していた宛先にもう片方も通信しようとするとENDPOINT_INDEPENDENCE_CONFLICTで失敗します(通信が通らない)。こんな実装をしてはいけません。

あと、この資料https://www.janog.gr.jp/meeting/janog30/doc/janog30-v64-pre-stun-ryosato-02.pdfの「ポート多重」のところにあるようなNAT(「最後に送信した方にレスポンスを届ける」)も、本当に存在するのか知りませんが、まともに通信できないのでダメです。

二重NAT(二重ルーター)について

環境によっては、NAT機器の内側に別のNAT機器があってその内側でインターネットを利用することになる場合もあります。このときは、全体としての(内側の端末がインターネットを利用するにあたっての)NATの動作というのは、基本的には2つのうちで厳しい方(Endpoint Independent<Address Dependent<Address and Port Dependent)を継承することになるのではないかと思います(ちゃんと証明してはいませんが)。例えばEIM/APDFとADM/ADFが二重ルーターになっていたらADM/APDFになるはずです。二重NATだとSymmetric NATになるというような説明を見かけることもありますがそれは誤りで、EIM/EIFであるNATが二重になっていたならそれは全体として引き続きEIM/EIFとして機能するはずです。一応、この資料とかはちゃんとそんな感じで書いてありますね。

ファイアウォールの影響

NATではありませんが、内側から出て行った通信への応答だけを通す(いわゆるステートフル)ようなファイアウォールは、(厳密には実装によりますがおそらく基本的には)UDPならAddress and Port Dependent Filtering、TCPならConnection Dependent Filteringとして機能します。つまり、PCの外側がEIM/EIFだったとしてもPC内部からはEIM/APDFのように見えるということになります。

ポートの消費とマッピングタイムアウト

NAT機器は様々なアルゴリズムを使って内側の機器に未使用のポートを割り当てていくわけですが、使用可能なポートの数は有限なので、いつかはポートが全て埋まってしまいます。ポートを割り当てることができなければ、ルーターの外側との新規の通信はできなくなってしまいます(ポートの枯渇)。

このため、どのMappingの手法を採用するにしても、タイムアウト時間が設定されており、一定期間使用されなかったMappingは削除されます。タイムアウト時間は、ルーターの設定やプロトコルによりますが数十秒-数時間程度です。これにより、有限個のポートを使い回しながらインターネットを使い続けられます。タイムアウト時間が短いほうがポートの消費は少なくて済みますが、短いと頻繁にテーブルの更新が必要でオーバーヘッドが大きくなります(多分)。また、時間経過ではなく実際に不足した際にもっとも長く使用されていないものを無効にする(LRU)方式のものもあるようです。

「フィルタリングのタイムアウト」(=過去何分以内に通信した相手なら履歴が残っているか)も存在しそうですが、これがシビアに影響してくる場面はあまりなさそうで、詳しい説明は見たことがありません。単純にマッピングタイムアウトするまで無制限に保持されているという可能性もあるでしょう。

利用可能なポート数とポートの共有

EIMではポートの共有ができず、一度内側の(Address, Port)に対して割り当てられたポートはタイムアウトまでは他の(Address, Port)からは一切利用できません。それに対してADMやAPDMでは宛先が異なればポートの共有が可能(お互いのキャパシティを圧迫しない)なので、特定の宛先への通信が大量に行われなければポートの枯渇は発生しません。

EIMの場合、通常の家庭での使用で消費するポートの数はおそらく100-200程度と思われます(IPoE接続でIPv4の固定IPが利用が使えるプロバイダ | インターリンク【公式】などでは200-300と書いてありますが、実際には200も消費することはあまりないでしょう)。

従来のPPPoE方式のインターネット契約ではグローバルIP1つを専有できるため使用可能なポート数は65535であるため、20人の家族が全員同時に300ポートを使用してやっと枯渇するかどうかというレベルですが、最近増えてきているIPv4 over IPv6 (IPoE)方式のISPでは、MAP-E方式と呼ばれる「v6プラス」(240ポート)、「OCNバーチャルコネクト」(1008ポート)、DS-Lite方式の「transix」「クロスパス」(1024ポート)など、65535ポートより少ないポートしか使えない仕様のものも多くあります。このようなISPを利用する場合は、ルーターのNATがポートの共有を行う仕様であることが望ましいと言えます。

ADMやAPDMであるにもかかわらずポートの共有を行わない場合、新たな宛先と通信するたびに未使用のポートの割り当てが必要なので、むしろEIM以上にポートを消費することになります。ただ、現実にポート消費の多くの割合を占めると思われるHTTPなどでは(クライアントPCの)1つのポートから異なる宛先に通信することは多分ないので、意外と問題ないのかもしれません。

「ポートセービングIPマスカレード」について

お気づきの方もいると思いますが、上級ユーザーの間で人気が高いYAMAHA製のルーターの機能である「ポートセービングIPマスカレード」は、基本的には今まで説明してきたような「ポートの共有」を指します。ポートの共有を指す一般的な用語として「ポートセービングIPマスカレード」(あるいは「ポートセービングNAT(NAPT)」など)が使われることも多くあります。「port saving」で検索してもほとんど何も出てこないので、英語圏ではおそらく通用しないと思います。

YAMAHA製品に関しては、公式のNAT動作タイプの違いについてなどに「宛先のポートが異なっていれば共有されうる」ことが明記されているので、内部的には宛先のアドレスとポートに基づくマッピングで管理している(念のため、これは必ずしも"APDM"の定義とは合致しないことに注意)ことになります。

UDPホールパンチング

ここからUDPホールパンチングの話題になります。

基本的に、インターネット上の2箇所の間で通信を行うには、少なくとも片方(受信側)はグローバルIP上に常時有効なポートを所有していることが求められますが、NATのマッピングによって一時的に割り当てられるポートをそのかわりに使用することも可能です。つまり、条件が合えば、受信側のPCの(Address, Port)に一時的に割り当てられたグローバルIP上のポートを通じて発信側のPCからの通信が受信側に通るようになります。一旦通信が通ってしまえば、定期的にパケットのやりとりがある限りはマッピングが維持されるため、いつまでも通信を継続することができます。これにより、静的なNAT設定(いわゆる「ポートの開放」)を行っていないNAT環境にいる端末同士の間でも直接的な(中継サーバーを使用しない)通信(=P2P通信)が可能になります。

成立のしやすさはNAT機器の動作やプロトコルに応じて様々(後述)ですが、基本的にはUDPのほうが成立しやすく、よく使われます。(基本的には外側からの通信を内側に通さない)NATの動作に「穴」を開けるイメージから、この手法は「UDPホールパンチング」と呼ばれています。

EIM/EIFでのUDPホールパンチングの流れ

今までNAT機器のMappingとFilteringについて説明してきましたが、いずれについても、Endpoint Independent→Address Dependent→Address and Port Dependentと後にいくほど「限られた相手との通信しか許容しない」傾向になるため、UDPホールパンチングを成立させるのがより難しくなります。

まずは最も簡単に成立するEIM/EIFを例に、UDPホールパンチングの流れを説明します。

まず、片方(例えばPC1とします)は、自分の(Address, Port)から(PC2とは異なる)インターネット上のサーバーに対してアクセスを行い、自分の(Address, Port)にどのようなグローバルIPとポートが割り当てられたかをそのサーバーから通知してもらいます(NAT機器が自分の管理下にあるのでなければ、内側から直接それを知るのは困難)。次に、その「グローバルIPとポート」をもう一方(PC2とします)に通知します(この時点では中継サーバーなどを使用)。PC2はその「グローバルIPとポート」にアクセスすると、NATがEIM/EIFであることから、PC2からのパケットはNATで破棄されることなくそのままPC1に到達します。これにより、P2P通信が成立します。いわば、EIM/EIFというのは「一時的にポートを開放できる」ような仕様であるため、きわめて容易にUDPホールパンチングが成立します。片方がEIM/EIFであればよく、もう一方は何でも構いません。

上記のうち「自分に割り当てられたIPとポートを通知してもらう」部分に関してはSTUN(Session Traversal Utilities for NAT)というクライアント-サーバー形式のシンプルなプロトコルがあります。稼働しているSTUNサーバーに対してクライアントがアクセスすると、サーバーはクライアント側のグローバルIPとポートを通知します。STUNは前述の(cone NATなどを定義している)RFC 3489で標準化されましたが、その後RFC 5389で更新され、現在はRFC 8489が最新の仕様となっているようです。STUNとは - 意味をわかりやすく - IT用語辞典 e-Wordsの説明もわかりやすいです。

その他の各NATタイプにおけるUDPホールパンチング

両者がEIM同士であれば、EIFでなくてもUDPホールパンチングは成立します。まず、両者(PC1, PC2とします)がそれぞれSTUNサーバーと通信して自分自身に割り当てられたIPとポートを知り、それをお互いに通知します。次にPC1がPC2のグローバルIPとポートに向かってパケットを送信します(EIMなのでこれは先ほどと同じポートを通じて送信されます)。PC2側がADFまたはAPDFであった場合、PC1のIPは未知なのでこのパケットは破棄されますが、PC1側のほうではPC2側へのパケットの送信履歴が残ります。次にこんどはPC2側からPC1側にパケットを送信(これもやはり同じポートが使用される)すると、PC1側のNATには既にPC2側に送信した履歴があるためにPC2は既知の通信先と判断され(APDFを通過し)、PC1に到達します。これでUDPホールパンチングが成立します。

あと残っているのは、片方がEIMであるもののEIFでなく、もう片方はEIMでないという場合です。

まず、片方(PC1)がEIM/ADF、もう一方(PC2)がADMまたはAPDMという場合ですが、このときはUDPホールパンチングが成立します。この場合、まずPC1とPC2がSTUNで自分のグローバルIPとポートを取得し、次にPC1がPC2側のポートにパケットを送信し、破棄されます。ここまでは先ほど完全に同じです。次にPC2がPC1側のポートに向かってパケットを送信しますが、PC2側はEIMでないため、これはSTUNで取得したものとは別のポートを通じてPC1に送信されます。しかし、PC1側はADFなので、既知の送信先とアドレスさえ一致していれば、ポートが未知でも通信を通します。これでUDPホールパンチングが成立します。

PC2側の環境によっては割り当てアドレスも変化するかもしれませんが、それほど大量のIPアドレスが選ばれうる環境は多くないと思うので、数回試行すればUDPホールパンチングは成立する可能性が高いでしょう。一般に、P2P通信ではメインの通信のスループットが重要であり、P2P通信が開始するまでに多少の遅延が生じることは許容できる場合が多いと思います。

次に、PC1がADFではなくAPDFだった場合は、途中まで同じですが、PC2からのパケットが来たときに、PC1のNATがAPDFなので未知のポートからの通信を破棄してしまいます。従ってUDPホールパンチングを成立させるのは困難です。

また、両方ともADMまたはAPDMの場合は、お互いに相手との通信の際に割り当てられるポートがわからないため、(フィルタリングによらず)UDPホールパンチングは困難です。

まとめるとだいたい以下の表のようになります。

SymmetricPort RestrictedConeRestrictedConeFull ConeFull ConeRestricted Cone×Port RestrictedCone××Symmetric

ポート番号の確率的な予測

上記のように、ADM/APDMではSTUNを使ったとしても実際の相手との通信に使用されるポート番号がわからないためUDPホールパンチングが困難ですが、異なるポート番号が使用されるといっても実際には何らかの規則性が存在する場合も多くあります。よくあるのは、宛先が変わると以前割り当てたポートより一つ大きい番号のポートを使用するというようなもので、この場合であれば、STUNでポート番号を取得した直後に一つ大きい番号のところに向かって他方のPCからパケットを送ればホールパンチングが成功します。P2Pソフトウェアではこのような動作が実装されているものも多くあります。

ものによっては宛先ごとに完全にランダムなポートが割り当てられるNATもあり、こうなるとホールパンチングは確率的にも非常に困難になります。

TCPホールパンチングはできるのか?

STUNプロトコル自体はTCPでもUDPでも使用可能で、その他の部分に関してもTCPUDPで特に差は無いため、TCPにおいてホールパンチングが成立する条件は(Mapping/Filteringが同じであれば)実はUDPと変わりません。しかし実際にはLinuxのnetfilterなどはConnection Dependent Filteringを採用しています。例えば両者がEIM/Connection Dependent Filteringである場合を考えると、外側からの新規の接続は基本的に全てブロックされてしまうため、タイミングを揃えて両側からSYNパケットを送信するような困難で非効率な手法が必要になります(参考: TCPでホールパンチングしてみるなど)。

このような事情があるために、ホールパンチングはほとんどUDPで行われます。

NATとセキュリティ

NAT(NAPT)はセキュリティのための機能ではないとよく言われますが、同時に(一部の)NATは実際にそれなりのセキュリティ効果があるというのも事実です。

NATタイプの中では、Mapping/Filteringの制約が強いものほどセキュリティ効果も高くなります。EIM/EIFでは割り当てられたポートは全世界からのアクセスを内側に通すようになる(「一時的にポートが開放された」状態になる)ので、セキュリティとしてはあまり高くありません。一方、EIMであってもADFあるいはAPDFなら特定の相手との通信しか通さなくなります。ADMとAPDMも同様です。TCPの場合、Connection Dependentなら一段とセキュリティは高くなります。

過去記事のDROP vs REJECT論争、そしてWindowsとLinuxのファイアウォールの動作の違いについて - turgenev’s blogでも書いた通り、TCPは開いているポートを隠蔽することができず、またsshなど重要なサービスが実行されていることが多いので、厳しめのNATにしておくのがいいかもしれません。

また、NATタイプによらず、外側に出て行ったのと同じポートを即座に別のアプリケーションで使用するようなことをするとアクセスが通る可能性があり危険なので、一般論としてポートを使用するアプリケーションではエフェメラルポートとして使用されない29999番以下程度のポートを使用するのが安全ではないかと思います。

STUNによるNATタイプの判定

STUNでは、PCのポートに対して割り当てられたグローバルIPとポートを知ることができるので、これを使ってNATタイプを判定することができます。つまり、host1:port1とhost1:port2とhost2:port3という3か所でSTUNサーバーが稼働していれば、クライアントはそれぞれにアクセスして自らに割り当てられたポートを知ることで、マッピングがポートとアドレスに応じてそれぞれ変化するかどうかを知ることができます。さらに、host1:port1へのアクセス時に割り当てられたポートに向かってhost1:port2とhost2:port3からそれぞれパケットを送信してもらい、それが届くかどうかによってフィルタリングがポートとアドレスそれぞれに基づいているかどうか判断できます。

事前にhost1:port1とhost1:port2とhost2:port3を全て把握しておくのでももちろんいいですが、サーバーが対応していれば、自らのポートやアドレスを変更したSTUNサーバーの情報を"Other Address"として通知してくれるのでこれを用いることもできます。

若干雑な説明でしたが伝わったでしょうか。詳細はゲームでよくある「NATタイプ」はどう判定しているの?接続 | 好奇心旺盛な人のためのWebRTCなどにあります。

利用可能なSTUNのクライアント・サーバー

STUNのクライアントとしてはStuntman - open source STUN serverのstunclient(Windows/Linux/macOS)が利用可能です。(サーバーがOther Addressに対応していれば)--mode behaviorと--mode filteringでそれぞれmappingとfilteringの情報が出力されます。ただしTCPに関しては動作が不自然で、NATタイプを調べるにはPC側のポートを固定する必要があるにもかかわらずサーバーごとに異なるポートを使用してしまっているようなので--localportを指定する必要があります。これで--mode behaviorではちゃんと動くのですが--mode filteringではエラーになってしまいます(かといって指定しないと結果がおかしくなる)。手動でパケット送ったりサーバー立てたりすれば一応調べられるのですが面倒なので、ちょっとしたテストツールを作ってみました: GitHub - ge9/tcp-nat-filtering-discovery。ちゃんと2つのIPからテストパケットを送って調べてくれます。

Androidアプリは現在利用不可のものしか見つけられませんでしたが、stunclientはtermuxで動作しました。ビルドにあたって、common/commonincludes.hppの"sys/termios.h"を"termios.h"に書き換え、pkg installでboost-headersとbinutilsを入れる必要がありました(書いてから気づいたけど、普通にpkg install stuntmanでインストールできた…(openssl-toolも必要))。

あとは、リモートアクセスチェックツール|DiXiM.NETというアプリも簡単な判定を行ってくれます。Android/iOS版があります。ただEIM/ADFとEIM/APDFが区別されておらず(この2つがまとめてタイプ4)、IPアドレスやポートなどの詳細も出ないのであまりおすすめはしません。

上記のStuntmanはサーバーソフトウェア(stunserver)も入っています。そのほかGitHub - coturn/coturn: coturn TURN server projectなどもstunサーバー/クライアントとして機能しそうです。ちなみにTURNというのはSTUNを用いたUDPホールパンチングが成立しないときによく使われるリレーサーバーのプロトコルです。

STUN自体は相手のポートとアドレスを通知するだけの軽量なプロトコルなので、無料で利用可能なサーバーもあります。STUN server list · GitHubに色々と載っています。

ただし、TCPに対応しているものは少なく、見つけられた中でTCPでも使えるのは

stunserver.stunprotocol.org(Stuntmanのサイトに載っている)
stun.f.haeder.net(個人運営っぽい)
freestun.net

の3つでした。UDPでは上記に加えて、

stun.l.google.com:19302
stun1.l.google.com:19302
stun2.l.google.com:19302
stun3.l.google.com:19302
stun4.l.google.com:19302
stun.ekiga.net
stun.schlund.de
stun.voipbuster.com
stun.voipstunt.com
stun.xten.com

が利用可能でした(ポート番号の記載がないものは全てSTUNプロトコルのデフォルトの3478番)。このうち太字になっているものでは"Other Address"に対応しているのでstunclientのbehavior/filteringによるテストができました。

前述の通り、stunclientの不具合(?)によりTCPのfilteringについてはSTUNでのテストの方法がよくわからないので、手元ではかわりにnetcatなどを用いて手動で疎通確認をしていました。

またSTUNではありませんがIP確認のためによく使われるアクセス情報【使用中のIPアドレス確認】ではクライアント側のポートも表示されます。ただしPC側のポートを指定してHTTPリクエストを投げるのは結構面倒なのであまり使う場面は無さそうです。

前述しましたがfilteringについて調べるときはファイアウォールを適宜無効にする(Linuxなら一部ポートを開ける、Windowsなら実行ファイルに対して許可設定をする)のを忘れないようにしましょう。

PS4Nintendo SwitchでのNATの呼称

PS4Nintendo Switchなどの対戦ゲーム機ではUDPホールパンチングが使われており、通信の成立条件の目安としてゲーム機で「NATタイプ」を表示する機能があります。

Nintendo Switchでは「NATタイプA/B/C/D/F」と分類されており、それぞれフルコーンNATまたは制限コーンNAT、ポート制限コーンNAT、シンメトリックNATのうちポートが連番で割り当てられるもの、シンメトリックNATのうちポート番号の予測が困難なもの、UDP通信ができない環境(NATの問題ではない)、に対応するようです。(参考: Nintendo Switch の「NATタイプ」判定条件 #Network - Qiita

他にはPS4/PS5やXboxなどにNATタイプの概念がありますが、情報が少なくあまりよくわかっていません。特にEIM/ADF(制限コーンNAT)は実機がほぼ流通していなことから情報がほとんどありません。

PS4/PS5では「NATタイプ1/2/3」と分けられており、フルコーンNATは1、ポート制限コーンNATは2、シンメトリックNATは3になるようです。

Xboxでは「オープン/モデレート/ストリクト」に分かれており、フルコーンNATはオープン、ポート制限コーンNATはモデレート、シンメトリックNATはストリクトになるようです。

実際のNAT機器の動作

Linuxのnetfilterのconnection trackingとNAT動作の仕組み - turgenev’s blogに書いた通り、Linuxは普通に設定するとUDPがEIM/APDF(ポート制限コーンNAT)、TCPがEIM/Connection Dependent Filteringになります。

Windowsの「モバイルホットスポット」はUDPがEIM/EIF、TCPがAPDM/Connection Dependent Filteringでした。試せなかったのですがおそらく「インターネット接続の共有」もそうでしょう。

AndroidLinuxなのでAndroidテザリングLinuxと同様になります。iPhoneは持っていないのでわかりませんが「iPhone テザリング NAT」で調べるとNATタイプAになったという情報が結構出てくるのでUDPはEIM/EIFなのではないかと思います。

家庭用ルーターに関しては、カタログスペックには載らない部分なので情報は少ないのですが、低価格帯のものはたいていLinux搭載なので同じくPort Restricted Coneのものが多いのと思います。少なくとも自宅のRX-600KI(NTTのHGW)、WSR-1166DHPL2(Buffalo Aterm  WX1500HP(NEC)、WRC-1167FS-B(ELECOM)はそうです。ネットで調べるとBuffaloのWSR-5400AX6とかWSR-1800AX4とかはNATタイプがCになる(Symmetric)という情報を見かけます(ファームウェアアップデートで治る場合もあるらしい)。

他に情報としては以下のようなものがあります。

家庭用ルーターのNAT特性

プロバイダによるCG-NATなど業務用のものではフルコーンNATも多くあります。例えばdocomoスマホ回線やDS-Lite(transix)はTCP/UDPともにフルコーンNATです(TCPホールパンチングも成功します)。d-WiFiは0001docomoがフルコーンで0000docomoがシンメトリックです。

まとめ

NATタイプとかUDPホールパンチングに関して大体のことはまとめられたかと思います。質問なども歓迎しております。