mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
Switch to using the AsyncResolver with aiohttp (#114529)
* Bump aiodns to 3.2.0 changelog: https://github.com/saghul/aiodns/compare/v3.1.1...v3.2.0 * Switch to using the AsyncResolver with aiohttp This avoids creating executor jobs to do DNS resolution AsyncResolver was not usable before https://github.com/aio-libs/aiohttp/pull/8270 because it did not fallback to fallback to A records and only returned AAAA records in most cases when IPv6 was available This is a backport of https://github.com/aio-libs/aiohttp/pull/8270 * Switch to using the AsyncResolver with aiohttp This avoids creating executor jobs to do DNS resolution AsyncResolver was not usable before https://github.com/aio-libs/aiohttp/pull/8270 because it did not fallback to fallback to A records and only returned AAAA records in most cases when IPv6 was available This is a backport of https://github.com/aio-libs/aiohttp/pull/8270 * Switch to using the AsyncResolver with aiohttp This avoids creating executor jobs to do DNS resolution AsyncResolver was not usable before https://github.com/aio-libs/aiohttp/pull/8270 because it did not fallback to fallback to A records and only returned AAAA records in most cases when IPv6 was available This is a backport of https://github.com/aio-libs/aiohttp/pull/8270 * Switch to using the AsyncResolver with aiohttp This avoids creating executor jobs to do DNS resolution AsyncResolver was not usable before https://github.com/aio-libs/aiohttp/pull/8270 because it did not fallback to fallback to A records and only returned AAAA records in most cases when IPv6 was available This is a backport of https://github.com/aio-libs/aiohttp/pull/8270 * fixes * fix mocking in next_dns * fix unmocked calls in blink * more mocking fixes * more fixes * more fixes * Fix missing mocking in nextdns tests extracted from #114539 * extract from context
This commit is contained in:
parent
9c27e632fb
commit
f497c461ed
@ -7,6 +7,7 @@ source = homeassistant
|
|||||||
omit =
|
omit =
|
||||||
homeassistant/__main__.py
|
homeassistant/__main__.py
|
||||||
homeassistant/helpers/signal.py
|
homeassistant/helpers/signal.py
|
||||||
|
homeassistant/helpers/backports/*
|
||||||
homeassistant/scripts/__init__.py
|
homeassistant/scripts/__init__.py
|
||||||
homeassistant/scripts/check_config.py
|
homeassistant/scripts/check_config.py
|
||||||
homeassistant/scripts/ensure_config.py
|
homeassistant/scripts/ensure_config.py
|
||||||
|
@ -22,6 +22,7 @@ from homeassistant.loader import bind_hass
|
|||||||
from homeassistant.util import ssl as ssl_util
|
from homeassistant.util import ssl as ssl_util
|
||||||
from homeassistant.util.json import json_loads
|
from homeassistant.util.json import json_loads
|
||||||
|
|
||||||
|
from .backports.aiohttp_resolver import AsyncResolver
|
||||||
from .frame import warn_use
|
from .frame import warn_use
|
||||||
from .json import json_dumps
|
from .json import json_dumps
|
||||||
|
|
||||||
@ -310,6 +311,7 @@ def _async_get_connector(
|
|||||||
ssl=ssl_context,
|
ssl=ssl_context,
|
||||||
limit=MAXIMUM_CONNECTIONS,
|
limit=MAXIMUM_CONNECTIONS,
|
||||||
limit_per_host=MAXIMUM_CONNECTIONS_PER_HOST,
|
limit_per_host=MAXIMUM_CONNECTIONS_PER_HOST,
|
||||||
|
resolver=AsyncResolver(),
|
||||||
)
|
)
|
||||||
connectors[connector_key] = connector
|
connectors[connector_key] = connector
|
||||||
|
|
||||||
|
1
homeassistant/helpers/backports/__init__.py
Normal file
1
homeassistant/helpers/backports/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Backports for helpers."""
|
116
homeassistant/helpers/backports/aiohttp_resolver.py
Normal file
116
homeassistant/helpers/backports/aiohttp_resolver.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
"""Backport of aiohttp's AsyncResolver for Home Assistant.
|
||||||
|
|
||||||
|
This is a backport of the AsyncResolver class from aiohttp 3.10.
|
||||||
|
|
||||||
|
Before aiohttp 3.10, on system with IPv6 support, AsyncResolver would not fallback
|
||||||
|
to providing A records when AAAA records were not available.
|
||||||
|
|
||||||
|
Additionally, unlike the ThreadedResolver, AsyncResolver
|
||||||
|
did not handle link-local addresses correctly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
from typing import Any, TypedDict
|
||||||
|
|
||||||
|
import aiodns
|
||||||
|
from aiohttp.abc import AbstractResolver
|
||||||
|
|
||||||
|
# This is a backport of https://github.com/aio-libs/aiohttp/pull/8270
|
||||||
|
# This can be removed once aiohttp 3.10 is the minimum supported version.
|
||||||
|
|
||||||
|
_NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV
|
||||||
|
_SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0)
|
||||||
|
|
||||||
|
|
||||||
|
class ResolveResult(TypedDict):
|
||||||
|
"""Resolve result.
|
||||||
|
|
||||||
|
This is the result returned from an AbstractResolver's
|
||||||
|
resolve method.
|
||||||
|
|
||||||
|
:param hostname: The hostname that was provided.
|
||||||
|
:param host: The IP address that was resolved.
|
||||||
|
:param port: The port that was resolved.
|
||||||
|
:param family: The address family that was resolved.
|
||||||
|
:param proto: The protocol that was resolved.
|
||||||
|
:param flags: The flags that were resolved.
|
||||||
|
"""
|
||||||
|
|
||||||
|
hostname: str
|
||||||
|
host: str
|
||||||
|
port: int
|
||||||
|
family: int
|
||||||
|
proto: int
|
||||||
|
flags: int
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncResolver(AbstractResolver):
|
||||||
|
"""Use the `aiodns` package to make asynchronous DNS lookups."""
|
||||||
|
|
||||||
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Initialize the resolver."""
|
||||||
|
if aiodns is None:
|
||||||
|
raise RuntimeError("Resolver requires aiodns library")
|
||||||
|
|
||||||
|
self._loop = asyncio.get_running_loop()
|
||||||
|
self._resolver = aiodns.DNSResolver(*args, loop=self._loop, **kwargs) # type: ignore[misc]
|
||||||
|
|
||||||
|
async def resolve( # type: ignore[override]
|
||||||
|
self, host: str, port: int = 0, family: int = socket.AF_INET
|
||||||
|
) -> list[ResolveResult]:
|
||||||
|
"""Resolve a host name to an IP address."""
|
||||||
|
try:
|
||||||
|
resp = await self._resolver.getaddrinfo(
|
||||||
|
host,
|
||||||
|
port=port,
|
||||||
|
type=socket.SOCK_STREAM,
|
||||||
|
family=family, # type: ignore[arg-type]
|
||||||
|
flags=socket.AI_ADDRCONFIG,
|
||||||
|
)
|
||||||
|
except aiodns.error.DNSError as exc:
|
||||||
|
msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed"
|
||||||
|
raise OSError(msg) from exc
|
||||||
|
hosts: list[ResolveResult] = []
|
||||||
|
for node in resp.nodes:
|
||||||
|
address: tuple[bytes, int] | tuple[bytes, int, int, int] = node.addr
|
||||||
|
family = node.family
|
||||||
|
if family == socket.AF_INET6:
|
||||||
|
if len(address) > 3 and address[3] and _SUPPORTS_SCOPE_ID:
|
||||||
|
# This is essential for link-local IPv6 addresses.
|
||||||
|
# LL IPv6 is a VERY rare case. Strictly speaking, we should use
|
||||||
|
# getnameinfo() unconditionally, but performance makes sense.
|
||||||
|
result = await self._resolver.getnameinfo(
|
||||||
|
(address[0].decode("ascii"), *address[1:]),
|
||||||
|
_NUMERIC_SOCKET_FLAGS,
|
||||||
|
)
|
||||||
|
resolved_host = result.node
|
||||||
|
else:
|
||||||
|
resolved_host = address[0].decode("ascii")
|
||||||
|
port = address[1]
|
||||||
|
else: # IPv4
|
||||||
|
assert family == socket.AF_INET
|
||||||
|
resolved_host = address[0].decode("ascii")
|
||||||
|
port = address[1]
|
||||||
|
hosts.append(
|
||||||
|
ResolveResult(
|
||||||
|
hostname=host,
|
||||||
|
host=resolved_host,
|
||||||
|
port=port,
|
||||||
|
family=family,
|
||||||
|
proto=0,
|
||||||
|
flags=_NUMERIC_SOCKET_FLAGS,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not hosts:
|
||||||
|
raise OSError("DNS lookup failed")
|
||||||
|
|
||||||
|
return hosts
|
||||||
|
|
||||||
|
async def close(self) -> None:
|
||||||
|
"""Close the resolver."""
|
||||||
|
self._resolver.cancel()
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
aiodhcpwatcher==1.0.0
|
aiodhcpwatcher==1.0.0
|
||||||
aiodiscover==2.0.0
|
aiodiscover==2.0.0
|
||||||
|
aiodns==3.2.0
|
||||||
aiohttp-fast-url-dispatcher==0.3.0
|
aiohttp-fast-url-dispatcher==0.3.0
|
||||||
aiohttp-zlib-ng==0.3.1
|
aiohttp-zlib-ng==0.3.1
|
||||||
aiohttp==3.9.3
|
aiohttp==3.9.3
|
||||||
|
@ -23,6 +23,7 @@ classifiers = [
|
|||||||
]
|
]
|
||||||
requires-python = ">=3.12.0"
|
requires-python = ">=3.12.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aiodns==3.2.0",
|
||||||
"aiohttp==3.9.3",
|
"aiohttp==3.9.3",
|
||||||
"aiohttp_cors==0.7.0",
|
"aiohttp_cors==0.7.0",
|
||||||
"aiohttp-fast-url-dispatcher==0.3.0",
|
"aiohttp-fast-url-dispatcher==0.3.0",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
-c homeassistant/package_constraints.txt
|
-c homeassistant/package_constraints.txt
|
||||||
|
|
||||||
# Home Assistant Core
|
# Home Assistant Core
|
||||||
|
aiodns==3.2.0
|
||||||
aiohttp==3.9.3
|
aiohttp==3.9.3
|
||||||
aiohttp_cors==0.7.0
|
aiohttp_cors==0.7.0
|
||||||
aiohttp-fast-url-dispatcher==0.3.0
|
aiohttp-fast-url-dispatcher==0.3.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user