UDPでNAT超え

あらすじ

NAT超えという技術を知り、いったいどういうものなのか?を調べて、Goで簡単に実装してみました。

NAT

NATというのはグローバルIPとローカルIPを変換する機構です。グローバルIPの枯渇を防ぐため、ローカルIP以下のデバイスに対して同じグローバルIPを割り振るための仕組み提供します。最近はIPだけではなくポートも変換するNAPTというタイプが主流のようです。

例えば、2台のパソコンがNAT下にぶら下がっていてそれぞれがサーバーにアクセスするような場合は以下のような構成になります。

f:id:bamch0h:20200627184558j:plain

PC1がWEBサーバーにアクセスする時、アクセス要求はNATを経由してインターネットを通じてサーバーに到達します。サーバーは要求を処理し応答を返します。応答はインターネットを経由してNATまで届きます。NATはその応答をPC1に送り、無事に通信ができます。

NATがどのように応答を振り分けるというと、NAT内部にグローバルIPとローカルIPの変換表を持っていてそれをもとに応答を振り分けています。TCP/IPUDP/IPの場合、変換表には以下の情報が記録されます。

PC1がWEBサーバーにアクセスした場合は以下のような情報が変換表に記録されます。

プロトコル 送信元IP 送信元ポート グローバルIP グローバルポート 相手先IP 相手先ポート
TCP 192.168.0.2 49152 1.2.3.4 49152 2.3.4.5 80

サーバー側からの応答は送信先IPと相手先IPが変換表とは逆になって返信されます。NATはそれを考慮して変換表に当てはまるものがあるかを調べ、当てはまるものがあったなら、そのデバイスへ応答を渡します。なかった場合はその時点で破棄されます。

送信元ポートグローバルポートは基本的に同じになります。別々のローカルIPで同じポート番号から通信が来た場合にのみ変換されます。例えば、PC1とPC2が同じ送信元ポートで通信をした場合、ポートを変換せずに変換表に記録した場合、以下のようになります。

プロトコル 送信元IP 送信元ポート グローバルIP グローバルポート 相手先IP 相手先ポート
TCP 192.168.0.2 49152 1.2.3.4 49152 2.3.4.5 80
TCP 192.168.0.3 49152 1.2.3.4 49152 2.3.4.5 80

サーバーからの応答はどちらの要求に対しても 1.2.3.4:49152 に返信されるので、NATは変換表をチェックしたときに1番目にも2番目にも対象のIPがヒットするので応答を振り分けることができません。そこで、以下のようにグローバル側に見せるポートを変換することで、この問題を解決します。

プロトコル 送信元IP 送信元ポート グローバルIP グローバルポート 相手先IP 相手先ポート
TCP 192.168.0.2 49152 1.2.3.4 49152 2.3.4.5 80
TCP 192.168.0.3 49152 1.2.3.4 49153 2.3.4.5 80

こうすることで、PC1の応答は 1.2.3.4:49152 に、PC2の応答は 1.2.3.4:49153 に返されるため、NATは問題なく応答を振り分けることができるというわけです。

ポートマッピング

NATは基本的にグローバル側からアクセスは受け付けません。というのも、グローバル側から唐突に通信が来ても変換表から対応するローカルIPを割り出せないため、すべて破棄されるためです。これだと、ローカルにWEBサーバーを立てても外部に公開できなくて不便ですよね。なので、NATにはポートマッピングという機能があるものがあります。これは、特定のポートにやってきた通信を対応するIPのデバイスに転送する機能です。例えば、以下のようなマッピングテーブルがあったとします。

プロトコル ポート 転送IP
TCP 80 192.168.0.2

この場合、NATにやってきた要求のうち TCPポート80番に来た要求は全て 192.168.0.2 に転送されます。192.168.0.2 にWEBサーバーが起動していれば要求は処理され応答が返ります。

P2P

P2Pとは、サーバーを持たず、クライアント同士が通信する方法のことを指します。サーバー運営コストが少ない、サーバーの処理性能に左右されない等のメリットがあります。ただ、そのままではクライアント間で通信はできないため、古き良きインターネッツ時代にあったP2Pファイル共有ソフトではポート開放(ポートマッピング)を行うことで各ノード間の通信を可能にしていました。一方で、ポート開放はセキュリティリスクが高く、悪意のあるユーザーがそのポートに対してアクセスして悪意のある操作を可能にする可能性があるため、ユーザーとしてはあまりやりたいことではないと思います。そこで出てくるのがNAT超えです。

NAT超え

NAT超えとは、ポート開放せずにP2Pを可能にする技術のことです。クライアントだけではNAT越えは難しいので、STUNサーバーやTURNサーバーを使ってNAT超えを行うのがセオリーのようです。STUNサーバーやTURNサーバーのRFCがあるようです。私はまだ見れていません。。。

UDPでのNAT超え

UDPコネクションレスなので内側からデータを送信するだけでNATはポートを開放します。相手側にはデータは届きませんが応答を待っている状態ですので、相手側からも同じようにデータを送信することで通信が可能となります。ただ、内から外への通信によってポートが自動で開放されるとしても、相手のIPアドレスとポートを知る方法が必要です。

f:id:bamch0h:20200627184529j:plain

IP取得用サーバー

相手のIPアドレスとポートを知るためにサーバーを一つ用意します。そのサーバーに自分のIPアドレスとポートを登録しておき、相手から自由に取得できるようにしておきます。いかに例を示します。

f:id:bamch0h:20200628002505j:plain

  • ① サーバーにPC1のIPを登録
  • ② サーバーにPC2のIPを登録
  • ⓷ PC2のIPを取得
  • ⓸ NATのポート開放のためにPC2に通信する
  • ⑤ PC2に通信したことをサーバーに通知
  • ⑥ サーバーがPC2にPC1が通信したことを通知(合わせてPC1のIPとポートを伝える)
  • ⓻ PC2からPC1に伝えられたポートで通信

このような感じでPC1とPC2の疎通が可能となります。

Goのサンプル

上記を実装したのが以下になります。

クライアント

NAT超え over UDP

サーバー

STUN server over UDP

まとめ

  • UDPでNAT越えをするための理屈を説明しました。
  • Goでのサンプルを実装しました。