2010-10-06
UPnPのNAT越えについて調べてみた
はじめに
P2Pアプリケーションを作ろう!と思い立ったのでP2Pについて調べていました。すると、NAT(またはNAPT)越えをしないとP2P通信が出来ないそうじゃありませんか!!
ということで、今度はNAT越えについて調べてみるとUPnP、STUN、UDP Hole Punchingという方法があるそうですね。なので、今回はUPnPのNAT越えについて調べてみました。UPnPについては以下のページを参考にしました。
UPnPを利用してグローバルIPを取得する - 2 | ::Hikaru’s blog
ネットワーク環境(自宅)
NTTからレンタルしているモデムがUPnPに対応していたので、外部から自宅のPCにアクセスできる(NAT越えができる)ようにしてみました。自宅のネットワーク環境を図で表すと、以下のような感じです。
UPnP対応機器を探し出す
まずは、自宅LAN内のUPnP対応機器(上の図のモデム)を探し出します。以下のM-SEARCHメッセージをマルチキャストしてUPnP対応機器からのレスポンスを調べます。
M-SEARCH * HTTP/1.1 MX: 3 HOST: 239.255.255.250:1900 MAN: "ssdp:discover" ST: upnp:rootdevice
このM-SEARCHメッセージをマルチキャストするPythonスクリプトを書きました。以下のコードをsearchRootDevice.pyという名前で保存します。
#!/usr/bin/env python #-*- coding:utf-8 -*- import socket M_SEARCH = 'M-SEARCH * HTTP/1.1\r\n' M_SEARCH += 'MX: 3\r\n' M_SEARCH += 'HOST: 239.255.255.250:1900\r\n' M_SEARCH += 'MAN: "ssdp:discover"\r\n' M_SEARCH += 'ST: upnp:rootdevice\r\n' M_SEARCH += '\r\n' s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.settimeout(5) # 5秒でタイムアウト s.bind(('', 1900)) # M-SEARCHをマルチキャストする s.sendto(M_SEARCH, ('239.255.255.250', 1900)) while True: try: response, address = s.recvfrom(8192) print 'from', address print response print '=' * 40 except socket.timeout, e: # タイムアウトしたときの処理 print e break s.close()
このスクリプトを実行すると以下のような結果になりました。
% ./searcheRootDevice.py from ('192.168.1.1', 1900) HTTP/1.1 200 OK Ext: Date: WED, 22 JAN 2070 04:40:41 GMT ST: upnp:rootdevice USN: uuid:53563300-0101-0000-000ba233dcd7::upnp:rootdevice Location: http://192.168.1.1:2869/DeviceDescription.xml ← ここ大事! Cache-Control: max-age=600 Server: VxWorks/5.4.2 UPnP/1.0 UPnP-Device-Host/1.0 Content-Length: 0 ======================================== timed out
同じメッセージが繰り返し表示されるときもありますが、この結果で注目するのはLocationの値です。ここにはデバイス情報を取得するためにアクセスするURLが書かれています。なので、実際にこのURLにアクセスしてみます。
% wget http://192.168.1.1:2869/DeviceDescription.xml --23:28:17-- http://192.168.1.1:2869/DeviceDescription.xml => `DeviceDescription.xml' Connecting to 192.168.1.1:2869... connected. HTTP request sent, awaiting response... 200 OK Length: 5,056 (4.9K) [text/xml] 100%[==================================================>] 5,056 --.--K/s 23:28:17 (161.25 KB/s) - `DeviceDescription.xml' saved [5056/5056]
このファイル(DeviceDescription.xml)の内容は以下のとおりです。
<?xml version="1.0"?> <root xmlns="urn:schemas-upnp-org:device-1-0"> <specVersion> <major>1</major> <minor>0</minor> </specVersion> <URLBase>http://192.168.1.1:2869/</URLBase> <device> <deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType> <friendlyName>ADSL Modem-SV3</friendlyName> <manufacturer>NTTEAST/NTTWEST</manufacturer> <manufacturerURL></manufacturerURL> <modelDescription>ADSL Broadband Router with VoIP function</modelDescription> <modelName>ADSL Modem-SV3</modelName> <modelNumber></modelNumber> <modelURL></modelURL> <serialNumber></serialNumber> <UDN>uuid:53563300-0101-0000-000ba233dcd7</UDN> <UPC></UPC> <serviceList> <service> <serviceType>urn:schemas-upnp-org:service:Layer3Forwarding:1</serviceType> <serviceId>urn:upnp-org:serviceId:Layer3Forwarding1</serviceId> <SCPDURL>/Layer3Forwarding.xml</SCPDURL> <controlURL>/UD/?0</controlURL> <eventSubURL>/?0</eventSubURL> </service> <service> <serviceType>urn:schemas-microsoft-com:service:OSInfo:1</serviceType> <serviceId>urn:microsoft-com:serviceId:OSInfo1</serviceId> <SCPDURL>/OSInfo.xml</SCPDURL> <controlURL>/UD/?1</controlURL> <eventSubURL>/?1</eventSubURL> </service> </serviceList> <deviceList> <device> <deviceType>urn:schemas-upnp-org:device:WANDevice:1</deviceType> <friendlyName>WANDevice1</friendlyName> <manufacturer>NTTEAST/NTTWEST</manufacturer> <manufacturerURL></manufacturerURL> <modelDescription>ADSL Broadband Router with VoIP function</modelDescription> <modelName>ADSL Modem-SV3</modelName> <modelNumber></modelNumber> <modelURL></modelURL> <serialNumber></serialNumber> <UDN>uuid:53563300-0201-0000-000ba233dcd7</UDN> <UPC></UPC> <serviceList> <service> <serviceType>urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANCommonIFC1</serviceId> <SCPDURL>/WANCommonInterfaceConfig.xml</SCPDURL> <controlURL>/UD/?2</controlURL> <eventSubURL>/?2</eventSubURL> </service> </serviceList> <deviceList> <device> <deviceType>urn:schemas-upnp-org:device:WANConnectionDevice:1</deviceType> <friendlyName>WANConnectionDevice1</friendlyName> <manufacturer>NTTEAST/NTTWEST</manufacturer> <manufacturerURL></manufacturerURL> <modelDescription>ADSL Broadband Router with VoIP function</modelDescription> <modelName>ADSL Modem-SV3</modelName> <modelNumber></modelNumber> <modelURL></modelURL> <serialNumber></serialNumber> <UDN>uuid:53563300-0301-0000-000ba233dcd7</UDN> <UPC></UPC> <serviceList> <service> <serviceType>urn:schemas-upnp-org:service:WANDSLLinkConfig:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANDSLLinkC1</serviceId> <SCPDURL>/WANDSLLinkConfig.xml</SCPDURL> <controlURL>/UD/?4</controlURL> <eventSubURL>/?4</eventSubURL> </service> <service> <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANPPPConn1</serviceId> <SCPDURL>/WANPPPConnection.xml</SCPDURL> <controlURL>/UD/?6</controlURL> <eventSubURL>/?6</eventSubURL> </service> <service> <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANPPPConn2</serviceId> <SCPDURL>/WANPPPConnection.xml</SCPDURL> <controlURL>/UD/?7</controlURL> <eventSubURL>/?7</eventSubURL> </service> <service> <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANPPPConn3</serviceId> <SCPDURL>/WANPPPConnection.xml</SCPDURL> <controlURL>/UD/?8</controlURL> <eventSubURL>/?8</eventSubURL> </service> <service> <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANPPPConn4</serviceId> <SCPDURL>/WANPPPConnection.xml</SCPDURL> <controlURL>/UD/?9</controlURL> <eventSubURL>/?9</eventSubURL> </service> <service> <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANPPPConn5</serviceId> <SCPDURL>/WANPPPConnection.xml</SCPDURL> <controlURL>/UD/?10</controlURL> <eventSubURL>/?10</eventSubURL> </service> <service> <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANPPPConn6</serviceId> <SCPDURL>/WANPPPConnection.xml</SCPDURL> <controlURL>/UD/?11</controlURL> <eventSubURL>/?11</eventSubURL> </service> <service> <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANPPPConn7</serviceId> <SCPDURL>/WANPPPConnection.xml</SCPDURL> <controlURL>/UD/?12</controlURL> <eventSubURL>/?12</eventSubURL> </service> <service> <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANPPPConn8</serviceId> <SCPDURL>/WANPPPConnection.xml</SCPDURL> <controlURL>/UD/?13</controlURL> <eventSubURL>/?13</eventSubURL> </service> </serviceList> </device> </deviceList> </device> </deviceList> <presentationURL>http://192.168.1.1/</presentationURL> </device> </root>
このファイルの中で大事なのは以下の部分です。
<?xml version="1.0"?> <root xmlns="urn:schemas-upnp-org:device-1-0"> <specVersion> <major>1</major> <minor>0</minor> </specVersion> <URLBase>http://192.168.1.1:2869/</URLBase> ← ここが大事! <device> <deviceType>urn:schemas-upnp-org:device:InternetGatewayDevice:1</deviceType> 省略 <service> <serviceType>urn:schemas-upnp-org:service:WANPPPConnection:1</serviceType> <serviceId>urn:upnp-org:serviceId:WANPPPConn1</serviceId> <SCPDURL>/WANPPPConnection.xml</SCPDURL> <controlURL>/UD/?6</controlURL> ← ここが大事! <eventSubURL>/?6</eventSubURL> </service> <service>
上記のURLBaseとcontrolURLの値を用いてWAN側のIPアドレスの取得、ポートマッピングの設定・削除などを行っていきます。
WAN側のIPアドレスの取得
上記の方法でcontrolURLが分かれば、以下のようなSOAPアクションをUPnP対応機器に送信することでWAN側のIPアドレス(グローバル)が取得できます。
POST /UD/?6 HTTP/1.1 HOST: 192.168.1.1:2869 CONTENT-LENGTH: 294 CONTENT-TYPE: text/xml; charset="utf-8" SOAPACTION: "urn:schemas-upnp-org:service:WANPPPConnection:1#GetExternalIPAddress" <?xml version="1.0"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:GetExternalIPAddress xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"> </u:GetExternalIPAddress> </s:Body> </s:Envelope>
SOAPアクションはtelnetコマンドで192.168.1.1:2869に送信できますが、とりあえずPythonスクリプトを書いてみました。以下のコードをgetExternalIPAddress.pyという名前で保存します。
#!/usr/bin/env python #-*- coding:utf-8 -*- import urllib2 HOST = '192.168.1.1' PORT = 2869 CONTROL = '/UD/?6' URL = 'http://' + HOST + ':' + str(PORT) + CONTROL SOAP = '<?xml version="1.0"?>\r\n' SOAP += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\r\n' SOAP += '<s:Body>\r\n' SOAP += '<u:GetExternalIPAddress xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1">\r\n' SOAP += '</u:GetExternalIPAddress>\r\n' SOAP += '</s:Body>\r\n' SOAP += '</s:Envelope>\r\n' req = urllib2.Request(URL) req.add_header('Content-Type', 'text/xml; charset="utf-8"') req.add_header('SOAPACTION', '"urn:schemas-upnp-org:service:WANPPPConnection:1#GetExternalIPAddress"') req.add_data(SOAP) res = urllib2.urlopen(req) print res.read()
このスクリプトを実行すると以下のような結果になりました。
% ./getExternalIPAddress.py <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"> <NewExternalIPAddress>XXX.XXX.XXX.XXX</NewExternalIPAddress> ← ここ大事! </u:GetExternalIPAddressResponse> </s:Body> </s:Envelope>
WAN側のIPアドレスが記述されている部分は隠していますが、ちゃんと取得することができました。念のため、レンタルしているモデムにWebブラウザーからアクセスしてWAN側のIPアドレス(モデムではADSLアドレスという表現をしてた)を見てみると、SOAPアクションで取得したアドレスと一致していました。
現在のポートマッピング情報の取得
WAN側のIPアドレス取得と同様に、以下のSOAPアクションを送信することで現在のポートマッピング情報を取得できます。
POST /UD/?6 HTTP/1.1 HOST: 192.168.1.1:2869 CONTENT-LENGTH: 353 CONTENT-TYPE: text/xml; charset="utf-8" SOAPACTION: "urn:schemas-upnp-org:service:WANPPPConnection:1#GetGenericPortMappingEntry" <?xml version="1.0"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <m:GetGenericPortMappingEntry xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1"> <NewPortMappingIndex>0</NewPortMappingIndex> ← ここが大事! </m:GetGenericPortMappingEntry> </s:Body> </s:Envelope>
このSOAPアクションのNewPortMappingIndexの値を0から順に変化させることでポートマッピングの情報を取得できます。もし、NewPortMappingIndexの値が登録されているポートマッピングの数より大きいとHTTPステータスコード 500を返すそうです。以下のコードをgetGenericPortMappingEntry.pyという名前で保存します。
#!/usr/bin/env python #-*- coding:utf-8 -*- import urllib2 HOST = '192.168.1.1' PORT = 2869 CONTROL = '/UD/?6' URL = 'http://' + HOST + ':' + str(PORT) + CONTROL ID = 0 while True: SOAP = '<?xml version="1.0"?>\r\n' SOAP += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\r\n' SOAP += '<s:Body>\r\n' SOAP += '<m:GetGenericPortMappingEntry xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1">\r\n' SOAP += '<NewPortMappingIndex>' + str(ID) + '</NewPortMappingIndex>\r\n' SOAP += '</m:GetGenericPortMappingEntry>\r\n' SOAP += '</s:Body>\r\n' SOAP += '</s:Envelope>\r\n' req = urllib2.Request(URL) req.add_header('Content-Type', 'text/xml; charset="utf-8"') req.add_header('SOAPACTION', '"urn:schemas-upnp-org:service:WANPPPConnection:1#GetGenericPortMappingEntry"') req.add_data(SOAP) try: res = urllib2.urlopen(req) print res.read() except urllib2.HTTPError, e: if e.code != 500: print e.code print e.msg break print '=' * 40 ID += 1
このスクリプトを実行すると以下のような結果になりました。
% ./getGenericPortMappingEntry.py <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:GetGenericPortMappingEntryResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"> <NewRemoteHost></NewRemoteHost> <NewExternalPort>80</NewExternalPort> <NewProtocol>TCP</NewProtocol> <NewInternalPort>80</NewInternalPort> <NewInternalClient></NewInternalClient> <NewEnabled>0</NewEnabled> <NewPortMappingDescription>Webサーバ (HTTP)</NewPortMappingDescription> <NewLeaseDuration>0</NewLeaseDuration> </u:GetGenericPortMappingEntryResponse> </s:Body> </s:Envelope> ========================================
私は何も設定していませんでしたが、始めから80番ポートに対して何らかの転送設定がされていました。ここで、先ほどのgetExternalIPAddress.pyで取得したWAN側のIPアドレスにWebブラウザーからアクセスすると、http://192.168.1.1と同じ画面(モデムの設定画面)が表示されることがわかりました。なので、既に登録された転送設定はWAN側のIPアドレスからでもモデムの設定が行えるようにするためだと思います。
ポートマッピングの設定を行う
以下のSOAPアクションを送信して、モデムの9090番ポートにきたアクセスを自宅PC(192.168.1.3)の9090番ポートに転送するように設定します。
POST /UD/?6 HTTP/1.1 Host: 192.168.1.1:2869 Content-Length: 671 Content-Type: text/xml; charset="utf-8" Connection: Close SOAPACTION: "urn:schemas-upnp-org:service:WANPPPConnection:1#AddPortMapping" <?xml version="1.0"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1"> <NewRemoteHost></NewRemoteHost> <NewExternalPort>9090</NewExternalPort> <NewProtocol>TCP</NewProtocol> <NewInternalPort>9090</NewInternalPort> <NewInternalClient>192.168.1.3</NewInternalClient> <NewEnabled>1</NewEnabled> <NewPortMappingDescription>Test</NewPortMappingDescription> <NewLeaseDuration>0</NewLeaseDuration> </m:AddPortMapping> </s:Body> </s:Envelope>
これをPythonスクリプトで行うために、以下のコードをaddPortMapping.pyという名前で保存します。
#!/usr/bin/env python #-*- coding:utf-8 -*- import urllib2 HOST = '192.168.1.1' PORT = 2869 CONTROL = '/UD/?6' URL = 'http://' + HOST + ':' + str(PORT) + CONTROL NEW_EXTERNAL_PORT = 9090 # WAN側のポート番号 NEW_INTERNAL_PORT = 9090 # 転送先ホストのポート番号 NEW_INTERNAL_CLIENT = '192.168.1.3' # 転送先ホストのIPアドレス NEW_PROTOCOL = 'TCP' LEASE_DURATION = '0' # 設定の有効期間(秒)。0のときは無期限 DESCRIPTION = 'test' SOAP = '<?xml version="1.0"?>\r\n' SOAP += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\r\n' SOAP += '<s:Body>\r\n' SOAP += '<m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1">\r\n' SOAP += '<NewRemoteHost></NewRemoteHost>\r\n' SOAP += '<NewExternalPort>' + str(NEW_EXTERNAL_PORT) + '</NewExternalPort>\r\n' SOAP += '<NewProtocol>' + NEW_PROTOCOL + '</NewProtocol>\r\n' SOAP += '<NewInternalPort>' + str(NEW_INTERNAL_PORT) + '</NewInternalPort>\r\n' SOAP += '<NewInternalClient>' + NEW_INTERNAL_CLIENT + '</NewInternalClient>\r\n' SOAP += '<NewEnabled>1</NewEnabled>\r\n' SOAP += '<NewPortMappingDescription>' + DESCRIPTION + '</NewPortMappingDescription>\r\n' SOAP += '<NewLeaseDuration>' + LEASE_DURATION + '</NewLeaseDuration>\r\n' SOAP += '</m:AddPortMapping>\r\n' SOAP += '</s:Body>\r\n' SOAP += '</s:Envelope>\r\n' req = urllib2.Request(URL) req.add_header('Content-Type', 'text/xml; charset="utf-8"') req.add_header('SOAPACTION', '"urn:schemas-upnp-org:service:WANPPPConnection:1#AddPortMapping"') req.add_data(SOAP) try: res = urllib2.urlopen(req) print res.read() except urllib2.HTTPError, e: print e.code print e.msg
このスクリプトを実行すると、以下のような結果になりました。
% ./addPortMapping.py <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:AddPortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"> </u:AddPortMappingResponse> </s:Body> </s:Envelope>
特にエラーらしきものが出力されていないので、9090番ポートに対する設定が行われたと思います。本当に設定できたのかをgetGenericPortMappingEntry.pyを実行して確認してみます。
% ./getGenericPortMappingEntry.py <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:GetGenericPortMappingEntryResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"> <NewRemoteHost></NewRemoteHost> <NewExternalPort>9090</NewExternalPort> <NewProtocol>TCP</NewProtocol> <NewInternalPort>9090</NewInternalPort> <NewInternalClient>192.168.1.3</NewInternalClient> <NewEnabled>1</NewEnabled> <NewPortMappingDescription>test</NewPortMappingDescription> <NewLeaseDuration>0</NewLeaseDuration> </u:GetGenericPortMappingEntryResponse> </s:Body> </s:Envelope> ======================================== <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:GetGenericPortMappingEntryResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"> <NewRemoteHost></NewRemoteHost> <NewExternalPort>80</NewExternalPort> <NewProtocol>TCP</NewProtocol> <NewInternalPort>80</NewInternalPort> <NewInternalClient></NewInternalClient> <NewEnabled>0</NewEnabled> <NewPortMappingDescription>Webサーバ (HTTP)</NewPortMappingDescription> <NewLeaseDuration>0</NewLeaseDuration> </u:GetGenericPortMappingEntryResponse> </s:Body> </s:Envelope> ========================================
この実行結果より、新たに9090番ポートへの転送設定が追加されていることが分かります。また、実際に自宅PC上でechoサーバーを立ち上げ(192.168.1.3:9090)、外部のネットワークからWAN側のIPアドレス:9090にtelnetでアクセスすると、NAT越えをしてechoサーバーと通信することができました!
ポートマッピングの設定を削除する
以下のSOAPアクションを送信して、先ほど追加した9090番ポートへの転送設定を削除することができます。
POST /UD/?6 HTTP/1.1 Host: 192.168.1.1:2869 Content-Length: 424 Content-Type: text/xml; charset="utf-8" Connection: Close SOAPACTION: "urn:schemas-upnp-org:service:WANPPPConnection:1#DeletePortMapping" <?xml version="1.0"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <m:DeletePortMapping xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1"> <NewRemoteHost></NewRemoteHost> <NewExternalPort>9090</NewExternalPort> <NewProtocol>TCP</NewProtocol> </m:DeletePortMapping> </s:Body> </s:Envelope>
これをPythonスクリプトで行うために、以下のコードをdeletePortMapping.pyという名前で保存しました。
#!/usr/bin/env python #-*- coding:utf-8 -*- import urllib2 HOST = '192.168.1.1' PORT = 2869 CONTROL = '/UD/?6' URL = 'http://' + HOST + ':' + str(PORT) + CONTROL NEW_EXTERNAL_PORT = 9090 NEW_PROTOCOL = 'TCP' SOAP = '<?xml version="1.0"?>\r\n' SOAP += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\r\n' SOAP += '<s:Body>\r\n' SOAP += '<m:DeletePortMapping xmlns:m="urn:schemas-upnp-org:service:WANPPPConnection:1">\r\n' SOAP += '<NewRemoteHost></NewRemoteHost>\r\n' SOAP += '<NewExternalPort>' + str(NEW_EXTERNAL_PORT) + '</NewExternalPort>\r\n' SOAP += '<NewProtocol>' + NEW_PROTOCOL + '</NewProtocol>\r\n' SOAP += '</m:DeletePortMapping>\r\n' SOAP += '</s:Body>\r\n' SOAP += '</s:Envelope>\r\n' req = urllib2.Request(URL) req.add_header('Content-Type', 'text/xml; charset="utf-8"') req.add_header('SOAPACTION', '"urn:schemas-upnp-org:service:WANPPPConnection:1#DeletePortMapping"') req.add_data(SOAP) try: res = urllib2.urlopen(req) print res.read() except urllib2.HTTPError, e: print e.code print e.msg
このスクリプトを実行すると、以下のような結果になりました。
% ./deletePortMapping.py <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:DeletePortMappingResponse xmlns:u="urn:schemas-upnp-org:service:WANPPPConnection:1"> </u:DeletePortMappingResponse> </s:Body> </s:Envelope>
特にエラーらしきものが出力がされていないので、9090番ポートへの転送設定は削除されたはずです。この後のgetGenericPortMappingEntry.pyの実行結果は割愛しますが、実際に外部からWAN側のIPアドレス:9090への通信はできなくなっていました。
WAN側のIPアドレスがプライベートだった場合
多分、2重ルーティングになってると思います。私の自宅も、始めは2重ルーティングになっていたのでgetExternalIPAddress.pyを実行するとWAN側のIPアドレスが192.168.1.2というプライベートアドレスになっていました。原因は、モデムとPCの間にある無線LANルーターがルーティングモードに設定されていたためでした。なので、Webブラウザーから無線LANルーターの設定をブリッジモードに変換することで解決できました。
おわりに
とりあえず、私の自宅においてはUPnPでNAT越えを行うことが出来ました。他の環境では試していないので、上のスクリプトがちゃんと動くかどうかわかりません。あしからず。
この調子で、次はSTUNかUDP Hole Punchingについて調べてみようと思います。
- 15 http://www.google.co.jp/search?sourceid=navclient&hl=ja&ie=UTF-8&rls=IBMA,IBMA:2010-19,IBMA:ja&q=WebSocket+ポート番号
- 10 http://d.hatena.ne.jp/keyword/UPnP
- 7 http://www.google.co.jp/search?hl=ja&lr=lang_ja&tbs=lr:lang_1ja&q=hg+commit+-exclude&aq=f&aqi=&aql=&oq=&gs_rfai=
- 7 http://www.google.co.jp/search?q=javascript+ローカルファイル+開く&hl=ja&client=firefox-a&hs=RCJ&rls=org.mozilla:ja:official&ei=gLS2TJDCKI6YvAOR1Mz1CA&start=10&sa=N
- 4 http://www.google.co.jp/search?hl=ja&client=firefox-a&rls=org.mozilla:ja:official&q=actionscript+テキストファイル 保存 FileReference&aq=f&aqi=&aql=&oq=&gs_rfai=
- 4 http://www.google.co.jp/search?hl=ja&source=hp&biw=1096&bih=644&q=python+websocket&aq=f&aqi=g1&aql=&oq=&gs_rfai=
- 4 http://www.google.co.jp/search?q=hg+commit&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:ja:official&hl=ja&client=firefox
- 3 http://www.google.co.jp/hws/search?hl=ja&q=GetGenericPortMappingEntry&client=fenrir&adsafe=off&safe=off&lr=lang_ja
- 3 http://www.google.co.jp/search?hl=&q=websocket+プロトコル&sourceid=navclient-ff&rlz=1B3GGGL_ja___JP225&ie=UTF-8
- 3 http://www.google.co.jp/search?sourceid=chrome&ie=UTF-8&q=javascript+ローカルファイル+作成
sazae 2015/07/02 11:16 この方法は最寄のルータがたまたま同一だったからうまくいったように見えるのですが、本当に外出先で各種ルータを経由するような状況を想定すると、そもそもM-SEARCHを飛ばしたい目的のローカルネットワークを探し出せない気がしますが、その点は考慮されてますか?