Symfoware

Symfowareについての考察blog

PythonでUPnPによるポートマップ(なぜなに Torrent)

なぜなに Torrent ( P2P プログラム入門 )

こちらを参考に、PythonでTorrentクライアントを作ってみます。
最終目標は、Debianのインストーラーをダウンロードしてみること。

最小の CD を使って、ネットワークインストールする


前回までで、Torrentファイルの内容を読み取ることができました。
PythonでBencodingのデコード(なぜなに Torrent)

今回は、UPnPによるポートマップを試してみます。
UPnPによるポートマップ



UPnPによるポートマップ




UDP Multicast を利用して、使用中のルーターに、ポートマッピングを依頼するためのアドレスを教えてもらう。
TCP を使って、教えてもらったアドレスからポートマッピングの依頼をだす。



ルーターにこんな機能があったとは。知りませんでした。

Pythonでの実装はこちらを参考にしています。
DLNAってなんじゃらほい? - SSDPを喋ってみる -
UPnPを使ってルータの外向けIPアドレスを取得する




依頼先を調べる



UDPマルチキャストでUPnPに対応しているルーターの情報を調べます。

送信するデータ


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





Pythonでの実装はこうなりました。


  1. # -*- coding:utf-8 -*-
  2. import socket
  3. host = '239.255.255.250'
  4. port = 1900
  5. messages = [
  6.     'M-SEARCH * HTTP/1.1',
  7.     'MX: 3',
  8.     'HOST: 239.255.255.250:1900',
  9.     'MAN: "ssdp:discover"',
  10.     'ST: urn:schemas-upnp-org:service:WANIPConnection:1',
  11. ]
  12. message = '\r\n'.join(messages)
  13. message += '\r\n\r\n' # 末尾に改行コードを2つ付与
  14. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  15. sock.sendto(message, (host, port))
  16. res = sock.recv(4096)
  17. print(res)
  18. sock.close()




実行してみるとこんな応答があります。


$ python sample.py
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
DATE: Thu, 11 May 2017 13:08:30 GMT
EXT:
LOCATION: http://192.168.1.1:49152/gatedesc.xml
SERVER: Linux/3.2.26, UPnP/1.0, Intel SDK for UPnP devices/1.3.1
ST: urn:schemas-upnp-org:service:WANIPConnection:1
USN: uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx::urn:schemas-upnp-org:service:WANIPConnection:1





重要なのは「LOCATION」の情報です。
このURLをブラウザで開くと、xmlで情報が表示されます。


「serviceType」が「urn:schemas-upnp-org:service:WANIPConnection:1」となっているタグの、
「controlURL」が重要です。

このURLに対して、SOAP通信で要求を行っていくことになります。

751_01.png



UDPマルチキャストでUPnPに対応しているルーターの情報を取得。
応答の「LOCATION」からxmlを取得・解析し、controlURLを取得するまでのプログラムはこうなりました。


  1. # -*- coding:utf-8 -*-
  2. import socket
  3. import urllib
  4. import urlparse
  5. import xml.etree.ElementTree as ET
  6. # --- ルーターの情報を取得
  7. host = '239.255.255.250'
  8. port = 1900
  9. messages = [
  10.     'M-SEARCH * HTTP/1.1',
  11.     'MX: 3',
  12.     'HOST: 239.255.255.250:1900',
  13.     'MAN: "ssdp:discover"',
  14.     'ST: urn:schemas-upnp-org:service:WANIPConnection:1',
  15. ]
  16. message = '\r\n'.join(messages)
  17. message += '\r\n\r\n' # 末尾に改行コードを2つ付与
  18. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  19. sock.sendto(message, (host, port))
  20. res = sock.recv(4096)
  21. sock.close()
  22. for line in res.split('\n'):
  23.     info = line.strip()
  24.     if not info.startswith('LOCATION:'):
  25.         continue
  26.     
  27.     locaction = info[len('LOCATION:'):].strip()
  28.     break
  29.     
  30. print 'locaction:', locaction
  31. # --- XMLの解析
  32. f = urllib.urlopen(locaction)
  33. xml_string = f.read()
  34. f.close()
  35. xml = ET.fromstring(xml_string)
  36. ns = {
  37.     'ns' : 'urn:schemas-upnp-org:device-1-0'
  38. }
  39. # controlURLを取得する
  40. for child in xml.findall(".//ns:service", ns):
  41.     if child.find('ns:serviceType', ns).text == 'urn:schemas-upnp-org:service:WANIPConnection:1':
  42.         controlURL = child.find('ns:controlURL', ns).text
  43.         break
  44.     
  45. print controlURL
  46. print urlparse.urljoin(locaction, controlURL)





実行結果


$ python sample.py
locaction: http://192.168.1.1:49152/gatedesc.xml
/upnp/control/WANIPConn1
http://192.168.1.1:49152/upnp/control/WANIPConn1




試行錯誤しながらなので不格好ですが、希望通りSOAPアクションを送信する
http://192.168.1.1:49152/upnp/control/WANIPConn1
というURLが取得できました。





外部の端末から見えているアドレスを聞く



SOAP通信、何かライブラリを使用しても良かったのですが、urllibだけでも行けそうだったので、
素のpythonだけで実装してみます。

Can I use urllib to submit a SOAP request?
4.2. Python XML HTTP Post to send a SOAP message to a JWSDP or to a .NET web service.


  1. # -*- coding:utf-8 -*-
  2. import socket
  3. import urllib2
  4. import urlparse
  5. import xml.etree.ElementTree as ET
  6. # --- ルーターの情報を取得
  7. host = '239.255.255.250'
  8. port = 1900
  9. messages = [
  10.     'M-SEARCH * HTTP/1.1',
  11.     'MX: 3',
  12.     'HOST: 239.255.255.250:1900',
  13.     'MAN: "ssdp:discover"',
  14.     'ST: urn:schemas-upnp-org:service:WANIPConnection:1',
  15. ]
  16. message = '\r\n'.join(messages)
  17. message += '\r\n\r\n' # 末尾に改行コードを2つ付与
  18. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  19. sock.sendto(message, (host, port))
  20. res = sock.recv(4096)
  21. sock.close()
  22. for line in res.split('\n'):
  23.     info = line.strip()
  24.     if not info.startswith('LOCATION:'):
  25.         continue
  26.     
  27.     locaction = info[len('LOCATION:'):].strip()
  28.     break
  29.     
  30. print 'locaction:', locaction
  31. # --- XMLの解析
  32. f = urllib2.urlopen(locaction)
  33. xml_string = f.read()
  34. f.close()
  35. xml = ET.fromstring(xml_string)
  36. ns = {
  37.     'ns' : 'urn:schemas-upnp-org:device-1-0'
  38. }
  39. # controlURLを取得する
  40. for child in xml.findall(".//ns:service", ns):
  41.     if child.find('ns:serviceType', ns).text == 'urn:schemas-upnp-org:service:WANIPConnection:1':
  42.         controlURL = child.find('ns:controlURL', ns).text
  43.         break
  44.     
  45. print controlURL
  46. get_ip_soap = """<?xml version="1.0"?>
  47. <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
  48.      s:encodingStyle="http://schemas.xmlsoap.org/encoding/">
  49. <s:Body>
  50.     <m:GetExternalIPAddress xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1">
  51.     </m:GetExternalIPAddress>
  52. </s:Body>
  53. </s:Envelope>"""
  54. soap_url = urlparse.urljoin(locaction, controlURL)
  55. req = urllib2.Request(soap_url, data=get_ip_soap)
  56. req.add_header("Content-type", "text/xml; charset=\"UTF-8\"")
  57. req.add_header("SOAPAction", '"urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"')
  58. response = urllib2.urlopen(req)
  59. ret = response.read()
  60. print 'Response:' + '-' * 10
  61. print ret




実行結果


  1. $ python sample.py
  2. locaction: http://192.168.1.1:49152/gatedesc.xml
  3. /upnp/control/WANIPConn1
  4. Response:----------
  5. <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>
  6. <u:GetExternalIPAddressResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">
  7. <NewExternalIPAddress>93.184.216.34/NewExternalIPAddress>
  8. </u:GetExternalIPAddressResponse>
  9. </s:Body> </s:Envelope>




ちゃんと外向きのIPアドレスが取得できました。





【参考URL】

DLNAってなんじゃらほい? - SSDPを喋ってみる -
UPnPを使ってルータの外向けIPアドレスを取得する
Can I use urllib to submit a SOAP request?
4.2. Python XML HTTP Post to send a SOAP message to a JWSDP or to a .NET web service.
UPnPのNAT越えについて調べてみた

関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2017/05/11(木) 23:37:29|
  2. Python
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
| ホーム | PythonでBencodingのデコード(なぜなに Torrent)>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://symfoware.blog68.fc2.com/tb.php/1990-83d18391
この記事にトラックバックする(FC2ブログユーザー)