Stop filtering out IPv6 interfaces for "localhost" if IPv6 is supported
@@ -1670,7 +1670,7 @@ async def _resolve_localhost() -> List[str]:
# we explicitly disable the AF_INET6 component of 'localhost'.
loop = asyncio.get_running_loop()
- localhost = await loop.getaddrinfo(
+ localhost_infos = await loop.getaddrinfo(
'localhost',
0,
family=socket.AF_UNSPEC,
@@ -1679,16 +1679,21 @@ async def _resolve_localhost() -> List[str]:
proto=0,
)
- infos = [a for a in localhost if a[0] == socket.AF_INET]
+ if _is_ipv6_functional():
+ # IPv6 is validated to work on this server, so return the resolved
+ # addresses unfiltered.
+ return [a[4][0] for a in localhost_infos]
- if not infos:
+ ipv4_infos = [a for a in localhost_infos if a[0] == socket.AF_INET]
+
+ if not ipv4_infos:
# "localhost" did not resolve to an IPv4 address,
- # let create_server handle the situation.
+ # let asyncio.create_server() handle the situation.
return ["localhost"]
# Replace 'localhost' with explicitly resolved AF_INET addresses.
hosts = []
- for info in reversed(infos):
+ for info in reversed(ipv4_infos):
addr, *_ = info[4]
hosts.append(addr)
@@ -1715,3 +1720,43 @@ def _find_available_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(("localhost", 0))
return sock.getsockname()[1]
+
+
+_IPv6_FUNCTIONAL: bool | None = None
+
+
+def _is_ipv6_functional() -> bool:
+ # A functional test that IPv6 is not only available but can
+ # actually function.
+
+ global _IPv6_FUNCTIONAL
+
+ if _IPv6_FUNCTIONAL is not None:
+ return _IPv6_FUNCTIONAL
+
+ if not hasattr(socket, 'AF_INET6'):
+ return False
+
+ try:
+ with socket.socket(socket.AF_INET6) as srv:
+ srv.bind(('::1', 0))
+ srv.listen()
+
+ srv_addr = srv.getsockname()[:2]
+
+ with socket.socket(socket.AF_INET6) as client:
+ client.connect(srv_addr)
+
+ srv_client, _ = srv.accept()
+ if client.send(b'1') != 1:
+ raise RuntimeError('could not send data via IPv6')
+
+ if srv_client.recv(1) != b'1':
+ raise RuntimeError('could not recv data via IPv6')
+ except Exception:
+ logger.info('IPv6 is not fully supported', exc_info=True)
+ _IPv6_FUNCTIONAL = False
+ else:
+ _IPv6_FUNCTIONAL = True
+
+ return _IPv6_FUNCTIONAL
