| | import asyncio
|
| | import socket
|
| | import sys
|
| | from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
| |
|
| | from .abc import AbstractResolver, ResolveResult
|
| |
|
| | __all__ = ("ThreadedResolver", "AsyncResolver", "DefaultResolver")
|
| |
|
| |
|
| | try:
|
| | import aiodns
|
| |
|
| | aiodns_default = hasattr(aiodns.DNSResolver, "getaddrinfo")
|
| | except ImportError:
|
| | aiodns = None
|
| | aiodns_default = False
|
| |
|
| |
|
| | _NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV
|
| | _NAME_SOCKET_FLAGS = socket.NI_NUMERICHOST | socket.NI_NUMERICSERV
|
| | _SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0)
|
| |
|
| |
|
| | class ThreadedResolver(AbstractResolver):
|
| | """Threaded resolver.
|
| |
|
| | Uses an Executor for synchronous getaddrinfo() calls.
|
| | concurrent.futures.ThreadPoolExecutor is used by default.
|
| | """
|
| |
|
| | def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
|
| | self._loop = loop or asyncio.get_running_loop()
|
| |
|
| | async def resolve(
|
| | self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET
|
| | ) -> List[ResolveResult]:
|
| | infos = await self._loop.getaddrinfo(
|
| | host,
|
| | port,
|
| | type=socket.SOCK_STREAM,
|
| | family=family,
|
| | flags=socket.AI_ADDRCONFIG,
|
| | )
|
| |
|
| | hosts: List[ResolveResult] = []
|
| | for family, _, proto, _, address in infos:
|
| | if family == socket.AF_INET6:
|
| | if len(address) < 3:
|
| |
|
| |
|
| | continue
|
| | if address[3] and _SUPPORTS_SCOPE_ID:
|
| |
|
| |
|
| |
|
| | resolved_host, _port = await self._loop.getnameinfo(
|
| | address, _NAME_SOCKET_FLAGS
|
| | )
|
| | port = int(_port)
|
| | else:
|
| | resolved_host, port = address[:2]
|
| | else:
|
| | assert family == socket.AF_INET
|
| | resolved_host, port = address
|
| | hosts.append(
|
| | ResolveResult(
|
| | hostname=host,
|
| | host=resolved_host,
|
| | port=port,
|
| | family=family,
|
| | proto=proto,
|
| | flags=_NUMERIC_SOCKET_FLAGS,
|
| | )
|
| | )
|
| |
|
| | return hosts
|
| |
|
| | async def close(self) -> None:
|
| | pass
|
| |
|
| |
|
| | class AsyncResolver(AbstractResolver):
|
| | """Use the `aiodns` package to make asynchronous DNS lookups"""
|
| |
|
| | def __init__(
|
| | self,
|
| | loop: Optional[asyncio.AbstractEventLoop] = None,
|
| | *args: Any,
|
| | **kwargs: Any
|
| | ) -> None:
|
| | if aiodns is None:
|
| | raise RuntimeError("Resolver requires aiodns library")
|
| |
|
| | self._resolver = aiodns.DNSResolver(*args, **kwargs)
|
| |
|
| | if not hasattr(self._resolver, "gethostbyname"):
|
| |
|
| | self.resolve = self._resolve_with_query
|
| |
|
| | async def resolve(
|
| | self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET
|
| | ) -> List[ResolveResult]:
|
| | try:
|
| | resp = await self._resolver.getaddrinfo(
|
| | host,
|
| | port=port,
|
| | type=socket.SOCK_STREAM,
|
| | family=family,
|
| | 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(None, msg) from exc
|
| | hosts: List[ResolveResult] = []
|
| | for node in resp.nodes:
|
| | address: Union[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:
|
| |
|
| |
|
| |
|
| | result = await self._resolver.getnameinfo(
|
| | (address[0].decode("ascii"), *address[1:]),
|
| | _NAME_SOCKET_FLAGS,
|
| | )
|
| | resolved_host = result.node
|
| | else:
|
| | resolved_host = address[0].decode("ascii")
|
| | port = address[1]
|
| | else:
|
| | 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(None, "DNS lookup failed")
|
| |
|
| | return hosts
|
| |
|
| | async def _resolve_with_query(
|
| | self, host: str, port: int = 0, family: int = socket.AF_INET
|
| | ) -> List[Dict[str, Any]]:
|
| | if family == socket.AF_INET6:
|
| | qtype = "AAAA"
|
| | else:
|
| | qtype = "A"
|
| |
|
| | try:
|
| | resp = await self._resolver.query(host, qtype)
|
| | except aiodns.error.DNSError as exc:
|
| | msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed"
|
| | raise OSError(None, msg) from exc
|
| |
|
| | hosts = []
|
| | for rr in resp:
|
| | hosts.append(
|
| | {
|
| | "hostname": host,
|
| | "host": rr.host,
|
| | "port": port,
|
| | "family": family,
|
| | "proto": 0,
|
| | "flags": socket.AI_NUMERICHOST,
|
| | }
|
| | )
|
| |
|
| | if not hosts:
|
| | raise OSError(None, "DNS lookup failed")
|
| |
|
| | return hosts
|
| |
|
| | async def close(self) -> None:
|
| | self._resolver.cancel()
|
| |
|
| |
|
| | _DefaultType = Type[Union[AsyncResolver, ThreadedResolver]]
|
| | DefaultResolver: _DefaultType = AsyncResolver if aiodns_default else ThreadedResolver
|
| |
|