NAT越えでマイクラサーバ接続実験 [STUN]
はじめに
Minecraftで友人と遊ぶためにサーバを立てたことがあるという人は多いのではないでしょうか。最近は自分でやらなくても代わりに設定含めてサーバを立ててくれるサービスもあるようですが、かつて自宅でサーバを立てるためにポート開放するのに悪戦苦闘した方もいると思います。
この記事では、STUNサーバを利用して手動でP2P通信を確立することで、ポート開放不要、外部サーバの中継不要で、自宅のサーバに外部からアクセスする方法について、友人とのMinecraftプレイを例に実験します。
NAPT
NAPT (Network Address Port Translation) もしくはNATは、1つのグローバルIPアドレスを用いて家にある複数の機器がインターネットに対して通信できるようにするために利用されます。IPアドレス(IPv4)は32bitであるため2^32個しかなく、世界中の全ての機器に割り当てることはできません。NAPTを利用することで、1つのグローバルIPアドレスでもポート番号を変えることで複数の機器を区別でき、通信できるようにします。
NAPTはLAN側のIPアドレス・ポート番号の組をWAN側のグローバルIPアドレス・ポート番号の組に書き換えます。
例えば下の画像のように、PCでWebページを閲覧する場合を考えます。①②でサーバにリクエストが送られ、③④でPCにデータが送られますが、赤字で書かれている部分がルータを通過することで書き換わっていることが分かります。
さきほどは身近なWeb閲覧を例にしましたが、Web通信(HTTPS)はTCP上のTLS上で行われており、TCPだと通信の開始時にハンドシェイクがあり話が分かりづらくなるので、以後UDPの話をします。また、この記事ではデータグラムのこともパケットと呼びます。
ルータではアドレスの変換だけでなく、インターネット(WAN側)から来るパケットのフィルタリングも行います。このNAPTとフィルタリングの方式は数種類あるようですが、私の環境の場合は一度送ったことのあるIPアドレス・ポートの組からのパケットしかLANに中継しないようになっていました。送ったことのないIPアドレス・ポートから来たパケットは破棄されます(Port restrictedタイプ)。
また、異なる宛先に送っても同じアドレス・ポート変換を行う仕様となっていました(Cone型)。この仕様を利用していきます。
STUN
STUN (Session Traversal Utilities for NAT) は、NATトラバーサルを扱う他のプロトコルのツールとなるプロトコルです。具体的には自身のパケットがNATを通ってアドレス・ポート変換された後のアドレス・ポートを知ることができます。STUNサーバに問い合わせを行い、その応答にアドレス・ポートの情報が載っているという流れになります。例えば、GoogleのSTUNサーバに対して問い合わせを行い、先ほどの図と同じアドレス変換が行われた場合、下図のようになります。
さて、このSTUNサーバとCone型Port restrictedのNAPTの仕様をうまく利用すると以下の手順を踏むことでNAPTの外からのパケットを受信できます。
STUNサーバに問い合わせる。
STUNサーバの応答から自身のNAPT通過後のIPアドレス・ポート(例えば198.51.100.20:39311)を知る。
何らかの手段で通信相手に自分の外から見たIPアドレス・ポート(198.51.100.20:39311)を教える。
通信相手のIPアドレス・ポート(例えば203.0.113.125:28411)に対してUDPパケットを送信する。これによりルータのフィルタリングに通信相手を追加し、次で受信するパケットを破棄されないようにします。
通信相手から自分のIPアドレス・ポート(198.51.100.20:39311)にUDPパケットを送ることができる。
上の例では通信相手にはNAPTが無く、IPアドレス・ポートを知っているという前提となっていますが、通常のP2Pの場合、通信相手にもNAPTがあり、相手のIPアドレス・ポートも知らないということが普通です。したがって、以下の手順でP2P通信を確立します。両者ともにCone型のNAPTであるとします。
通信したい二人が各々STUNサーバに問い合わせる
各々自身のNAPT通過後のIPアドレス・ポートを知る。(例えば、左のPCが198.51.100.20:39311、右のPCが198.51.100.30:26200)
何らかの手段で互いに自身のIPアドレス・ポートを教える。
まずどちらかが他方へパケットを送信する(図では左のPCが198.51.100.30:26200に送信)。このパケットは通信相手側のルータのフィルタリングによって破棄される。また、自分側のルータには通信相手(198.51.100.30:26200)がフィルタリングに追加され、相手からのパケットが受信できるようになる。
先ほどとは人が通信相手にパケットを送信すると問題なく相手に届く(図では右のPCが198.51.100.20:39311に送信)。このパケットの送信者側のルータにも通信相手(198.51.100.20:39311)がフィルタリングに追加される。
以後は互いに自由に通信することができる。ただし、一定時間通信しないとルータ上のNAPTのテーブルの当該エントリが削除され、通信できなくなる。
先ほど"何らかの手段で互いに自身のIPアドレス・ポートを教える"と書きましたが、実際のビデオ通話などのWebRTCのP2Pではシグナリングサーバを利用して、互いのIPアドレス・ポートの情報を交換します。この記事では、とりあえずP2Pできるか実験することが趣旨なのでシグナリングサーバは用意しません。
通信中継ソフト作成編
今までの説明したやり方で、通信路を確保できることが分かりました。この通信路はUDPパケットでしか利用できませんが、Minecraftサーバの通信はTCPです。プロトコルが異なるので単純なポートフォワーディングのような方法では通信を確立することができません。
すぐに思いつく解決策はTCPパケットをカプセル化してUDP通信路上で送ることです。しかし、私が調べたところWindowsではTCPをカプセル化してUDPで送信する良い感じのソフトが無さそうでした(知っている人いたら教えてください)。Linuxならできそうですが、LinuxでMinecraftをプレイする人は少ないと思うのでこの方法は諦め、TCPパケットのペイロードのみをUDP通信路で送信します。
下図のようなWindowsフォームアプリケーションを作成しました。STUNの部分はRFC 8489を参考に実装しています。
STUNボタン - GoogleのSTUNサーバに問い合わせて自身のIPアドレス・ポート番号を2.に表示します
メッセージ表示用テキストボックス
メッセージ送信テキストボックス・送信ボタン - 5.と6.で指定される送信先にテキストデータを送信
TCPポート番号設定 - Minecraftのサーバ(またはクライアント)と通信するためのTCPポートを設定(デフォルト:25565)
送信先IPアドレス - UDPパケットを送信する相手のIPアドレス
送信先ポート番号 - UDPパケットを送信する相手のポート番号
動作モード選択 - Minecraftサーバを動かしているPCではServer、クライアント(ゲーム本体)を動かしているPCではClientを選択
送信先確定ボタン - 5.と6.の設定を確定し、メッセージ送信できるようにする
転送開始ボタン - 4.と7.の設定を確定し、TCPコネクションを確立し、5.と6.で確立したUDP通信路を利用した通信の中継を開始する
通信中継実験編
家にインターネット回線を2つも引いている特殊な環境ではないので、家の光回線とスマホのテザリングでお互いに通信できるか確認します。
左側のウィンドウがPC A、右側がPC Bです。まず、それぞれがSTUNボタンを押し、自身のIPアドレス確認し、通信相手のIPアドレスとポート番号をフォームに入力します。
次に、上図のようにメッセージ送信をします。[local]と先頭に付いているのは自分が送信したメッセージです。
最初にPC BからPC Aにメッセージを送信しましたが、これはNAPTによって破棄されています。次に、PC AからPC Bへとメッセージを送信し、PC Bはこれを受信しています。最後にPC BからPC Aにメッセージを送信し、今度は正常に受信されています。
ということで、正常にP2Pで通信ができました。
次にMinecraftで実証と言いたいのですが、私の再送・シーケンス制御の悪さ、ウィンドウ制御を実装していないこととモバイル回線の通信の不安定さとがあり、マイクラサーバにログインはできましたが、まともにプレイできませんでした。
ということで代わりに家のインターネット回線のみでSTUNでP2P通信路を確立し、その上でマイクラマルチ実証をします。私の回線はISP側でもNAPTが行われている(キャリアグレードNAT)ので、家から出たパケットは一瞬だけISP側の装置を通り戻ってくるという構成になると思われます。
上図のように、中継ソフトでP2Pを確立した状態にし、マイクラのサーバ・クライアントを準備します。
ここでB側のTCPポート番号を25566、A側のモードをServerにし、中継ソフト上で転送開始ボタンを押すと、マイクラクライアントでサーバに入れるようになります。(下図)
デバッグ画面でデータ転送レート・遅延を見ると
平均転送レートが6.6KiB/s、平均遅延が7msとなっていて、グラフから安定して通信できていることが分かります。
おわりに
NAT越え技術のSTUNについて軽く説明して、簡単な実験を行いました。NATの動きやSTUNについては他に分かりやすく説明している人がいますので興味がある方は調べてみると面白いかもしれません。実験に関しては異なる回線で上手くできなかったのが残念です。通信の再送制御やフロー制御も実装したのですがその設計が適当であったために上手く動かなかったのかもしれません。ソースコードはGithubに置いておくので触りたい人いればどうぞ。