Faster gateway discovery #18
11 changed files with 245 additions and 244 deletions
|
@ -75,8 +75,7 @@ def main(argv: typing.Optional[typing.List[typing.Optional[str]]] = None,
|
||||||
'interface': 'default',
|
'interface': 'default',
|
||||||
'gateway_address': '',
|
'gateway_address': '',
|
||||||
'lan_address': '',
|
'lan_address': '',
|
||||||
'timeout': 30,
|
'timeout': 3,
|
||||||
'unicast': False
|
|
||||||
}
|
}
|
||||||
|
|
||||||
options: typing.Dict[str, typing.Union[bool, str, int]] = OrderedDict()
|
options: typing.Dict[str, typing.Union[bool, str, int]] = OrderedDict()
|
||||||
|
@ -114,10 +113,9 @@ def main(argv: typing.Optional[typing.List[typing.Optional[str]]] = None,
|
||||||
gateway_address: str = str(options.pop('gateway_address'))
|
gateway_address: str = str(options.pop('gateway_address'))
|
||||||
timeout: int = int(options.pop('timeout'))
|
timeout: int = int(options.pop('timeout'))
|
||||||
interface: str = str(options.pop('interface'))
|
interface: str = str(options.pop('interface'))
|
||||||
unicast: bool = bool(options.pop('unicast'))
|
|
||||||
|
|
||||||
run_cli(
|
run_cli(
|
||||||
command.replace('-', '_'), options, lan_address, gateway_address, timeout, interface, unicast, kwargs, loop
|
command.replace('-', '_'), options, lan_address, gateway_address, timeout, interface, kwargs, loop
|
||||||
)
|
)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,12 @@ import re
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections import OrderedDict
|
from typing import Dict, List, Optional
|
||||||
from typing import Dict, List
|
|
||||||
from aioupnp.util import get_dict_val_case_insensitive
|
from aioupnp.util import get_dict_val_case_insensitive
|
||||||
from aioupnp.constants import SPEC_VERSION, SERVICE
|
from aioupnp.constants import SPEC_VERSION, SERVICE
|
||||||
from aioupnp.commands import SOAPCommands, SCPDRequestDebuggingInfo
|
from aioupnp.commands import SOAPCommands
|
||||||
from aioupnp.device import Device, Service
|
from aioupnp.device import Device, Service
|
||||||
from aioupnp.protocols.ssdp import fuzzy_m_search, m_search
|
from aioupnp.protocols.ssdp import m_search, multi_m_search
|
||||||
from aioupnp.protocols.scpd import scpd_get
|
from aioupnp.protocols.scpd import scpd_get
|
||||||
from aioupnp.serialization.ssdp import SSDPDatagram
|
from aioupnp.serialization.ssdp import SSDPDatagram
|
||||||
from aioupnp.util import flatten_keys
|
from aioupnp.util import flatten_keys
|
||||||
|
@ -69,12 +68,10 @@ def parse_location(location: bytes) -> typing.Tuple[bytes, int]:
|
||||||
|
|
||||||
|
|
||||||
class Gateway:
|
class Gateway:
|
||||||
def __init__(self, ok_packet: SSDPDatagram, m_search_args: typing.Dict[str, typing.Union[int, str]],
|
def __init__(self, ok_packet: SSDPDatagram, lan_address: str, gateway_address: str,
|
||||||
lan_address: str, gateway_address: str,
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
|
||||||
loop: typing.Optional[asyncio.AbstractEventLoop] = None) -> None:
|
|
||||||
self._loop = loop or asyncio.get_event_loop()
|
self._loop = loop or asyncio.get_event_loop()
|
||||||
self._ok_packet = ok_packet
|
self._ok_packet = ok_packet
|
||||||
self._m_search_args = m_search_args
|
|
||||||
self._lan_address = lan_address
|
self._lan_address = lan_address
|
||||||
self.usn: bytes = (ok_packet.usn or '').encode()
|
self.usn: bytes = (ok_packet.usn or '').encode()
|
||||||
self.ext: bytes = (ok_packet.ext or '').encode()
|
self.ext: bytes = (ok_packet.ext or '').encode()
|
||||||
|
@ -92,10 +89,10 @@ class Gateway:
|
||||||
assert self.base_ip == gateway_address.encode()
|
assert self.base_ip == gateway_address.encode()
|
||||||
self.path = self.location.split(b"%s:%i/" % (self.base_ip, self.port))[1]
|
self.path = self.location.split(b"%s:%i/" % (self.base_ip, self.port))[1]
|
||||||
|
|
||||||
self.spec_version: typing.Optional[str] = None
|
self.spec_version: Optional[str] = None
|
||||||
self.url_base: typing.Optional[str] = None
|
self.url_base: Optional[str] = None
|
||||||
|
|
||||||
self._device: typing.Optional[Device] = None
|
self._device: Optional[Device] = None
|
||||||
self._devices: List[Device] = []
|
self._devices: List[Device] = []
|
||||||
self._services: List[Service] = []
|
self._services: List[Service] = []
|
||||||
|
|
||||||
|
@ -130,7 +127,7 @@ class Gateway:
|
||||||
devices[device.udn] = device
|
devices[device.udn] = device
|
||||||
return devices
|
return devices
|
||||||
|
|
||||||
# def get_service(self, service_type: str) -> typing.Optional[Service]:
|
# def get_service(self, service_type: str) -> Optional[Service]:
|
||||||
# for service in self._services:
|
# for service in self._services:
|
||||||
# if service.serviceType and service.serviceType.lower() == service_type.lower():
|
# if service.serviceType and service.serviceType.lower() == service_type.lower():
|
||||||
# return service
|
# return service
|
||||||
|
@ -149,7 +146,6 @@ class Gateway:
|
||||||
'gateway_xml': self._xml_response.decode(),
|
'gateway_xml': self._xml_response.decode(),
|
||||||
'services_xml': self._service_descriptors,
|
'services_xml': self._service_descriptors,
|
||||||
'services': {service.SCPDURL: service.as_dict() for service in self._services},
|
'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(),
|
'reply': self._ok_packet.as_dict(),
|
||||||
'soap_port': self.port,
|
'soap_port': self.port,
|
||||||
'registered_soap_commands': self._registered_commands,
|
'registered_soap_commands': self._registered_commands,
|
||||||
|
@ -158,26 +154,16 @@ class Gateway:
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def _discover_gateway(cls, lan_address: str, gateway_address: str, timeout: int = 30,
|
async def _try_gateway_from_ssdp(cls, datagram: SSDPDatagram, lan_address: str,
|
||||||
igd_args: typing.Optional[typing.Dict[str, typing.Union[int, str]]] = None,
|
gateway_address: str,
|
||||||
loop: typing.Optional[asyncio.AbstractEventLoop] = None,
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> Optional['Gateway']:
|
||||||
unicast: bool = False) -> 'Gateway':
|
|
||||||
ignored: typing.Set[str] = set()
|
|
||||||
required_commands: typing.List[str] = [
|
required_commands: typing.List[str] = [
|
||||||
'AddPortMapping',
|
'AddPortMapping',
|
||||||
'DeletePortMapping',
|
'DeletePortMapping',
|
||||||
'GetExternalIPAddress'
|
'GetExternalIPAddress'
|
||||||
]
|
]
|
||||||
while True:
|
|
||||||
if not igd_args:
|
|
||||||
m_search_args, datagram = await fuzzy_m_search(
|
|
||||||
lan_address, gateway_address, timeout, loop, ignored, unicast
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
m_search_args = OrderedDict(igd_args)
|
|
||||||
datagram = await m_search(lan_address, gateway_address, igd_args, timeout, loop, ignored, unicast)
|
|
||||||
try:
|
try:
|
||||||
gateway = cls(datagram, m_search_args, lan_address, gateway_address, loop=loop)
|
gateway = cls(datagram, lan_address, gateway_address, loop=loop)
|
||||||
log.debug('get gateway descriptor %s', datagram.location)
|
log.debug('get gateway descriptor %s', datagram.location)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
requirements_met = all([gateway.commands.is_registered(required) for required in required_commands])
|
requirements_met = all([gateway.commands.is_registered(required) for required in required_commands])
|
||||||
|
@ -188,44 +174,59 @@ class Gateway:
|
||||||
assert datagram.location is not None
|
assert datagram.location is not None
|
||||||
log.debug("found gateway %s at %s, but it does not implement required soap commands: %s",
|
log.debug("found gateway %s at %s, but it does not implement required soap commands: %s",
|
||||||
gateway.manufacturer_string, gateway.location, not_met)
|
gateway.manufacturer_string, gateway.location, not_met)
|
||||||
ignored.add(datagram.location)
|
return None
|
||||||
continue
|
|
||||||
else:
|
else:
|
||||||
log.debug('found gateway %s at %s', gateway.manufacturer_string or "device", datagram.location)
|
log.debug('found gateway %s at %s', gateway.manufacturer_string or "device", datagram.location)
|
||||||
return gateway
|
return gateway
|
||||||
except (asyncio.TimeoutError, UPnPError) as err:
|
except (asyncio.TimeoutError, UPnPError) as err:
|
||||||
assert datagram.location is not None
|
assert datagram.location is not None
|
||||||
log.debug("get %s failed (%s), looking for other devices", datagram.location, str(err))
|
log.debug("get %s failed (%s), looking for other devices", datagram.location, str(err))
|
||||||
ignored.add(datagram.location)
|
return None
|
||||||
continue
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def discover_gateway(cls, lan_address: str, gateway_address: str, timeout: int = 30,
|
async def _gateway_from_igd_args(cls, lan_address: str, gateway_address: str,
|
||||||
igd_args: typing.Optional[typing.Dict[str, typing.Union[int, str]]] = None,
|
igd_args: typing.Dict[str, typing.Union[int, str]],
|
||||||
loop: typing.Optional[asyncio.AbstractEventLoop] = None,
|
timeout: int = 30,
|
||||||
unicast: typing.Optional[bool] = None) -> 'Gateway':
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> 'Gateway':
|
||||||
loop = loop or asyncio.get_event_loop()
|
datagram = await m_search(lan_address, gateway_address, igd_args, timeout, loop)
|
||||||
if unicast is not None:
|
gateway = await cls._try_gateway_from_ssdp(datagram, lan_address, gateway_address, loop)
|
||||||
return await cls._discover_gateway(lan_address, gateway_address, timeout, igd_args, loop, unicast)
|
if not gateway:
|
||||||
|
raise UPnPError("no gateway found for given args")
|
||||||
|
return gateway
|
||||||
|
|
||||||
done, pending = await asyncio.wait([
|
@classmethod
|
||||||
cls._discover_gateway(
|
async def _discover_gateway(cls, lan_address: str, gateway_address: str, timeout: int = 3,
|
||||||
lan_address, gateway_address, timeout, igd_args, loop, unicast=True
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> 'Gateway':
|
||||||
),
|
ignored: typing.Set[str] = set()
|
||||||
cls._discover_gateway(
|
ssdp_proto = await multi_m_search(
|
||||||
lan_address, gateway_address, timeout, igd_args, loop, unicast=False
|
lan_address, gateway_address, timeout, loop
|
||||||
)
|
)
|
||||||
], return_when=asyncio.tasks.FIRST_COMPLETED, loop=loop)
|
|
||||||
|
|
||||||
for task in pending:
|
|
||||||
task.cancel()
|
|
||||||
for task in done:
|
|
||||||
try:
|
try:
|
||||||
task.exception()
|
while True:
|
||||||
except asyncio.CancelledError:
|
datagram = await ssdp_proto.devices.get()
|
||||||
pass
|
if datagram.location in ignored:
|
||||||
results: typing.List['asyncio.Future[Gateway]'] = list(done)
|
continue
|
||||||
return results[0].result()
|
gateway = await cls._try_gateway_from_ssdp(datagram, lan_address, gateway_address, loop)
|
||||||
|
if gateway:
|
||||||
|
return gateway
|
||||||
|
elif datagram.location:
|
||||||
|
ignored.add(datagram.location)
|
||||||
|
finally:
|
||||||
|
ssdp_proto.disconnect()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def discover_gateway(cls, lan_address: str, gateway_address: str, timeout: int = 3,
|
||||||
|
igd_args: Optional[typing.Dict[str, typing.Union[int, str]]] = None,
|
||||||
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> 'Gateway':
|
||||||
|
loop = loop or asyncio.get_event_loop()
|
||||||
|
if igd_args:
|
||||||
|
return await cls._gateway_from_igd_args(lan_address, gateway_address, igd_args, timeout, loop)
|
||||||
|
try:
|
||||||
|
return await asyncio.wait_for(loop.create_task(
|
||||||
|
cls._discover_gateway(lan_address, gateway_address, timeout, loop)
|
||||||
|
), timeout, loop=loop)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
raise UPnPError(f"M-SEARCH for {gateway_address}:1900 timed out")
|
||||||
|
|
||||||
async def discover_commands(self) -> None:
|
async def discover_commands(self) -> None:
|
||||||
response, xml_bytes, get_err = await scpd_get(
|
response, xml_bytes, get_err = await scpd_get(
|
||||||
|
@ -270,7 +271,7 @@ class Gateway:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def register_commands(self, service: Service,
|
async def register_commands(self, service: Service,
|
||||||
loop: typing.Optional[asyncio.AbstractEventLoop] = None) -> None:
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
|
||||||
if not service.SCPDURL:
|
if not service.SCPDURL:
|
||||||
raise UPnPError("no scpd url")
|
raise UPnPError("no scpd url")
|
||||||
if not service.serviceType:
|
if not service.serviceType:
|
||||||
|
|
|
@ -105,7 +105,7 @@ async def scpd_get(control_url: str, address: str, port: int,
|
||||||
typing.Dict[str, typing.Any], bytes, typing.Optional[Exception]]:
|
typing.Dict[str, typing.Any], bytes, typing.Optional[Exception]]:
|
||||||
loop = loop or asyncio.get_event_loop()
|
loop = loop or asyncio.get_event_loop()
|
||||||
packet = serialize_scpd_get(control_url, address)
|
packet = serialize_scpd_get(control_url, address)
|
||||||
finished: 'asyncio.Future[typing.Tuple[bytes, bytes, int, bytes]]' = asyncio.Future(loop=loop)
|
finished: 'asyncio.Future[typing.Tuple[bytes, bytes, int, bytes]]' = loop.create_future()
|
||||||
proto_factory: typing.Callable[[], SCPDHTTPClientProtocol] = lambda: SCPDHTTPClientProtocol(packet, finished)
|
proto_factory: typing.Callable[[], SCPDHTTPClientProtocol] = lambda: SCPDHTTPClientProtocol(packet, finished)
|
||||||
connect_tup: typing.Tuple[asyncio.BaseTransport, asyncio.BaseProtocol] = await loop.create_connection(
|
connect_tup: typing.Tuple[asyncio.BaseTransport, asyncio.BaseProtocol] = await loop.create_connection(
|
||||||
proto_factory, address, port
|
proto_factory, address, port
|
||||||
|
@ -141,7 +141,7 @@ async def scpd_post(control_url: str, address: str, port: int, method: str, para
|
||||||
**kwargs: typing.Dict[str, typing.Any]
|
**kwargs: typing.Dict[str, typing.Any]
|
||||||
) -> typing.Tuple[typing.Dict, bytes, typing.Optional[Exception]]:
|
) -> typing.Tuple[typing.Dict, bytes, typing.Optional[Exception]]:
|
||||||
loop = loop or asyncio.get_event_loop()
|
loop = loop or asyncio.get_event_loop()
|
||||||
finished: 'asyncio.Future[typing.Tuple[bytes, bytes, int, bytes]]' = asyncio.Future(loop=loop)
|
finished: 'asyncio.Future[typing.Tuple[bytes, bytes, int, bytes]]' = loop.create_future()
|
||||||
packet = serialize_soap_post(method, param_names, service_id, address.encode(), control_url.encode(), **kwargs)
|
packet = serialize_soap_post(method, param_names, service_id, address.encode(), control_url.encode(), **kwargs)
|
||||||
proto_factory: typing.Callable[[], SCPDHTTPClientProtocol] = lambda:\
|
proto_factory: typing.Callable[[], SCPDHTTPClientProtocol] = lambda:\
|
||||||
SCPDHTTPClientProtocol(packet, finished, soap_method=method, soap_service_id=service_id.decode())
|
SCPDHTTPClientProtocol(packet, finished, soap_method=method, soap_service_id=service_id.decode())
|
||||||
|
|
|
@ -4,6 +4,7 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
import socket
|
import socket
|
||||||
|
from typing import List, Set, Dict, Tuple, Optional
|
||||||
from asyncio.transports import DatagramTransport
|
from asyncio.transports import DatagramTransport
|
||||||
from aioupnp.fault import UPnPError
|
from aioupnp.fault import UPnPError
|
||||||
from aioupnp.serialization.ssdp import SSDPDatagram
|
from aioupnp.serialization.ssdp import SSDPDatagram
|
||||||
|
@ -16,17 +17,22 @@ ADDRESS_REGEX = re.compile("^http:\/\/(\d+\.\d+\.\d+\.\d+)\:(\d*)(\/[\w|\/|\:|\-
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PendingSearch(typing.NamedTuple):
|
||||||
|
address: str
|
||||||
|
st: str
|
||||||
|
fut: 'asyncio.Future[SSDPDatagram]'
|
||||||
|
|
||||||
|
|
||||||
class SSDPProtocol(MulticastProtocol):
|
class SSDPProtocol(MulticastProtocol):
|
||||||
def __init__(self, multicast_address: str, lan_address: str, ignored: typing.Optional[typing.Set[str]] = None,
|
def __init__(self, multicast_address: str, lan_address: str,
|
||||||
unicast: bool = False, loop: typing.Optional[asyncio.AbstractEventLoop] = None) -> None:
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
|
||||||
super().__init__(multicast_address, lan_address)
|
super().__init__(multicast_address, lan_address)
|
||||||
self.loop: asyncio.AbstractEventLoop = loop or asyncio.get_event_loop()
|
self.loop: asyncio.AbstractEventLoop = loop or asyncio.get_event_loop()
|
||||||
self.transport: typing.Optional[DatagramTransport] = None
|
self.transport: Optional[DatagramTransport] = None
|
||||||
self._unicast = unicast
|
self._pending_searches: List[PendingSearch] = []
|
||||||
self._ignored: typing.Set[str] = ignored or set() # ignored locations
|
self.notifications: List[SSDPDatagram] = []
|
||||||
self._pending_searches: typing.List[typing.Tuple[str, str, 'asyncio.Future[SSDPDatagram]', asyncio.Handle]] = []
|
|
||||||
self.notifications: typing.List[SSDPDatagram] = []
|
|
||||||
self.connected = asyncio.Event(loop=self.loop)
|
self.connected = asyncio.Event(loop=self.loop)
|
||||||
|
self.devices: 'asyncio.Queue[SSDPDatagram]' = asyncio.Queue(loop=self.loop)
|
||||||
|
|
||||||
def connection_made(self, transport: asyncio.DatagramTransport) -> None: # type: ignore
|
def connection_made(self, transport: asyncio.DatagramTransport) -> None: # type: ignore
|
||||||
super().connection_made(transport)
|
super().connection_made(transport)
|
||||||
|
@ -45,46 +51,56 @@ class SSDPProtocol(MulticastProtocol):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _callback_m_search_ok(self, address: str, packet: SSDPDatagram) -> None:
|
def _callback_m_search_ok(self, address: str, packet: SSDPDatagram) -> None:
|
||||||
if packet.location not in self._ignored:
|
futures: Set['asyncio.Future[SSDPDatagram]'] = set()
|
||||||
# TODO: fix this
|
replied: List[PendingSearch] = []
|
||||||
tmp: typing.List[typing.Tuple[str, str, 'asyncio.Future[SSDPDatagram]', asyncio.Handle]] = []
|
|
||||||
set_futures: typing.List['asyncio.Future[SSDPDatagram]'] = []
|
for pending in self._pending_searches:
|
||||||
while len(self._pending_searches):
|
# if pending.address == address and pending.st in (packet.st, "upnp:rootdevice"):
|
||||||
t = self._pending_searches.pop()
|
if pending.address == address and pending.st == packet.st:
|
||||||
if (address == t[0]) and (t[1] in [packet.st, "upnp:rootdevice"]):
|
replied.append(pending)
|
||||||
f = t[2]
|
if pending.fut not in futures:
|
||||||
if f not in set_futures:
|
futures.add(pending.fut)
|
||||||
set_futures.append(f)
|
if replied:
|
||||||
if not f.done():
|
self.devices.put_nowait(packet)
|
||||||
f.set_result(packet)
|
|
||||||
elif t[2] not in set_futures:
|
while replied:
|
||||||
tmp.append(t)
|
self._pending_searches.remove(replied.pop())
|
||||||
while tmp:
|
|
||||||
self._pending_searches.append(tmp.pop())
|
while futures:
|
||||||
return None
|
fut = futures.pop()
|
||||||
|
if not fut.done():
|
||||||
|
fut.set_result(packet)
|
||||||
|
|
||||||
def _send_m_search(self, address: str, packet: SSDPDatagram, fut: 'asyncio.Future[SSDPDatagram]') -> None:
|
def _send_m_search(self, address: str, packet: SSDPDatagram, fut: 'asyncio.Future[SSDPDatagram]') -> None:
|
||||||
dest = address if self._unicast else SSDP_IP_ADDRESS
|
|
||||||
if not self.transport:
|
if not self.transport:
|
||||||
if not fut.done():
|
if not fut.done():
|
||||||
fut.set_exception(UPnPError("SSDP transport not connected"))
|
fut.set_exception(UPnPError("SSDP transport not connected"))
|
||||||
return None
|
return
|
||||||
log.debug("send m search to %s: %s", dest, packet.st)
|
assert packet.st is not None
|
||||||
self.transport.sendto(packet.encode().encode(), (dest, SSDP_PORT))
|
self._pending_searches.append(
|
||||||
return None
|
PendingSearch(address, packet.st, fut)
|
||||||
|
)
|
||||||
|
self.transport.sendto(packet.encode().encode(), (SSDP_IP_ADDRESS, SSDP_PORT))
|
||||||
|
|
||||||
async def m_search(self, address: str, timeout: float,
|
# also send unicast
|
||||||
datagrams: typing.List[typing.Dict[str, typing.Union[str, int]]]) -> SSDPDatagram:
|
log.debug("send m search to %s: %s", address, packet.st)
|
||||||
fut: 'asyncio.Future[SSDPDatagram]' = asyncio.Future(loop=self.loop)
|
self.transport.sendto(packet.encode().encode(), (address, SSDP_PORT))
|
||||||
|
|
||||||
|
def send_m_searches(self, address: str,
|
||||||
|
datagrams: List[Dict[str, typing.Union[str, int]]]) -> 'asyncio.Future[SSDPDatagram]':
|
||||||
|
fut: 'asyncio.Future[SSDPDatagram]' = self.loop.create_future()
|
||||||
for datagram in datagrams:
|
for datagram in datagrams:
|
||||||
packet = SSDPDatagram("M-SEARCH", datagram)
|
packet = SSDPDatagram("M-SEARCH", datagram)
|
||||||
assert packet.st is not None
|
assert packet.st is not None
|
||||||
self._pending_searches.append(
|
self._send_m_search(address, packet, fut)
|
||||||
(address, packet.st, fut, self.loop.call_soon(self._send_m_search, address, packet, fut))
|
return fut
|
||||||
)
|
|
||||||
return await asyncio.wait_for(fut, timeout)
|
|
||||||
|
|
||||||
def datagram_received(self, data: bytes, addr: typing.Tuple[str, int]) -> None: # type: ignore
|
async def m_search(self, address: str, timeout: float,
|
||||||
|
datagrams: List[Dict[str, typing.Union[str, int]]]) -> SSDPDatagram:
|
||||||
|
fut = self.send_m_searches(address, datagrams)
|
||||||
|
return await asyncio.wait_for(fut, timeout, loop=self.loop)
|
||||||
|
|
||||||
|
def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None: # type: ignore
|
||||||
if addr[0] == self.bind_address:
|
if addr[0] == self.bind_address:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
|
@ -94,7 +110,6 @@ class SSDPProtocol(MulticastProtocol):
|
||||||
log.warning("failed to decode SSDP packet from %s:%i (%s): %s", addr[0], addr[1], err,
|
log.warning("failed to decode SSDP packet from %s:%i (%s): %s", addr[0], addr[1], err,
|
||||||
binascii.hexlify(data))
|
binascii.hexlify(data))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if packet._packet_type == packet._OK:
|
if packet._packet_type == packet._OK:
|
||||||
self._callback_m_search_ok(addr[0], packet)
|
self._callback_m_search_ok(addr[0], packet)
|
||||||
return None
|
return None
|
||||||
|
@ -118,14 +133,13 @@ class SSDPProtocol(MulticastProtocol):
|
||||||
# return
|
# return
|
||||||
|
|
||||||
|
|
||||||
async def listen_ssdp(lan_address: str, gateway_address: str, loop: typing.Optional[asyncio.AbstractEventLoop] = None,
|
async def listen_ssdp(lan_address: str, gateway_address: str,
|
||||||
ignored: typing.Optional[typing.Set[str]] = None,
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> Tuple[SSDPProtocol, str, str]:
|
||||||
unicast: bool = False) -> typing.Tuple[SSDPProtocol, str, str]:
|
|
||||||
loop = loop or asyncio.get_event_loop()
|
loop = loop or asyncio.get_event_loop()
|
||||||
try:
|
try:
|
||||||
sock: socket.socket = SSDPProtocol.create_multicast_socket(lan_address)
|
sock: socket.socket = SSDPProtocol.create_multicast_socket(lan_address)
|
||||||
listen_result: typing.Tuple[asyncio.BaseTransport, asyncio.BaseProtocol] = await loop.create_datagram_endpoint(
|
listen_result: Tuple[asyncio.BaseTransport, asyncio.BaseProtocol] = await loop.create_datagram_endpoint(
|
||||||
lambda: SSDPProtocol(SSDP_IP_ADDRESS, lan_address, ignored, unicast), sock=sock
|
lambda: SSDPProtocol(SSDP_IP_ADDRESS, lan_address), sock=sock
|
||||||
)
|
)
|
||||||
protocol = listen_result[1]
|
protocol = listen_result[1]
|
||||||
assert isinstance(protocol, SSDPProtocol)
|
assert isinstance(protocol, SSDPProtocol)
|
||||||
|
@ -137,58 +151,28 @@ async def listen_ssdp(lan_address: str, gateway_address: str, loop: typing.Optio
|
||||||
return protocol, gateway_address, lan_address
|
return protocol, gateway_address, lan_address
|
||||||
|
|
||||||
|
|
||||||
async def m_search(lan_address: str, gateway_address: str, datagram_args: typing.Dict[str, typing.Union[int, str]],
|
async def m_search(lan_address: str, gateway_address: str, datagram_args: Dict[str, typing.Union[int, str]],
|
||||||
timeout: int = 1, loop: typing.Optional[asyncio.AbstractEventLoop] = None,
|
timeout: int = 1,
|
||||||
ignored: typing.Set[str] = None, unicast: bool = False) -> SSDPDatagram:
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> SSDPDatagram:
|
||||||
protocol, gateway_address, lan_address = await listen_ssdp(
|
protocol, gateway_address, lan_address = await listen_ssdp(
|
||||||
lan_address, gateway_address, loop, ignored, unicast
|
lan_address, gateway_address, loop
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
return await protocol.m_search(address=gateway_address, timeout=timeout, datagrams=[datagram_args])
|
return await protocol.m_search(address=gateway_address, timeout=timeout, datagrams=[datagram_args])
|
||||||
except (asyncio.TimeoutError, asyncio.CancelledError):
|
except asyncio.TimeoutError:
|
||||||
raise UPnPError("M-SEARCH for {}:{} timed out".format(gateway_address, SSDP_PORT))
|
raise UPnPError("M-SEARCH for {}:{} timed out".format(gateway_address, SSDP_PORT))
|
||||||
finally:
|
finally:
|
||||||
protocol.disconnect()
|
protocol.disconnect()
|
||||||
|
|
||||||
|
|
||||||
async def _fuzzy_m_search(lan_address: str, gateway_address: str, timeout: int = 30,
|
async def multi_m_search(lan_address: str, gateway_address: str, timeout: int = 3,
|
||||||
loop: typing.Optional[asyncio.AbstractEventLoop] = None,
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> SSDPProtocol:
|
||||||
ignored: typing.Set[str] = None,
|
loop = loop or asyncio.get_event_loop()
|
||||||
unicast: bool = False) -> typing.List[typing.Dict[str, typing.Union[int, str]]]:
|
|
||||||
protocol, gateway_address, lan_address = await listen_ssdp(
|
protocol, gateway_address, lan_address = await listen_ssdp(
|
||||||
lan_address, gateway_address, loop, ignored, unicast
|
lan_address, gateway_address, loop
|
||||||
)
|
)
|
||||||
await protocol.connected.wait()
|
fut = asyncio.ensure_future(protocol.send_m_searches(
|
||||||
packet_args = list(packet_generator())
|
address=gateway_address, datagrams=list(packet_generator())
|
||||||
batch_size = 2
|
), loop=loop)
|
||||||
batch_timeout = float(timeout) / float(len(packet_args))
|
loop.call_later(timeout, lambda: None if not fut or fut.done() else fut.cancel())
|
||||||
while packet_args:
|
return protocol
|
||||||
args = packet_args[:batch_size]
|
|
||||||
packet_args = packet_args[batch_size:]
|
|
||||||
log.debug("sending batch of %i M-SEARCH attempts", batch_size)
|
|
||||||
try:
|
|
||||||
await protocol.m_search(gateway_address, batch_timeout, args)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
protocol.disconnect()
|
|
||||||
return args
|
|
||||||
protocol.disconnect()
|
|
||||||
raise UPnPError("M-SEARCH for {}:{} timed out".format(gateway_address, SSDP_PORT))
|
|
||||||
|
|
||||||
|
|
||||||
async def fuzzy_m_search(lan_address: str, gateway_address: str, timeout: int = 30,
|
|
||||||
loop: typing.Optional[asyncio.AbstractEventLoop] = None,
|
|
||||||
ignored: typing.Set[str] = None,
|
|
||||||
unicast: bool = False) -> typing.Tuple[typing.Dict[str,
|
|
||||||
typing.Union[int, str]], SSDPDatagram]:
|
|
||||||
# we don't know which packet the gateway replies to, so send small batches at a time
|
|
||||||
args_to_try = await _fuzzy_m_search(lan_address, gateway_address, timeout, loop, ignored, unicast)
|
|
||||||
# check the args in the batch that got a reply one at a time to see which one worked
|
|
||||||
for args in args_to_try:
|
|
||||||
try:
|
|
||||||
packet = await m_search(lan_address, gateway_address, args, 3, loop=loop, ignored=ignored, unicast=unicast)
|
|
||||||
return args, packet
|
|
||||||
except UPnPError:
|
|
||||||
continue
|
|
||||||
raise UPnPError("failed to discover gateway")
|
|
||||||
|
|
|
@ -164,10 +164,15 @@ class SSDPDatagram:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def decode(cls, datagram: bytes) -> 'SSDPDatagram':
|
def decode(cls, datagram: bytes) -> 'SSDPDatagram':
|
||||||
|
try:
|
||||||
packet = cls._from_string(datagram.decode())
|
packet = cls._from_string(datagram.decode())
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
raise UPnPError(
|
||||||
|
f"failed to decode datagram: {binascii.hexlify(datagram).decode()}"
|
||||||
|
)
|
||||||
if packet is None:
|
if packet is None:
|
||||||
raise UPnPError(
|
raise UPnPError(
|
||||||
"failed to decode datagram: {}".format(binascii.hexlify(datagram))
|
f"failed to decode datagram: {binascii.hexlify(datagram).decode()}"
|
||||||
)
|
)
|
||||||
for attr_name in packet._required_fields[packet._packet_type]:
|
for attr_name in packet._required_fields[packet._packet_type]:
|
||||||
if getattr(packet, attr_name, None) is None:
|
if getattr(packet, attr_name, None) is None:
|
||||||
|
|
|
@ -4,12 +4,10 @@
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
from collections import OrderedDict
|
|
||||||
from typing import Tuple, Dict, List, Union, Optional, Any
|
from typing import Tuple, Dict, List, Union, Optional, Any
|
||||||
from aioupnp.fault import UPnPError
|
from aioupnp.fault import UPnPError
|
||||||
from aioupnp.gateway import Gateway
|
from aioupnp.gateway import Gateway
|
||||||
from aioupnp.interfaces import get_gateway_and_lan_addresses
|
from aioupnp.interfaces import get_gateway_and_lan_addresses
|
||||||
from aioupnp.protocols.ssdp import m_search, fuzzy_m_search
|
|
||||||
from aioupnp.serialization.ssdp import SSDPDatagram
|
from aioupnp.serialization.ssdp import SSDPDatagram
|
||||||
from aioupnp.commands import GetGenericPortMappingEntryResponse, GetSpecificPortMappingEntryResponse
|
from aioupnp.commands import GetGenericPortMappingEntryResponse, GetSpecificPortMappingEntryResponse
|
||||||
|
|
||||||
|
@ -61,7 +59,7 @@ class UPnP:
|
||||||
return lan_address, gateway_address
|
return lan_address, gateway_address
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
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 = 3,
|
||||||
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':
|
||||||
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)
|
||||||
|
@ -72,7 +70,7 @@ class UPnP:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def m_search(cls, lan_address: str = '', gateway_address: str = '', timeout: int = 1,
|
async def m_search(cls, lan_address: str = '', gateway_address: str = '', timeout: int = 1,
|
||||||
unicast: bool = True, interface_name: str = 'default',
|
interface_name: str = 'default',
|
||||||
igd_args: Optional[Dict[str, Union[str, int]]] = None,
|
igd_args: Optional[Dict[str, Union[str, int]]] = None,
|
||||||
loop: Optional[asyncio.AbstractEventLoop] = None
|
loop: Optional[asyncio.AbstractEventLoop] = None
|
||||||
) -> Dict[str, Union[str, Dict[str, Union[str, int]]]]:
|
) -> Dict[str, Union[str, Dict[str, Union[str, int]]]]:
|
||||||
|
@ -82,7 +80,6 @@ class UPnP:
|
||||||
:param lan_address: (str) the local interface ipv4 address
|
:param lan_address: (str) the local interface ipv4 address
|
||||||
:param gateway_address: (str) the gateway ipv4 address
|
:param gateway_address: (str) the gateway ipv4 address
|
||||||
:param timeout: (int) m search timeout
|
:param timeout: (int) m search timeout
|
||||||
:param unicast: (bool) use unicast
|
|
||||||
:param interface_name: (str) name of the network interface
|
:param interface_name: (str) name of the network interface
|
||||||
:param igd_args: (dict) case sensitive M-SEARCH headers. if used all headers to be used must be provided.
|
:param igd_args: (dict) case sensitive M-SEARCH headers. if used all headers to be used must be provided.
|
||||||
|
|
||||||
|
@ -101,16 +98,14 @@ class UPnP:
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise UPnPError("failed to get lan and gateway addresses for interface \"%s\": %s" % (interface_name,
|
raise UPnPError("failed to get lan and gateway addresses for interface \"%s\": %s" % (interface_name,
|
||||||
str(err)))
|
str(err)))
|
||||||
if not igd_args:
|
gateway = await Gateway.discover_gateway(
|
||||||
igd_args, datagram = await fuzzy_m_search(lan_address, gateway_address, timeout, loop, unicast=unicast)
|
lan_address, gateway_address, timeout, igd_args, loop
|
||||||
else:
|
)
|
||||||
igd_args = OrderedDict(igd_args)
|
|
||||||
datagram = await m_search(lan_address, gateway_address, igd_args, timeout, loop, unicast=unicast)
|
|
||||||
return {
|
return {
|
||||||
'lan_address': lan_address,
|
'lan_address': lan_address,
|
||||||
'gateway_address': gateway_address,
|
'gateway_address': gateway_address,
|
||||||
'm_search_kwargs': SSDPDatagram("M-SEARCH", igd_args).get_cli_igd_kwargs(),
|
# 'm_search_kwargs': SSDPDatagram("M-SEARCH", igd_args).get_cli_igd_kwargs(),
|
||||||
'discover_reply': datagram.as_dict()
|
'discover_reply': gateway._ok_packet.as_dict()
|
||||||
}
|
}
|
||||||
|
|
||||||
async def get_external_ip(self) -> str:
|
async def get_external_ip(self) -> str:
|
||||||
|
@ -372,20 +367,20 @@ cli_commands = [
|
||||||
|
|
||||||
|
|
||||||
def run_cli(method: str, igd_args: Dict[str, Union[bool, str, int]], lan_address: str = '',
|
def run_cli(method: str, igd_args: Dict[str, Union[bool, str, int]], lan_address: str = '',
|
||||||
gateway_address: str = '', timeout: int = 30, interface_name: str = 'default',
|
gateway_address: str = '', timeout: int = 3, interface_name: str = 'default',
|
||||||
unicast: bool = True, kwargs: Optional[Dict[str, str]] = None,
|
kwargs: Optional[Dict[str, str]] = None,
|
||||||
loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
|
loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
|
||||||
|
|
||||||
kwargs = kwargs or {}
|
kwargs = kwargs or {}
|
||||||
igd_args = igd_args
|
igd_args = igd_args
|
||||||
timeout = int(timeout)
|
timeout = int(timeout)
|
||||||
loop = loop or asyncio.get_event_loop()
|
loop = loop or asyncio.get_event_loop()
|
||||||
fut: 'asyncio.Future' = asyncio.Future(loop=loop)
|
fut: 'asyncio.Future' = loop.create_future()
|
||||||
|
|
||||||
async def wrapper(): # wrap the upnp setup and call of the command in a coroutine
|
async def wrapper(): # wrap the upnp setup and call of the command in a coroutine
|
||||||
if method == 'm_search': # if we're only m_searching don't do any device discovery
|
if method == 'm_search': # if we're only m_searching don't do any device discovery
|
||||||
fn = lambda *_a, **_kw: UPnP.m_search(
|
fn = lambda *_a, **_kw: UPnP.m_search(
|
||||||
lan_address, gateway_address, timeout, unicast, interface_name, igd_args, loop
|
lan_address, gateway_address, timeout, interface_name, igd_args, loop
|
||||||
)
|
)
|
||||||
else: # automatically discover the gateway
|
else: # automatically discover the gateway
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -16,7 +16,8 @@ except ImportError:
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def mock_tcp_and_udp(loop, udp_expected_addr=None, udp_replies=None, udp_delay_reply=0.0, sent_udp_packets=None,
|
def mock_tcp_and_udp(loop, udp_expected_addr=None, udp_replies=None, udp_delay_reply=0.0, sent_udp_packets=None,
|
||||||
tcp_replies=None, tcp_delay_reply=0.0, sent_tcp_packets=None, add_potato_datagrams=False):
|
tcp_replies=None, tcp_delay_reply=0.0, sent_tcp_packets=None, add_potato_datagrams=False,
|
||||||
|
raise_oserror_on_bind=False):
|
||||||
sent_udp_packets = sent_udp_packets if sent_udp_packets is not None else []
|
sent_udp_packets = sent_udp_packets if sent_udp_packets is not None else []
|
||||||
udp_replies = udp_replies or {}
|
udp_replies = udp_replies or {}
|
||||||
|
|
||||||
|
@ -72,7 +73,13 @@ def mock_tcp_and_udp(loop, udp_expected_addr=None, udp_replies=None, udp_delay_r
|
||||||
with mock.patch('socket.socket') as mock_socket:
|
with mock.patch('socket.socket') as mock_socket:
|
||||||
mock_sock = mock.Mock(spec=socket.socket)
|
mock_sock = mock.Mock(spec=socket.socket)
|
||||||
mock_sock.setsockopt = lambda *_: None
|
mock_sock.setsockopt = lambda *_: None
|
||||||
mock_sock.bind = lambda *_: None
|
|
||||||
|
def bind(*_):
|
||||||
|
if raise_oserror_on_bind:
|
||||||
|
raise OSError()
|
||||||
|
return
|
||||||
|
|
||||||
|
mock_sock.bind = bind
|
||||||
mock_sock.setblocking = lambda *_: None
|
mock_sock.setblocking = lambda *_: None
|
||||||
mock_sock.getsockname = lambda: "0.0.0.0"
|
mock_sock.getsockname = lambda: "0.0.0.0"
|
||||||
mock_sock.getpeername = lambda: ""
|
mock_sock.getpeername = lambda: ""
|
||||||
|
|
|
@ -3,7 +3,7 @@ from aioupnp.fault import UPnPError
|
||||||
from aioupnp.protocols.m_search_patterns import packet_generator
|
from aioupnp.protocols.m_search_patterns import packet_generator
|
||||||
from aioupnp.serialization.ssdp import SSDPDatagram
|
from aioupnp.serialization.ssdp import SSDPDatagram
|
||||||
from aioupnp.constants import SSDP_IP_ADDRESS
|
from aioupnp.constants import SSDP_IP_ADDRESS
|
||||||
from aioupnp.protocols.ssdp import fuzzy_m_search, m_search, SSDPProtocol
|
from aioupnp.protocols.ssdp import m_search, SSDPProtocol
|
||||||
from tests import AsyncioTestCase, mock_tcp_and_udp
|
from tests import AsyncioTestCase, mock_tcp_and_udp
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +28,11 @@ class TestSSDP(AsyncioTestCase):
|
||||||
])
|
])
|
||||||
reply_packet = SSDPDatagram("OK", reply_args)
|
reply_packet = SSDPDatagram("OK", reply_args)
|
||||||
|
|
||||||
|
async def test_socket_setup_error(self):
|
||||||
|
with mock_tcp_and_udp(self.loop, raise_oserror_on_bind=True):
|
||||||
|
with self.assertRaises(UPnPError):
|
||||||
|
await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop)
|
||||||
|
|
||||||
async def test_transport_not_connected_error(self):
|
async def test_transport_not_connected_error(self):
|
||||||
try:
|
try:
|
||||||
await SSDPProtocol('', '').m_search('1.2.3.4', 2, [self.query_packet.as_dict()])
|
await SSDPProtocol('', '').m_search('1.2.3.4', 2, [self.query_packet.as_dict()])
|
||||||
|
@ -35,6 +40,16 @@ class TestSSDP(AsyncioTestCase):
|
||||||
except UPnPError as err:
|
except UPnPError as err:
|
||||||
self.assertEqual(str(err), "SSDP transport not connected")
|
self.assertEqual(str(err), "SSDP transport not connected")
|
||||||
|
|
||||||
|
async def test_deadbeef_response(self):
|
||||||
|
replies = {
|
||||||
|
(self.query_packet.encode().encode(), ("10.0.0.1", 1900)): b'\xde\xad\xbe\xef'
|
||||||
|
}
|
||||||
|
sent = []
|
||||||
|
|
||||||
|
with mock_tcp_and_udp(self.loop, udp_replies=replies, udp_expected_addr="10.0.0.1", sent_udp_packets=sent):
|
||||||
|
with self.assertRaises(UPnPError):
|
||||||
|
await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop)
|
||||||
|
|
||||||
async def test_m_search_reply_unicast(self):
|
async def test_m_search_reply_unicast(self):
|
||||||
replies = {
|
replies = {
|
||||||
(self.query_packet.encode().encode(), ("10.0.0.1", 1900)): self.reply_packet.encode().encode()
|
(self.query_packet.encode().encode(), ("10.0.0.1", 1900)): self.reply_packet.encode().encode()
|
||||||
|
@ -42,14 +57,14 @@ class TestSSDP(AsyncioTestCase):
|
||||||
sent = []
|
sent = []
|
||||||
|
|
||||||
with mock_tcp_and_udp(self.loop, udp_replies=replies, udp_expected_addr="10.0.0.1", sent_udp_packets=sent):
|
with mock_tcp_and_udp(self.loop, udp_replies=replies, udp_expected_addr="10.0.0.1", sent_udp_packets=sent):
|
||||||
reply = await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop, unicast=True)
|
reply = await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop)
|
||||||
|
|
||||||
self.assertEqual(reply.encode(), self.reply_packet.encode())
|
self.assertEqual(reply.encode(), self.reply_packet.encode())
|
||||||
self.assertListEqual(sent, [self.query_packet.encode().encode()])
|
self.assertIn(self.query_packet.encode().encode(), sent)
|
||||||
|
|
||||||
with self.assertRaises(UPnPError):
|
with self.assertRaises(UPnPError):
|
||||||
with mock_tcp_and_udp(self.loop, udp_expected_addr="10.0.0.1", udp_replies=replies):
|
with mock_tcp_and_udp(self.loop, udp_expected_addr="10.0.0.10", udp_replies=replies):
|
||||||
await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop, unicast=False)
|
await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop)
|
||||||
|
|
||||||
async def test_m_search_reply_multicast(self):
|
async def test_m_search_reply_multicast(self):
|
||||||
replies = {
|
replies = {
|
||||||
|
@ -61,39 +76,40 @@ class TestSSDP(AsyncioTestCase):
|
||||||
reply = await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop)
|
reply = await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop)
|
||||||
|
|
||||||
self.assertEqual(reply.encode(), self.reply_packet.encode())
|
self.assertEqual(reply.encode(), self.reply_packet.encode())
|
||||||
self.assertListEqual(sent, [self.query_packet.encode().encode()])
|
self.assertIn(self.query_packet.encode().encode(), sent)
|
||||||
|
|
||||||
with self.assertRaises(UPnPError):
|
with self.assertRaises(UPnPError):
|
||||||
with mock_tcp_and_udp(self.loop, udp_replies=replies, udp_expected_addr="10.0.0.1"):
|
with mock_tcp_and_udp(self.loop, udp_replies=replies, udp_expected_addr="10.0.0.10"):
|
||||||
await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop, unicast=True)
|
await m_search("10.0.0.2", "10.0.0.1", self.successful_args, timeout=1, loop=self.loop)
|
||||||
|
|
||||||
async def test_packets_sent_fuzzy_m_search(self):
|
# async def test_packets_sent_fuzzy_m_search(self):
|
||||||
sent = []
|
# sent = []
|
||||||
|
#
|
||||||
with self.assertRaises(UPnPError):
|
# with self.assertRaises(UPnPError):
|
||||||
with mock_tcp_and_udp(self.loop, udp_expected_addr="10.0.0.1", sent_udp_packets=sent):
|
# with mock_tcp_and_udp(self.loop, udp_expected_addr="10.0.0.1", sent_udp_packets=sent):
|
||||||
await fuzzy_m_search("10.0.0.2", "10.0.0.1", 1, self.loop)
|
# await fuzzy_m_search("10.0.0.2", "10.0.0.1", 1, self.loop)
|
||||||
|
# for packet in self.byte_packets:
|
||||||
self.assertListEqual(sent, self.byte_packets)
|
# self.assertIn(packet, sent)
|
||||||
|
#
|
||||||
async def test_packets_fuzzy_m_search(self):
|
# async def test_packets_fuzzy_m_search(self):
|
||||||
replies = {
|
# replies = {
|
||||||
(self.query_packet.encode().encode(), (SSDP_IP_ADDRESS, 1900)): self.reply_packet.encode().encode()
|
# (self.query_packet.encode().encode(), (SSDP_IP_ADDRESS, 1900)): self.reply_packet.encode().encode()
|
||||||
}
|
# }
|
||||||
sent = []
|
# sent = []
|
||||||
|
#
|
||||||
with mock_tcp_and_udp(self.loop, udp_expected_addr="10.0.0.1", udp_replies=replies, sent_udp_packets=sent):
|
# with mock_tcp_and_udp(self.loop, udp_expected_addr="10.0.0.1", udp_replies=replies, sent_udp_packets=sent):
|
||||||
args, reply = await fuzzy_m_search("10.0.0.2", "10.0.0.1", 1, self.loop)
|
# args, reply = await fuzzy_m_search("10.0.0.2", "10.0.0.1", 1, self.loop)
|
||||||
|
#
|
||||||
self.assertEqual(reply.encode(), self.reply_packet.encode())
|
# self.assertEqual(reply.encode(), self.reply_packet.encode())
|
||||||
self.assertEqual(args, self.successful_args)
|
# self.assertEqual(args, self.successful_args)
|
||||||
|
#
|
||||||
async def test_packets_sent_fuzzy_m_search_ignore_invalid_datagram_replies(self):
|
# async def test_packets_sent_fuzzy_m_search_ignore_invalid_datagram_replies(self):
|
||||||
sent = []
|
# sent = []
|
||||||
|
#
|
||||||
with self.assertRaises(UPnPError):
|
# with self.assertRaises(UPnPError):
|
||||||
with mock_tcp_and_udp(self.loop, udp_expected_addr="10.0.0.1", sent_udp_packets=sent,
|
# with mock_tcp_and_udp(self.loop, udp_expected_addr="10.0.0.1", sent_udp_packets=sent,
|
||||||
add_potato_datagrams=True):
|
# add_potato_datagrams=True):
|
||||||
await fuzzy_m_search("10.0.0.2", "10.0.0.1", 1, self.loop)
|
# await fuzzy_m_search("10.0.0.2", "10.0.0.1", 1, self.loop)
|
||||||
|
#
|
||||||
self.assertListEqual(sent, self.byte_packets)
|
# for packet in self.byte_packets:
|
||||||
|
# self.assertIn(packet, sent)
|
|
@ -10,7 +10,6 @@ from aioupnp.__main__ import main
|
||||||
m_search_cli_result = """{
|
m_search_cli_result = """{
|
||||||
"lan_address": "10.0.0.2",
|
"lan_address": "10.0.0.2",
|
||||||
"gateway_address": "10.0.0.1",
|
"gateway_address": "10.0.0.1",
|
||||||
"m_search_kwargs": "--HOST=239.255.255.250:1900 --MAN=ssdp:discover --MX=1 --ST=urn:schemas-upnp-org:device:WANDevice:1",
|
|
||||||
"discover_reply": {
|
"discover_reply": {
|
||||||
"CACHE_CONTROL": "max-age=1800",
|
"CACHE_CONTROL": "max-age=1800",
|
||||||
"LOCATION": "http://10.0.0.1:49152/InternetGatewayDevice.xml",
|
"LOCATION": "http://10.0.0.1:49152/InternetGatewayDevice.xml",
|
||||||
|
@ -22,14 +21,13 @@ m_search_cli_result = """{
|
||||||
|
|
||||||
|
|
||||||
m_search_help_msg = """aioupnp [-h] [--debug_logging] m_search [--lan_address=<str>] [--gateway_address=<str>]
|
m_search_help_msg = """aioupnp [-h] [--debug_logging] m_search [--lan_address=<str>] [--gateway_address=<str>]
|
||||||
[--timeout=<int>] [--unicast] [--interface_name=<str>] [--<header key>=<header value>, ...]
|
[--timeout=<int>] [--interface_name=<str>] [--<header key>=<header value>, ...]
|
||||||
|
|
||||||
Perform a M-SEARCH for a upnp gateway.
|
Perform a M-SEARCH for a upnp gateway.
|
||||||
|
|
||||||
:param lan_address: (str) the local interface ipv4 address
|
:param lan_address: (str) the local interface ipv4 address
|
||||||
:param gateway_address: (str) the gateway ipv4 address
|
:param gateway_address: (str) the gateway ipv4 address
|
||||||
:param timeout: (int) m search timeout
|
:param timeout: (int) m search timeout
|
||||||
:param unicast: (bool) use unicast
|
|
||||||
:param interface_name: (str) name of the network interface
|
:param interface_name: (str) name of the network interface
|
||||||
:param igd_args: (dict) case sensitive M-SEARCH headers. if used all headers to be used must be provided.
|
:param igd_args: (dict) case sensitive M-SEARCH headers. if used all headers to be used must be provided.
|
||||||
|
|
||||||
|
@ -219,7 +217,7 @@ class TestCLI(AsyncioTestCase):
|
||||||
actual_output = StringIO()
|
actual_output = StringIO()
|
||||||
timeout_msg = "aioupnp encountered an error: M-SEARCH for 10.0.0.1:1900 timed out\n"
|
timeout_msg = "aioupnp encountered an error: M-SEARCH for 10.0.0.1:1900 timed out\n"
|
||||||
with contextlib.redirect_stdout(actual_output):
|
with contextlib.redirect_stdout(actual_output):
|
||||||
with mock_tcp_and_udp(self.loop, '10.0.0.1', tcp_replies=self.scpd_replies, udp_replies=self.udp_replies):
|
with mock_tcp_and_udp(self.loop, '10.0.0.1', tcp_replies={}, udp_replies={}):
|
||||||
main(
|
main(
|
||||||
[None, '--timeout=1', '--gateway_address=10.0.0.1', '--lan_address=10.0.0.2', 'm-search'],
|
[None, '--timeout=1', '--gateway_address=10.0.0.1', '--lan_address=10.0.0.2', 'm-search'],
|
||||||
self.loop
|
self.loop
|
||||||
|
@ -230,7 +228,7 @@ class TestCLI(AsyncioTestCase):
|
||||||
with contextlib.redirect_stdout(actual_output):
|
with contextlib.redirect_stdout(actual_output):
|
||||||
with mock_tcp_and_udp(self.loop, '10.0.0.1', tcp_replies=self.scpd_replies, udp_replies=self.udp_replies):
|
with mock_tcp_and_udp(self.loop, '10.0.0.1', tcp_replies=self.scpd_replies, udp_replies=self.udp_replies):
|
||||||
main(
|
main(
|
||||||
[None, '--timeout=1', '--gateway_address=10.0.0.1', '--lan_address=10.0.0.2', '--unicast', 'm-search'],
|
[None, '--timeout=1', '--gateway_address=10.0.0.1', '--lan_address=10.0.0.2', 'm-search'],
|
||||||
self.loop
|
self.loop
|
||||||
)
|
)
|
||||||
self.assertEqual(m_search_cli_result, actual_output.getvalue())
|
self.assertEqual(m_search_cli_result, actual_output.getvalue())
|
||||||
|
|
|
@ -176,8 +176,7 @@ class TestDiscoverDLinkDIR890L(AsyncioTestCase):
|
||||||
[('serviceType', 'urn:schemas-upnp-org:service:WANIPConnection:1'),
|
[('serviceType', 'urn:schemas-upnp-org:service:WANIPConnection:1'),
|
||||||
('serviceId', 'urn:upnp-org:serviceId:WANIPConn1'), ('controlURL', '/soap.cgi?service=WANIPConn1'),
|
('serviceId', 'urn:upnp-org:serviceId:WANIPConn1'), ('controlURL', '/soap.cgi?service=WANIPConn1'),
|
||||||
('eventSubURL', '/gena.cgi?service=WANIPConn1'), ('SCPDURL', '/WANIPConnection.xml')])},
|
('eventSubURL', '/gena.cgi?service=WANIPConn1'), ('SCPDURL', '/WANIPConnection.xml')])},
|
||||||
'm_search_args': OrderedDict([('HOST', '239.255.255.250:1900'), ('MAN', 'ssdp:discover'), ('MX', 1),
|
'reply': OrderedDict(
|
||||||
('ST', 'urn:schemas-upnp-org:device:WANDevice:1')]), 'reply': OrderedDict(
|
|
||||||
[('CACHE_CONTROL', 'max-age=1800'), ('LOCATION', 'http://10.0.0.1:49152/InternetGatewayDevice.xml'),
|
[('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'),
|
('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')]),
|
('USN', 'uuid:11111111-2222-3333-4444-555555555555::urn:schemas-upnp-org:device:WANDevice:1')]),
|
||||||
|
@ -232,14 +231,14 @@ class TestDiscoverDLinkDIR890L(AsyncioTestCase):
|
||||||
with self.assertRaises(UPnPError) as e2:
|
with self.assertRaises(UPnPError) as e2:
|
||||||
with mock_tcp_and_udp(self.loop):
|
with mock_tcp_and_udp(self.loop):
|
||||||
await Gateway.discover_gateway(self.client_address, self.gateway_info['gateway_address'], 2,
|
await Gateway.discover_gateway(self.client_address, self.gateway_info['gateway_address'], 2,
|
||||||
unicast=False, loop=self.loop)
|
loop=self.loop)
|
||||||
self.assertEqual(str(e1.exception), f"M-SEARCH for {self.gateway_info['gateway_address']}:1900 timed out")
|
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")
|
self.assertEqual(str(e2.exception), f"M-SEARCH for {self.gateway_info['gateway_address']}:1900 timed out")
|
||||||
|
|
||||||
async def test_discover_commands(self):
|
async def test_discover_commands(self):
|
||||||
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
||||||
gateway = Gateway(
|
gateway = Gateway(
|
||||||
SSDPDatagram("OK", self.gateway_info['reply']), self.gateway_info['m_search_args'],
|
SSDPDatagram("OK", self.gateway_info['reply']),
|
||||||
self.client_address, self.gateway_info['gateway_address'], loop=self.loop
|
self.client_address, self.gateway_info['gateway_address'], loop=self.loop
|
||||||
)
|
)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
|
@ -274,9 +273,7 @@ class TestDiscoverNetgearNighthawkAC2350(TestDiscoverDLinkDIR890L):
|
||||||
[('serviceType', 'urn:schemas-upnp-org:service:WANIPConnection:1'),
|
[('serviceType', 'urn:schemas-upnp-org:service:WANIPConnection:1'),
|
||||||
('serviceId', 'urn:upnp-org:serviceId:WANIPConn1'), ('controlURL', '/ctl/IPConn'),
|
('serviceId', 'urn:upnp-org:serviceId:WANIPConn1'), ('controlURL', '/ctl/IPConn'),
|
||||||
('eventSubURL', '/evt/IPConn'), ('SCPDURL', '/WANIPCn.xml')])},
|
('eventSubURL', '/evt/IPConn'), ('SCPDURL', '/WANIPCn.xml')])},
|
||||||
'm_search_args': OrderedDict(
|
'reply': 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'),
|
[('CACHE_CONTROL', 'max-age=1800'), ('ST', 'upnp:rootdevice'),
|
||||||
('USN', 'uuid:11111111-2222-3333-4444-555555555555::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')]),
|
('Server', 'R7500v2 UPnP/1.0 miniupnpd/1.0'), ('Location', 'http://192.168.0.1:5555/rootDesc.xml')]),
|
||||||
|
|
|
@ -45,7 +45,7 @@ class TestGetExternalIPAddress(UPnPCommandTestCase):
|
||||||
self.replies.update({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<?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:GetExternalIPAddressResponse xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\n<NewExternalIPAddress>11.222.3.44</NewExternalIPAddress>\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
self.replies.update({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<?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:GetExternalIPAddressResponse xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\n<NewExternalIPAddress>11.222.3.44</NewExternalIPAddress>\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
||||||
self.addCleanup(self.replies.pop, request)
|
self.addCleanup(self.replies.pop, request)
|
||||||
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
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)
|
gateway = Gateway(self.reply, self.client_address, self.gateway_address, loop=self.loop)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
||||||
external_ip = await upnp.get_external_ip()
|
external_ip = await upnp.get_external_ip()
|
||||||
|
@ -57,7 +57,7 @@ class TestGetExternalIPAddress(UPnPCommandTestCase):
|
||||||
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: 354 \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:GetExternalIPAddressResponse xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\n<NewExternalIPAddress></NewExternalIPAddress>\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
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: 354 \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:GetExternalIPAddressResponse xmlns:u=\"urn:schemas-upnp-org:service:WANIPConnection:1\">\n<NewExternalIPAddress></NewExternalIPAddress>\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
||||||
self.addCleanup(self.replies.pop, request)
|
self.addCleanup(self.replies.pop, request)
|
||||||
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
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)
|
gateway = Gateway(self.reply, self.client_address, self.gateway_address, loop=self.loop)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
||||||
with self.assertRaises(UPnPError):
|
with self.assertRaises(UPnPError):
|
||||||
|
@ -73,7 +73,7 @@ class TestMalformedGetExternalIPAddressResponse(UPnPCommandTestCase):
|
||||||
b"<derp>11.222.3.44</derp>\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
b"<derp>11.222.3.44</derp>\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
||||||
self.addCleanup(self.replies.pop, self.get_ip_request)
|
self.addCleanup(self.replies.pop, self.get_ip_request)
|
||||||
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
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)
|
gateway = Gateway(self.reply, self.client_address, self.gateway_address, loop=self.loop)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
||||||
with self.assertRaises(UPnPError):
|
with self.assertRaises(UPnPError):
|
||||||
|
@ -84,7 +84,7 @@ class TestMalformedGetExternalIPAddressResponse(UPnPCommandTestCase):
|
||||||
b"<newexternalipaddress>11.222.3.44</newexternalipaddress>\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
b"<newexternalipaddress>11.222.3.44</newexternalipaddress>\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
||||||
self.addCleanup(self.replies.pop, self.get_ip_request)
|
self.addCleanup(self.replies.pop, self.get_ip_request)
|
||||||
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
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)
|
gateway = Gateway(self.reply, self.client_address, self.gateway_address, loop=self.loop)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
||||||
external_ip = await upnp.get_external_ip()
|
external_ip = await upnp.get_external_ip()
|
||||||
|
@ -95,7 +95,7 @@ class TestMalformedGetExternalIPAddressResponse(UPnPCommandTestCase):
|
||||||
b"11.222.3.44\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
b"11.222.3.44\n</u:GetExternalIPAddressResponse>\n\t</s:Body>\n</s:Envelope>\n"})
|
||||||
self.addCleanup(self.replies.pop, self.get_ip_request)
|
self.addCleanup(self.replies.pop, self.get_ip_request)
|
||||||
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
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)
|
gateway = Gateway(self.reply, self.client_address, self.gateway_address, loop=self.loop)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
||||||
external_ip = await upnp.get_external_ip()
|
external_ip = await upnp.get_external_ip()
|
||||||
|
@ -113,7 +113,7 @@ class TestGetGenericPortMappingEntry(UPnPCommandTestCase):
|
||||||
|
|
||||||
async def test_get_port_mapping_by_index(self):
|
async def test_get_port_mapping_by_index(self):
|
||||||
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
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)
|
gateway = Gateway(self.reply, self.client_address, self.gateway_address, loop=self.loop)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
||||||
result = await upnp.get_port_mapping_by_index(0)
|
result = await upnp.get_port_mapping_by_index(0)
|
||||||
|
@ -135,7 +135,7 @@ class TestGetNextPortMapping(UPnPCommandTestCase):
|
||||||
|
|
||||||
async def test_get_next_mapping(self):
|
async def test_get_next_mapping(self):
|
||||||
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
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)
|
gateway = Gateway(self.reply, self.client_address, self.gateway_address, loop=self.loop)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
||||||
ext_port = await upnp.get_next_mapping(4567, "UDP", "aioupnp test mapping")
|
ext_port = await upnp.get_next_mapping(4567, "UDP", "aioupnp test mapping")
|
||||||
|
@ -155,7 +155,7 @@ class TestGetSpecificPortMapping(UPnPCommandTestCase):
|
||||||
|
|
||||||
async def test_get_specific_port_mapping(self):
|
async def test_get_specific_port_mapping(self):
|
||||||
with mock_tcp_and_udp(self.loop, tcp_replies=self.replies):
|
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)
|
gateway = Gateway(self.reply, self.client_address, self.gateway_address, loop=self.loop)
|
||||||
await gateway.discover_commands()
|
await gateway.discover_commands()
|
||||||
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
upnp = UPnP(self.client_address, self.gateway_address, gateway)
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in a new issue