import asyncio import random import secrets import socket import psutil # Don't assign rpc or p2p ports lower than this PORT_MIN = 1024 PORT_MAX = 65535 mapped_ports = {} async def get_port(n, service): key = f"{service}:{n}" mapped = mapped_ports.get(key) if mapped is not None: return mapped port = await get_random_unused_port() print(f"providing port {port} to {service}:{n}") mapped_ports[key] = port return port async def try_bind_port(port: int) -> bool: try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind(("localhost", port)) return True except OSError: return False async def is_port_in_use(port: int) -> bool: async def check_port(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: return s.connect_ex(("localhost", port)) == 0 if await check_port(): # Random sleep and check again to mitigate race conditions await asyncio.sleep(random.uniform(0, 3)) return await check_port() return False taken_ports = [] async def get_random_unused_port(): # pylint: disable=global-statement global taken_ports if len(taken_ports) == 0: # first time function is called # seed randomness seed = int.from_bytes(secrets.token_bytes(16), "big") random.seed(seed) # try to prefill taken_ports try: connections = psutil.net_connections() taken_ports = { conn.laddr.port for conn in connections if conn.status == "LISTEN" } except psutil.AccessDenied: # don't have privileges taken_ports = set() except Exception as e: print(f"Unexpected exception: {e}") taken_ports = set() while True: port = random.randint(PORT_MIN, PORT_MAX) if port in taken_ports: continue taken_ports.add(port) if await try_bind_port(port): return port async def p2p_port(n): return await get_port(n, "p2p") async def rpc_port(n): return await get_port(n, "rpc") async def electrum_rpc_port(n): return await get_port(n, "electrum_rpc") async def electrum_ws_port(n): return await get_port(n, "electrum_ws") async def electrum_monitoring_port(n): return await get_port(n, "electrum_monitoring")