Fix dual-stack socket handling: IPV6_V6ONLY, IPv4-first, is_port_available all-family check (#20643)

This commit is contained in:
Liangsheng Yin
2026-03-15 17:17:23 -07:00
committed by GitHub
parent 7c498a6538
commit d852f26cb6
2 changed files with 98 additions and 16 deletions

View File

@@ -765,8 +765,16 @@ def _get_addrinfos_for_bind(host=None, port=0):
0,
socket.AI_ADDRCONFIG | socket.AI_PASSIVE,
)
seen = set()
return [i for i in infos if i[0] not in seen and not seen.add(i[0])]
deduped = []
seen_families = set()
for info in infos:
if info[0] not in seen_families:
seen_families.add(info[0])
deduped.append(info)
# Prefer IPv4 so that callers without an explicit host get consistent
# behaviour across platforms (some OSes list IPv6 first).
deduped.sort(key=lambda x: (x[0] != socket.AF_INET,))
return deduped
except socket.gaierror:
fallback_host = "0.0.0.0" if host is None else host
return [(socket.AF_INET, socket.SOCK_STREAM, 0, "", (fallback_host, port))]
@@ -794,22 +802,31 @@ def try_bind_socket(host=None, port=0, *, reuse_addr=True, listen=False):
for family, socktype, proto, _, sockaddr in _get_addrinfos_for_bind(host, port):
sock = socket.socket(family, socktype, proto)
try:
if family == socket.AF_INET6:
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
if reuse_addr:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(sockaddr)
if listen:
sock.listen(1)
return sock
except OSError:
except (OSError, OverflowError):
sock.close()
raise OSError(f"Could not bind port {port} on any configured address family")
def is_port_available(port):
"""Return whether a port is available."""
"""Return whether a port is available on all configured address families."""
try:
sock = try_bind_socket(port=port, listen=True)
sock.close()
for family, socktype, proto, _, sockaddr in _get_addrinfos_for_bind(port=port):
sock = socket.socket(family, socktype, proto)
try:
if family == socket.AF_INET6:
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(sockaddr)
finally:
sock.close()
return True
except (OSError, OverflowError):
return False
@@ -2695,13 +2712,10 @@ def get_open_port() -> int:
if port is not None:
port = int(port)
while True:
try:
sock = try_bind_socket(port=port, reuse_addr=False)
sock.close()
if is_port_available(port):
return port
except OSError:
logger.info("Port %d is already in use, trying port %d", port, port + 1)
port += 1
logger.info("Port %d is already in use, trying port %d", port, port + 1)
port += 1
sock = try_bind_socket()
port = sock.getsockname()[1]
sock.close()