UPnP Python and Wireshark
Introduction
UPnP
Today's subject is all about Universal Plug and Play, shortly designated by UPnP. The main interest in this technology, well at least for me and in a LAN perspective in mind is that UPnP is a collection of protocols that enable us to find and setup devices on the network. One of such device I have very interest is the router of my network and one task I would love to automate is the finding of the external Ip and the setup and listing of the network mappings to enable port forwarding to some of my services and to enable P2P communication behind NAT. Much more can be said about UPnP and many more operations can be done, not just with routers but also with other network devices as well.
Python & Wireshark
For this task I decided to use a hacker language and a hacker tool to inspect the set of protocols needed to do the job.
Python
The first reason to choose python for this task is because of the network API that is as low level as I need it to be. The second reason is because the language is interpreted and we don't need to spend time to compile code, another important reason is that it is a dynamic language wich let us concentrate on the job and not so on the syntax details and verbosity of strongly typed languages.
Wireshark
To avoid spending many time reading technical papers the best way to tackle a problem like this is to find a tool that already do the job like this one JavaPortMapper and reverse all the comunications on the network between your machine and the network. For this we use the Wireshark Tool that is a GUI application for network packet sniffing.
Reversing Job
The first protocol we enconter is called Simple Service Discovery Protocol and is a UDP Multicast based protocol. The idea is very simple. Before we talk to the dealer (in this case the router) we need to find him. By checking the SSDP packets in Wireshark we find that the protocol is very simple in concept and implementation. All that is needed is to send a udp datagram to a multicast ip 239.255.255.250 with the port 1900. The datagram is like the http headers. The only purpose is to ask for available services and wait for an answer of the eventual existing devices. The datagram looks like this
M-SEARCH * HTTP/1.1
Host: 239.255.255.250:1900
ST: urn:Microsoft Windows Peer Name Resolution
Protocol: V4:IPV6:LinkLocal
Man:"ssdp:discover"
MX:3
the udp is built in this part of the python code
class Upnp():
SSDP_ADDR = "239.255.255.250";
SSDP_PORT = 1900;
bcastend=(SSDP_ADDR,SSDP_PORT)
SSDP_MX = 3;
SSDP_ST = "urn:all";
ssdpRequest = "M-SEARCH * HTTP/1.1\r\n" + \
"HOST: %s:%d\r\n" % (SSDP_ADDR, SSDP_PORT) + \
"MAN: \"ssdp:discover\"\r\n" + \
"MX: %d\r\n" % (SSDP_MX, ) + \
"ST: %s\r\n" % (SSDP_ST, ) + "\r\n";
Next all we got to do to be able to talk with the router and find all the services available is to read the responses from de devices (hopefully one of them being the router) and parsing them and use that information to invoke the services available. The step of finding the services is done here
def broadcastRequest(self):
# Create the socket
sockr = socket.socket(
socket.AF_INET, socket.SOCK_DGRAM
)
sockr.setsockopt(
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1
)
# Bind to the server address
sockr.bind(('',1900))
ipmulticast,port=self.bcastend
group = socket.inet_aton(ipmulticast)
#pack the multicast address
mreq = struct.pack('4sL', group, socket.INADDR_ANY)
sockr.setsockopt(
socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,mreq
)
i=0
sockr.sendto(self.ssdpRequest,self.bcastend)
sockr.settimeout(2.0)
sdpResp=[]
try:
while True:
data,address=sockr.recvfrom(1024)
sdpm=SSDPMessage(data).sdpMap
if 'LOCATION' in sdpm.keys():
sdpResp.append(sdpm)
i=i+1
except:
pass
return sdpResp
If you look at the code you'll see something like this SSDPMessage(data).sdpMap this is just a auxiliary class that acts as a parser and container for the SSDP response messages sent by the devices
class SSDPMessage():
sdpMap={}
def __init__(self,data):
self.sdpMap=self.parseData(data)
def parseData(self,data):
dmap={}
lines=data.split("\n")
for line in lines:
prop=line.split(":",1)
if(len(prop)>1):
dmap[prop[0].replace(":","")]=prop[1].replace("\r","")
return dmap
All that is done in this class is to transform the response messages that are like http headers into a dictionary form.
The discovery work is done and the output can be achieved by running
a=Upnp()
responses=a.broadcastRequest()
for resp in responses:
print resp
print '****************************'
And the output will be something like this
{
'USN': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c::upnp:rootdevice',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:22 GMT',
'ST': ' upnp:rootdevice'
}
****************************
{'USN': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:22 GMT',
'ST': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c'
}
****************************
{'USN': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c
::urn:schemas-upnp-org:device:InternetGatewayDevice:1',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:22 GMT',
'ST': ' urn:schemas-upnp-org:device:InternetGatewayDevice:1'
}
****************************
{'USN': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c
::urn:schemas-upnp-org:service:Layer3Forwarding:1',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:22 GMT',
'ST': ' urn:schemas-upnp-org:service:Layer3Forwarding:1'
}
****************************
{'USN': ' uuid:254e9977-8964-49f3-b8d5-51acb7bd40fc',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:22 GMT',
'ST': ' uuid:254e9977-8964-49f3-b8d5-51acb7bd40fc'
}
****************************
{'USN': ' uuid:254e9977-8964-49f3-b8d5-51acb7bd40fc
::urn:schemas-upnp-org:device:WANDevice:1',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:22 GMT',
'ST': ' urn:schemas-upnp-org:device:WANDevice:1'
}
****************************
{'USN': ' uuid:254e9977-8964-49f3-b8d5-51acb7bd40fc
::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:22 GMT',
'ST': ' urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'
}
****************************
{
'USN': ' uuid:9f0865b3-f5da-4ad5-85b7-7404637fdf37',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:22 GMT',
'ST': ' uuid:9f0865b3-f5da-4ad5-85b7-7404637fdf37'
}
****************************
{'USN': ' uuid:9f0865b3-f5da-4ad5-85b7-7404637fdf37
::urn:schemas-upnp-org:device:WANConnectionDevice:1',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:22 GMT',
'ST': ' urn:schemas-upnp-org:device:WANConnectionDevice:1'
}
****************************
{'USN': ' uuid:9f0865b3-f5da-4ad5-85b7-7404637fdf37
::urn:schemas-upnp-org:service:WANIPConnection:1',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:23 GMT',
'ST': ' urn:schemas-upnp-org:service:WANIPConnection:1'
}
****************************
{'USN': ' uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:23 GMT',
'ST': ' uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311'
}
****************************
{
'USN': ' uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311
::urn:schemas-wifialliance-org:device:WFADevice:1',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:23 GMT',
'ST': ' urn:schemas-wifialliance-org:device:WFADevice:1'
}
****************************
{'USN': ' uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311
::urn:schemas-wifialliance-org:service:WFAWLANConfig:1',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'EXT': '',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'DATE': ' Fri, 26 Dec 2014 02:38:23 GMT',
'ST': ' urn:schemas-wifialliance-org:service:WFAWLANConfig:1'
}
****************************
{'USN': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c
::upnp:rootdevice',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' upnp:rootdevice'
}
****************************
{'USN': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c'
}
****************************
{'USN': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c
::urn:schemas-upnp-org:device:InternetGatewayDevice:1',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' urn:schemas-upnp-org:device:InternetGatewayDevice:1'
}
****************************
{'USN': ' uuid:060b7353-fca6-4070-85f4-1fbfb9add62c
::urn:schemas-upnp-org:service:Layer3Forwarding:1',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' urn:schemas-upnp-org:service:Layer3Forwarding:1'
}
****************************
{'USN': ' uuid:254e9977-8964-49f3-b8d5-51acb7bd40fc',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' uuid:254e9977-8964-49f3-b8d5-51acb7bd40fc'
}
****************************
{'USN': ' uuid:254e9977-8964-49f3-b8d5-51acb7bd40fc
::urn:schemas-upnp-org:device:WANDevice:1',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' urn:schemas-upnp-org:device:WANDevice:1'
}
****************************
{'USN': ' uuid:254e9977-8964-49f3-b8d5-51acb7bd40fc
::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'
}
****************************
{'USN': ' uuid:9f0865b3-f5da-4ad5-85b7-7404637fdf37
::urn:schemas-upnp-org:service:WANIPConnection:1',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' urn:schemas-upnp-org:service:WANIPConnection:1'
}
****************************
{'USN': ' uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311'
}
****************************
{'USN': ' uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311
::urn:schemas-wifialliance-org:device:WFADevice:1',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' urn:schemas-wifialliance-org:device:WFADevice:1'
}
****************************
{'USN': ' uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311
::urn:schemas-wifialliance-org:service:WFAWLANConfig:1',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' urn:schemas-wifialliance-org:service:WFAWLANConfig:1'
}
****************************
Well at this point we already found the devices and all the services available in the network. Next step is again reversing the network protocol. Remember that our aim is to retrieve the external ip of the network and to identify a port mapping if available in the router.
For that we run the java upnp application and use it to ask the router for the external ip and use it to ask for the defined port forwarding mappings. At the same time, as usual, we kick wireshark and see what is happening behind the scenes.
Well we see a first http request with the following data
GET /igd.xml HTTP/1.1
User-Agent: Java/1.7.0_65
Host: 192.168.1.1:1900
Accept: text/html, image/gif,
image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
HTTP/1.1 200 OK
CONTENT-LENGTH: 4588
CONTENT-TYPE: text/xml
DATE: Fri, 26 Dec 2014 01:12:14 GMT
LAST-MODIFIED: Tue, 28 Oct 2003 08:46:08 GMT
SERVER: ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0
CONNECTION: close
<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
<specVersion>
<major>1</major>
<minor>0</minor>
</specVersion>
<URLBase></URLBase>
<device>
<deviceType>
urn:schemas-upnp-org:device:InternetGatewayDevice:1
</deviceType>
<presentationURL>
http://192.168.1.1:9999/
</presentationURL>
<friendlyName>
Wireless Router TL-WR740N
</friendlyName>
<manufacturer>
TP-LINK
</manufacturer>
<manufacturerURL>
http://www.tp-link.com
</manufacturerURL>
<modelDescription>
Wireless Router TL-WR740N
</modelDescription>
<modelName>
TL-WR740N
</modelName>
<modelNumber>1.0/2.0</modelNumber
<modelURL>
http://192.168.1.1:9999
</modelURL>
<serialNumber>none</serialNumber>
<UDN>uuid:060b7353-fca6-4070-85f4-1fbfb9add62c</UDN>
<UPC>00000-00001</UPC>
<serviceList>
<service>
<serviceType>
urn:schemas-upnp-org:service:Layer3Forwarding:1
</serviceType>
<serviceId>
urn:upnp-org:serviceId:L3Forwarding1
</serviceId>
<controlURL>/l3f</controlURL>
<eventSubURL>/l3f</eventSubURL>
<SCPDURL>/l3f.xml</SCPDURL>
</service>
</serviceList>
<deviceList>
<device>
<deviceType>
urn:schemas-upnp-org:device:WANDevice:1
</deviceType>
<friendlyName>
Wireless Router TL-WR740N
</friendlyName>
<manufacturer>
TP-LINK
</manufacturer>
<manufacturerURL>
http://www.tp-link.com</manufacturerURL>
<modelDescription>
Wireless Router TL-WR740N
</modelDescription>
<modelName>
TL-WR740N
</modelName>
<modelNumber>
1.0/2.0
</modelNumber>
<modelURL>
http://192.168.1.1:9999
</modelURL>
<serialNumber>
none
</serialNumber>
<UDN>
uuid:254e9977-8964-49f3-b8d5-51acb7bd40fc
</UDN>
<UPC>
00000-00001
</UPC>
<serviceList>
<service>
<serviceType>
urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1
</serviceType>
<serviceId>
urn:upnp-org:serviceId:WANCommonIFC1
</serviceId>
<controlURL>/ifc</controlURL>
<eventSubURL>/ifc</eventSubURL>
<SCPDURL>/ifc.xml</SCPDURL>
</service>
</serviceList>
<deviceList>
<device>
<deviceType>
urn:schemas-upnp-org:device:WANConnectionDevice:1
</deviceType>
<friendlyName>
Wireless Router TL-WR740N</friendlyName>
<manufacturer>
TP-LINK
</manufacturer>
<manufacturerURL>
http://www.tp-link.com
</manufacturerURL>
<modelDescription>
Wireless Router TL-WR740N</modelDescription>
<modelName>TL-WR740N</modelName>
<modelNumber>1.0/2.0</modelNumber
<modelURL>
http://192.168.1.1:9999
</modelURL>
<serialNumber>none</serialNumber>
<UDN>
uuid:9f0865b3-f5da-4ad5-85b7-7404637fdf37
</UDN>
<UPC>00000-00001</UPC>
<serviceList>
<service>
<serviceType>
urn:schemas-upnp-org:service:WANIPConnection:1
</serviceType>
<serviceId>
urn:upnp-org:serviceId:WANIPConn1
</serviceId>
<controlURL>/ipc</controlURL>
<eventSubURL>/ipc</eventSubURL>
<SCPDURL>/ipc.xml</SCPDURL>
</service>
</serviceList>
</device>
</deviceList>
</device>
<!-- WFAWC goes here -->
<device>
<deviceType>
urn:schemas-wifialliance-org:device:WFADevice:1
</deviceType>
<presentationURL>
http://192.168.1.1:9999/
</presentationURL>
<friendlyName>
Wireless Router TL-WR740N
</friendlyName>
<manufacturer>TP-LINK</manufacturer
<manufacturerURL>
http://www.tp-link.com
</manufacturerURL>
<modelDescription>
Wireless Router TL-WR740N
</modelDescription>
<modelName>
TL-WR740N
</modelName>
<modelNumber>1.0/2.0</modelNumber>
<modelURL>
http://192.168.1.1:9999
</modelURL>
<serialNumber>none</serialNumber>
<UDN>
uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311
</UDN>
<UPC>00000-00001</UPC>
<serviceList>
<service>
<serviceType>
urn:schemas-wifialliance-org:service:WFAWLANConfig:1
</serviceType>
<serviceId>
urn:wifialliance-org:serviceId:WFAWLANConfig1
</serviceId>
<controlURL>
http://192.168.1.1:1910/WFAWLANConfig/control
</controlURL>
<eventSubURL>
http://192.168.1.1:1910/WFAWLANConfig/event
</eventSubURL>
<SCPDURL>
http://192.168.1.1:1900/wfc.xml</SCPDURL>
</service>
</serviceList>
</device>
<!-- WFAWC ends here -->
</deviceList>
</device>
</root>
If we look carefully we know this endpoint is not strange at all. It is just the value of the field LOCATION we fetch throught SSDP
{'USN':
' uuid:565aa949-67c1-4c0e-aa8f-f349e6f59311::urn:schemas-wifialliance-org:service:WFAWLANConfig:1',
'NTS': ' ssdp:alive',
'SERVER': ' ipos/7.0 UPnP/1.0 TL-WR740N/1.0/2.0',
'HOST': ' 239.255.255.250:1900',
'LOCATION': ' http://192.168.1.1:1900/igd.xml',
'CACHE-CONTROL': ' max-age=100',
'NT': ' urn:schemas-wifialliance-org:service:WFAWLANConfig:1'
}
The final part of the job is just a matter of invoking web services throught soap protocol
The code to retrieve all the available services and the post operations to retrieve the external IP as well as the mapping existing is in the hack version of the following code
link="http://192.168.1.1:1900/igd.xml"
http = urllib3.PoolManager()
r = http.request('GET', link)
soup=BeautifulSoup(r.data)
for service in soup.find_all('service'):
print service
print "Posting xml soap command to retrieve the external IP"
GET_EXTERNAL_IP_XML_MESSAGE='''
<?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:WANIPConnection:1"
>
</u:GetExternalIPAddress>
</s:Body>
</s:Envelope>'''
GET_GENERIC_PORTMAP_ENTRY='''
<?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:GetGenericPortMappingEntry
xmlns:u
="urn:schemas-upnp-org:service:WANIPConnection:1"
>
<NewPortMappingIndex>0</NewPortMappingIndex>
</u:GetGenericPortMappingEntry>
</s:Body>
</s:Envelope>
'''
HEADER_GENERIC_PORT_MAPPING
='"urn:schemas-upnp-org:service:WANIPConnection:
1#GetGenericPortMappingEntry"'
eiplink="http://192.168.1.1:1900/ipc"
HEADER_IPADDRESS='"urn:schemas-upnp-org:service:WANIPConnection:
1#GetExternalIPAddress"'
heip={'SOAPACTION':HEADER_IPADDRESS,'CONTENT-TYPE':'text/xml; charset="utf-8"'}
r=requests.post(url=eiplink,
data=GET_EXTERNAL_IP_XML_MESSAGE,
headers=heip)
print "Response: ",r.text
ipxml=BeautifulSoup(r.text)
print ipxml.find_all('newexternalipaddress')[0].string
hgpm={
'SOAPACTION':HEADER_GENERIC_PORT_MAPPING,
'CONTENT-TYPE':'text/xml; charset="utf-8"'
}
r=requests.post(
url=eiplink,
data=GET_GENERIC_PORTMAP_ENTRY,
headers=hgpm)
print "Response: ",r.text
rhxml=BeautifulSoup(r.text)
print "Remote Host: ", rhxml.find('newremotehost').string
print "Internal Host: ",rhxml.find('newinternalclient').string
print "Description: ", rhxml.find('newportmappingdescription').string
print "External Port: ", rhxml.find('newexternalport').string
print "Internal Port: ", rhxml.find('newinternalport').string
The output is something like this for the mapping info
<?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>
<u:GetGenericPortMappingEntryResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"><NewRemoteHost></NewRemoteHost><NewExternalPort>51413</NewExternalPort><NewProtocol>TCP</NewProtocol><NewInternalPort>51413</NewInternalPort><NewInternalClient>192.168.1.169</NewInternalClient><NewEnabled>1</NewEnabled><NewPortMappingDescription>Transmission at 51413</NewPortMappingDescription><NewLeaseDuration>0</NewLeaseDuration></u:GetGenericPortMappingEntryResponse></SOAP-ENV:Body>
</SOAP-ENV:Envelope>
after parsed
Remote Host: None
Internal Host: 192.168.1.169
Description: Transmission at 51413
External Port: 51413
Internal Port: 51413
And like this for the external ip request operation
<SOAP-ENV:Envelope xmlns:SOAP-ENV
="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle
="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<u:GetExternalIPAddressResponse
xmlns:u
="urn:schemas-upnp-org:service:WANIPConnection:1">
<NewExternalIPAddress>
89.155.33.96
</NewExternalIPAddress> </u:GetExternalIPAddressResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
And so the external ip is 89.155.33.96
Conclusions
UPnP is a nice concept. At the core it is nothing more than a way to specify a way for machines communicate and get their lives on in an happy way. SSDP is a very straightforward and simple protocol. Is a lightweight and simple way to find the devices and the list of services available. What is not so funny and certainly not so simple is the soap protocol that is used to negotiate the services between the machines and to get the work really done. The xml/soap choice is a little bit overwhelming in my humble opinion and adds a lot of complexity to the network developer if it wants to develop things from ground up. Of course this low level details are already hidden in a bunch of apis nevertheless the work must be done. This work was fun because it was very helpful to get a deeper understanding of the UPnP protocol but I should warn you that it is really easy to get stuck. Http GET/POST operations to invoke the services are tricky because the routers are implemented in a very sensitive way and if you fail to send a header like Content-Type some routers will respond, for instance, with a 500 internal error code. Yes you will encounter things WTF like these...