You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
233 lines
8.9 KiB
233 lines
8.9 KiB
3 years ago
|
# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
|
||
|
|
||
|
# Copyright (C) 2003-2017 Nominum, Inc.
|
||
|
#
|
||
|
# Permission to use, copy, modify, and distribute this software and its
|
||
|
# documentation for any purpose with or without fee is hereby granted,
|
||
|
# provided that the above copyright notice and this permission notice
|
||
|
# appear in all copies.
|
||
|
#
|
||
|
# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
|
||
|
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
|
||
|
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||
|
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
|
||
|
"""Asynchronous DNS stub resolver."""
|
||
|
|
||
|
import time
|
||
|
|
||
|
import dns.asyncbackend
|
||
|
import dns.asyncquery
|
||
|
import dns.exception
|
||
|
import dns.query
|
||
|
import dns.resolver
|
||
|
|
||
|
# import some resolver symbols for brevity
|
||
|
from dns.resolver import NXDOMAIN, NoAnswer, NotAbsolute, NoRootSOA
|
||
|
|
||
|
|
||
|
# for indentation purposes below
|
||
|
_udp = dns.asyncquery.udp
|
||
|
_tcp = dns.asyncquery.tcp
|
||
|
|
||
|
|
||
|
class Resolver(dns.resolver.BaseResolver):
|
||
|
"""Asynchronous DNS stub resolver."""
|
||
|
|
||
|
async def resolve(self, qname, rdtype=dns.rdatatype.A,
|
||
|
rdclass=dns.rdataclass.IN,
|
||
|
tcp=False, source=None, raise_on_no_answer=True,
|
||
|
source_port=0, lifetime=None, search=None,
|
||
|
backend=None):
|
||
|
"""Query nameservers asynchronously to find the answer to the question.
|
||
|
|
||
|
*backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
|
||
|
the default, then dnspython will use the default backend.
|
||
|
|
||
|
See :py:func:`dns.resolver.Resolver.resolve()` for the
|
||
|
documentation of the other parameters, exceptions, and return
|
||
|
type of this method.
|
||
|
"""
|
||
|
|
||
|
resolution = dns.resolver._Resolution(self, qname, rdtype, rdclass, tcp,
|
||
|
raise_on_no_answer, search)
|
||
|
if not backend:
|
||
|
backend = dns.asyncbackend.get_default_backend()
|
||
|
start = time.time()
|
||
|
while True:
|
||
|
(request, answer) = resolution.next_request()
|
||
|
# Note we need to say "if answer is not None" and not just
|
||
|
# "if answer" because answer implements __len__, and python
|
||
|
# will call that. We want to return if we have an answer
|
||
|
# object, including in cases where its length is 0.
|
||
|
if answer is not None:
|
||
|
# cache hit!
|
||
|
return answer
|
||
|
done = False
|
||
|
while not done:
|
||
|
(nameserver, port, tcp, backoff) = resolution.next_nameserver()
|
||
|
if backoff:
|
||
|
await backend.sleep(backoff)
|
||
|
timeout = self._compute_timeout(start, lifetime,
|
||
|
resolution.errors)
|
||
|
try:
|
||
|
if dns.inet.is_address(nameserver):
|
||
|
if tcp:
|
||
|
response = await _tcp(request, nameserver,
|
||
|
timeout, port,
|
||
|
source, source_port,
|
||
|
backend=backend)
|
||
|
else:
|
||
|
response = await _udp(request, nameserver,
|
||
|
timeout, port,
|
||
|
source, source_port,
|
||
|
raise_on_truncation=True,
|
||
|
backend=backend)
|
||
|
else:
|
||
|
response = await dns.asyncquery.https(request,
|
||
|
nameserver,
|
||
|
timeout=timeout)
|
||
|
except Exception as ex:
|
||
|
(_, done) = resolution.query_result(None, ex)
|
||
|
continue
|
||
|
(answer, done) = resolution.query_result(response, None)
|
||
|
# Note we need to say "if answer is not None" and not just
|
||
|
# "if answer" because answer implements __len__, and python
|
||
|
# will call that. We want to return if we have an answer
|
||
|
# object, including in cases where its length is 0.
|
||
|
if answer is not None:
|
||
|
return answer
|
||
|
|
||
|
async def resolve_address(self, ipaddr, *args, **kwargs):
|
||
|
"""Use an asynchronous resolver to run a reverse query for PTR
|
||
|
records.
|
||
|
|
||
|
This utilizes the resolve() method to perform a PTR lookup on the
|
||
|
specified IP address.
|
||
|
|
||
|
*ipaddr*, a ``str``, the IPv4 or IPv6 address you want to get
|
||
|
the PTR record for.
|
||
|
|
||
|
All other arguments that can be passed to the resolve() function
|
||
|
except for rdtype and rdclass are also supported by this
|
||
|
function.
|
||
|
|
||
|
"""
|
||
|
|
||
|
return await self.resolve(dns.reversename.from_address(ipaddr),
|
||
|
rdtype=dns.rdatatype.PTR,
|
||
|
rdclass=dns.rdataclass.IN,
|
||
|
*args, **kwargs)
|
||
|
|
||
|
# pylint: disable=redefined-outer-name
|
||
|
|
||
|
async def canonical_name(self, name):
|
||
|
"""Determine the canonical name of *name*.
|
||
|
|
||
|
The canonical name is the name the resolver uses for queries
|
||
|
after all CNAME and DNAME renamings have been applied.
|
||
|
|
||
|
*name*, a ``dns.name.Name`` or ``str``, the query name.
|
||
|
|
||
|
This method can raise any exception that ``resolve()`` can
|
||
|
raise, other than ``dns.resolver.NoAnswer`` and
|
||
|
``dns.resolver.NXDOMAIN``.
|
||
|
|
||
|
Returns a ``dns.name.Name``.
|
||
|
"""
|
||
|
try:
|
||
|
answer = await self.resolve(name, raise_on_no_answer=False)
|
||
|
canonical_name = answer.canonical_name
|
||
|
except dns.resolver.NXDOMAIN as e:
|
||
|
canonical_name = e.canonical_name
|
||
|
return canonical_name
|
||
|
|
||
|
|
||
|
default_resolver = None
|
||
|
|
||
|
|
||
|
def get_default_resolver():
|
||
|
"""Get the default asynchronous resolver, initializing it if necessary."""
|
||
|
if default_resolver is None:
|
||
|
reset_default_resolver()
|
||
|
return default_resolver
|
||
|
|
||
|
|
||
|
def reset_default_resolver():
|
||
|
"""Re-initialize default asynchronous resolver.
|
||
|
|
||
|
Note that the resolver configuration (i.e. /etc/resolv.conf on UNIX
|
||
|
systems) will be re-read immediately.
|
||
|
"""
|
||
|
|
||
|
global default_resolver
|
||
|
default_resolver = Resolver()
|
||
|
|
||
|
|
||
|
async def resolve(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
|
||
|
tcp=False, source=None, raise_on_no_answer=True,
|
||
|
source_port=0, lifetime=None, search=None, backend=None):
|
||
|
"""Query nameservers asynchronously to find the answer to the question.
|
||
|
|
||
|
This is a convenience function that uses the default resolver
|
||
|
object to make the query.
|
||
|
|
||
|
See :py:func:`dns.asyncresolver.Resolver.resolve` for more
|
||
|
information on the parameters.
|
||
|
"""
|
||
|
|
||
|
return await get_default_resolver().resolve(qname, rdtype, rdclass, tcp,
|
||
|
source, raise_on_no_answer,
|
||
|
source_port, lifetime, search,
|
||
|
backend)
|
||
|
|
||
|
|
||
|
async def resolve_address(ipaddr, *args, **kwargs):
|
||
|
"""Use a resolver to run a reverse query for PTR records.
|
||
|
|
||
|
See :py:func:`dns.asyncresolver.Resolver.resolve_address` for more
|
||
|
information on the parameters.
|
||
|
"""
|
||
|
|
||
|
return await get_default_resolver().resolve_address(ipaddr, *args, **kwargs)
|
||
|
|
||
|
async def canonical_name(name):
|
||
|
"""Determine the canonical name of *name*.
|
||
|
|
||
|
See :py:func:`dns.resolver.Resolver.canonical_name` for more
|
||
|
information on the parameters and possible exceptions.
|
||
|
"""
|
||
|
|
||
|
return await get_default_resolver().canonical_name(name)
|
||
|
|
||
|
async def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False,
|
||
|
resolver=None, backend=None):
|
||
|
"""Find the name of the zone which contains the specified name.
|
||
|
|
||
|
See :py:func:`dns.resolver.Resolver.zone_for_name` for more
|
||
|
information on the parameters and possible exceptions.
|
||
|
"""
|
||
|
|
||
|
if isinstance(name, str):
|
||
|
name = dns.name.from_text(name, dns.name.root)
|
||
|
if resolver is None:
|
||
|
resolver = get_default_resolver()
|
||
|
if not name.is_absolute():
|
||
|
raise NotAbsolute(name)
|
||
|
while True:
|
||
|
try:
|
||
|
answer = await resolver.resolve(name, dns.rdatatype.SOA, rdclass,
|
||
|
tcp, backend=backend)
|
||
|
if answer.rrset.name == name:
|
||
|
return name
|
||
|
# otherwise we were CNAMEd or DNAMEd and need to look higher
|
||
|
except (NXDOMAIN, NoAnswer):
|
||
|
pass
|
||
|
try:
|
||
|
name = name.parent()
|
||
|
except dns.name.NoParent: # pragma: no cover
|
||
|
raise NoRootSOA
|