From a9d94e456318f60a19afa3a10bf86bbd69f31fdb Mon Sep 17 00:00:00 2001 From: Johan Westerlund Date: Wed, 26 Oct 2022 14:57:56 +0200 Subject: [PATCH] Optionally restrict the range of ephemeral ports --- src/aioice/ice.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/aioice/ice.py b/src/aioice/ice.py index f33a8dc..6870aab 100644 --- a/src/aioice/ice.py +++ b/src/aioice/ice.py @@ -282,6 +282,8 @@ class Connection: :param turn_transport: The transport for TURN server, `"udp"` or `"tcp"`. :param use_ipv4: Whether to use IPv4 candidates. :param use_ipv6: Whether to use IPv6 candidates. + :param port_lower: The lowest port to bind to. + :param port_upper: The highest port to bind to. """ def __init__( @@ -296,6 +298,8 @@ def __init__( turn_transport: str = "udp", use_ipv4: bool = True, use_ipv6: bool = True, + port_lower: int = 0, + port_upper: int = 0, ) -> None: self.ice_controlling = ice_controlling #: Local username, automatically set to a random value. @@ -340,6 +344,8 @@ def __init__( self._tie_breaker = secrets.randbits(64) self._use_ipv4 = use_ipv4 self._use_ipv6 = use_ipv6 + self._port_lower = port_lower + self._port_upper = port_upper @property def local_candidates(self) -> List[Candidate]: @@ -843,20 +849,42 @@ def _find_pair( return pair return None + async def _create_datagram_endpoint(self, address): + """ + Create a datagram endpoint on the specified address, + with port optionally in the range [port_lower, port_upper] + """ + # when port_lower and port_upper are both zero: ports = [0] + # which will let the OS pick any random free port + ports = list(range( + self._port_lower if self._port_lower or not self._port_upper else 1, + (65535 if self._port_lower and not self._port_upper else self._port_upper) + 1 + )) + random.shuffle(ports) + loop = asyncio.get_event_loop() + for port in ports: + try: + transport, protocol = await loop.create_datagram_endpoint( + lambda: StunProtocol(self), local_addr=(address, port) + ) + return transport, protocol + except OSError as exc: + if port == ports[-1]: + # this was the last port, give up + raise exc + raise ValueError("Illegal port range: [%d, %d]" % (self._port_lower, self._port_upper)) + async def get_component_candidates( self, component: int, addresses: List[str], timeout: int = 5 ) -> List[Candidate]: candidates = [] - loop = asyncio.get_event_loop() # gather host candidates host_protocols = [] for address in addresses: # create transport try: - transport, protocol = await loop.create_datagram_endpoint( - lambda: StunProtocol(self), local_addr=(address, 0) - ) + transport, protocol = await self._create_datagram_endpoint(address) sock = transport.get_extra_info("socket") if sock is not None: sock.setsockopt(