○ NATの弊害
「端末から見えている自分のIP」と「通信相手から見えている自分のIP」がことなる場合があるからです。※ 異なるのが普通と事と考えてもよいでしょう。
サーバーとしての機能を活用するためには、相手に自分のIPを伝える必要があります。そもそも、相手が自分のIPを知らないと、接続してもらえません。
○ 相手から見えているIPを知る方法
そこで、相手から見えているIPを調べて、相手に通知してあげましょう。そうすれば、サーバーとして機能を果たす事ができます。相手から見えているIPを知る方法はいくつかあります。
* a. ルータに確認する
* b.適当なサーバーに接続してみて、相手から見えているアドレスを返してもらうぬ
などです。
ここでは、「a.ルータに確認する方法」について紹介します。
○ UPnPを使おう
ルーターとは、UPnPプロトコルを通して会話する事ができます。1 "239.255.255.250" 1900に参加する
例えば以下のような感じ
<pre>
MulticastSocket ssdpSocket = null;
InetSocketAddress ssdpGroup = new InetSocketAddress("239.255.255.250", 1900);
InetAddress nicAddress = InetAddress.getByName(hostName);
ssdpSocket = new MulticastSocket(new InetSocketAddress(nicAddress, SSDP_PORT));
ssdpSocket.joinGroup(ssdpGroup, NetworkInterface.getByInetAddress(nicAddress));
</pre>
2. ルータを探す
UPnPは同一ネットワーク(ルータ内)のコンピュータへ、「M-SEARCH」をブルードキャスト送信する事で実現できます。例えば以下のような感じ
<pre>
String message =
"M-SEARCH * HTTP/1.1"+"\r\b"+
"HOST:239.255.255.250:1900"+"\r\b"+
"MAN:\"ssdp:discover\""+"\r\b"+
"ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1"+"\r\b"+
"MX:3"+"\r\b"+
"+"\r\b";
ssdpSocket.send(new DatagramPacket(
message.getBytes(), message.getBytes().length, ssdpGroup);
</pre>
こんなのが返る
<pre>
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=120
Location: http://192.168.0.1:2869/upnp/rootdevice.xml
SERVER: IGD-HTTP/1.1 UPnP/1.0 UPnP-Device-Host/1.0
ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1
EXT:
USN: uuid:79f0447f-860fbb81::urn:schemas-upnp-org:device:InternetGatewayDevice:1
</pre>
3. NAT関連のサービスを探す
http://192.168.0.1:2869/upnp/rootdevice.xmlをGetします。このXMLファイルには、そのデバイスが提供しているサービスが書かれています。このファイルをパースしてNAT関連のサービスを探しましょう。例えば、
GET /upnp/rootdevice.xml HTTP/1.1
Host: 192.168.0.1
を送る。
</pre>
こんなのが返る
<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
...
<service>
<serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType>
<serviceId>urn:upnp-org:serviceId:L3Frwd1</serviceId>
<controlURL>/upnp/control/L3Frwd1</controlURL>
<eventSubURL>/upnp/event/L3Frwd1</eventSubURL>
<SCPDURL>/upnp/L3Frwd1.xml</SCPDURL>
</service>
...
..
<service>
<serviceType>urn:schemas-upnp-org:service:WANIPConnection:1</serviceType>
<serviceId>urn:upnp-org:serviceId:WANIPConn1</serviceId>
<controlURL>/upnp/control/WANIPConn1</controlURL>
<eventSubURL>/upnp/event/WANIPConn1</eventSubURL>
<SCPDURL>/upnp/WANIPConn1.xml</SCPDURL>
</service>
..
.
</root>
serviceTypeが、「WANIPConnection」「WANPPPConnection」のものが、NAT関連のサービスになります。
4. 通信相手から見えている自分のIPを取得する。
見つかったサービスから、IPアドレスを取得する事ができます。
こんな感じで要求を出す事ができます。
<pre>
POST /upnp/control/WANIPConn1 HTTP/1.0
SOAPACTION: "urn:schemas-upnp-org:service:WANPPPConnection:1#AddPortMapping"
Content-Length: 718
Host: 192.168.0.1
<?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>8081</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>8081</NewInternalPort>
<NewInternalClient>192.168.0.3</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>test</NewPortMappingDescription>
<NewLeaseDuration>3600</NewLeaseDuration>
</m:AddPortMapping>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
</pre>
こんなのが返ります。
HTTP/1.1 200 OK
CONTENT-LENGTH: 423
CONTENT-TYPE: text/xml; charset="utf-8"
SERVER: IGD-HTTP/1.1 UPnP/1.0 UPnP-Device-Host/1.0
EXT:
<?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:GetExternalIPAddressResponse xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewExternalIPAddress>xxx.xxx.xxx.xxx</NewExternalIPAddress>
</m:GetExternalIPAddressResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
5. Portをマッピングする
「通信相手から見えているポート番号」と「自分から見えているポート番号」を関連付けます。見つかったサービスへ要求を出す事で実現できます。例えば以下のような感じ
<pre>
POST /upnp/control/WANIPConn1 HTTP/1.0
SOAPACTION: "urn:schemas-upnp-org:service:WANPPPConnection:1#AddPortMapping"
Content-Length: 718
Host: 192.168.0.1
<?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>8081</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
<NewInternalPort>8081</NewInternalPort>
<NewInternalClient>192.168.0.3</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>test</NewPortMappingDescription>
<NewLeaseDuration>3600</NewLeaseDuration>
</m:AddPortMapping>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
</pre>
成功すると、200 OKが返ります。
6. Portをマッピングを解除する
必要ななくなったら、ポートの関連づけを解除しましょう。以下のような感じででせきます。
POST /upnp/control/WANIPConn1 HTTP/1.0
SOAPACTION: "urn:schemas-upnp-org:service:WANPPPConnection:1#
DeletePortMapping"
Content-Length: xxx
Host: 192.168.0.1
<?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:DeletePortMapping xmlns:m=\"urn:schemas-upnp-org:service:WANPPPConnection:1\">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>8081</NewExternalPort>
<NewProtocol>TCP</NewProtocol>
</m:DeletePortMapping>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
成功すると、200 OKが返ります。
○参考
http://upnp.org/sdcps-and-certification/
○検証用に書いたコード
https://github.com/kyorohiro/Hetimatan/tree/master/Hetimatan/src_util/net/hetimatan/net/ssdp
------- kyorohiro work kyorohiro.strikingly.com
ssdpSocket.joinGroupはなくても良いみたいです..orz
返信削除