Add initial IGD (UPnP) support

This commit is contained in:
Oleksii Shevchuk 2016-11-22 00:26:15 +02:00
parent a39670a78f
commit e320af34ae
6 changed files with 1180 additions and 1 deletions

View File

@ -40,6 +40,7 @@ import urllib2
import getpass
import __future__
import bz2
import netaddr
#needed for scapy :
import new
import fractions

View File

@ -3,3 +3,4 @@ pycrypto
psutil
pyaml
rsa
netaddr

View File

@ -14,7 +14,7 @@ PYTHONVC="https://download.microsoft.com/download/7/9/6/796EF2E4-801B-4FC4-AB28-
PYCRYPTO32="http://www.voidspace.org.uk/downloads/pycrypto26/pycrypto-2.6.win32-py2.7.exe"
PYCRYPTO64="http://www.voidspace.org.uk/downloads/pycrypto26/pycrypto-2.6.win-amd64-py2.7.exe"
PACKAGES="rpyc psutil pyaml rsa pefile image rsa netaddr pypiwin32 win_inet_pton"
PACKAGES="rpyc psutil pyaml rsa pefile image rsa netaddr pypiwin32 win_inet_pton netaddr"
BUILDENV=${1:-`pwd`/buildenv}

420
pupy/modules/igd.py Normal file
View File

@ -0,0 +1,420 @@
# -*- coding: utf-8 -*-
from pupylib.PupyModule import *
from pupylib.utils.term import colorize
__class_name__ = "IGDClient"
class IGDCMDClient(object):
def __init__(self):
self.igdc = None
def init(self, IGDClient, args, log):
"""
initiate the IGDClient
"""
self.igdc = IGDClient(
args.source, args.url,
args.DEBUG, args.pretty_print)
self.log = log
def show(self, values):
if hasattr(values, 'iterkeys'):
column_size = max([len(x) for x in values.iterkeys()])
fmt = '{{:<{}}}'.format(column_size)
for k, v in values.iteritems():
if k.startswith('New'):
k = k[3:]
self.log(colorize(fmt.format(k), 'yellow')+' {}'.format(v))
else:
values = list(values)
columns = []
column_sizes = {}
for value in values:
for column, cvalue in value.iteritems():
if not column in columns:
if column.startswith('New'):
columnlen = len(column) - 3
else:
columnlen = len(column)
columns.append(column)
column_sizes[column] = max(len(str(cvalue)), columnlen)
else:
column_sizes[column] = max(column_sizes[column], len(str(cvalue)))
lines = []
header = ''
for column in columns:
fmt = ' {{:<{}}} '.format(column_sizes[column])
if column.startswith('New'):
column = column[3:]
header += colorize(fmt.format(column), 'yellow')
lines.append(header)
for value in values:
row = ''
for column in columns:
fmt = ' {{:<{}}} '.format(column_sizes[column])
row += fmt.format(value[column])
lines.append(row)
self.log('\n'.join(lines))
def addPM(self, args):
self.igdc.AddPortMapping(
args.intIP, args.extPort,
args.proto, args.intPort,
args.enabled, args.duration,
args.desc, args.remote)
def delPM(self, args):
self.igdc.DeletePortMapping(
args.extPort,
args.proto, args.remote)
def getExtIP(self, args):
extip = self.igdc.GetExternalIP()
self.show(extip)
def getGPM(self, args):
pm = self.igdc.GetGenericPortMappingEntry(args.index, True)
self.show(pm)
def getSPM(self, args):
pm = self.igdc.GetSpecificPortMappingEntry(
args.extPort, args.proto, args.remote)
self.show(pm)
def getNRSS(self, args):
pm = self.igdc.GetNATRSIPStatus()
self.show(pm)
def getWDD(self, args):
pm = self.igdc.GetWarnDisconnectDelay()
self.show(pm)
def getIDT(self, args):
pm = self.igdc.GetIdleDisconnectTime()
self.show(pm)
def getADT(self, args):
pm = self.igdc.GetAutoDisconnectTime()
self.show(pm)
def getSI(self, args):
pm = self.igdc.GetStatusInfo()
self.show(pm)
def setWDD(self, args):
self.igdc.SetWarnDisconnectDelay(args.delay)
def setIDT(self, args):
self.igdc.SetIdleDisconnectTime(args.time)
def setADT(self, args):
self.igdc.SetAutoDisconnectTime(args.time)
def forceTerm(self, args):
self.igdc.ForceTermination()
def requestTerm(self, args):
self.igdc.RequestTermination()
def requestConn(self, args):
self.igdc.RequestConnection()
def getCT(self, args):
pm = self.igdc.GetConnectionTypeInfo()
self.show(pm)
def setCT(self, args):
self.igdc.SetConnectionType(args.ct_type)
def custom(self, args):
args.input_args
iargs = json.loads(args.input_args)
resp_xml = self.igdc.customAction(args.method_name, iargs, args.svc)
if self.igdc.pprint:
xml = minidom.parseString(resp_xml)
xml.toprettyxml()
else:
resp_xml
# following are for IPv6FWControl
def getFWStatus(self, args):
pm = self.igdc.GetFWStatus()
self.show(pm)
def addPH(self, args):
r = self.igdc.AddPinhole(
args.intIP,
args.rIP,
args.rPort,
args.intPort,
args.proto,
args.lease)
self.show(r)
def getOPHT(self, args):
r = self.igdc.GetPinholeTimeout(
args.intIP, args.rIP, args.rPort, args.intPort, args.proto)
self.show(r)
def updatePH(self, args):
self.igdc.UpdatePinhole(args.uid, args.lease)
def delPH(self, args):
self.igdc.DelPinhole(args.uid)
def getPHPkts(self, args):
r = self.igdc.GetPinholePkts(args.uid)
self.show(r)
def chkPH(self, args):
r = self.igdc.CheckPinhole(args.uid)
self.show(r)
@config(cat='admin')
class IGDClient(PupyModule):
""" UPnP IGD Client """
def init_argparse(self):
cli = IGDCMDClient()
parser = PupyArgumentParser(
prog='igdc',
description=self.__doc__
)
parser.add_argument('-d', '--DEBUG', action='store_true',
help='enable DEBUG output')
parser.add_argument(
'-pp',
'--pretty_print',
action='store_true',
help='enable xml pretty output for debug and custom action')
parser.add_argument('-s', '--source', default='0.0.0.0',
help='source address of requests')
parser.add_argument('-u', '--url',
help='control URL')
subparsers = parser.add_subparsers()
parser_start = subparsers.add_parser('add', help='add port mapping')
parser_start.add_argument('intIP',
help='Internal IP')
parser_start.add_argument('intPort', type=int,
help='Internal Port')
parser_start.add_argument('extPort', type=int,
help='External Port')
parser_start.add_argument('proto', choices=['UDP', 'TCP'],
help='Protocol')
parser_start.add_argument('-r', '--remote', default='',
help='remote host')
parser_start.add_argument('-d', '--desc', default='',
help='Description of port mapping')
parser_start.add_argument(
'-e',
'--enabled',
type=int,
choices=[
1,
0],
default=1,
help='enable or disable port mapping')
parser_start.add_argument('-du', '--duration', type=int, default=0,
help='Duration of the mapping')
parser_start.set_defaults(func=cli.addPM)
parser_del = subparsers.add_parser('del', help='del port mapping')
parser_del.add_argument('extPort', type=int,
help='External Port')
parser_del.add_argument('proto', choices=['UDP', 'TCP'],
help='Protocol')
parser_del.add_argument('-r', '--remote', default='',
help='remote host')
parser_del.set_defaults(func=cli.delPM)
parser_geip = subparsers.add_parser('getextip', help='get external IP')
parser_geip.set_defaults(func=cli.getExtIP)
parser_gpm = subparsers.add_parser('getgpm', help='get generic pm entry')
parser_gpm.add_argument('-i', '--index', type=int,
help='index of PM entry')
parser_gpm.set_defaults(func=cli.getGPM)
parser_spm = subparsers.add_parser(
'getspm', help='get specific port mapping')
parser_spm.add_argument('extPort', type=int,
help='External Port')
parser_spm.add_argument('proto', choices=['UDP', 'TCP'],
help='Protocol')
parser_spm.add_argument('-r', '--remote', default='',
help='remote host')
parser_spm.set_defaults(func=cli.getSPM)
parser_nrss = subparsers.add_parser(
'getnrss', help='get NAT and RSIP status')
parser_nrss.set_defaults(func=cli.getNRSS)
parser_gwdd = subparsers.add_parser(
'getwdd', help='get warn disconnect delay')
parser_gwdd.set_defaults(func=cli.getWDD)
parser_swdd = subparsers.add_parser(
'setwdd', help='set warn disconnect delay')
parser_swdd.add_argument('delay', type=int,
help='warn disconnect delay')
parser_swdd.set_defaults(func=cli.setWDD)
parser_gidt = subparsers.add_parser(
'getidt', help='get idle disconnect time')
parser_gidt.set_defaults(func=cli.getIDT)
parser_sidt = subparsers.add_parser(
'setidt', help='set idle disconnect time')
parser_sidt.add_argument('time', type=int,
help='idle disconnect time')
parser_sidt.set_defaults(func=cli.setIDT)
parser_gadt = subparsers.add_parser(
'getadt', help='get auto disconnect time')
parser_gadt.set_defaults(func=cli.getADT)
parser_sadt = subparsers.add_parser(
'setadt', help='set auto disconnect time')
parser_sadt.add_argument('time', type=int,
help='auto disconnect time')
parser_sadt.set_defaults(func=cli.setADT)
parser_gsi = subparsers.add_parser('getsi', help='get status info')
parser_gsi.set_defaults(func=cli.getSI)
parser_rt = subparsers.add_parser('rt', help='request termination')
parser_rt.set_defaults(func=cli.requestTerm)
parser_ft = subparsers.add_parser('ft', help='force termination')
parser_ft.set_defaults(func=cli.forceTerm)
parser_rc = subparsers.add_parser('rc', help='request connection')
parser_rc.set_defaults(func=cli.requestConn)
parser_gct = subparsers.add_parser(
'getct', help='get connection type info')
parser_gct.set_defaults(func=cli.getCT)
parser_sct = subparsers.add_parser('setct', help='set connection type')
parser_sct.add_argument('ct_type',
help='connection type')
parser_sct.set_defaults(func=cli.setCT)
parser_cust = subparsers.add_parser('custom', help='use custom action')
parser_cust.add_argument('method_name',
help='name of custom action')
parser_cust.add_argument('-svc', type=str,
choices=['WANIPConnection',
'WANIPv6FirewallControl'],
default='WANIPConnection',
help='IGD service, default is WANIPConnection')
parser_cust.add_argument(
'-iargs',
'--input_args',
default='{}',
help='input args, the format is same as python dict,'
'e.g. "{\'NewPortMappingIndex\': [0, \'ui4\']}"')
parser_cust.set_defaults(func=cli.custom)
# following for IPv6FWControl
parser_gfwstatus = subparsers.add_parser(
'getfwstatus', help='get IPv6 FW status')
parser_gfwstatus.set_defaults(func=cli.getFWStatus)
parser_addph = subparsers.add_parser('addph', help='add IPv6 FW Pinhole')
parser_addph.add_argument('intIP',
help='Internal IP')
parser_addph.add_argument('-intPort', type=int, default=0,
help='Internal Port')
parser_addph.add_argument('proto', choices=['UDP', 'TCP', 'ALL'],
help='Protocol')
parser_addph.add_argument('-rIP', default='',
help='Remote IP')
parser_addph.add_argument('-rPort', type=int, default=0,
help='Remote Port')
parser_addph.add_argument('-lease', type=int, default=3600,
help='leasetime of the pinhole')
parser_addph.set_defaults(func=cli.addPH)
parser_gopht = subparsers.add_parser(
'getopht', help='get IPv6 FW OutboundPinholeTimeout')
parser_gopht.add_argument('-intIP', type=str, default='',
help='Internal IP')
parser_gopht.add_argument('-intPort', type=int, default=0,
help='Internal Port')
parser_gopht.add_argument(
'-proto',
choices=[
'UDP',
'TCP',
'ALL'],
default='ALL',
help='Protocol')
parser_gopht.add_argument('-rIP', default='',
help='Remote IP')
parser_gopht.add_argument('-rPort', type=int, default=0,
help='Remote Port')
parser_gopht.set_defaults(func=cli.getOPHT)
parser_uph = subparsers.add_parser(
'updateph', help='update IPv6 FW pinhole')
parser_uph.add_argument('uid', type=int, help='UniqueID of the pinhole')
parser_uph.add_argument('lease', type=int,
help='new leasetime of the pinhole')
parser_uph.set_defaults(func=cli.updatePH)
parser_dph = subparsers.add_parser('delph', help='delete IPv6 FW pinhole')
parser_dph.add_argument('uid', type=int, help='UniqueID of the pinhole')
parser_dph.set_defaults(func=cli.delPH)
parser_gphpkts = subparsers.add_parser(
'getphpkts', help='get number of packets go through specified IPv6FW pinhole')
parser_gphpkts.add_argument(
'uid', type=int, help='UniqueID of the pinhole')
parser_gphpkts.set_defaults(func=cli.getPHPkts)
parser_chkph = subparsers.add_parser(
'chkph', help='check if the specified pinhole is working')
parser_chkph.add_argument('uid', type=int, help='UniqueID of the pinhole')
parser_chkph.set_defaults(func=cli.chkPH)
self.arg_parser = parser
self.cli = cli
def run(self, args):
igdc = self.client.conn.modules['network.lib.igd'].IGDClient
UPNPError = self.client.conn.modules['network.lib.igd'].UPNPError
self.cli.init(igdc, args, self.log)
self.cli.igdc.enableDebug(args.DEBUG)
self.cli.igdc.enablePPrint(args.pretty_print)
try:
args.func(args)
except Exception as e:
if hasattr(e, 'description'):
self.error('IGD: {}'.format(e.description))
else:
self.error('Exception: {}'.format(e))

734
pupy/network/lib/igd.py Normal file
View File

@ -0,0 +1,734 @@
# -*- coding: utf-8 -*-
# Original code from: https://github.com/hujun-open/pyigdc
# Reworked by Oleskii Shevchuk (@alxchk)
# License: MIT
import socket
import argparse
import urllib2
from StringIO import StringIO
from httplib import HTTPResponse
from xml.etree.cElementTree import fromstring
from urlparse import urlparse
import ctypes
import os
import netaddr
def str2bool(bstr):
return bool(int(bstr))
def getProtoId(proto_name):
if isinstance(proto_name, int):
if proto_name > 0 and proto_name <= 65535:
return proto_name
proto_name = 'IPPROTO_{}'.format(proto_name)
if not hasattr(socket, proto_name):
return False
return getattr(socket, proto_name)
class UPNPError(Exception):
def __init__(self, hcode, ucode, udes):
"""
hcode is the http error code
ucode is the upnp error code
udes is the upnp error description
"""
self.http_code = hcode
self.code = ucode
self.description = udes
def __str__(self):
return "HTTP Error Code {hc}, UPnP Error Code {c}, {d}"\
.format(hc=self.http_code, c=self.code, d=self.description)
class FakeSocket(StringIO):
def makefile(self, *args, **kw):
return self
def httpparse(fp):
socket = FakeSocket(fp.read())
response = HTTPResponse(socket)
response.begin()
return response
# sendSOAP is based on part of source code from miranda-upnp.
class IGDClient:
"""
UPnP IGD v1 Client class, supports all actions
"""
UPNPTYPEDICT = {
'NewAutoDisconnectTime': int,
'NewIdleDisconnectTime': int,
'NewWarnDisconnectDelay': int,
'NewPortMappingNumberOfEntries': int,
'NewLeaseDuration': int,
'NewExternalPort': int,
'NewInternalPort': int,
'NewRSIPAvailable': str2bool,
'NewNATEnabled': str2bool,
'NewEnabled': str2bool,
'FirewallEnabled': str2bool,
'InboundPinholeAllowed': str2bool,
'OutboundPinholeTimeout': int,
'UniqueID': int,
'PinholePackets': int,
'IsWorking': str2bool,
}
NS = {
'device': 'urn:schemas-upnp-org:device-1-0',
'control': 'urn:schemas-upnp-org:control-1-0',
'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
}
def __init__(
self,
bindIP='0.0.0.0',
ctrlURL=None,
service="WANIPC",
edebug=False,
pprint=False,
timeout=2.0):
"""
- intIP is the source address of the request packet, which implies the source interface
- ctrlURL is the the control URL of IGD server, client will do discovery if it is None
"""
self.debug = edebug
self.pprint = pprint
self.isv6 = False
self.timeout = timeout
if ctrlURL:
self.ctrlURL = urlparse(self.ctrlURL)
self.bindIP = self._getOutgoingLocalAddress(self.ctrlURL.hostname)
self.isv6 = self.bindIP.version == 6
else:
self.ctrlURL = None
self.bindIP = netaddr.IPAddress(bindIP)
self.isv6 = self.bindIP.version == 6
if self.isv6:
self.igdsvc = "IP6FWCTL"
else:
self.igdsvc = "WANIPC"
self.discovery()
if self.available:
self.intIP = self._getOutgoingLocalAddress()
@property
def available(self):
return self.ctrlURL != None
def enableDebug(self, d=True):
"""
enable debug output
"""
self.debug = d
def enablePPrint(self, p=True):
"""
enable pretty print for XML output
"""
self.pprint = p
def _getOutgoingLocalAddress(self):
remote_addr = netaddr.IPAddress(urlparse(self.ctrlURL).hostname)
rcon = socket.socket(
socket.AF_INET if remote_addr.version == 4 else socket.AF_INET6,
)
rcon.connect((remote_addr.format(), 1900))
return netaddr.IPAddress(rcon.getsockname()[0])
def _get1stTagText(self, xmls, tagname_list):
"""
return 1st tag's value in the xmls
"""
dom = fromstring(xmls)
r = {}
for tagn in tagname_list:
try:
txt_node = dom.find('.//{}'.format(tagn))
if txt_node is not None:
if tagn in self.UPNPTYPEDICT:
r[tagn] = self.UPNPTYPEDICT[tagn](txt_node.text)
else:
r[tagn] = txt_node.text
else:
r[tagn] = None
except:
print"xml parse err: {tag} not found".format(tag=tagn)
return r
def _parseErrMsg(self, err_resp):
"""
parse UPnP error message, err_resp is the returned XML in http body
reurn UPnP error code and error description
"""
dom = fromstring(err_resp)
err_code = dom.find('.//control:errorCode', self.NS)
err_desc = dom.find('.//control:errorDescription', self.NS)
return (err_code.text, err_desc.text)
def discovery(self):
"""
Find IGD device and its control URL via UPnP multicast discovery
"""
if not self.isv6:
up_disc = '\r\n'.join([
'M-SEARCH * HTTP/1.1',
'HOST:239.255.255.250:1900',
'ST:upnp:rootdevice',
'MX:2',
'MAN:"ssdp:discover"'
]) + '\r\n' * 2
sock = socket.socket(
socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.bind((self.bindIP.format(), 19110))
sock.sendto(up_disc, ("239.255.255.250", 1900))
else:
if self.bindIP.is_link_local():
dst_ip = "ff02::c"
else:
dst_ip = "ff05::c"
up_disc = '\r\n'.join([
'M-SEARCH * HTTP/1.1',
'HOST:[{dst}]:1900'.format(dst=dst_ip),
'ST:upnp:rootdevice',
'MX:2',
'MAN:"ssdp:discover"'
]) + '\r\n' * 2
sock = socket.socket(
socket.AF_INET6, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
if self.debug:
print "trying to bind to address:", self.bindIP
socketaddr = socket.getaddrinfo(
self.bindIP.format(), 19110)[-1:][0][-1:][0]
sock.bind(socketaddr)
sock.sendto(up_disc, (dst_ip, 1900))
if self.debug:
print "Discovery: ----- tx request -----\n " + up_disc
sock.settimeout(self.timeout)
try:
data, addr = sock.recvfrom(1024) # buffer size is 1024 bytes
except socket.error:
return
sock.close()
if self.debug:
print "Discovery: ----- rx reply -----\n " + data
descURL = httpparse(StringIO(data)).getheader('location')
descXMLs = urllib2.urlopen(descURL).read()
self.pr = urlparse(descURL)
baseURL = self.pr.scheme + "://" + self.pr.netloc
dom = fromstring(descXMLs)
if self.igdsvc == "WANIPC":
svctype = 'urn:schemas-upnp-org:service:WANIPConnection'
else:
svctype = 'urn:schemas-upnp-org:service:WANIPv6FirewallControl'
for e in dom.findall('.//device:service', self.NS):
stn = e.find('device:serviceType', self.NS)
if not stn is None:
if stn.text[0:-2] == svctype:
cun = e.find('device:controlURL', self.NS).text
self.ctrlURL = baseURL + cun
break
if self.debug:
print "control URL is ", self.ctrlURL
def AddPortMapping(self, extPort, proto, intPort, enabled=1, duration=0, intIP=None, desc='', remoteHost=''):
upnp_method = 'AddPortMapping'
sendArgs = {
'NewPortMappingDescription': (desc, 'string'),
'NewLeaseDuration': (duration, 'ui4'),
'NewInternalClient': (intIP or self.intIP, 'string'),
'NewEnabled': (enabled, 'boolean'),
'NewExternalPort': (extPort, 'ui2'),
'NewRemoteHost': (remoteHost, 'string'),
'NewProtocol': (proto, 'string'),
'NewInternalPort': (intPort, 'ui2')
}
self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL, upnp_method, sendArgs
)
def DeletePortMapping(self, extPort, proto, remoteHost=''):
upnp_method = 'DeletePortMapping'
sendArgs = {
'NewExternalPort': (extPort, 'ui2'),
'NewRemoteHost': (remoteHost, 'string'),
'NewProtocol': (proto, 'string')
}
self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL, upnp_method, sendArgs
)
def GetExternalIP(self):
upnp_method = 'GetExternalIPAddress'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL, upnp_method, sendArgs
)
if resp_xml:
return self._get1stTagText(resp_xml, [
"NewExternalIPAddress"
])
def GetGenericPortMappingEntryAll(self):
index = 0
items = []
while True:
try:
items.append(self.GetGenericPortMappingEntry(index))
except UPNPError as e:
break
index += 1
return items
def GetGenericPortMappingEntry(self, index=None, hideErr=False):
if index is None:
return self.GetGenericPortMappingEntryAll()
upnp_method = 'GetGenericPortMappingEntry'
sendArgs = {
'NewPortMappingIndex': (index, 'ui4'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL, upnp_method, sendArgs, hideErr=hideErr
)
if resp_xml:
return self._get1stTagText(resp_xml, [
"NewExternalPort", "NewRemoteHost",
"NewProtocol", "NewInternalPort",
"NewInternalClient", "NewPortMappingDescription",
"NewLeaseDuration", "NewEnabled"
])
def GetSpecificPortMappingEntry(self, extPort, proto, remote):
upnp_method = 'GetSpecificPortMappingEntry'
sendArgs = {
'NewExternalPort': (extPort, 'ui2'),
'NewRemoteHost': (remote, 'string'),
'NewProtocol': (proto, 'string'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL, upnp_method, sendArgs
)
if resp_xml:
return self._get1stTagText(resp_xml, [
"NewInternalPort",
"NewInternalClient", "NewPortMappingDescription",
"NewLeaseDuration", "NewEnabled"
])
def GetNATRSIPStatus(self):
upnp_method = 'GetNATRSIPStatus'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"NewRSIPAvailable",
"NewNATEnabled",
])
def GetWarnDisconnectDelay(self):
upnp_method = 'GetWarnDisconnectDelay'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"NewWarnDisconnectDelay",
])
def GetIdleDisconnectTime(self):
upnp_method = 'GetIdleDisconnectTime'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"NewIdleDisconnectTime",
])
def GetAutoDisconnectTime(self):
upnp_method = 'GetAutoDisconnectTime'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"NewAutoDisconnectTime",
])
def GetStatusInfo(self):
upnp_method = 'GetStatusInfo'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"NewConnectionStatus",
"NewLastConnectionError",
"NewUptime"
])
def SetWarnDisconnectDelay(self, delay):
upnp_method = 'SetWarnDisconnectDelay'
sendArgs = {
'NewWarnDisconnectDelay': (delay, 'ui4'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL, upnp_method, sendArgs
)
def SetIdleDisconnectTime(self, disconnect_time):
upnp_method = 'SetIdleDisconnectTime'
sendArgs = {
'NewIdleDisconnectTime': (disconnect_time, 'ui4'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL, upnp_method, sendArgs
)
def SetAutoDisconnectTime(self, disconnect_time):
upnp_method = 'SetAutoDisconnectTime'
sendArgs = {
'NewAutoDisconnectTime': (disconnect_time, 'ui4'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL, upnp_method, sendArgs
)
def ForceTermination(self):
upnp_method = 'ForceTermination'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL, upnp_method, sendArgs
)
def RequestTermination(self):
upnp_method = 'RequestTermination'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL,
upnp_method,
sendArgs)
def RequestConnection(self):
upnp_method = 'RequestConnection'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL,
upnp_method,
sendArgs)
def GetConnectionTypeInfo(self):
upnp_method = 'GetConnectionTypeInfo'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"NewConnectionType",
"NewPossibleConnectionTypes", ])
def SetConnectionType(self, ctype):
upnp_method = 'SetConnectionType'
sendArgs = {
'NewConnectionType': (ctype, 'string'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPConnection:1',
self.ctrlURL,
upnp_method,
sendArgs)
def customAction(self, method_name, in_args={}, svc="WANIPConnection"):
"""
this is for the vendor specific action
in_args is a dict,
svc is the IGD service,
the format is :
key is the argument name
value is a two element list, 1st one is the value of arguement, 2nd
is the UPnP data type defined in the spec. following is an example:
{'NewPortMappingIndex': [0, 'ui4'],}
"""
upnp_method = method_name
sendArgs = dict(in_args)
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:{svc}:1'.format(
svc=svc),
self.ctrlURL,
upnp_method,
sendArgs
)
return resp_xml
def sendSOAP(self, hostName, serviceType, controlURL, actionName,
actionArguments, hideErr=False):
"""
send a SOAP request and get the response
"""
argList = ''
if not controlURL:
self.discovery()
# Create a string containing all of the SOAP action's arguments and
# values
for arg, (val, dt) in actionArguments.iteritems():
argList += '<%s>%s</%s>' % (arg, val, arg)
# Create the SOAP request
soapBody = '<?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:{} xmlns:m="{}">' \
'{}' \
'</m:{}>' \
'</SOAP-ENV:Body>' \
'</SOAP-ENV:Envelope>'.format(
actionName,
serviceType,
argList,
actionName
)
try:
response = urllib2.urlopen(
urllib2.Request(controlURL, soapBody, {
'Content-Type': 'text/xml',
'SOAPAction': '"{}#{}"'.format(
serviceType,
actionName
)
}))
except urllib2.HTTPError as e:
err_code, err_desc = self._parseErrMsg(e.read())
raise UPNPError(e.code, err_code, err_desc)
return response.read()
# following are for IP6FWControl
def GetFWStatus(self):
upnp_method = 'GetFirewallStatus'
sendArgs = {}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPv6FirewallControl:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"FirewallEnabled", "InboundPinholeAllowed"])
def AddPinhole(
self,
iclient,
rhost="",
rport=0,
iport=0,
proto=65535,
leasetime=3600):
upnp_method = "AddPinhole"
pid = getProtoId(proto)
if not pid:
print proto, " is not a supported protocol"
return
sendArgs = {
"RemoteHost": (rhost, 'string'),
"RemotePort": (rport, 'ui2'),
"InternalClient": (iclient, 'string'),
"InternalPort": (iport, 'ui2'),
"Protocol": (pid, 'ui2'),
"LeaseTime": (leasetime, 'ui4'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPv6FirewallControl:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"UniqueID", ])
def GetPinholeTimeout(
self,
iclient="",
rhost="",
rport=0,
iport=0,
proto=65535):
upnp_method = "GetOutboundPinholeTimeout"
pid = getProtoId(proto)
if not pid:
print proto, " is not a supported protocol"
return
sendArgs = {
"RemoteHost": (rhost, 'string'),
"RemotePort": (rport, 'ui2'),
"InternalClient": (iclient, 'string'),
"InternalPort": (iport, 'ui2'),
"Protocol": (pid, 'ui2'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPv6FirewallControl:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"OutboundPinholeTimeout",
])
def UpdatePinhole(self, uid, lease):
upnp_method = "UpdatePinhole"
sendArgs = {
"UniqueID": (uid, 'ui2'),
"NewLeaseTime": (lease, 'ui4'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPv6FirewallControl:1',
self.ctrlURL,
upnp_method,
sendArgs
)
def DelPinhole(self, uid):
upnp_method = "DeletePinhole"
sendArgs = {
"UniqueID": (uid, 'ui2'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPv6FirewallControl:1',
self.ctrlURL,
upnp_method,
sendArgs
)
def GetPinholePkts(self, uid):
upnp_method = "GetPinholePackets"
sendArgs = {
"UniqueID": (uid, 'ui2'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPv6FirewallControl:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"PinholePackets",
])
def CheckPinhole(self, uid):
upnp_method = "CheckPinholeWorking"
sendArgs = {
"UniqueID": (uid, 'ui2'),
}
resp_xml = self.sendSOAP(
self.pr.netloc,
'urn:schemas-upnp-org:service:WANIPv6FirewallControl:1',
self.ctrlURL,
upnp_method,
sendArgs)
if resp_xml:
return self._get1stTagText(resp_xml, [
"IsWorking",
])

View File

@ -21,6 +21,8 @@ from threading import Thread, RLock
from streams.PupySocketStream import addGetPeer
from network.lib.connection import PupyConnection
from network.lib.igd import IGDClient, UPNPError
class PupyTCPServer(ThreadedServer):
def __init__(self, *args, **kwargs):
@ -34,12 +36,24 @@ class PupyTCPServer(ThreadedServer):
self.transport_class = kwargs["transport"]
self.transport_kwargs = kwargs["transport_kwargs"]
self.igd = None
self.igd_mapping = False
del kwargs["stream"]
del kwargs["transport"]
del kwargs["transport_kwargs"]
ThreadedServer.__init__(self, *args, **kwargs)
try:
self.igd = IGDClient()
if self.igd.available:
self.igd.AddPortMapping(self.port, 'TCP', self.port)
self.igd_mapping = True
except UPNPError as e:
self.logger.warn("Couldn't create IGD mapping: {}".format(e.description))
def _setup_connection(self, lock, sock, queue):
'''Authenticate a client and if it succeeds, wraps the socket in a connection object.
Note that this code is cut and paste from the rpyc internals and may have to be
@ -124,6 +138,15 @@ class PupyTCPServer(ThreadedServer):
self.clients.discard(sock)
def close(self):
ThreadedServer.close(self)
if self.igd_mapping:
try:
self.igd.DeletePortMapping(self.port, 'TCP')
except Exception as e:
self.logger.info('IGD Exception: {}/{}'.format(type(e), e))
class PupyUDPServer(object):
def __init__(self, service, **kwargs):
if not "stream" in kwargs: