UPnPポートマップの実装



  • UDPでSSDPグループに参加する


  • UDPでSSDPグループからデバイスを検索する


  • TCPでルーターへリクエストを送る



UPnPを実装しよう

概要は説明した通りです。実際に実装してみましょう。サンプル実装を以下におきました。

実際に作ってもらうのが、もっとも効果的です。しかし、そんな時間がない方がほとんどでしょう。また、UPnP用のライブラリを利用するので、実装をする必要がないという方もいることでしょう。

なので、本章では実際に実装しながら、処理の流れを解説していきます

注意

Dart を利用するのてすが、Socket API は、hetimanetを使用します。2015/6/1において、Dart用に提供されている socketは、"chrome api ver1 " "chrome api ver2"、"dart:io"等いくつかあります。

利用するAPIに依存しないように、hetimanetでsocketを再定義して対応することにしました。

SSDPグループに参加する

通常、UDP Socketを生成して、IPが"239.255.255.250"、Portが"1900"のグループに参加します。 参加することで、SSDPグループに追加されたデバイス等が把握できるようになったり、グループに参加していないデバイスを無視したりできるようになります。

しかし、Chrome Socketから、上手く動作できなかったので、今回は、グループに参加していません。

(1) UDPソケットを生成する。

まずは、UDPソケットを生成しましょう。こんな感じです。

HetiSocketBuilder _socketBuilder = new HetiSocketBuilderChrome();

HetiUdpSocket _socket = _socketBuilder.createUdpClient();
_socket.onReceive().listen((HetiReceiveUdpInfo info) {
    print("receive udp info");
});

_socket.bind("0.0.0.0", 0).then((int v){
    if (v >=0) {
      print("bind ok");
    } else {
      print("bind error");
    }
}

「bind ok」とコンソールに文字が表示されれば成功です。

(2) SSDPグループからデバイスを検索する

無事にUDPソケットを生成できる事を確認できたら、次はSSDPグループに、ポートマップに対応できるデバイスがないか依頼を出します。

_socket.send(
   convert.UTF8.encode(SSDP_M_SEARCH_WANPPPConnectionV1), 
   SSDP_ADDRESS,
   SSDP_PORT).then((HetiUdpSendInfo iii) {
  print("send ok");
}).catchError((e) {
  completer.completeError(e);
});

先ほど生成したUDPSocketを利用して、"239.255.255.250"、Portが"1900"に 以下のメッセージを送っているだけです。

M-SEARCH * HTTP/1.1
MX: 3
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
ST: urn:schemas-upnp-org:service:WANIPConnection:1

難しい事はないですね。これで、UPnPに対応したルーターが存在していれば、前の章で説明した。タグを含むxmlファイルを取得できます。

(3) グローバルIPを取得する

ルーターへ依頼をだせるようになりました。試しに、ルーターにGlobal IPについて問い合わせて見ましょう。

UPnPに対応しているルーターは、TCPサーバーが立ち上がっています。TCPサーバーへリクエストを送ります。

HetiHttpClient client = new HetiHttpClient(new HetiSocketBuilderChrome()));
client.connect(host, port).then((int v) {
  return client.post(path, convert.UTF8.encode(body), {
    KEY_SOAPACTION: soapAction,
    "Content-Type": "text/xml"
  });
}).then((HetiHttpClientResponse response) {
  print("receive response");
}).catchError((e){
  print("failed request");
});

前章で説明した値を、pathとbodyに設定すれば無事リクエストを送信できます。

作成したライブラリは以下の通り

  • ルータを発見する

    UpnpDeviceSearcher
    .createInstance(new hetimacl.HetiSocketBuilderChrome())
    .then((hetima.UpnpDeviceSearcher searcher) {
      searcher.onReceive().listen((hetima.UPnpDeviceInfo info) {
      });
      searcher.searchWanPPPDevice();
    });
    
  • ポートマッピングする

    UpnpPPPDevice#addPortMapping(
    localIP, 
    localPort, 
    remotePort,
    UPnpPPPDevice.VALUE_PORT_MAPPING_PROTOCOL_TCP)
      .then((UpnpPortMappingResult r) {
      });
    
  • グローバルアドレスを調べる

    UpnpPPPDevice#requestGetExternalIPAddress()
    .then((String address){
    });
    
  • ポートマッピングする

    UpnpPPPDevice#addPortMapping(
    localIP, 
    localPort, 
    remotePort,
    UPnpPPPDevice.VALUE_PORT_MAPPING_PROTOCOL_TCP)
      .then((UpnpPortMappingResult r) {
    });
    
  • グローバルアドレスを調べる

    UpnpPPPDevice#requestGetExternalIPAddress()
    .then((String address){
    });
    

Kyorohiro work

http://kyorohiro.strikingly.com