asyncresolver.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. # Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license
  2. # Copyright (C) 2003-2017 Nominum, Inc.
  3. #
  4. # Permission to use, copy, modify, and distribute this software and its
  5. # documentation for any purpose with or without fee is hereby granted,
  6. # provided that the above copyright notice and this permission notice
  7. # appear in all copies.
  8. #
  9. # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES
  10. # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  11. # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR
  12. # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  13. # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  14. # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  15. # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  16. """Asynchronous DNS stub resolver."""
  17. import time
  18. import dns.asyncbackend
  19. import dns.asyncquery
  20. import dns.exception
  21. import dns.query
  22. import dns.resolver
  23. # import some resolver symbols for brevity
  24. from dns.resolver import NXDOMAIN, NoAnswer, NotAbsolute, NoRootSOA
  25. # for indentation purposes below
  26. _udp = dns.asyncquery.udp
  27. _tcp = dns.asyncquery.tcp
  28. class Resolver(dns.resolver.BaseResolver):
  29. """Asynchronous DNS stub resolver."""
  30. async def resolve(self, qname, rdtype=dns.rdatatype.A,
  31. rdclass=dns.rdataclass.IN,
  32. tcp=False, source=None, raise_on_no_answer=True,
  33. source_port=0, lifetime=None, search=None,
  34. backend=None):
  35. """Query nameservers asynchronously to find the answer to the question.
  36. *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``,
  37. the default, then dnspython will use the default backend.
  38. See :py:func:`dns.resolver.Resolver.resolve()` for the
  39. documentation of the other parameters, exceptions, and return
  40. type of this method.
  41. """
  42. resolution = dns.resolver._Resolution(self, qname, rdtype, rdclass, tcp,
  43. raise_on_no_answer, search)
  44. if not backend:
  45. backend = dns.asyncbackend.get_default_backend()
  46. start = time.time()
  47. while True:
  48. (request, answer) = resolution.next_request()
  49. # Note we need to say "if answer is not None" and not just
  50. # "if answer" because answer implements __len__, and python
  51. # will call that. We want to return if we have an answer
  52. # object, including in cases where its length is 0.
  53. if answer is not None:
  54. # cache hit!
  55. return answer
  56. done = False
  57. while not done:
  58. (nameserver, port, tcp, backoff) = resolution.next_nameserver()
  59. if backoff:
  60. await backend.sleep(backoff)
  61. timeout = self._compute_timeout(start, lifetime,
  62. resolution.errors)
  63. try:
  64. if dns.inet.is_address(nameserver):
  65. if tcp:
  66. response = await _tcp(request, nameserver,
  67. timeout, port,
  68. source, source_port,
  69. backend=backend)
  70. else:
  71. response = await _udp(request, nameserver,
  72. timeout, port,
  73. source, source_port,
  74. raise_on_truncation=True,
  75. backend=backend)
  76. else:
  77. response = await dns.asyncquery.https(request,
  78. nameserver,
  79. timeout=timeout)
  80. except Exception as ex:
  81. (_, done) = resolution.query_result(None, ex)
  82. continue
  83. (answer, done) = resolution.query_result(response, None)
  84. # Note we need to say "if answer is not None" and not just
  85. # "if answer" because answer implements __len__, and python
  86. # will call that. We want to return if we have an answer
  87. # object, including in cases where its length is 0.
  88. if answer is not None:
  89. return answer
  90. async def resolve_address(self, ipaddr, *args, **kwargs):
  91. """Use an asynchronous resolver to run a reverse query for PTR
  92. records.
  93. This utilizes the resolve() method to perform a PTR lookup on the
  94. specified IP address.
  95. *ipaddr*, a ``str``, the IPv4 or IPv6 address you want to get
  96. the PTR record for.
  97. All other arguments that can be passed to the resolve() function
  98. except for rdtype and rdclass are also supported by this
  99. function.
  100. """
  101. return await self.resolve(dns.reversename.from_address(ipaddr),
  102. rdtype=dns.rdatatype.PTR,
  103. rdclass=dns.rdataclass.IN,
  104. *args, **kwargs)
  105. # pylint: disable=redefined-outer-name
  106. async def canonical_name(self, name):
  107. """Determine the canonical name of *name*.
  108. The canonical name is the name the resolver uses for queries
  109. after all CNAME and DNAME renamings have been applied.
  110. *name*, a ``dns.name.Name`` or ``str``, the query name.
  111. This method can raise any exception that ``resolve()`` can
  112. raise, other than ``dns.resolver.NoAnswer`` and
  113. ``dns.resolver.NXDOMAIN``.
  114. Returns a ``dns.name.Name``.
  115. """
  116. try:
  117. answer = await self.resolve(name, raise_on_no_answer=False)
  118. canonical_name = answer.canonical_name
  119. except dns.resolver.NXDOMAIN as e:
  120. canonical_name = e.canonical_name
  121. return canonical_name
  122. default_resolver = None
  123. def get_default_resolver():
  124. """Get the default asynchronous resolver, initializing it if necessary."""
  125. if default_resolver is None:
  126. reset_default_resolver()
  127. return default_resolver
  128. def reset_default_resolver():
  129. """Re-initialize default asynchronous resolver.
  130. Note that the resolver configuration (i.e. /etc/resolv.conf on UNIX
  131. systems) will be re-read immediately.
  132. """
  133. global default_resolver
  134. default_resolver = Resolver()
  135. async def resolve(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
  136. tcp=False, source=None, raise_on_no_answer=True,
  137. source_port=0, lifetime=None, search=None, backend=None):
  138. """Query nameservers asynchronously to find the answer to the question.
  139. This is a convenience function that uses the default resolver
  140. object to make the query.
  141. See :py:func:`dns.asyncresolver.Resolver.resolve` for more
  142. information on the parameters.
  143. """
  144. return await get_default_resolver().resolve(qname, rdtype, rdclass, tcp,
  145. source, raise_on_no_answer,
  146. source_port, lifetime, search,
  147. backend)
  148. async def resolve_address(ipaddr, *args, **kwargs):
  149. """Use a resolver to run a reverse query for PTR records.
  150. See :py:func:`dns.asyncresolver.Resolver.resolve_address` for more
  151. information on the parameters.
  152. """
  153. return await get_default_resolver().resolve_address(ipaddr, *args, **kwargs)
  154. async def canonical_name(name):
  155. """Determine the canonical name of *name*.
  156. See :py:func:`dns.resolver.Resolver.canonical_name` for more
  157. information on the parameters and possible exceptions.
  158. """
  159. return await get_default_resolver().canonical_name(name)
  160. async def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False,
  161. resolver=None, backend=None):
  162. """Find the name of the zone which contains the specified name.
  163. See :py:func:`dns.resolver.Resolver.zone_for_name` for more
  164. information on the parameters and possible exceptions.
  165. """
  166. if isinstance(name, str):
  167. name = dns.name.from_text(name, dns.name.root)
  168. if resolver is None:
  169. resolver = get_default_resolver()
  170. if not name.is_absolute():
  171. raise NotAbsolute(name)
  172. while True:
  173. try:
  174. answer = await resolver.resolve(name, dns.rdatatype.SOA, rdclass,
  175. tcp, backend=backend)
  176. if answer.rrset.name == name:
  177. return name
  178. # otherwise we were CNAMEd or DNAMEd and need to look higher
  179. except (NXDOMAIN, NoAnswer):
  180. pass
  181. try:
  182. name = name.parent()
  183. except dns.name.NoParent: # pragma: no cover
  184. raise NoRootSOA