diff --git a/aioupnp/commands.py b/aioupnp/commands.py index 163ad44..a254216 100644 --- a/aioupnp/commands.py +++ b/aioupnp/commands.py @@ -4,6 +4,7 @@ import typing import logging from aioupnp.protocols.scpd import scpd_post from aioupnp.device import Service +from aioupnp.fault import UPnPError log = logging.getLogger(__name__) @@ -35,17 +36,38 @@ class GetGenericPortMappingEntryResponse(typing.NamedTuple): lease_time: int -def recast_return(return_annotation, result: typing.Dict[str, typing.Union[int, str]], +class SCPDRequestDebuggingInfo(typing.NamedTuple): + method: str + kwargs: typing.Dict[str, typing.Union[str, int, bool]] + response_xml: bytes + result: typing.Optional[typing.Union[str, int, bool, GetSpecificPortMappingEntryResponse, + GetGenericPortMappingEntryResponse]] + err: typing.Optional[Exception] + ts: float + + +def recast_return(return_annotation, result: typing.Union[str, int, bool, typing.Dict[str, typing.Union[int, str]]], result_keys: typing.List[str]) -> typing.Optional[ typing.Union[str, int, bool, GetSpecificPortMappingEntryResponse, GetGenericPortMappingEntryResponse]]: if len(result_keys) == 1: - single_result = result[result_keys[0]] + if isinstance(result, (str, int, bool)): + single_result = result + else: + if result_keys[0] in result: + single_result = result[result_keys[0]] + else: # check for the field having incorrect capitalization + flattened = {k.lower(): v for k, v in result.items()} + if result_keys[0].lower() in flattened: + single_result = flattened[result_keys[0].lower()] + else: + raise UPnPError(f"expected response key {result_keys[0]}, got {list(result.keys())}") if return_annotation is bool: return soap_bool(single_result) if return_annotation is str: return soap_optional_str(single_result) - return int(result[result_keys[0]]) if result_keys[0] in result else None + return None if single_result is None else int(single_result) elif return_annotation in [GetGenericPortMappingEntryResponse, GetSpecificPortMappingEntryResponse]: + assert isinstance(result, dict) arg_types: typing.Dict[str, typing.Type[typing.Any]] = return_annotation._field_types assert len(arg_types) == len(result_keys) recast_results: typing.Dict[str, typing.Optional[typing.Union[str, int, bool]]] = {} @@ -108,11 +130,7 @@ class SOAPCommands: self._base_address = base_address self._port = port - self._requests: typing.List[typing.Tuple[str, typing.Dict[str, typing.Any], bytes, - typing.Optional[typing.Union[str, int, bool, - GetSpecificPortMappingEntryResponse, - GetGenericPortMappingEntryResponse]], - typing.Optional[Exception], float]] = [] + self._request_debug_infos: typing.List[SCPDRequestDebuggingInfo] = [] def is_registered(self, name: str) -> bool: if name not in self.SOAP_COMMANDS: @@ -147,11 +165,17 @@ class SOAPCommands: ) if err is not None: assert isinstance(xml_bytes, bytes) - self._requests.append((name, kwargs, xml_bytes, None, err, time.time())) + self._request_debug_infos.append(SCPDRequestDebuggingInfo(name, kwargs, xml_bytes, None, err, time.time())) raise err assert 'return' in annotations - result = recast_return(annotations['return'], response, output_names) - self._requests.append((name, kwargs, xml_bytes, result, None, time.time())) + try: + result = recast_return(annotations['return'], response, output_names) + self._request_debug_infos.append(SCPDRequestDebuggingInfo(name, kwargs, xml_bytes, result, None, time.time())) + except Exception as err: + if isinstance(err, asyncio.CancelledError): + raise + self._request_debug_infos.append(SCPDRequestDebuggingInfo(name, kwargs, xml_bytes, None, err, time.time())) + raise UPnPError(f"Raised {str(type(err).__name__)}({str(err)}) parsing response for {name}") return result if not len(list(k for k in annotations if k != 'return')): diff --git a/aioupnp/gateway.py b/aioupnp/gateway.py index cd2a68c..95dd81c 100644 --- a/aioupnp/gateway.py +++ b/aioupnp/gateway.py @@ -6,7 +6,7 @@ from collections import OrderedDict from typing import Dict, List from aioupnp.util import get_dict_val_case_insensitive from aioupnp.constants import SPEC_VERSION, SERVICE -from aioupnp.commands import SOAPCommands +from aioupnp.commands import SOAPCommands, SCPDRequestDebuggingInfo from aioupnp.device import Device, Service from aioupnp.protocols.ssdp import fuzzy_m_search, m_search from aioupnp.protocols.scpd import scpd_get @@ -85,7 +85,7 @@ class Gateway: self.urn: bytes = (ok_packet.st or '').encode() self._xml_response: bytes = b"" - self._service_descriptors: Dict[str, bytes] = {} + self._service_descriptors: Dict[str, str] = {} self.base_address, self.port = parse_location(self.location) self.base_ip = self.base_address.lstrip(b"http://").split(b":")[0] @@ -103,17 +103,6 @@ class Gateway: self._registered_commands: Dict[str, str] = {} self.commands = SOAPCommands(self._loop, self.base_ip, self.port) - # def gateway_descriptor(self) -> dict: - # r = { - # 'server': self.server.decode(), - # 'urlBase': self.url_base, - # 'location': self.location.decode(), - # "specVersion": self.spec_version, - # 'usn': self.usn.decode(), - # 'urn': self.urn.decode(), - # } - # return r - @property def manufacturer_string(self) -> str: manufacturer_string = "UNKNOWN GATEWAY" @@ -147,37 +136,26 @@ class Gateway: # return service # return None - # @property - # def soap_requests(self) -> typing.List[typing.Tuple[str, typing.Dict[str, typing.Any], bytes, - # typing.Optional[typing.Tuple], - # typing.Optional[Exception], float]]: - # soap_call_infos: typing.List[typing.Tuple[str, typing.Dict[str, typing.Any], bytes, - # typing.Optional[typing.Tuple], - # typing.Optional[Exception], float]] = [] - # soap_call_infos.extend([ - # (name, request_args, raw_response, decoded_response, soap_error, ts) - # for ( - # name, request_args, raw_response, decoded_response, soap_error, ts - # ) in self.commands._requests - # ]) - # soap_call_infos.sort(key=lambda x: x[5]) - # return soap_call_infos - - # def debug_gateway(self) -> Dict[str, Union[str, bytes, int, Dict, List]]: - # return { - # 'manufacturer_string': self.manufacturer_string, - # 'gateway_address': self.base_ip, - # 'gateway_descriptor': self.gateway_descriptor(), - # 'gateway_xml': self._xml_response, - # 'services_xml': self._service_descriptors, - # 'services': {service.SCPDURL: service.as_dict() for service in self._services}, - # 'm_search_args': [(k, v) for (k, v) in self._m_search_args.items()], - # 'reply': self._ok_packet.as_dict(), - # 'soap_port': self.port, - # 'registered_soap_commands': self._registered_commands, - # 'unsupported_soap_commands': self._unsupported_actions, - # 'soap_requests': self.soap_requests - # } + def debug_gateway(self) -> Dict[str, typing.Union[str, bytes, int, Dict, List]]: + return { + 'manufacturer_string': self.manufacturer_string, + 'gateway_address': self.base_ip.decode(), + 'server': self.server.decode(), + 'urlBase': self.url_base or '', + 'location': self.location.decode(), + "specVersion": self.spec_version or '', + 'usn': self.usn.decode(), + 'urn': self.urn.decode(), + 'gateway_xml': self._xml_response.decode(), + 'services_xml': self._service_descriptors, + 'services': {service.SCPDURL: service.as_dict() for service in self._services}, + 'm_search_args': OrderedDict(self._m_search_args), + 'reply': self._ok_packet.as_dict(), + 'soap_port': self.port, + 'registered_soap_commands': self._registered_commands, + 'unsupported_soap_commands': self._unsupported_actions, + 'soap_requests': list(self.commands._request_debug_infos) + } @classmethod async def _discover_gateway(cls, lan_address: str, gateway_address: str, timeout: int = 30, @@ -201,7 +179,7 @@ class Gateway: try: gateway = cls(datagram, m_search_args, lan_address, gateway_address, loop=loop) log.debug('get gateway descriptor %s', datagram.location) - await gateway.discover_commands(loop) + await gateway.discover_commands() requirements_met = all([gateway.commands.is_registered(required) for required in required_commands]) if not requirements_met: not_met = [ @@ -249,8 +227,10 @@ class Gateway: results: typing.List['asyncio.Future[Gateway]'] = list(done) return results[0].result() - async def discover_commands(self, loop: typing.Optional[asyncio.AbstractEventLoop] = None) -> None: - response, xml_bytes, get_err = await scpd_get(self.path.decode(), self.base_ip.decode(), self.port, loop=loop) + async def discover_commands(self) -> None: + response, xml_bytes, get_err = await scpd_get( + self.path.decode(), self.base_ip.decode(), self.port, loop=self._loop + ) self._xml_response = xml_bytes if get_err is not None: raise get_err @@ -286,7 +266,7 @@ class Gateway: else: self._device = Device(self._devices, self._services) for service_type in self.services.keys(): - await self.register_commands(self.services[service_type], loop) + await self.register_commands(self.services[service_type], self._loop) return None async def register_commands(self, service: Service, @@ -298,7 +278,7 @@ class Gateway: log.debug("get descriptor for %s from %s", service.serviceType, service.SCPDURL) service_dict, xml_bytes, get_err = await scpd_get(service.SCPDURL, self.base_ip.decode(), self.port, loop=loop) - self._service_descriptors[service.SCPDURL] = xml_bytes + self._service_descriptors[service.SCPDURL] = xml_bytes.decode() if get_err is not None: log.debug("failed to get descriptor for %s from %s", service.serviceType, service.SCPDURL) diff --git a/aioupnp/protocols/scpd.py b/aioupnp/protocols/scpd.py index 9412946..a85baab 100644 --- a/aioupnp/protocols/scpd.py +++ b/aioupnp/protocols/scpd.py @@ -45,7 +45,7 @@ class SCPDHTTPClientProtocol(Protocol): and devices respond with an invalid HTTP version line """ - def __init__(self, message: bytes, finished: 'asyncio.Future[typing.Tuple[bytes, int, bytes]]', + def __init__(self, message: bytes, finished: 'asyncio.Future[typing.Tuple[bytes, bytes, int, bytes]]', soap_method: typing.Optional[str] = None, soap_service_id: typing.Optional[str] = None) -> None: self.message = message self.response_buff = b"" @@ -85,7 +85,7 @@ class SCPDHTTPClientProtocol(Protocol): self._got_headers = True body = b'\r\n'.join(self.response_buff.split(b'\r\n')[i+1:]) if self._content_length == len(body): - self.finished.set_result((body, self._response_code, self._response_msg)) + self.finished.set_result((self.response_buff, body, self._response_code, self._response_msg)) elif self._content_length > len(body): pass else: @@ -105,7 +105,7 @@ async def scpd_get(control_url: str, address: str, port: int, typing.Dict[str, typing.Any], bytes, typing.Optional[Exception]]: loop = loop or asyncio.get_event_loop() packet = serialize_scpd_get(control_url, address) - finished: 'asyncio.Future[typing.Tuple[bytes, int, bytes]]' = asyncio.Future(loop=loop) + finished: 'asyncio.Future[typing.Tuple[bytes, bytes, int, bytes]]' = asyncio.Future(loop=loop) proto_factory: typing.Callable[[], SCPDHTTPClientProtocol] = lambda: SCPDHTTPClientProtocol(packet, finished) connect_tup: typing.Tuple[asyncio.BaseTransport, asyncio.BaseProtocol] = await loop.create_connection( proto_factory, address, port @@ -115,24 +115,25 @@ async def scpd_get(control_url: str, address: str, port: int, assert isinstance(protocol, SCPDHTTPClientProtocol) error = None - wait_task: typing.Awaitable[typing.Tuple[bytes, int, bytes]] = asyncio.wait_for(protocol.finished, 1.0, loop=loop) + wait_task: typing.Awaitable[typing.Tuple[bytes, bytes, int, bytes]] = asyncio.wait_for(protocol.finished, 1.0, loop=loop) + body = b'' + raw_response = b'' try: - body, response_code, response_msg = await wait_task + raw_response, body, response_code, response_msg = await wait_task except asyncio.TimeoutError: error = UPnPError("get request timed out") - body = b'' except UPnPError as err: error = err - body = protocol.response_buff + raw_response = protocol.response_buff finally: transport.close() if not error: try: - return deserialize_scpd_get_response(body), body, None + return deserialize_scpd_get_response(body), raw_response, None except Exception as err: error = UPnPError(err) - return {}, body, error + return {}, raw_response, error async def scpd_post(control_url: str, address: str, port: int, method: str, param_names: list, service_id: bytes, @@ -140,7 +141,7 @@ async def scpd_post(control_url: str, address: str, port: int, method: str, para **kwargs: typing.Dict[str, typing.Any] ) -> typing.Tuple[typing.Dict, bytes, typing.Optional[Exception]]: loop = loop or asyncio.get_event_loop() - finished: 'asyncio.Future[typing.Tuple[bytes, int, bytes]]' = asyncio.Future(loop=loop) + finished: 'asyncio.Future[typing.Tuple[bytes, bytes, int, bytes]]' = asyncio.Future(loop=loop) packet = serialize_soap_post(method, param_names, service_id, address.encode(), control_url.encode(), **kwargs) proto_factory: typing.Callable[[], SCPDHTTPClientProtocol] = lambda:\ SCPDHTTPClientProtocol(packet, finished, soap_method=method, soap_service_id=service_id.decode()) @@ -152,18 +153,17 @@ async def scpd_post(control_url: str, address: str, port: int, method: str, para assert isinstance(protocol, SCPDHTTPClientProtocol) try: - wait_task: typing.Awaitable[typing.Tuple[bytes, int, bytes]] = asyncio.wait_for(finished, 1.0, loop=loop) - body, response_code, response_msg = await wait_task + wait_task: typing.Awaitable[typing.Tuple[bytes, bytes, int, bytes]] = asyncio.wait_for(finished, 1.0, loop=loop) + raw_response, body, response_code, response_msg = await wait_task except asyncio.TimeoutError: return {}, b'', UPnPError("Timeout") except UPnPError as err: return {}, protocol.response_buff, err finally: - # raw_response = protocol.response_buff transport.close() try: return ( - deserialize_soap_post_response(body, method, service_id.decode()), body, None + deserialize_soap_post_response(body, method, service_id.decode()), raw_response, None ) except Exception as err: - return {}, body, UPnPError(err) + return {}, raw_response, UPnPError(err) diff --git a/aioupnp/serialization/soap.py b/aioupnp/serialization/soap.py index 9ebc07a..9ee2c08 100644 --- a/aioupnp/serialization/soap.py +++ b/aioupnp/serialization/soap.py @@ -1,5 +1,6 @@ import re import typing +import json from aioupnp.util import flatten_keys from aioupnp.fault import UPnPError from aioupnp.constants import XML_VERSION, ENVELOPE, BODY, FAULT, CONTROL @@ -54,7 +55,10 @@ def deserialize_soap_post_response(response: bytes, method: str, fault: typing.Dict[str, typing.Dict[str, typing.Dict[str, str]]] = flatten_keys( response_body[FAULT], "{%s}" % CONTROL ) - raise UPnPError(fault['detail']['UPnPError']['errorDescription']) + try: + raise UPnPError(fault['detail']['UPnPError']['errorDescription']) + except (KeyError, TypeError, ValueError): + raise UPnPError(f"Failed to decode error response: {json.dumps(fault)}") response_key = None for key in response_body: if method in key: diff --git a/tests/protocols/test_scpd.py b/tests/protocols/test_scpd.py index 6ff5442..3d991f9 100644 --- a/tests/protocols/test_scpd.py +++ b/tests/protocols/test_scpd.py @@ -126,7 +126,7 @@ class TestSCPDGet(AsyncioTestCase): with mock_tcp_and_udp(self.loop, tcp_replies=replies, sent_tcp_packets=sent): result, raw, err = await scpd_get(self.path, self.lan_address, self.port, self.loop) self.assertDictEqual({}, result) - self.assertEqual(self.bad_xml, raw) + self.assertEqual(self.bad_response, raw) self.assertTrue(isinstance(err, UPnPError)) self.assertTrue(str(err).startswith('no element found')) @@ -187,7 +187,7 @@ class TestSCPDPost(AsyncioTestCase): self.path, self.gateway_address, self.port, self.method, self.param_names, self.st, self.loop ) self.assertEqual(None, err) - self.assertEqual(self.envelope, raw) + self.assertEqual(self.post_response, raw) self.assertDictEqual({'NewExternalIPAddress': '11.22.33.44'}, result) async def test_scpd_post_timeout(self): @@ -211,7 +211,7 @@ class TestSCPDPost(AsyncioTestCase): ) self.assertTrue(isinstance(err, UPnPError)) self.assertTrue(str(err).startswith('no element found')) - self.assertEqual(self.bad_envelope, raw) + self.assertEqual(self.bad_envelope_response, raw) self.assertDictEqual({}, result) async def test_scpd_post_overrun_response(self): diff --git a/tests/serialization/test_soap.py b/tests/serialization/test_soap.py index ea3cf04..0105498 100644 --- a/tests/serialization/test_soap.py +++ b/tests/serialization/test_soap.py @@ -58,6 +58,16 @@ class TestSOAPSerialization(unittest.TestCase): b"\r\n" \ b"\n\n\t\n\t\t\n\t\t\ts:Client\n\t\t\tUPnPError\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t713\n\t\t\t\t\tSpecifiedArrayIndexInvalid\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\n" + error_response_no_description = b"HTTP/1.1 500 Internal Server Error\r\n" \ + b"Server: WebServer\r\n" \ + b"Date: Thu, 11 Oct 2018 22:16:17 GMT\r\n" \ + b"Connection: close\r\n" \ + b"CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n" \ + b"CONTENT-LENGTH: 429 \r\n" \ + b"EXT:\r\n" \ + b"\r\n" \ + b"\n\n\t\n\t\t\n\t\t\ts:Client\n\t\t\tUPnPError\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t713\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\n" + def test_serialize_post(self): self.assertEqual(serialize_soap_post( self.method, self.param_names, self.st, self.gateway_address, self.path, **self.kwargs @@ -94,3 +104,13 @@ class TestSOAPSerialization(unittest.TestCase): raised = True self.assertTrue(str(err) == 'SpecifiedArrayIndexInvalid') self.assertTrue(raised) + + def test_raise_from_error_response_without_error_description(self): + raised = False + expected = 'Failed to decode error response: {"faultcode": "s:Client", "faultstring": "UPnPError", "detail": {"UPnPError": {"errorCode": "713"}}}' + try: + deserialize_soap_post_response(self.error_response_no_description, self.method, service_id=self.st.decode()) + except UPnPError as err: + raised = True + self.assertTrue(str(err) == expected) + self.assertTrue(raised) diff --git a/tests/test_gateway.py b/tests/test_gateway.py index 1680ed4..1512947 100644 --- a/tests/test_gateway.py +++ b/tests/test_gateway.py @@ -127,133 +127,175 @@ class TestParseActionList(AsyncioTestCase): ('RequestConnection', [], []), ('ForceTermination', [], []), ('GetStatusInfo', [], ['NewConnectionStatus', 'NewLastConnectionError', 'NewUptime']), ('GetNATRSIPStatus', [], ['NewRSIPAvailable', 'NewNATEnabled']), ( - 'GetGenericPortMappingEntry', ['NewPortMappingIndex'], - ['NewRemoteHost', 'NewExternalPort', 'NewProtocol', 'NewInternalPort', 'NewInternalClient', - 'NewEnabled', 'NewPortMappingDescription', 'NewLeaseDuration']), ( - 'GetSpecificPortMappingEntry', ['NewRemoteHost', 'NewExternalPort', 'NewProtocol'], - ['NewInternalPort', 'NewInternalClient', 'NewEnabled', 'NewPortMappingDescription', - 'NewLeaseDuration']), ('AddPortMapping', - ['NewRemoteHost', 'NewExternalPort', 'NewProtocol', 'NewInternalPort', - 'NewInternalClient', 'NewEnabled', 'NewPortMappingDescription', - 'NewLeaseDuration'], []), + 'GetGenericPortMappingEntry', ['NewPortMappingIndex'], + ['NewRemoteHost', 'NewExternalPort', 'NewProtocol', 'NewInternalPort', 'NewInternalClient', + 'NewEnabled', 'NewPortMappingDescription', 'NewLeaseDuration']), ( + 'GetSpecificPortMappingEntry', ['NewRemoteHost', 'NewExternalPort', 'NewProtocol'], + ['NewInternalPort', 'NewInternalClient', 'NewEnabled', 'NewPortMappingDescription', + 'NewLeaseDuration']), ('AddPortMapping', + ['NewRemoteHost', 'NewExternalPort', 'NewProtocol', 'NewInternalPort', + 'NewInternalClient', 'NewEnabled', 'NewPortMappingDescription', + 'NewLeaseDuration'], []), ('DeletePortMapping', ['NewRemoteHost', 'NewExternalPort', 'NewProtocol'], []), ('GetExternalIPAddress', [], ['NewExternalIPAddress'])] self.assertEqual(expected, get_action_list(self.test_action_list)) class TestDiscoverDLinkDIR890L(AsyncioTestCase): - gateway_address = "10.0.0.1" + gateway_info = \ + {'manufacturer_string': 'D-Link DIR-890L', 'gateway_address': '10.0.0.1', + 'server': 'Linux, UPnP/1.0, DIR-890L Ver 1.20', 'urlBase': 'http://10.0.0.1:49152', + 'location': 'http://10.0.0.1:49152/InternetGatewayDevice.xml', 'specVersion': {'major': '1', 'minor': '0'}, + 'usn': 'uuid:11111111-2222-3333-4444-555555555555::urn:schemas-upnp-org:device:WANDevice:1', + 'urn': 'urn:schemas-upnp-org:device:WANDevice:1', + 'gateway_xml': 'HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 3921\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\n\n\t\n\t\t1\n\t\t0\n\t\n\thttp://10.0.0.1:49152\n\t\n\t\turn:schemas-upnp-org:device:InternetGatewayDevice:1\n\t\tWireless Broadband Router\n\t\tD-Link Corporation\n\t\thttp://www.dlink.com\n\t\tD-Link Router\n\t\tD-Link Router\n\t\tDIR-890L\n\t\thttp://www.dlink.com\n\t\t120\n\t\tuuid:11111111-2222-3333-4444-555555555555\n\t\t\n\t\t\t\n\t\t\t\timage/gif\n\t\t\t\t118\n\t\t\t\t119\n\t\t\t\t8\n\t\t\t\t/ligd.gif\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\turn:schemas-microsoft-com:service:OSInfo:1\n\t\t\t\turn:microsoft-com:serviceId:OSInfo1\n\t\t\t\t/soap.cgi?service=OSInfo1\n\t\t\t\t/gena.cgi?service=OSInfo1\n\t\t\t\t/OSInfo.xml\n\t\t\t\n\t\t\t\n\t\t\t\turn:schemas-upnp-org:service:Layer3Forwarding:1\n\t\t\t\turn:upnp-org:serviceId:L3Forwarding1\n\t\t\t\t/soap.cgi?service=L3Forwarding1\n\t\t\t\t/gena.cgi?service=L3Forwarding1\n\t\t\t\t/Layer3Forwarding.xml\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\turn:schemas-upnp-org:device:WANDevice:1\n\t\t\t\tWANDevice\n\t\t\t\tD-Link\n\t\t\t\thttp://www.dlink.com\n\t\t\t\tWANDevice\n\t\t\t\tDIR-890L\n\t\t\t\t1\n\t\t\t\thttp://www.dlink.com\n\t\t\t\t120\n\t\t\t\tuuid:11111111-2222-3333-4444-555555555555\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\turn:schemas-upnp-org:service:WANCommonInterfaceConfig:1\n\t\t\t\t\t\turn:upnp-org:serviceId:WANCommonIFC1\n\t\t\t\t\t\t/soap.cgi?service=WANCommonIFC1\n\t\t\t\t\t\t/gena.cgi?service=WANCommonIFC1\n\t\t\t\t\t\t/WANCommonInterfaceConfig.xml\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\turn:schemas-upnp-org:device:WANConnectionDevice:1\n\t\t\t\t\t\tWANConnectionDevice\n\t\t\t\t\t\tD-Link\n\t\t\t\t\t\thttp://www.dlink.com\n\t\t\t\t\t\tWanConnectionDevice\n\t\t\t\t\t\tDIR-890L\n\t\t\t\t\t\t1\n\t\t\t\t\t\thttp://www.dlink.com\n\t\t\t\t\t\t120\n\t\t\t\t\t\tuuid:11111111-2222-3333-4444-555555555555\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\turn:schemas-upnp-org:service:WANEthernetLinkConfig:1\n\t\t\t\t\t\t\t\turn:upnp-org:serviceId:WANEthLinkC1\n\t\t\t\t\t\t\t\t/soap.cgi?service=WANEthLinkC1\n\t\t\t\t\t\t\t\t/gena.cgi?service=WANEthLinkC1\n\t\t\t\t\t\t\t\t/WANEthernetLinkConfig.xml\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\turn:schemas-upnp-org:service:WANIPConnection:1\n\t\t\t\t\t\t\t\turn:upnp-org:serviceId:WANIPConn1\n\t\t\t\t\t\t\t\t/soap.cgi?service=WANIPConn1\n\t\t\t\t\t\t\t\t/gena.cgi?service=WANIPConn1\n\t\t\t\t\t\t\t\t/WANIPConnection.xml\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\thttp://10.0.0.1\n\t\n\n', + 'services_xml': { + '/OSInfo.xml': 'HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 219\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\n\n\t\n\t\t1\n\t\t0\n\t\n\t\n\t\n\t\n\t\n\n', + '/Layer3Forwarding.xml': 'HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 920\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\n\n\t\n\t\t1\n\t\t0\n\t\n\t\n\t\t\n\t\t\tGetDefaultConnectionService\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tNewDefaultConnectionService\n\t\t\t\t\tout\n\t\t\t\t\tDefaultConnectionService\n\t\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\tSetDefaultConnectionService\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tNewDefaultConnectionService\n\t\t\t\t\tin\n\t\t\t\t\tDefaultConnectionService\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\tDefaultConnectionService\n\t\t\tstring\n\t\t\n\t\n\n', + '/WANCommonInterfaceConfig.xml': 'HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 5343\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\r\n\r\n\t\r\n\t\t1\r\n\t\t0\r\n\t\r\n\t\r\n\t\t\r\n\t\t\tGetCommonLinkProperties\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewWANAccessType\r\n\t\t\t\t\tout\r\n\t\t\t\t\tWANAccessType\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLayer1UpstreamMaxBitRate\r\n\t\t\t\t\tout\r\n\t\t\t\t\tLayer1UpstreamMaxBitRate\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLayer1DownstreamMaxBitRate\r\n\t\t\t\t\tout\r\n\t\t\t\t\tLayer1DownstreamMaxBitRate\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPhysicalLinkStatus\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPhysicalLinkStatus\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetTotalBytesSent\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewTotalBytesSent\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalBytesSent\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetTotalBytesReceived\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewTotalBytesReceived\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalBytesReceived\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetTotalPacketsSent\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewTotalPacketsSent\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalPacketsSent\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetTotalPacketsReceived\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewTotalPacketsReceived\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalPacketsReceived\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tX_GetICSStatistics\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tTotalBytesSent\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalBytesSent\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tTotalBytesReceived\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalBytesReceived\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tTotalPacketsSent\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalPacketsSent\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tTotalPacketsReceived\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalPacketsReceived\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tLayer1DownstreamMaxBitRate\r\n\t\t\t\t\tout\r\n\t\t\t\t\tLayer1DownstreamMaxBitRate\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tUptime\r\n\t\t\t\t\tout\r\n\t\t\t\t\tX_Uptime\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\tWANAccessType\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\t\tDSL\r\n\t\t\t\tPOTS\r\n\t\t\t\tCable\r\n\t\t\t\tEthernet\r\n\t\t\t\tOther\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tLayer1UpstreamMaxBitRate\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tLayer1DownstreamMaxBitRate\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tPhysicalLinkStatus\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\t\tUp\r\n\t\t\t\tDown\r\n\t\t\t\tInitializing\r\n\t\t\t\tUnavailable\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tWANAccessProvider\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tMaximumActiveConnections\r\n\t\t\tui2\r\n\t\t\t\r\n\t\t\t\t1\r\n\t\t\t\t\r\n\t\t\t\t1\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tTotalBytesSent\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tTotalBytesReceived\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tTotalPacketsSent\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tTotalPacketsReceived\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tX_PersonalFirewallEnabled\r\n\t\t\tboolean\r\n\t\t\r\n\t\t\r\n\t\t\tX_Uptime\r\n\t\t\tui4\r\n\t\t\r\n\t\r\n\r\n', + '/WANEthernetLinkConfig.xml': 'HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 773\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\n\n\t\n\t\t1\n\t\t0\n\t\n\t\n\t\t\n\t\t\tGetEthernetLinkStatus\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tNewEthernetLinkStatus\n\t\t\t\t\tout\n\t\t\t\t\tEthernetLinkStatus\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\tEthernetLinkStatus\n\t\t\tstring\n\t\t\t\n\t\t\t\tUp\n\t\t\t\tDown\n\t\t\t\tUnavailable\n\t\t\t\n\t\t\n\t\n\n', + '/WANIPConnection.xml': 'HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 12078\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\r\n\r\n\t\r\n\t\t1\r\n\t\t0\r\n\t\r\n\t\r\n\t\t\r\n\t\t\tSetConnectionType\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewConnectionType\r\n\t\t\t\t\tin\r\n\t\t\t\t\tConnectionType\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t \r\n\t\t\r\n\t\t\tGetConnectionTypeInfo\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewConnectionType\r\n\t\t\t\t\tout\r\n\t\t\t\t\tConnectionType\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPossibleConnectionTypes\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPossibleConnectionTypes\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tRequestConnection\r\n\t\t\r\n\t\t\r\n\t\t\tForceTermination\r\n\t\t\r\n\t\t\r\n\t\t\tGetStatusInfo\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewConnectionStatus\r\n\t\t\t\t\tout\r\n\t\t\t\t\tConnectionStatus\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLastConnectionError\r\n\t\t\t\t\tout\r\n\t\t\t\t\tLastConnectionError\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewUptime\r\n\t\t\t\t\tout\r\n\t\t\t\t\tUptime\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetNATRSIPStatus\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRSIPAvailable\r\n\t\t\t\t\tout\r\n\t\t\t\t\tRSIPAvailable\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewNATEnabled\r\n\t\t\t\t\tout\r\n\t\t\t\t\tNATEnabled\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetGenericPortMappingEntry\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPortMappingIndex\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingNumberOfEntries\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRemoteHost\r\n\t\t\t\t\tout\r\n\t\t\t\t\tRemoteHost\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalPort\r\n\t\t\t\t\tout\r\n\t\t\t\t\tExternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewProtocol\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingProtocol\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalPort\r\n\t\t\t\t\tout\r\n\t\t\t\t\tInternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalClient\r\n\t\t\t\t\tout\r\n\t\t\t\t\tInternalClient\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewEnabled\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingEnabled\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPortMappingDescription\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingDescription\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLeaseDuration\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingLeaseDuration\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetSpecificPortMappingEntry\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRemoteHost\r\n\t\t\t\t\tin\r\n\t\t\t\t\tRemoteHost\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalPort\r\n\t\t\t\t\tin\r\n\t\t\t\t\tExternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewProtocol\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingProtocol\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalPort\r\n\t\t\t\t\tout\r\n\t\t\t\t\tInternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalClient\r\n\t\t\t\t\tout\r\n\t\t\t\t\tInternalClient\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewEnabled\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingEnabled\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPortMappingDescription\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingDescription\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLeaseDuration\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingLeaseDuration\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tAddPortMapping\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRemoteHost\r\n\t\t\t\t\tin\r\n\t\t\t\t\tRemoteHost\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalPort\r\n\t\t\t\t\tin\r\n\t\t\t\t\tExternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewProtocol\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingProtocol\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalPort\r\n\t\t\t\t\tin\r\n\t\t\t\t\tInternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalClient\r\n\t\t\t\t\tin\r\n\t\t\t\t\tInternalClient\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewEnabled\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingEnabled\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPortMappingDescription\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingDescription\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLeaseDuration\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingLeaseDuration\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tDeletePortMapping\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRemoteHost\r\n\t\t\t\t\tin\r\n\t\t\t\t\tRemoteHost\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalPort\r\n\t\t\t\t\tin\r\n\t\t\t\t\tExternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewProtocol\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingProtocol\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetExternalIPAddress\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalIPAddress\r\n\t\t\t\t\tout\r\n\t\t\t\t\tExternalIPAddress\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\tConnectionType\r\n\t\t\tstring\r\n\t\t\tUnconfigured\r\n\t\t\r\n\t\t\r\n\t\t\tPossibleConnectionTypes\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\t\tUnconfigured\r\n\t\t\t\tIP_Routed\r\n\t\t\t\tIP_Bridged\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tConnectionStatus\r\n\t\t\tstring\r\n\t\t\tUnconfigured\r\n\t\t\t\r\n\t\t\t\tUnconfigured\r\n\t\t\t\tConnecting\r\n\t\t\t\tAuthenticating\r\n\t\t\t\tPendingDisconnect\r\n\t\t\t\tDisconnecting\r\n\t\t\t\tDisconnected\r\n\t\t\t\tConnected\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tUptime\r\n\t\t\tui4\r\n\t\t\t0\r\n\t\t\t\r\n\t\t\t\t0\r\n\t\t\t\t\r\n\t\t\t\t1\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tRSIPAvailable\r\n\t\tboolean\r\n\t\t\t0\r\n\t\t\r\n\t\t\r\n\t\t\tNATEnabled\r\n\t\t\tboolean\r\n\t\t\t1\r\n\t\t \r\n\t\t\r\n\t\t\tX_Name\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tLastConnectionError\r\n\t\t\tstring\r\n\t\t\tERROR_NONE\r\n\t\t\t\r\n\t\t\t\tERROR_NONE\r\n\t\t\t\tERROR_ISP_TIME_OUT\r\n\t\t\t\tERROR_COMMAND_ABORTED\r\n\t\t\t\tERROR_NOT_ENABLED_FOR_INTERNET\r\n\t\t\t\tERROR_BAD_PHONE_NUMBER\r\n\t\t\t\tERROR_USER_DISCONNECT\r\n\t\t\t\tERROR_ISP_DISCONNECT\r\n\t\t\t\tERROR_IDLE_DISCONNECT\r\n\t\t\t\tERROR_FORCED_DISCONNECT\r\n\t\t\t\tERROR_SERVER_OUT_OF_RESOURCES\r\n\t\t\t\tERROR_RESTRICTED_LOGON_HOURS\r\n\t\t\t\tERROR_ACCOUNT_DISABLED\r\n\t\t\t\tERROR_ACCOUNT_EXPIRED\r\n\t\t\t\tERROR_PASSWORD_EXPIRED\r\n\t\t\t\tERROR_AUTHENTICATION_FAILURE\r\n\t\t\t\tERROR_NO_DIALTONE\r\n\t\t\t\tERROR_NO_CARRIER\r\n\t\t\t\tERROR_NO_ANSWER\r\n\t\t\t\tERROR_LINE_BUSY\r\n\t\t\t\tERROR_UNSUPPORTED_BITSPERSECOND\r\n\t\t\t\tERROR_TOO_MANY_LINE_ERRORS\r\n\t\t\t\tERROR_IP_CONFIGURATION\r\n\t\t\t\tERROR_UNKNOWN\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tExternalIPAddress\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tRemoteHost\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tExternalPort\r\n\t\t\tui2\r\n\t\t\r\n\t\t\r\n\t\t\tInternalPort\r\n\t\t\tui2\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingProtocol\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\t\tTCP\r\n\t\t\t\tUDP\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tInternalClient\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingDescription\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingEnabled\r\n\t\t\tboolean\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingLeaseDuration\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingNumberOfEntries\r\n\t\t\tui2\r\n\t\t\r\n\t\r\n\r\n'}, + 'services': {'/OSInfo.xml': OrderedDict([('serviceType', 'urn:schemas-microsoft-com:service:OSInfo:1'), + ('serviceId', 'urn:microsoft-com:serviceId:OSInfo1'), + ('controlURL', '/soap.cgi?service=OSInfo1'), + ('eventSubURL', '/gena.cgi?service=OSInfo1'), + ('SCPDURL', '/OSInfo.xml')]), '/Layer3Forwarding.xml': OrderedDict( + [('serviceType', 'urn:schemas-upnp-org:service:Layer3Forwarding:1'), + ('serviceId', 'urn:upnp-org:serviceId:L3Forwarding1'), ('controlURL', '/soap.cgi?service=L3Forwarding1'), + ('eventSubURL', '/gena.cgi?service=L3Forwarding1'), ('SCPDURL', '/Layer3Forwarding.xml')]), + '/WANCommonInterfaceConfig.xml': OrderedDict( + [('serviceType', 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'), + ('serviceId', 'urn:upnp-org:serviceId:WANCommonIFC1'), + ('controlURL', '/soap.cgi?service=WANCommonIFC1'), + ('eventSubURL', '/gena.cgi?service=WANCommonIFC1'), + ('SCPDURL', '/WANCommonInterfaceConfig.xml')]), '/WANEthernetLinkConfig.xml': OrderedDict( + [('serviceType', 'urn:schemas-upnp-org:service:WANEthernetLinkConfig:1'), + ('serviceId', 'urn:upnp-org:serviceId:WANEthLinkC1'), + ('controlURL', '/soap.cgi?service=WANEthLinkC1'), ('eventSubURL', '/gena.cgi?service=WANEthLinkC1'), + ('SCPDURL', '/WANEthernetLinkConfig.xml')]), '/WANIPConnection.xml': OrderedDict( + [('serviceType', 'urn:schemas-upnp-org:service:WANIPConnection:1'), + ('serviceId', 'urn:upnp-org:serviceId:WANIPConn1'), ('controlURL', '/soap.cgi?service=WANIPConn1'), + ('eventSubURL', '/gena.cgi?service=WANIPConn1'), ('SCPDURL', '/WANIPConnection.xml')])}, + 'm_search_args': OrderedDict([('HOST', '239.255.255.250:1900'), ('MAN', 'ssdp:discover'), ('MX', 1), + ('ST', 'urn:schemas-upnp-org:device:WANDevice:1')]), 'reply': OrderedDict( + [('CACHE_CONTROL', 'max-age=1800'), ('LOCATION', 'http://10.0.0.1:49152/InternetGatewayDevice.xml'), + ('SERVER', 'Linux, UPnP/1.0, DIR-890L Ver 1.20'), ('ST', 'urn:schemas-upnp-org:device:WANDevice:1'), + ('USN', 'uuid:11111111-2222-3333-4444-555555555555::urn:schemas-upnp-org:device:WANDevice:1')]), + 'soap_port': 49152, + 'registered_soap_commands': {'GetGenericPortMappingEntry': 'urn:schemas-upnp-org:service:WANIPConnection:1', + 'GetSpecificPortMappingEntry': 'urn:schemas-upnp-org:service:WANIPConnection:1', + 'AddPortMapping': 'urn:schemas-upnp-org:service:WANIPConnection:1', + 'DeletePortMapping': 'urn:schemas-upnp-org:service:WANIPConnection:1', + 'GetExternalIPAddress': 'urn:schemas-upnp-org:service:WANIPConnection:1'}, + 'unsupported_soap_commands': { + 'urn:schemas-upnp-org:service:Layer3Forwarding:1': ['GetDefaultConnectionService', + 'SetDefaultConnectionService'], + 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1': ['GetCommonLinkProperties', 'GetTotalBytesSent', + 'GetTotalBytesReceived', 'GetTotalPacketsSent', + 'GetTotalPacketsReceived', + 'X_GetICSStatistics'], + 'urn:schemas-upnp-org:service:WANEthernetLinkConfig:1': ['GetEthernetLinkStatus'], + 'urn:schemas-upnp-org:service:WANIPConnection:1': ['SetConnectionType', 'GetConnectionTypeInfo', + 'RequestConnection', 'ForceTermination', + 'GetStatusInfo', 'GetNATRSIPStatus']}, + 'soap_requests': []} + client_address = "10.0.0.2" - soap_port = 49152 - m_search_args = OrderedDict([ - ("HOST", "239.255.255.250:1900"), - ("MAN", "ssdp:discover"), - ("MX", 1), - ("ST", "urn:schemas-upnp-org:device:WANDevice:1") - ]) - - reply = SSDPDatagram("OK", OrderedDict([ - ("CACHE_CONTROL", "max-age=1800"), - ("LOCATION", "http://10.0.0.1:49152/InternetGatewayDevice.xml"), - ("SERVER", "Linux, UPnP/1.0, DIR-890L Ver 1.20"), - ("ST", "urn:schemas-upnp-org:device:WANDevice:1"), - ("USN", "uuid:11111111-2222-3333-4444-555555555555::urn:schemas-upnp-org:device:WANDevice:1") - ])) - - replies = { - b'GET /InternetGatewayDevice.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 10.0.0.1\r\nConnection: Close\r\n\r\n': b"HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 3921\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\n\n\t\n\t\t1\n\t\t0\n\t\n\thttp://10.0.0.1:49152\n\t\n\t\turn:schemas-upnp-org:device:InternetGatewayDevice:1\n\t\tWireless Broadband Router\n\t\tD-Link Corporation\n\t\thttp://www.dlink.com\n\t\tD-Link Router\n\t\tD-Link Router\n\t\tDIR-890L\n\t\thttp://www.dlink.com\n\t\t120\n\t\tuuid:11111111-2222-3333-4444-555555555555\n\t\t\n\t\t\t\n\t\t\t\timage/gif\n\t\t\t\t118\n\t\t\t\t119\n\t\t\t\t8\n\t\t\t\t/ligd.gif\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\turn:schemas-microsoft-com:service:OSInfo:1\n\t\t\t\turn:microsoft-com:serviceId:OSInfo1\n\t\t\t\t/soap.cgi?service=OSInfo1\n\t\t\t\t/gena.cgi?service=OSInfo1\n\t\t\t\t/OSInfo.xml\n\t\t\t\n\t\t\t\n\t\t\t\turn:schemas-upnp-org:service:Layer3Forwarding:1\n\t\t\t\turn:upnp-org:serviceId:L3Forwarding1\n\t\t\t\t/soap.cgi?service=L3Forwarding1\n\t\t\t\t/gena.cgi?service=L3Forwarding1\n\t\t\t\t/Layer3Forwarding.xml\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\turn:schemas-upnp-org:device:WANDevice:1\n\t\t\t\tWANDevice\n\t\t\t\tD-Link\n\t\t\t\thttp://www.dlink.com\n\t\t\t\tWANDevice\n\t\t\t\tDIR-890L\n\t\t\t\t1\n\t\t\t\thttp://www.dlink.com\n\t\t\t\t120\n\t\t\t\tuuid:11111111-2222-3333-4444-555555555555\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\turn:schemas-upnp-org:service:WANCommonInterfaceConfig:1\n\t\t\t\t\t\turn:upnp-org:serviceId:WANCommonIFC1\n\t\t\t\t\t\t/soap.cgi?service=WANCommonIFC1\n\t\t\t\t\t\t/gena.cgi?service=WANCommonIFC1\n\t\t\t\t\t\t/WANCommonInterfaceConfig.xml\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\turn:schemas-upnp-org:device:WANConnectionDevice:1\n\t\t\t\t\t\tWANConnectionDevice\n\t\t\t\t\t\tD-Link\n\t\t\t\t\t\thttp://www.dlink.com\n\t\t\t\t\t\tWanConnectionDevice\n\t\t\t\t\t\tDIR-890L\n\t\t\t\t\t\t1\n\t\t\t\t\t\thttp://www.dlink.com\n\t\t\t\t\t\t120\n\t\t\t\t\t\tuuid:11111111-2222-3333-4444-555555555555\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\turn:schemas-upnp-org:service:WANEthernetLinkConfig:1\n\t\t\t\t\t\t\t\turn:upnp-org:serviceId:WANEthLinkC1\n\t\t\t\t\t\t\t\t/soap.cgi?service=WANEthLinkC1\n\t\t\t\t\t\t\t\t/gena.cgi?service=WANEthLinkC1\n\t\t\t\t\t\t\t\t/WANEthernetLinkConfig.xml\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\turn:schemas-upnp-org:service:WANIPConnection:1\n\t\t\t\t\t\t\t\turn:upnp-org:serviceId:WANIPConn1\n\t\t\t\t\t\t\t\t/soap.cgi?service=WANIPConn1\n\t\t\t\t\t\t\t\t/gena.cgi?service=WANIPConn1\n\t\t\t\t\t\t\t\t/WANIPConnection.xml\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\thttp://10.0.0.1\n\t\n\n", - b'GET /OSInfo.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 10.0.0.1\r\nConnection: Close\r\n\r\n': b"HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 219\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\n\n\t\n\t\t1\n\t\t0\n\t\n\t\n\t\n\t\n\t\n\n", - b'GET /Layer3Forwarding.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 10.0.0.1\r\nConnection: Close\r\n\r\n': b"HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 920\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\n\n\t\n\t\t1\n\t\t0\n\t\n\t\n\t\t\n\t\t\tGetDefaultConnectionService\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tNewDefaultConnectionService\n\t\t\t\t\tout\n\t\t\t\t\tDefaultConnectionService\n\t\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\tSetDefaultConnectionService\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tNewDefaultConnectionService\n\t\t\t\t\tin\n\t\t\t\t\tDefaultConnectionService\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\tDefaultConnectionService\n\t\t\tstring\n\t\t\n\t\n\n", - b'GET /WANCommonInterfaceConfig.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 10.0.0.1\r\nConnection: Close\r\n\r\n': b"HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 5343\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\r\n\r\n\t\r\n\t\t1\r\n\t\t0\r\n\t\r\n\t\r\n\t\t\r\n\t\t\tGetCommonLinkProperties\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewWANAccessType\r\n\t\t\t\t\tout\r\n\t\t\t\t\tWANAccessType\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLayer1UpstreamMaxBitRate\r\n\t\t\t\t\tout\r\n\t\t\t\t\tLayer1UpstreamMaxBitRate\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLayer1DownstreamMaxBitRate\r\n\t\t\t\t\tout\r\n\t\t\t\t\tLayer1DownstreamMaxBitRate\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPhysicalLinkStatus\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPhysicalLinkStatus\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetTotalBytesSent\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewTotalBytesSent\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalBytesSent\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetTotalBytesReceived\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewTotalBytesReceived\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalBytesReceived\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetTotalPacketsSent\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewTotalPacketsSent\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalPacketsSent\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetTotalPacketsReceived\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewTotalPacketsReceived\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalPacketsReceived\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tX_GetICSStatistics\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tTotalBytesSent\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalBytesSent\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tTotalBytesReceived\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalBytesReceived\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tTotalPacketsSent\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalPacketsSent\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tTotalPacketsReceived\r\n\t\t\t\t\tout\r\n\t\t\t\t\tTotalPacketsReceived\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tLayer1DownstreamMaxBitRate\r\n\t\t\t\t\tout\r\n\t\t\t\t\tLayer1DownstreamMaxBitRate\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tUptime\r\n\t\t\t\t\tout\r\n\t\t\t\t\tX_Uptime\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\tWANAccessType\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\t\tDSL\r\n\t\t\t\tPOTS\r\n\t\t\t\tCable\r\n\t\t\t\tEthernet\r\n\t\t\t\tOther\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tLayer1UpstreamMaxBitRate\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tLayer1DownstreamMaxBitRate\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tPhysicalLinkStatus\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\t\tUp\r\n\t\t\t\tDown\r\n\t\t\t\tInitializing\r\n\t\t\t\tUnavailable\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tWANAccessProvider\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tMaximumActiveConnections\r\n\t\t\tui2\r\n\t\t\t\r\n\t\t\t\t1\r\n\t\t\t\t\r\n\t\t\t\t1\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tTotalBytesSent\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tTotalBytesReceived\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tTotalPacketsSent\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tTotalPacketsReceived\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tX_PersonalFirewallEnabled\r\n\t\t\tboolean\r\n\t\t\r\n\t\t\r\n\t\t\tX_Uptime\r\n\t\t\tui4\r\n\t\t\r\n\t\r\n\r\n", - b'GET /WANEthernetLinkConfig.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 10.0.0.1\r\nConnection: Close\r\n\r\n': b"HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 773\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\n\n\t\n\t\t1\n\t\t0\n\t\n\t\n\t\t\n\t\t\tGetEthernetLinkStatus\n\t\t\t\n\t\t\t\t\n\t\t\t\t\tNewEthernetLinkStatus\n\t\t\t\t\tout\n\t\t\t\t\tEthernetLinkStatus\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t\n\t\t\tEthernetLinkStatus\n\t\t\tstring\n\t\t\t\n\t\t\t\tUp\n\t\t\t\tDown\n\t\t\t\tUnavailable\n\t\t\t\n\t\t\n\t\n\n", - b'GET /WANIPConnection.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 10.0.0.1\r\nConnection: Close\r\n\r\n': b"HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Thu, 11 Oct 2018 22:16:16 GMT\r\nContent-Type: text/xml\r\nContent-Length: 12078\r\nLast-Modified: Thu, 09 Aug 2018 12:41:07 GMT\r\nConnection: close\r\n\r\n\r\n\r\n\t\r\n\t\t1\r\n\t\t0\r\n\t\r\n\t\r\n\t\t\r\n\t\t\tSetConnectionType\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewConnectionType\r\n\t\t\t\t\tin\r\n\t\t\t\t\tConnectionType\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t \r\n\t\t\r\n\t\t\tGetConnectionTypeInfo\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewConnectionType\r\n\t\t\t\t\tout\r\n\t\t\t\t\tConnectionType\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPossibleConnectionTypes\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPossibleConnectionTypes\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tRequestConnection\r\n\t\t\r\n\t\t\r\n\t\t\tForceTermination\r\n\t\t\r\n\t\t\r\n\t\t\tGetStatusInfo\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewConnectionStatus\r\n\t\t\t\t\tout\r\n\t\t\t\t\tConnectionStatus\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLastConnectionError\r\n\t\t\t\t\tout\r\n\t\t\t\t\tLastConnectionError\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewUptime\r\n\t\t\t\t\tout\r\n\t\t\t\t\tUptime\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetNATRSIPStatus\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRSIPAvailable\r\n\t\t\t\t\tout\r\n\t\t\t\t\tRSIPAvailable\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewNATEnabled\r\n\t\t\t\t\tout\r\n\t\t\t\t\tNATEnabled\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetGenericPortMappingEntry\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPortMappingIndex\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingNumberOfEntries\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRemoteHost\r\n\t\t\t\t\tout\r\n\t\t\t\t\tRemoteHost\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalPort\r\n\t\t\t\t\tout\r\n\t\t\t\t\tExternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewProtocol\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingProtocol\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalPort\r\n\t\t\t\t\tout\r\n\t\t\t\t\tInternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalClient\r\n\t\t\t\t\tout\r\n\t\t\t\t\tInternalClient\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewEnabled\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingEnabled\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPortMappingDescription\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingDescription\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLeaseDuration\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingLeaseDuration\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetSpecificPortMappingEntry\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRemoteHost\r\n\t\t\t\t\tin\r\n\t\t\t\t\tRemoteHost\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalPort\r\n\t\t\t\t\tin\r\n\t\t\t\t\tExternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewProtocol\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingProtocol\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalPort\r\n\t\t\t\t\tout\r\n\t\t\t\t\tInternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalClient\r\n\t\t\t\t\tout\r\n\t\t\t\t\tInternalClient\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewEnabled\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingEnabled\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPortMappingDescription\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingDescription\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLeaseDuration\r\n\t\t\t\t\tout\r\n\t\t\t\t\tPortMappingLeaseDuration\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tAddPortMapping\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRemoteHost\r\n\t\t\t\t\tin\r\n\t\t\t\t\tRemoteHost\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalPort\r\n\t\t\t\t\tin\r\n\t\t\t\t\tExternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewProtocol\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingProtocol\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalPort\r\n\t\t\t\t\tin\r\n\t\t\t\t\tInternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewInternalClient\r\n\t\t\t\t\tin\r\n\t\t\t\t\tInternalClient\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewEnabled\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingEnabled\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewPortMappingDescription\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingDescription\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewLeaseDuration\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingLeaseDuration\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tDeletePortMapping\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewRemoteHost\r\n\t\t\t\t\tin\r\n\t\t\t\t\tRemoteHost\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalPort\r\n\t\t\t\t\tin\r\n\t\t\t\t\tExternalPort\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewProtocol\r\n\t\t\t\t\tin\r\n\t\t\t\t\tPortMappingProtocol\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tGetExternalIPAddress\r\n\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t\tNewExternalIPAddress\r\n\t\t\t\t\tout\r\n\t\t\t\t\tExternalIPAddress\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\r\n\t\r\n\t\r\n\t\t\r\n\t\t\tConnectionType\r\n\t\t\tstring\r\n\t\t\tUnconfigured\r\n\t\t\r\n\t\t\r\n\t\t\tPossibleConnectionTypes\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\t\tUnconfigured\r\n\t\t\t\tIP_Routed\r\n\t\t\t\tIP_Bridged\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tConnectionStatus\r\n\t\t\tstring\r\n\t\t\tUnconfigured\r\n\t\t\t\r\n\t\t\t\tUnconfigured\r\n\t\t\t\tConnecting\r\n\t\t\t\tAuthenticating\r\n\t\t\t\tPendingDisconnect\r\n\t\t\t\tDisconnecting\r\n\t\t\t\tDisconnected\r\n\t\t\t\tConnected\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tUptime\r\n\t\t\tui4\r\n\t\t\t0\r\n\t\t\t\r\n\t\t\t\t0\r\n\t\t\t\t\r\n\t\t\t\t1\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tRSIPAvailable\r\n\t\tboolean\r\n\t\t\t0\r\n\t\t\r\n\t\t\r\n\t\t\tNATEnabled\r\n\t\t\tboolean\r\n\t\t\t1\r\n\t\t \r\n\t\t\r\n\t\t\tX_Name\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tLastConnectionError\r\n\t\t\tstring\r\n\t\t\tERROR_NONE\r\n\t\t\t\r\n\t\t\t\tERROR_NONE\r\n\t\t\t\tERROR_ISP_TIME_OUT\r\n\t\t\t\tERROR_COMMAND_ABORTED\r\n\t\t\t\tERROR_NOT_ENABLED_FOR_INTERNET\r\n\t\t\t\tERROR_BAD_PHONE_NUMBER\r\n\t\t\t\tERROR_USER_DISCONNECT\r\n\t\t\t\tERROR_ISP_DISCONNECT\r\n\t\t\t\tERROR_IDLE_DISCONNECT\r\n\t\t\t\tERROR_FORCED_DISCONNECT\r\n\t\t\t\tERROR_SERVER_OUT_OF_RESOURCES\r\n\t\t\t\tERROR_RESTRICTED_LOGON_HOURS\r\n\t\t\t\tERROR_ACCOUNT_DISABLED\r\n\t\t\t\tERROR_ACCOUNT_EXPIRED\r\n\t\t\t\tERROR_PASSWORD_EXPIRED\r\n\t\t\t\tERROR_AUTHENTICATION_FAILURE\r\n\t\t\t\tERROR_NO_DIALTONE\r\n\t\t\t\tERROR_NO_CARRIER\r\n\t\t\t\tERROR_NO_ANSWER\r\n\t\t\t\tERROR_LINE_BUSY\r\n\t\t\t\tERROR_UNSUPPORTED_BITSPERSECOND\r\n\t\t\t\tERROR_TOO_MANY_LINE_ERRORS\r\n\t\t\t\tERROR_IP_CONFIGURATION\r\n\t\t\t\tERROR_UNKNOWN\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tExternalIPAddress\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tRemoteHost\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tExternalPort\r\n\t\t\tui2\r\n\t\t\r\n\t\t\r\n\t\t\tInternalPort\r\n\t\t\tui2\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingProtocol\r\n\t\t\tstring\r\n\t\t\t\r\n\t\t\t\tTCP\r\n\t\t\t\tUDP\r\n\t\t\t\r\n\t\t\r\n\t\t\r\n\t\t\tInternalClient\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingDescription\r\n\t\t\tstring\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingEnabled\r\n\t\t\tboolean\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingLeaseDuration\r\n\t\t\tui4\r\n\t\t\r\n\t\t\r\n\t\t\tPortMappingNumberOfEntries\r\n\t\t\tui2\r\n\t\t\r\n\t\r\n\r\n" - } - - expected_commands = { - # 'GetDefaultConnectionService': 'urn:schemas-upnp-org:service:Layer3Forwarding:1', - # 'SetDefaultConnectionService': 'urn:schemas-upnp-org:service:Layer3Forwarding:1', - # 'GetCommonLinkProperties': 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1', - # 'GetTotalBytesSent': 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1', - # 'GetTotalBytesReceived': 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1', - # 'GetTotalPacketsSent': 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1', - # 'GetTotalPacketsReceived': 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1', - # 'X_GetICSStatistics': 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1', - # 'SetConnectionType': 'urn:schemas-upnp-org:service:WANIPConnection:1', - # 'GetConnectionTypeInfo': 'urn:schemas-upnp-org:service:WANIPConnection:1', - # 'RequestConnection': 'urn:schemas-upnp-org:service:WANIPConnection:1', - # 'ForceTermination': 'urn:schemas-upnp-org:service:WANIPConnection:1', - # 'GetStatusInfo': 'urn:schemas-upnp-org:service:WANIPConnection:1', - # 'GetNATRSIPStatus': 'urn:schemas-upnp-org:service:WANIPConnection:1', - 'GetGenericPortMappingEntry': 'urn:schemas-upnp-org:service:WANIPConnection:1', - 'GetSpecificPortMappingEntry': 'urn:schemas-upnp-org:service:WANIPConnection:1', - 'AddPortMapping': 'urn:schemas-upnp-org:service:WANIPConnection:1', - 'DeletePortMapping': 'urn:schemas-upnp-org:service:WANIPConnection:1', - 'GetExternalIPAddress': 'urn:schemas-upnp-org:service:WANIPConnection:1' - } + def setUp(self) -> None: + self.replies = { + ( + f"GET {path} HTTP/1.1\r\n" + f"Accept-Encoding: gzip\r\n" + f"Host: {self.gateway_info['gateway_address']}\r\n" + f"Connection: Close\r\n" + f"\r\n" + ).encode(): xml_bytes.encode() + for path, xml_bytes in self.gateway_info['services_xml'].items() + } + self.replies.update({ + ( + f"GET /{self.gateway_info['location'].lstrip(self.gateway_info['urlBase'])} HTTP/1.1\r\n" + f"Accept-Encoding: gzip\r\n" + f"Host: {self.gateway_info['gateway_address']}\r\n" + f"Connection: Close\r\n" + f"\r\n" + ).encode(): self.gateway_info['gateway_xml'].encode() + }) + super().setUp() async def test_discover_gateway(self): with self.assertRaises(UPnPError) as e1: with mock_tcp_and_udp(self.loop): - await Gateway.discover_gateway("10.0.0.2", "10.0.0.1", 2) + await Gateway.discover_gateway(self.client_address, self.gateway_info['gateway_address'], 2, + loop=self.loop) with self.assertRaises(UPnPError) as e2: with mock_tcp_and_udp(self.loop): - await Gateway.discover_gateway("10.0.0.2", "10.0.0.1", 2, unicast=False) - self.assertEqual(str(e1.exception), "M-SEARCH for 10.0.0.1:1900 timed out") - self.assertEqual(str(e2.exception), "M-SEARCH for 10.0.0.1:1900 timed out") + await Gateway.discover_gateway(self.client_address, self.gateway_info['gateway_address'], 2, + unicast=False, loop=self.loop) + self.assertEqual(str(e1.exception), f"M-SEARCH for {self.gateway_info['gateway_address']}:1900 timed out") + self.assertEqual(str(e2.exception), f"M-SEARCH for {self.gateway_info['gateway_address']}:1900 timed out") async def test_discover_commands(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) - self.assertDictEqual(self.expected_commands, gateway._registered_commands) + gateway = Gateway( + SSDPDatagram("OK", self.gateway_info['reply']), self.gateway_info['m_search_args'], + self.client_address, self.gateway_info['gateway_address'], loop=self.loop + ) + await gateway.discover_commands() + self.assertDictEqual(self.gateway_info['registered_soap_commands'], gateway._registered_commands) + self.assertDictEqual(gateway.debug_gateway(), self.gateway_info) class TestDiscoverNetgearNighthawkAC2350(TestDiscoverDLinkDIR890L): - gateway_address = "192.168.0.1" - client_address = "192.168.0.6" - soap_port = 5555 - - m_search_args = OrderedDict([ - ("HOST", "239.255.255.250:1900"), - ("MAN", "\"ssdp:discover\""), - ("MX", 1), - ("ST", "upnp:rootdevice") - ]) - - reply = SSDPDatagram("OK", OrderedDict([ - ("CACHE_CONTROL", "max-age=1800"), - ("ST", "upnp:rootdevice"), - ("USN", "uuid:11111111-2222-3333-4444-555555555555::upnp:rootdevice"), - ("Server", "R7500v2 UPnP/1.0 miniupnpd/1.0"), - ("Location", "http://192.168.0.1:5555/rootDesc.xml"), - ])) - - replies = { - b'GET /rootDesc.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 192.168.0.1\r\nConnection: Close\r\n\r\n': b'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 3720\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10http://192.168.0.1:5555VEN_01f2&DEV_0018&REV_02 VEN_01f2&DEV_8000&SUBSYS_01&REV_01 VEN_01f2&DEV_8000&REV_01 VEN_0033&DEV_0008&REV_01urn:schemas-upnp-org:device:InternetGatewayDevice:1NetworkInfrastructure.RouterNetwork.Router.Wirelessurn:schemas-upnp-org:device:InternetGatewayDevice:1R7500v2 (Gateway)NETGEAR, Inc.http://www.netgear.comNETGEAR R7500v2 NETGEAR Nighthawk X4 AC2350 Smart WiFi RouterNETGEAR Nighthawk X4 AC2350 Smart WiFi RouterR7500v2http://www.netgear.com/home/products/wirelessroutersv1uuid:11111111-2222-3333-4444-555555555555606449084528urn:schemas-upnp-org:service:Layer3Forwarding:1urn:upnp-org:serviceId:L3Forwarding1/ctl/L3Forwarding/evt/L3Forwarding/Layer3F.xmlurn:schemas-upnp-org:device:WANDevice:1WAN DeviceNETGEARhttp://www.netgear.comWAN Device on NETGEAR R7500v2 Wireless RouterNETGEAR Nighthawk X4 AC2350 Smart WiFi RouterR7500v2http://www.netgear.comv1uuid:11111111-2222-3333-4444-5555555555551234567890aburn:schemas-upnp-org:service:WANCommonInterfaceConfig:1urn:upnp-org:serviceId:WANCommonIFC1/ctl/CommonIfCfg/evt/CommonIfCfg/WANCfg.xmlurn:schemas-upnp-org:device:WANConnectionDevice:1WAN Connection DeviceNETGEARhttp://www.netgear.comWANConnectionDevice on NETGEAR R7500v2 Wireless RouterNETGEAR Nighthawk X4 AC2350 Smart WiFi RouterR7500v2http://www.netgear.comv1uuid:4d696e69-444c-164e-9d44-b0b98a4cd3c31234567890aburn:schemas-upnp-org:service:WANEthernetLinkConfig:1urn:upnp-org:serviceId:WANEthLinkC1/ctl/WanEth/evt/WanEth/WanEth.xmlurn:schemas-upnp-org:service:WANIPConnection:1urn:upnp-org:serviceId:WANIPConn1/ctl/IPConn/evt/IPConn/WANIPCn.xmlhttp://www.routerlogin.net', - b'GET /Layer3F.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 192.168.0.1\r\nConnection: Close\r\n\r\n': b'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 794\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10SetDefaultConnectionServiceNewDefaultConnectionServiceinDefaultConnectionServiceGetDefaultConnectionServiceNewDefaultConnectionServiceoutDefaultConnectionServiceDefaultConnectionServicestring', - b'GET /WANCfg.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 192.168.0.1\r\nConnection: Close\r\n\r\n': b'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 2942\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10GetCommonLinkPropertiesNewWANAccessTypeoutWANAccessTypeNewLayer1UpstreamMaxBitRateoutLayer1UpstreamMaxBitRateNewLayer1DownstreamMaxBitRateoutLayer1DownstreamMaxBitRateNewPhysicalLinkStatusoutPhysicalLinkStatusGetTotalBytesSentNewTotalBytesSentoutTotalBytesSentGetTotalBytesReceivedNewTotalBytesReceivedoutTotalBytesReceivedGetTotalPacketsSentNewTotalPacketsSentoutTotalPacketsSentGetTotalPacketsReceivedNewTotalPacketsReceivedoutTotalPacketsReceivedWANAccessTypestringDSLPOTSCableEthernetLayer1UpstreamMaxBitRateui4Layer1DownstreamMaxBitRateui4PhysicalLinkStatusstringUpDownInitializingUnavailableTotalBytesSentui4TotalBytesReceivedui4TotalPacketsSentui4TotalPacketsReceivedui4', - b'GET /WanEth.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 192.168.0.1\r\nConnection: Close\r\n\r\n': b'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 711\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10GetEthernetLinkStatusNewEthernetLinkStatusoutEthernetLinkStatusEthernetLinkStatusstringUpDownInitializingUnavailable', - b'GET /WANIPCn.xml HTTP/1.1\r\nAccept-Encoding: gzip\r\nHost: 192.168.0.1\r\nConnection: Close\r\n\r\n': b'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 8400\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10AddPortMappingNewRemoteHostinRemoteHostNewExternalPortinExternalPortNewProtocolinPortMappingProtocolNewInternalPortinInternalPortNewInternalClientinInternalClientNewEnabledinPortMappingEnabledNewPortMappingDescriptioninPortMappingDescriptionNewLeaseDurationinPortMappingLeaseDurationGetExternalIPAddressNewExternalIPAddressoutExternalIPAddressDeletePortMappingNewRemoteHostinRemoteHostNewExternalPortinExternalPortNewProtocolinPortMappingProtocolSetConnectionTypeNewConnectionTypeinConnectionTypeGetConnectionTypeInfoNewConnectionTypeoutConnectionTypeNewPossibleConnectionTypesoutPossibleConnectionTypesRequestConnectionForceTerminationGetStatusInfoNewConnectionStatusoutConnectionStatusNewLastConnectionErroroutLastConnectionErrorNewUptimeoutUptimeGetNATRSIPStatusNewRSIPAvailableoutRSIPAvailableNewNATEnabledoutNATEnabledGetGenericPortMappingEntryNewPortMappingIndexinPortMappingNumberOfEntriesNewRemoteHostoutRemoteHostNewExternalPortoutExternalPortNewProtocoloutPortMappingProtocolNewInternalPortoutInternalPortNewInternalClientoutInternalClientNewEnabledoutPortMappingEnabledNewPortMappingDescriptionoutPortMappingDescriptionNewLeaseDurationoutPortMappingLeaseDurationGetSpecificPortMappingEntryNewRemoteHostinRemoteHostNewExternalPortinExternalPortNewProtocolinPortMappingProtocolNewInternalPortoutInternalPortNewInternalClientoutInternalClientNewEnabledoutPortMappingEnabledNewPortMappingDescriptionoutPortMappingDescriptionNewLeaseDurationoutPortMappingLeaseDurationConnectionTypestringPossibleConnectionTypesstringUnconfiguredIP_RoutedIP_BridgedConnectionStatusstringUnconfiguredConnectingConnectedPendingDisconnectDisconnectingDisconnectedUptimeui4LastConnectionErrorstringERROR_NONERSIPAvailablebooleanNATEnabledbooleanExternalIPAddressstringPortMappingNumberOfEntriesui2PortMappingEnabledbooleanPortMappingLeaseDurationui4RemoteHoststringExternalPortui2InternalPortui2PortMappingProtocolstringTCPUDPInternalClientstringPortMappingDescriptionstring' - } - - expected_commands = { - # "SetDefaultConnectionService": "urn:schemas-upnp-org:service:Layer3Forwarding:1", - # "GetDefaultConnectionService": "urn:schemas-upnp-org:service:Layer3Forwarding:1", - # "GetCommonLinkProperties": "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1", - # "GetTotalBytesSent": "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1", - # "GetTotalBytesReceived": "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1", - # "GetTotalPacketsSent": "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1", - # "GetTotalPacketsReceived": "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1", - "AddPortMapping": "urn:schemas-upnp-org:service:WANIPConnection:1", - "GetExternalIPAddress": "urn:schemas-upnp-org:service:WANIPConnection:1", - "DeletePortMapping": "urn:schemas-upnp-org:service:WANIPConnection:1", - # "SetConnectionType": "urn:schemas-upnp-org:service:WANIPConnection:1", - # "GetConnectionTypeInfo": "urn:schemas-upnp-org:service:WANIPConnection:1", - # "RequestConnection": "urn:schemas-upnp-org:service:WANIPConnection:1", - # "ForceTermination": "urn:schemas-upnp-org:service:WANIPConnection:1", - # "GetStatusInfo": "urn:schemas-upnp-org:service:WANIPConnection:1", - # "GetNATRSIPStatus": "urn:schemas-upnp-org:service:WANIPConnection:1", - "GetGenericPortMappingEntry": "urn:schemas-upnp-org:service:WANIPConnection:1", - "GetSpecificPortMappingEntry": "urn:schemas-upnp-org:service:WANIPConnection:1" - } + gateway_info = {'manufacturer_string': 'NETGEAR NETGEAR Nighthawk X4 AC2350 Smart WiFi Router', + 'gateway_address': '192.168.0.1', 'server': 'R7500v2 UPnP/1.0 miniupnpd/1.0', + 'urlBase': 'http://192.168.0.1:5555', 'location': 'http://192.168.0.1:5555/rootDesc.xml', + 'specVersion': {'major': '1', 'minor': '0'}, + 'usn': 'uuid:11111111-2222-3333-4444-555555555555::upnp:rootdevice', 'urn': 'upnp:rootdevice', + 'gateway_xml': 'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 3720\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10http://192.168.0.1:5555VEN_01f2&DEV_0018&REV_02 VEN_01f2&DEV_8000&SUBSYS_01&REV_01 VEN_01f2&DEV_8000&REV_01 VEN_0033&DEV_0008&REV_01urn:schemas-upnp-org:device:InternetGatewayDevice:1NetworkInfrastructure.RouterNetwork.Router.Wirelessurn:schemas-upnp-org:device:InternetGatewayDevice:1R7500v2 (Gateway)NETGEAR, Inc.http://www.netgear.comNETGEAR R7500v2 NETGEAR Nighthawk X4 AC2350 Smart WiFi RouterNETGEAR Nighthawk X4 AC2350 Smart WiFi RouterR7500v2http://www.netgear.com/home/products/wirelessroutersv1uuid:11111111-2222-3333-4444-555555555555606449084528urn:schemas-upnp-org:service:Layer3Forwarding:1urn:upnp-org:serviceId:L3Forwarding1/ctl/L3Forwarding/evt/L3Forwarding/Layer3F.xmlurn:schemas-upnp-org:device:WANDevice:1WAN DeviceNETGEARhttp://www.netgear.comWAN Device on NETGEAR R7500v2 Wireless RouterNETGEAR Nighthawk X4 AC2350 Smart WiFi RouterR7500v2http://www.netgear.comv1uuid:11111111-2222-3333-4444-5555555555551234567890aburn:schemas-upnp-org:service:WANCommonInterfaceConfig:1urn:upnp-org:serviceId:WANCommonIFC1/ctl/CommonIfCfg/evt/CommonIfCfg/WANCfg.xmlurn:schemas-upnp-org:device:WANConnectionDevice:1WAN Connection DeviceNETGEARhttp://www.netgear.comWANConnectionDevice on NETGEAR R7500v2 Wireless RouterNETGEAR Nighthawk X4 AC2350 Smart WiFi RouterR7500v2http://www.netgear.comv1uuid:4d696e69-444c-164e-9d44-b0b98a4cd3c31234567890aburn:schemas-upnp-org:service:WANEthernetLinkConfig:1urn:upnp-org:serviceId:WANEthLinkC1/ctl/WanEth/evt/WanEth/WanEth.xmlurn:schemas-upnp-org:service:WANIPConnection:1urn:upnp-org:serviceId:WANIPConn1/ctl/IPConn/evt/IPConn/WANIPCn.xmlhttp://www.routerlogin.net', + 'services_xml': { + '/Layer3F.xml': 'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 794\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10SetDefaultConnectionServiceNewDefaultConnectionServiceinDefaultConnectionServiceGetDefaultConnectionServiceNewDefaultConnectionServiceoutDefaultConnectionServiceDefaultConnectionServicestring', + '/WANCfg.xml': 'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 2942\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10GetCommonLinkPropertiesNewWANAccessTypeoutWANAccessTypeNewLayer1UpstreamMaxBitRateoutLayer1UpstreamMaxBitRateNewLayer1DownstreamMaxBitRateoutLayer1DownstreamMaxBitRateNewPhysicalLinkStatusoutPhysicalLinkStatusGetTotalBytesSentNewTotalBytesSentoutTotalBytesSentGetTotalBytesReceivedNewTotalBytesReceivedoutTotalBytesReceivedGetTotalPacketsSentNewTotalPacketsSentoutTotalPacketsSentGetTotalPacketsReceivedNewTotalPacketsReceivedoutTotalPacketsReceivedWANAccessTypestringDSLPOTSCableEthernetLayer1UpstreamMaxBitRateui4Layer1DownstreamMaxBitRateui4PhysicalLinkStatusstringUpDownInitializingUnavailableTotalBytesSentui4TotalBytesReceivedui4TotalPacketsSentui4TotalPacketsReceivedui4', + '/WanEth.xml': 'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 711\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10GetEthernetLinkStatusNewEthernetLinkStatusoutEthernetLinkStatusEthernetLinkStatusstringUpDownInitializingUnavailable', + '/WANIPCn.xml': 'HTTP/1.1 200 OK\r\nContent-Type: text/xml; charset="utf-8"\r\nConnection: close\r\nContent-Length: 8400\r\nServer: R7500v2 UPnP/1.0 miniupnpd/1.0\r\nExt: \r\nContent-Language: en-US\r\n\r\n\n10AddPortMappingNewRemoteHostinRemoteHostNewExternalPortinExternalPortNewProtocolinPortMappingProtocolNewInternalPortinInternalPortNewInternalClientinInternalClientNewEnabledinPortMappingEnabledNewPortMappingDescriptioninPortMappingDescriptionNewLeaseDurationinPortMappingLeaseDurationGetExternalIPAddressNewExternalIPAddressoutExternalIPAddressDeletePortMappingNewRemoteHostinRemoteHostNewExternalPortinExternalPortNewProtocolinPortMappingProtocolSetConnectionTypeNewConnectionTypeinConnectionTypeGetConnectionTypeInfoNewConnectionTypeoutConnectionTypeNewPossibleConnectionTypesoutPossibleConnectionTypesRequestConnectionForceTerminationGetStatusInfoNewConnectionStatusoutConnectionStatusNewLastConnectionErroroutLastConnectionErrorNewUptimeoutUptimeGetNATRSIPStatusNewRSIPAvailableoutRSIPAvailableNewNATEnabledoutNATEnabledGetGenericPortMappingEntryNewPortMappingIndexinPortMappingNumberOfEntriesNewRemoteHostoutRemoteHostNewExternalPortoutExternalPortNewProtocoloutPortMappingProtocolNewInternalPortoutInternalPortNewInternalClientoutInternalClientNewEnabledoutPortMappingEnabledNewPortMappingDescriptionoutPortMappingDescriptionNewLeaseDurationoutPortMappingLeaseDurationGetSpecificPortMappingEntryNewRemoteHostinRemoteHostNewExternalPortinExternalPortNewProtocolinPortMappingProtocolNewInternalPortoutInternalPortNewInternalClientoutInternalClientNewEnabledoutPortMappingEnabledNewPortMappingDescriptionoutPortMappingDescriptionNewLeaseDurationoutPortMappingLeaseDurationConnectionTypestringPossibleConnectionTypesstringUnconfiguredIP_RoutedIP_BridgedConnectionStatusstringUnconfiguredConnectingConnectedPendingDisconnectDisconnectingDisconnectedUptimeui4LastConnectionErrorstringERROR_NONERSIPAvailablebooleanNATEnabledbooleanExternalIPAddressstringPortMappingNumberOfEntriesui2PortMappingEnabledbooleanPortMappingLeaseDurationui4RemoteHoststringExternalPortui2InternalPortui2PortMappingProtocolstringTCPUDPInternalClientstringPortMappingDescriptionstring'}, + 'services': {'/Layer3F.xml': OrderedDict( + [('serviceType', 'urn:schemas-upnp-org:service:Layer3Forwarding:1'), + ('serviceId', 'urn:upnp-org:serviceId:L3Forwarding1'), ('controlURL', '/ctl/L3Forwarding'), + ('eventSubURL', '/evt/L3Forwarding'), ('SCPDURL', '/Layer3F.xml')]), + '/WANCfg.xml': OrderedDict( + [('serviceType', 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'), + ('serviceId', 'urn:upnp-org:serviceId:WANCommonIFC1'), + ('controlURL', '/ctl/CommonIfCfg'), ('eventSubURL', '/evt/CommonIfCfg'), + ('SCPDURL', '/WANCfg.xml')]), '/WanEth.xml': OrderedDict( + [('serviceType', 'urn:schemas-upnp-org:service:WANEthernetLinkConfig:1'), + ('serviceId', 'urn:upnp-org:serviceId:WANEthLinkC1'), ('controlURL', '/ctl/WanEth'), + ('eventSubURL', '/evt/WanEth'), ('SCPDURL', '/WanEth.xml')]), '/WANIPCn.xml': OrderedDict( + [('serviceType', 'urn:schemas-upnp-org:service:WANIPConnection:1'), + ('serviceId', 'urn:upnp-org:serviceId:WANIPConn1'), ('controlURL', '/ctl/IPConn'), + ('eventSubURL', '/evt/IPConn'), ('SCPDURL', '/WANIPCn.xml')])}, + 'm_search_args': OrderedDict( + [('HOST', '239.255.255.250:1900'), ('MAN', '"ssdp:discover"'), ('MX', 1), + ('ST', 'upnp:rootdevice')]), 'reply': OrderedDict( + [('CACHE_CONTROL', 'max-age=1800'), ('ST', 'upnp:rootdevice'), + ('USN', 'uuid:11111111-2222-3333-4444-555555555555::upnp:rootdevice'), + ('Server', 'R7500v2 UPnP/1.0 miniupnpd/1.0'), ('Location', 'http://192.168.0.1:5555/rootDesc.xml')]), + 'soap_port': 5555, + 'registered_soap_commands': {'AddPortMapping': 'urn:schemas-upnp-org:service:WANIPConnection:1', + 'GetExternalIPAddress': 'urn:schemas-upnp-org:service:WANIPConnection:1', + 'DeletePortMapping': 'urn:schemas-upnp-org:service:WANIPConnection:1', + 'GetGenericPortMappingEntry': 'urn:schemas-upnp-org:service:WANIPConnection:1', + 'GetSpecificPortMappingEntry': 'urn:schemas-upnp-org:service:WANIPConnection:1'}, + 'unsupported_soap_commands': { + 'urn:schemas-upnp-org:service:Layer3Forwarding:1': ['SetDefaultConnectionService', + 'GetDefaultConnectionService'], + 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1': ['GetCommonLinkProperties', + 'GetTotalBytesSent', + 'GetTotalBytesReceived', + 'GetTotalPacketsSent', + 'GetTotalPacketsReceived'], + 'urn:schemas-upnp-org:service:WANEthernetLinkConfig:1': ['GetEthernetLinkStatus'], + 'urn:schemas-upnp-org:service:WANIPConnection:1': ['SetConnectionType', 'GetConnectionTypeInfo', + 'RequestConnection', 'ForceTermination', + 'GetStatusInfo', 'GetNATRSIPStatus']}, + 'soap_requests': []} diff --git a/tests/test_upnp.py b/tests/test_upnp.py index bd93570..267d9fe 100644 --- a/tests/test_upnp.py +++ b/tests/test_upnp.py @@ -44,8 +44,46 @@ class TestGetExternalIPAddress(UPnPCommandTestCase): async def test_get_external_ip(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) + gateway = Gateway(self.reply, self.m_search_args, self.client_address, self.gateway_address, loop=self.loop) + await gateway.discover_commands() + upnp = UPnP(self.client_address, self.gateway_address, gateway) + external_ip = await upnp.get_external_ip() + self.assertEqual("11.222.3.44", external_ip) + + +class TestMalformedGetExternalIPAddressResponse(UPnPCommandTestCase): + client_address = '11.2.3.222' + get_ip_request = 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: 285\r\nContent-Type: text/xml\r\nSOAPAction: "urn:schemas-upnp-org:service:WANIPConnection:1#GetExternalIPAddress"\r\nConnection: Close\r\nCache-Control: no-cache\r\nPragma: no-cache\r\n\r\n\r\n\r\n' + + async def test_response_key_mismatch(self): + self.replies.update({self.get_ip_request: b"HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Wed, 22 May 2019 03:25:57 GMT\r\nConnection: close\r\nCONTENT-TYPE: text/xml; charset=\"utf-8\"\r\nCONTENT-LENGTH: 333 \r\nEXT:\r\n\r\n\n\n\t\n\t\t\n" + b"11.222.3.44\n\n\t\n\n"}) + self.addCleanup(self.replies.pop, self.get_ip_request) + 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, loop=self.loop) + await gateway.discover_commands() + upnp = UPnP(self.client_address, self.gateway_address, gateway) + with self.assertRaises(UPnPError): + await upnp.get_external_ip() + + async def test_response_key_case_sensitivity(self): + self.replies.update({self.get_ip_request: b"HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Wed, 22 May 2019 03:25:57 GMT\r\nConnection: close\r\nCONTENT-TYPE: text/xml; charset=\"utf-8\"\r\nCONTENT-LENGTH: 365 \r\nEXT:\r\n\r\n\n\n\t\n\t\t\n" + b"11.222.3.44\n\n\t\n\n"}) + self.addCleanup(self.replies.pop, self.get_ip_request) + 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, loop=self.loop) + await gateway.discover_commands() + upnp = UPnP(self.client_address, self.gateway_address, gateway) + external_ip = await upnp.get_external_ip() + self.assertEqual("11.222.3.44", external_ip) + + async def test_non_encapsulated_single_field_response(self): + self.replies.update({self.get_ip_request: b"HTTP/1.1 200 OK\r\nServer: WebServer\r\nDate: Wed, 22 May 2019 03:25:57 GMT\r\nConnection: close\r\nCONTENT-TYPE: text/xml; charset=\"utf-8\"\r\nCONTENT-LENGTH: 320 \r\nEXT:\r\n\r\n\n\n\t\n\t\t\n" + b"11.222.3.44\n\n\t\n\n"}) + self.addCleanup(self.replies.pop, self.get_ip_request) + 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, loop=self.loop) + await gateway.discover_commands() upnp = UPnP(self.client_address, self.gateway_address, gateway) external_ip = await upnp.get_external_ip() self.assertEqual("11.222.3.44", external_ip) @@ -62,8 +100,8 @@ class TestGetGenericPortMappingEntry(UPnPCommandTestCase): async def test_get_port_mapping_by_index(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) + gateway = Gateway(self.reply, self.m_search_args, self.client_address, self.gateway_address, loop=self.loop) + await gateway.discover_commands() upnp = UPnP(self.client_address, self.gateway_address, gateway) result = await upnp.get_port_mapping_by_index(0) self.assertEqual(GetGenericPortMappingEntryResponse(None, 9308, 'UDP', 9308, "11.2.3.44", True, @@ -84,8 +122,8 @@ class TestGetNextPortMapping(UPnPCommandTestCase): async def test_get_next_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) + gateway = Gateway(self.reply, self.m_search_args, self.client_address, self.gateway_address, loop=self.loop) + await gateway.discover_commands() upnp = UPnP(self.client_address, self.gateway_address, gateway) ext_port = await upnp.get_next_mapping(4567, "UDP", "aioupnp test mapping") self.assertEqual(4567, ext_port) @@ -104,8 +142,8 @@ class TestGetSpecificPortMapping(UPnPCommandTestCase): 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) + gateway = Gateway(self.reply, self.m_search_args, self.client_address, self.gateway_address, loop=self.loop) + await gateway.discover_commands() upnp = UPnP(self.client_address, self.gateway_address, gateway) try: await upnp.get_specific_port_mapping(1000, 'UDP')