I checked that this works as expected - without it some ports are below 49152 and with it the selected ports are above (for a couple of dozen tries).
This covers only the port for the Socks5 server itself, but not ports used by the P2P listeners, created with network_event_loop.create_server(). Here is an extension to this patch that would cover the later as well:
0diff --git i/test/functional/test_framework/netutil.py w/test/functional/test_framework/netutil.py
1index 792afabff6..8fda0baa31 100644
2--- i/test/functional/test_framework/netutil.py
3+++ w/test/functional/test_framework/netutil.py
4@@ -180,13 +180,13 @@ def format_addr_port(addr, port):
5 if ":" in addr:
6 return f"[{addr}]:{port}"
7 else:
8 return f"{addr}:{port}"
9
10
11-def set_freebsd_high_port_range(sock):
12+def set_ephemeral_port_range(sock):
13 '''On FreeBSD, set socket to use the high ephemeral port range (49152-65535).
14
15 FreeBSD's default ephemeral port range (10000-65535) overlaps with the test
16 framework's static port range (11000-26000). Using IP_PORTRANGE_HIGH avoids
17 this overlap when binding to port 0 for dynamic port allocation.
18 '''
19diff --git i/test/functional/test_framework/p2p.py w/test/functional/test_framework/p2p.py
20index 986eaf1e88..b6903a6ba2 100755
21--- i/test/functional/test_framework/p2p.py
22+++ w/test/functional/test_framework/p2p.py
23@@ -19,16 +19,18 @@ P2PDataStore: A p2p interface class that keeps a store of transactions and block
24 and can respond correctly to getdata and getheaders messages
25 P2PTxInvStore: A p2p interface class that inherits from P2PDataStore, and keeps
26 a count of how many times each txid has been announced."""
27
28 import asyncio
29 from collections import defaultdict
30+import ipaddress
31 from io import BytesIO
32 import logging
33 import platform
34 import struct
35+import socket
36 import sys
37 import threading
38
39 from test_framework.messages import (
40 CBlockHeader,
41 MAX_HEADERS_RESULTS,
42@@ -73,12 +75,15 @@ from test_framework.messages import (
43 msg_wtxidrelay,
44 NODE_NETWORK,
45 NODE_WITNESS,
46 MAGIC_BYTES,
47 sha256,
48 )
49+from test_framework.netutil import (
50+ set_ephemeral_port_range,
51+)
52 from test_framework.util import (
53 assert_not_equal,
54 MAX_NODES,
55 p2p_port,
56 wait_until_helper_internal,
57 )
58@@ -744,12 +749,14 @@ class NetworkThread(threading.Thread):
59 self.network_event_loop.run_forever()
60
61 def close(self, *, timeout=10):
62 """Close the connections and network event loop."""
63 self.network_event_loop.call_soon_threadsafe(self.network_event_loop.stop)
64 wait_until_helper_internal(lambda: not self.network_event_loop.is_running(), timeout=timeout)
65+ for listener in NetworkThread.listeners.values():
66+ listener.close()
67 self.network_event_loop.close()
68 self.join(timeout)
69 # Safe to remove event loop.
70 NetworkThread.network_event_loop = None [@classmethod](/bitcoin-bitcoin/contributor/classmethod/)
71@@ -790,14 +797,28 @@ class NetworkThread(threading.Thread):
72 if port == 0 or (addr, port) not in cls.listeners:
73 # When creating a listener on a given (addr, port) we only need to
74 # do it once. If we want different behaviors for different
75 # connections, we can accomplish this by providing different
76 # `proto` functions
77
78- listener = await cls.network_event_loop.create_server(peer_protocol, addr, port)
79- port = listener.sockets[0].getsockname()[1]
80+ if port == 0:
81+ # Manually create the socket in order to set the range to be
82+ # used for the port before the bind() call.
83+ if ipaddress.ip_address(addr).version == 4:
84+ address_family = socket.AF_INET
85+ else:
86+ address_family = socket.AF_INET6
87+ s = socket.socket(address_family)
88+ set_ephemeral_port_range(s)
89+ s.bind((addr, 0))
90+ s.listen()
91+ listener = await cls.network_event_loop.create_server(peer_protocol, sock=s)
92+ port = listener.sockets[0].getsockname()[1]
93+ else:
94+ listener = await cls.network_event_loop.create_server(peer_protocol, addr, port)
95+
96 logger.debug("Listening server on %s:%d should be started" % (addr, port))
97 cls.listeners[(addr, port)] = listener
98
99 cls.protos[(addr, port)] = proto
100 callback(addr, port)
101
102diff --git i/test/functional/test_framework/socks5.py w/test/functional/test_framework/socks5.py
103index 4c77a6ff25..085c5a2e32 100644
104--- i/test/functional/test_framework/socks5.py
105+++ w/test/functional/test_framework/socks5.py
106@@ -9,13 +9,13 @@ import socket
107 import threading
108 import queue
109 import logging
110
111 from .netutil import (
112 format_addr_port,
113- set_freebsd_high_port_range,
114+ set_ephemeral_port_range,
115 )
116
117 logger = logging.getLogger("TestFramework.socks5")
118
119 # Protocol constants
120 class Command:
121@@ -203,13 +203,13 @@ class Socks5Server():
122 self.conf = conf
123 self.s = socket.socket(conf.af)
124 self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
125 # When using dynamic port allocation (port=0), ensure we don't get a
126 # port that conflicts with the test framework's static port range.
127 if conf.addr[1] == 0:
128- set_freebsd_high_port_range(self.s)
129+ set_ephemeral_port_range(self.s)
130 self.s.bind(conf.addr)
131 # When port=0, the OS assigns an available port. Update conf.addr
132 # to reflect the actual bound address so callers can use it.
133 self.conf.addr = self.s.getsockname()
134 self.s.listen(5)
135 self.running = False