mirror of
https://github.com/Av0idD4rk/firegex.git
synced 2026-10-23 21:42:05 +00:00
63 lines
2.8 KiB
Python
63 lines
2.8 KiB
Python
"""Ports and the small network primitives the tests share.
|
|
|
|
Ports are asked for rather than derived from a base. The suite used to take a `--port`
|
|
and reach for `port + 2`, `port + 3`, `port + 5`, `port + 7`, `port + 9`, `port + 11`,
|
|
`port + 13` — which is fine until two of those runs overlap, or the host already has
|
|
something on one of them, and the failure reads as firegex refusing traffic.
|
|
"""
|
|
|
|
import socket
|
|
|
|
|
|
#: Every port this process has handed out. The kernel does not remember, and over a
|
|
#: whole run it will offer the same ephemeral port twice — which is fine for a socket
|
|
#: and not fine here, because a port is also half of a service's identity. Two services
|
|
#: claiming one `(ip, port, proto)` is refused by the backend, correctly, and it arrives
|
|
#: as `could not create the service 'rx-36375'` in the setup of whichever test asked
|
|
#: second: a failure that belongs entirely to the test that asked first.
|
|
_HANDED_OUT: set[int] = set()
|
|
|
|
|
|
def free_port(ipv6: bool = False, udp: bool = False) -> int:
|
|
"""A port nothing is listening on, and that this run has not used before.
|
|
|
|
Probed on the transport it will actually be used for. TCP and UDP number their ports
|
|
separately, so a port free for one says nothing about the other — and a QUIC stand-in
|
|
handed a TCP-free port occasionally found it taken, which arrives as an
|
|
`Address already in use` from inside a server thread and reads as a test failure with
|
|
nothing to do with what it was testing.
|
|
|
|
Not handed out twice, for the neighbouring reason: the kernel is answering "nothing
|
|
is bound here", which is not the question a service asks. The number is remembered
|
|
across families and transports rather than per pair, because two numbers cost nothing
|
|
and one shared between a TCP test and a UDP test is one more thing to reason about.
|
|
"""
|
|
family = socket.AF_INET6 if ipv6 else socket.AF_INET
|
|
kind = socket.SOCK_DGRAM if udp else socket.SOCK_STREAM
|
|
port = 0
|
|
for _ in range(64):
|
|
with socket.socket(family, kind) as probe:
|
|
probe.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
probe.bind(("::1" if ipv6 else "127.0.0.1", 0))
|
|
port = probe.getsockname()[1]
|
|
if port not in _HANDED_OUT:
|
|
break
|
|
# Sixty-four collisions in a row means the ephemeral range is effectively exhausted,
|
|
# and a port that is at least free right now is a better answer than an exception.
|
|
_HANDED_OUT.add(port)
|
|
return port
|
|
|
|
|
|
def loopback(ipv6: bool = False) -> str:
|
|
return "::1" if ipv6 else "127.0.0.1"
|
|
|
|
|
|
def supports_ipv6() -> bool:
|
|
"""Whether this host can carry the IPv6 half of the suite at all."""
|
|
try:
|
|
with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as probe:
|
|
probe.bind(("::1", 0))
|
|
return True
|
|
except OSError:
|
|
return False
|