This commit is contained in:
Jack Robison 2019-05-22 02:56:59 -04:00
parent f5f3cc5f95
commit d87a1704ef
No known key found for this signature in database
GPG key ID: DF25C68FE0239BB2
4 changed files with 62 additions and 9 deletions

View file

@ -2,6 +2,7 @@ import socket
from collections import OrderedDict from collections import OrderedDict
import typing import typing
import netifaces import netifaces
from aioupnp.fault import UPnPError
def get_netifaces(): def get_netifaces():
@ -50,4 +51,4 @@ def get_gateway_and_lan_addresses(interface_name: str) -> typing.Tuple[str, str]
for iface_name, (gateway, lan) in get_interfaces().items(): for iface_name, (gateway, lan) in get_interfaces().items():
if interface_name == iface_name: if interface_name == iface_name:
return gateway, lan return gateway, lan
return '', '' raise UPnPError(f'failed to get lan and gateway addresses for {interface_name}')

View file

@ -47,10 +47,7 @@ class UPnP:
async def discover(cls, lan_address: str = '', gateway_address: str = '', timeout: int = 30, async def discover(cls, lan_address: str = '', gateway_address: str = '', timeout: int = 30,
igd_args: Optional[Dict[str, Union[str, int]]] = None, interface_name: str = 'default', igd_args: Optional[Dict[str, Union[str, int]]] = None, interface_name: str = 'default',
loop: Optional[asyncio.AbstractEventLoop] = None) -> 'UPnP': loop: Optional[asyncio.AbstractEventLoop] = None) -> 'UPnP':
try: lan_address, gateway_address = cls.get_lan_and_gateway(lan_address, gateway_address, interface_name)
lan_address, gateway_address = cls.get_lan_and_gateway(lan_address, gateway_address, interface_name)
except Exception as err:
raise UPnPError("failed to get lan and gateway addresses: %s" % str(err))
gateway = await Gateway.discover_gateway( gateway = await Gateway.discover_gateway(
lan_address, gateway_address, timeout, igd_args, loop lan_address, gateway_address, timeout, igd_args, loop
) )
@ -135,8 +132,14 @@ class UPnP:
async def get_next_mapping(self, port: int, protocol: str, description: str, async def get_next_mapping(self, port: int, protocol: str, description: str,
internal_port: Optional[int] = None) -> int: internal_port: Optional[int] = None) -> int:
if protocol not in ["UDP", "TCP"]: """
raise UPnPError("unsupported protocol: {}".format(protocol)) :param port: (int) external port to redirect from
:param protocol: (str) 'UDP' | 'TCP'
:param description: (str) mapping description
:param internal_port: (int) internal port to redirect to
:return: (int) <mapped port>
"""
_internal_port = int(internal_port or port) _internal_port = int(internal_port or port)
requested_port = int(_internal_port) requested_port = int(_internal_port)
port = int(port) port = int(port)

View file

@ -1,6 +1,7 @@
import unittest
from unittest import mock from unittest import mock
from aioupnp.fault import UPnPError
from aioupnp.upnp import UPnP from aioupnp.upnp import UPnP
from tests import AsyncioTestCase
class mock_netifaces: class mock_netifaces:
@ -46,7 +47,7 @@ class mock_netifaces:
}[interface] }[interface]
class TestParseInterfaces(unittest.TestCase): class TestParseInterfaces(AsyncioTestCase):
def test_parse_interfaces(self): def test_parse_interfaces(self):
with mock.patch('aioupnp.interfaces.get_netifaces') as patch: with mock.patch('aioupnp.interfaces.get_netifaces') as patch:
patch.return_value = mock_netifaces patch.return_value = mock_netifaces
@ -54,3 +55,16 @@ class TestParseInterfaces(unittest.TestCase):
lan, gateway = UPnP.get_lan_and_gateway(interface_name='test0') lan, gateway = UPnP.get_lan_and_gateway(interface_name='test0')
self.assertEqual(gateway, '192.168.1.1') self.assertEqual(gateway, '192.168.1.1')
self.assertEqual(lan, '192.168.1.2') self.assertEqual(lan, '192.168.1.2')
async def test_netifaces_fail(self):
checked = []
with mock.patch('aioupnp.interfaces.get_netifaces') as patch:
patch.return_value = mock_netifaces
try:
await UPnP.discover(interface_name='test1')
except UPnPError as err:
self.assertEqual(str(err), 'failed to get lan and gateway addresses for test1')
checked.append(True)
else:
self.assertTrue(False)
self.assertTrue(len(checked) == 1)

View file

@ -1,10 +1,22 @@
from tests import AsyncioTestCase, mock_tcp_and_udp from tests import AsyncioTestCase, mock_tcp_and_udp
from collections import OrderedDict from collections import OrderedDict
from aioupnp.upnp import UPnP from aioupnp.upnp import UPnP
from aioupnp.fault import UPnPError
from aioupnp.gateway import Gateway from aioupnp.gateway import Gateway
from aioupnp.serialization.ssdp import SSDPDatagram from aioupnp.serialization.ssdp import SSDPDatagram
class TestGetAnnotations(AsyncioTestCase):
def test_get_annotations(self):
expected = {
'NewRemoteHost': str, 'NewExternalPort': int, 'NewProtocol': str, 'NewInternalPort': int,
'NewInternalClient': str, 'NewEnabled': int, 'NewPortMappingDescription': str,
'NewLeaseDuration': str, 'return': None
}
self.assertDictEqual(expected, UPnP.get_annotations('AddPortMapping'))
class UPnPCommandTestCase(AsyncioTestCase): class UPnPCommandTestCase(AsyncioTestCase):
gateway_address = "11.2.3.4" gateway_address = "11.2.3.4"
client_address = "11.2.3.5" client_address = "11.2.3.5"
@ -88,3 +100,26 @@ class TestGetNextPortMapping(UPnPCommandTestCase):
self.assertEqual(4567, ext_port) self.assertEqual(4567, ext_port)
result = await upnp.delete_port_mapping(ext_port, "UDP") result = await upnp.delete_port_mapping(ext_port, "UDP")
self.assertEqual(None, result) self.assertEqual(None, result)
class TestGetSpecificPortMapping(UPnPCommandTestCase):
client_address = '11.2.3.4'
def setUp(self) -> None:
self.replies.update({
b'POST /soap.cgi?service=WANIPConn1 HTTP/1.1\r\nHost: 11.2.3.4\r\nUser-Agent: python3/aioupnp, UPnP/1.0, MiniUPnPc/1.9\r\nContent-Length: 399\r\nContent-Type: text/xml\r\nSOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetSpecificPortMappingEntry"\r\nConnection: Close\r\nCache-Control: no-cache\r\nPragma: no-cache\r\n\r\n<?xml version="1.0"?>\r\n<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetSpecificPortMappingEntry xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"><NewRemoteHost></NewRemoteHost><NewExternalPort>1000</NewExternalPort><NewProtocol>UDP</NewProtocol></u:GetSpecificPortMappingEntry></s:Body></s:Envelope>\r\n': b'HTTP/1.1 500 Internal Server Error\r\nServer: WebServer\r\nDate: Wed, 22 May 2019 06:48:57 GMT\r\nConnection: close\r\nCONTENT-TYPE: text/xml; charset="utf-8"\r\nCONTENT-LENGTH: 474 \r\nEXT:\r\n\r\n<?xml version="1.0"?>\n<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\n\t<s:Body>\n\t\t<s:Fault>\n\t\t\t<faultcode>s:Client</faultcode>\n\t\t\t<faultstring>UPnPError</faultstring>\n\t\t\t<detail>\n\t\t\t\t<UPnPError xmlns="urn:schemas-upnp-org:control-1-0">\n\t\t\t\t\t<errorCode>714</errorCode>\n\t\t\t\t\t<errorDescription>NoSuchEntryInArray</errorDescription>\n\t\t\t\t</UPnPError>\n\t\t\t</detail>\n\t\t</s:Fault>\n\t</s:Body>\n</s:Envelope>\n',
b'POST /soap.cgi?service=WANIPConn1 HTTP/1.1\r\nHost: 11.2.3.4\r\nUser-Agent: python3/aioupnp, UPnP/1.0, MiniUPnPc/1.9\r\nContent-Length: 399\r\nContent-Type: text/xml\r\nSOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetSpecificPortMappingEntry"\r\nConnection: Close\r\nCache-Control: no-cache\r\nPragma: no-cache\r\n\r\n<?xml version="1.0"?>\r\n<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:GetSpecificPortMappingEntry xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"><NewRemoteHost></NewRemoteHost><NewExternalPort>9308</NewExternalPort><NewProtocol>UDP</NewProtocol></u:GetSpecificPortMappingEntry></s:Body></s:Envelope>\r\n': b'HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Wed, 22 May 2019 06:50:07 GMT\r\nConnection: close\r\nCONTENT-TYPE: text/xml; charset="utf-8"\r\nCONTENT-LENGTH: 562 \r\nEXT:\r\n\r\n<?xml version="1.0"?>\n<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">\n\t<s:Body>\n\t\t<u:GetSpecificPortMappingEntryResponse xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1">\n\n<NewInternalPort>9308</NewInternalPort>\n<NewInternalClient>11.2.3.55</NewInternalClient>\n<NewEnabled>1</NewEnabled>\n<NewPortMappingDescription>11.2.3.55:9308 to 9308 (UDP)</NewPortMappingDescription>\n<NewLeaseDuration>0</NewLeaseDuration>\n</u:GetSpecificPortMappingEntryResponse>\n\t</s:Body>\n</s:Envelope>\n'
})
async def test_get_specific_port_mapping(self):
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
gateway = Gateway(self.reply, self.m_search_args, self.client_address, self.gateway_address)
await gateway.discover_commands(self.loop)
upnp = UPnP(self.client_address, self.gateway_address, gateway)
try:
await upnp.get_specific_port_mapping(1000, 'UDP')
except UPnPError:
result = await upnp.get_specific_port_mapping(9308, 'UDP')
self.assertEqual((9308, '11.2.3.55', True, '11.2.3.55:9308 to 9308 (UDP)', 0), result)
else:
self.assertTrue(False)