Skip to content

Commit a9d94e4

Browse files
committed
Optionally restrict the range of ephemeral ports
1 parent f08956b commit a9d94e4

File tree

1 file changed

+32
-4
lines changed

1 file changed

+32
-4
lines changed

src/aioice/ice.py

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ class Connection:
282282
:param turn_transport: The transport for TURN server, `"udp"` or `"tcp"`.
283283
:param use_ipv4: Whether to use IPv4 candidates.
284284
:param use_ipv6: Whether to use IPv6 candidates.
285+
:param port_lower: The lowest port to bind to.
286+
:param port_upper: The highest port to bind to.
285287
"""
286288

287289
def __init__(
@@ -296,6 +298,8 @@ def __init__(
296298
turn_transport: str = "udp",
297299
use_ipv4: bool = True,
298300
use_ipv6: bool = True,
301+
port_lower: int = 0,
302+
port_upper: int = 0,
299303
) -> None:
300304
self.ice_controlling = ice_controlling
301305
#: Local username, automatically set to a random value.
@@ -340,6 +344,8 @@ def __init__(
340344
self._tie_breaker = secrets.randbits(64)
341345
self._use_ipv4 = use_ipv4
342346
self._use_ipv6 = use_ipv6
347+
self._port_lower = port_lower
348+
self._port_upper = port_upper
343349

344350
@property
345351
def local_candidates(self) -> List[Candidate]:
@@ -843,20 +849,42 @@ def _find_pair(
843849
return pair
844850
return None
845851

852+
async def _create_datagram_endpoint(self, address):
853+
"""
854+
Create a datagram endpoint on the specified address,
855+
with port optionally in the range [port_lower, port_upper]
856+
"""
857+
# when port_lower and port_upper are both zero: ports = [0]
858+
# which will let the OS pick any random free port
859+
ports = list(range(
860+
self._port_lower if self._port_lower or not self._port_upper else 1,
861+
(65535 if self._port_lower and not self._port_upper else self._port_upper) + 1
862+
))
863+
random.shuffle(ports)
864+
loop = asyncio.get_event_loop()
865+
for port in ports:
866+
try:
867+
transport, protocol = await loop.create_datagram_endpoint(
868+
lambda: StunProtocol(self), local_addr=(address, port)
869+
)
870+
return transport, protocol
871+
except OSError as exc:
872+
if port == ports[-1]:
873+
# this was the last port, give up
874+
raise exc
875+
raise ValueError("Illegal port range: [%d, %d]" % (self._port_lower, self._port_upper))
876+
846877
async def get_component_candidates(
847878
self, component: int, addresses: List[str], timeout: int = 5
848879
) -> List[Candidate]:
849880
candidates = []
850-
loop = asyncio.get_event_loop()
851881

852882
# gather host candidates
853883
host_protocols = []
854884
for address in addresses:
855885
# create transport
856886
try:
857-
transport, protocol = await loop.create_datagram_endpoint(
858-
lambda: StunProtocol(self), local_addr=(address, 0)
859-
)
887+
transport, protocol = await self._create_datagram_endpoint(address)
860888
sock = transport.get_extra_info("socket")
861889
if sock is not None:
862890
sock.setsockopt(

0 commit comments

Comments
 (0)