aioupnp/txupnp/upnp.py

147 lines
5.7 KiB
Python
Raw Normal View History

2018-10-01 15:51:31 +02:00
import netifaces
2018-07-27 01:49:33 +02:00
import logging
from twisted.internet import defer
from txupnp.fault import UPnPError
2018-10-01 15:51:31 +02:00
from txupnp.ssdp import SSDPFactory
from txupnp.gateway import Gateway
2018-07-27 01:49:33 +02:00
log = logging.getLogger(__name__)
2018-10-01 15:51:31 +02:00
class UPnP:
def __init__(self, reactor, try_miniupnpc_fallback=False, debug_ssdp=False, router_ip=None,
lan_ip=None, iface_name=None):
2018-07-29 04:08:24 +02:00
self._reactor = reactor
2018-10-01 15:51:31 +02:00
if router_ip and lan_ip and iface_name:
self.router_ip, self.lan_address, self.iface_name = router_ip, lan_ip, iface_name
else:
self.router_ip, self.iface_name = netifaces.gateways()['default'][netifaces.AF_INET]
self.lan_address = netifaces.ifaddresses(self.iface_name)[netifaces.AF_INET][0]['addr']
self.sspd_factory = SSDPFactory(self._reactor, self.lan_address, self.router_ip, debug_packets=debug_ssdp)
2018-08-02 16:02:52 +02:00
self.try_miniupnpc_fallback = try_miniupnpc_fallback
2018-07-31 17:23:21 +02:00
self.miniupnpc_runner = None
2018-08-02 16:02:52 +02:00
self.miniupnpc_igd_url = None
2018-10-01 15:51:31 +02:00
self.gateway = None
2018-07-27 01:49:33 +02:00
2018-10-01 15:51:31 +02:00
def m_search(self, address, timeout=1, max_devices=1):
2018-07-27 01:49:33 +02:00
"""
Perform a HTTP over UDP M-SEARCH query
2018-07-29 04:08:24 +02:00
returns (list) [{
'server: <gateway os and version string>
2018-07-27 01:49:33 +02:00
'location': <upnp gateway url>,
'cache-control': <max age>,
'date': <server time>,
'usn': <usn>
2018-07-29 04:08:24 +02:00
}, ...]
2018-07-27 01:49:33 +02:00
"""
2018-10-01 15:51:31 +02:00
return self.sspd_factory.m_search(address, timeout=timeout, max_devices=max_devices)
2018-07-27 01:49:33 +02:00
@defer.inlineCallbacks
2018-10-01 15:51:31 +02:00
def _discover(self, timeout=1, max_devices=1):
server_infos = yield self.sspd_factory.m_search(
self.router_ip, timeout=timeout, max_devices=max_devices
)
2018-10-05 00:34:33 +02:00
if not server_infos:
return False
2018-10-01 15:51:31 +02:00
server_info = server_infos[0]
if 'st' in server_info:
gateway = Gateway(reactor=self._reactor, **server_info)
yield gateway.discover_commands()
self.gateway = gateway
2018-10-05 00:34:33 +02:00
return True
2018-10-01 15:51:31 +02:00
elif 'st' not in server_info:
log.error("don't know how to handle gateway: %s", server_info)
2018-10-05 00:34:33 +02:00
return False
2018-08-02 16:02:52 +02:00
@defer.inlineCallbacks
2018-10-01 15:51:31 +02:00
def discover(self, timeout=1, max_devices=1):
try:
found = yield self._discover(timeout=timeout, max_devices=max_devices)
except defer.TimeoutError:
found = False
finally:
self.sspd_factory.disconnect()
if found:
log.debug("found upnp device")
else:
log.debug("failed to find upnp device")
2018-10-05 00:34:33 +02:00
return found
2018-07-27 01:49:33 +02:00
2018-10-01 15:51:31 +02:00
def get_external_ip(self) -> str:
return self.gateway.commands.GetExternalIPAddress()
2018-07-27 01:49:33 +02:00
2018-10-01 15:51:31 +02:00
def add_port_mapping(self, external_port: int, protocol: str, internal_port, lan_address: str,
description: str) -> None:
return self.gateway.commands.AddPortMapping(
2018-08-01 23:57:27 +02:00
NewRemoteHost="", NewExternalPort=external_port, NewProtocol=protocol,
2018-07-27 01:49:33 +02:00
NewInternalPort=internal_port, NewInternalClient=lan_address,
2018-08-07 19:53:35 +02:00
NewEnabled=1, NewPortMappingDescription=description, NewLeaseDuration=""
2018-07-27 01:49:33 +02:00
)
2018-08-21 18:10:11 +02:00
@defer.inlineCallbacks
2018-10-01 15:51:31 +02:00
def get_port_mapping_by_index(self, index: int) -> (str, int, str, int, str, bool, str, int):
2018-08-21 18:10:11 +02:00
try:
2018-10-01 15:51:31 +02:00
redirect = yield self.gateway.commands.GetGenericPortMappingEntry(NewPortMappingIndex=index)
2018-08-21 18:10:11 +02:00
defer.returnValue(redirect)
except UPnPError:
defer.returnValue(None)
2018-07-27 01:49:33 +02:00
@defer.inlineCallbacks
def get_redirects(self):
redirects = []
cnt = 0
2018-08-21 18:10:11 +02:00
redirect = yield self.get_port_mapping_by_index(cnt)
while redirect:
redirects.append(redirect)
cnt += 1
redirect = yield self.get_port_mapping_by_index(cnt)
2018-07-27 01:49:33 +02:00
defer.returnValue(redirects)
2018-07-30 23:48:20 +02:00
@defer.inlineCallbacks
2018-10-01 15:51:31 +02:00
def get_specific_port_mapping(self, external_port: int, protocol: str) -> (int, str, bool, str, int):
2018-07-29 04:08:24 +02:00
"""
:param external_port: (int) external port to listen on
:param protocol: (str) 'UDP' | 'TCP'
:return: (int) <internal port>, (str) <lan ip>, (bool) <enabled>, (str) <description>, (int) <lease time>
"""
2018-07-30 23:48:20 +02:00
try:
2018-10-01 15:51:31 +02:00
result = yield self.gateway.commands.GetSpecificPortMappingEntry(
2018-07-30 23:48:20 +02:00
NewRemoteHost=None, NewExternalPort=external_port, NewProtocol=protocol
)
defer.returnValue(result)
2018-08-21 18:10:11 +02:00
except UPnPError:
defer.returnValue(None)
2018-07-27 01:49:33 +02:00
2018-10-01 15:51:31 +02:00
def delete_port_mapping(self, external_port: int, protocol: str) -> None:
2018-07-29 04:08:24 +02:00
"""
:param external_port: (int) external port to listen on
:param protocol: (str) 'UDP' | 'TCP'
:return: None
"""
2018-10-01 15:51:31 +02:00
return self.gateway.commands.DeletePortMapping(
NewRemoteHost="", NewExternalPort=external_port, NewProtocol=protocol
2018-07-27 01:49:33 +02:00
)
2018-07-29 04:08:24 +02:00
2018-07-30 23:48:20 +02:00
@defer.inlineCallbacks
2018-08-07 19:53:35 +02:00
def get_next_mapping(self, port, protocol, description, internal_port=None):
2018-07-30 23:48:20 +02:00
if protocol not in ["UDP", "TCP"]:
raise UPnPError("unsupported protocol: {}".format(protocol))
2018-08-07 19:53:35 +02:00
internal_port = internal_port or port
redirect_tups = yield self.get_redirects()
redirects = {
"%i:%s" % (ext_port, proto): (int_host, int_port, desc)
for (ext_host, ext_port, proto, int_port, int_host, enabled, desc, lease) in redirect_tups
}
while ("%i:%s" % (port, protocol)) in redirects:
int_host, int_port, _ = redirects["%i:%s" % (port, protocol)]
if int_host == self.lan_address and int_port == internal_port:
break
port += 1
yield self.add_port_mapping( # set one up
port, protocol, internal_port, self.lan_address, description
2018-07-30 23:48:20 +02:00
)
defer.returnValue(port)