C#でUPnPを使った ポートマッピング

ゲームを始めオンラインでの通信機能が必要になる事は多いと思います。

そんな時立ちはだかる難関の1つ。それはNAT越えです。

NAT越えをする手段はいくつか考えられますが、今回は一番簡単(?)なUPnPを使ってルーターにポートフォアリングを設定して通信通路を確保したいと思います。 この記事はその時の記録です。

実行・テスト環境

テスト環境:Unity 2018 2.9f1

今回のプログラムはUnity+C#で作成。

パケット送信などの仕組み・ソースコードは 前に書いた記事「UnityでUDP通信を行うには」 などを参考にしてください。

ルーターにポートフォアリング(ポート転送)をリクエストする

ネットワーク内のルーターを探索する

探索の為に「M-Search」リクエストを送信先「 239.255.255.250 : 1900 」(ブロードキャスト)にUDPで送信します。

//送信リクエスト C#
static readonly string REQUEST_MESSAGE = String.Concat(
            "M-SEARCH * HTTP/1.1\r\n",
            "MX: 3\r\n",
            "HOST: 239.255.255.250:1900\r\n",
            "MAN: \"ssdp: discover\"\r\n",
            "ST: service:WANIPConnection:1\r\n"
        );
「M-SEARCH」送信の様子

その後ルーターなどUPnP対応機器からのリスポンス「NOTIFY」が同じアドレスで返ってくるので受信します。
(2019.2 追記) 自アドレスへリスポンス「OK」が返ってきます。

UPnP対応機器からのリスポンス「NOTIFY」

ルーターの情報を取得

先ほどの情報を元にルーターに「HTTP / GET」で詳細情報をリクエストします。

リクエストは以下のようなプログラムで実装しました。

public void GetRequest(string url,Action<string> getCallBack)
    {
        Encoding enc = Encoding.GetEncoding("UTF-8");

        WebRequest req = WebRequest.Create(url);
        WebResponse res = req.GetResponse();

        Stream st = res.GetResponseStream();
        StreamReader sr = new StreamReader(st, enc);
        string html = sr.ReadToEnd();
        sr.Close();
        st.Close();

        getCallBack(html);
    }

私の環境では「192.168.0.12」のPCから「192.168.0.1」のルーターにリクエストを送信している

なお、リクエストに成功すると「HTTP 200 OK」が返され、XML形式の情報を入手することができます。

そのXMLのうち今回は <controlURL> の情報を元にリクエスト先アドレスを入手します。

リクエストをルーターに送信

先ほどの情報を元に対象のアドレスに「HTTP / POST」でリクエスト内容(SOAPプロトコル)を送信します。

今回テストに送信したリクエストは以下の通りです。

# XML Request To Router
# SOAP Message
<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1">
      <NewRemoteHost></NewRemoteHost>
      <NewExternalPort>12345</NewExternalPort>
      <NewProtocol>UDP</NewProtocol>
      <NewInternalPort>12345</NewInternalPort>
      <NewInternalClient>192.168.0.12</NewInternalClient>
      <NewEnabled>1</NewEnabled>
      <NewPortMappingDescription>YounashiProgram</NewPortMappingDescription>
      <NewLeaseDuration>0</NewLeaseDuration>
    </m:AddPortMapping>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

このリクエストではポート「12345」のUDP通信を「192.168.0.12」に転送するリクエストをしています。

ExternalPortとInternalPortは転送元と転送先なので通常同じにします。

リスポンスを受信し成功確認

上のSOAPリクエストを送信するとルーターからリスポンスを受け取ります。

この時、「HTTP 200 OK」なら成功です。

HTTP/1.1 200 OK で成功だとわかる
ルーターの管理者ページでも確認できる

成功すると上図のように管理者ページにも追加されているのがわかる。

なお、失敗だと「 HTTP/1.1 500 Internal Server Error 」が返されます。失敗原因としては「既にポートが使用中」「UPnP設定の上限まで達している」などが挙げられます。

そもそも何も返ってこない場合、そもそもどこかが間違えているはずですので再確認してみてくださいね。

外部からの送信テスト

ポートフォアリングの設定などがしっかり出来ていることを確認するために外からUDPでパケットを送信してみます。

パケット送信アプリで送信テスト

スマートフォンからDocomo(4G)のキャリア回線で自宅のパソコンへとメッセージを送信。

パケット(メッセージ)が届いているのが分かる

受信できていれば完璧です。

オンラインゲーム作りの第一歩ですかね^^

今度は「UDPホールパンチング」という方法でも挑戦してみたいと思います。
またその時は記事を書きますね


ここまで読んでくれて本当にありがとうございました~^

ホームページでは他にも

・様々な記事や作った作品および過程
・ソースコード、素材ファイル
・あらゆる”モノ”の作り方

などなど随時、記事や作品を新規公開・更新していますので是非見ていってくださいね!見ていただけると本当に嬉しいです!

また、「このアプリの作り方を知りたい。この部分どうなってるの?」「身の回りのこんなもの作れるの?」などなどご意見何でも受け付けていますので是非連絡くださいね!