1# Copyright (C) Dnspython Contributors, see LICENSE for text of ISC license 2 3# Copyright (C) 2003-2017 Nominum, Inc. 4# 5# Permission to use, copy, modify, and distribute this software and its 6# documentation for any purpose with or without fee is hereby granted, 7# provided that the above copyright notice and this permission notice 8# appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 16# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18"""Asynchronous DNS stub resolver.""" 19 20import time 21 22import dns.asyncbackend 23import dns.asyncquery 24import dns.exception 25import dns.query 26import dns.resolver 27 28# import some resolver symbols for brevity 29from dns.resolver import NXDOMAIN, NoAnswer, NotAbsolute, NoRootSOA 30 31 32# for indentation purposes below 33_udp = dns.asyncquery.udp 34_tcp = dns.asyncquery.tcp 35 36 37class Resolver(dns.resolver.Resolver): 38 39 async def resolve(self, qname, rdtype=dns.rdatatype.A, 40 rdclass=dns.rdataclass.IN, 41 tcp=False, source=None, raise_on_no_answer=True, 42 source_port=0, lifetime=None, search=None, 43 backend=None): 44 """Query nameservers asynchronously to find the answer to the question. 45 46 The *qname*, *rdtype*, and *rdclass* parameters may be objects 47 of the appropriate type, or strings that can be converted into objects 48 of the appropriate type. 49 50 *qname*, a ``dns.name.Name`` or ``str``, the query name. 51 52 *rdtype*, an ``int`` or ``str``, the query type. 53 54 *rdclass*, an ``int`` or ``str``, the query class. 55 56 *tcp*, a ``bool``. If ``True``, use TCP to make the query. 57 58 *source*, a ``str`` or ``None``. If not ``None``, bind to this IP 59 address when making queries. 60 61 *raise_on_no_answer*, a ``bool``. If ``True``, raise 62 ``dns.resolver.NoAnswer`` if there's no answer to the question. 63 64 *source_port*, an ``int``, the port from which to send the message. 65 66 *lifetime*, a ``float``, how many seconds a query should run 67 before timing out. 68 69 *search*, a ``bool`` or ``None``, determines whether the 70 search list configured in the system's resolver configuration 71 are used for relative names, and whether the resolver's domain 72 may be added to relative names. The default is ``None``, 73 which causes the value of the resolver's 74 ``use_search_by_default`` attribute to be used. 75 76 *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, 77 the default, then dnspython will use the default backend. 78 79 Raises ``dns.resolver.NXDOMAIN`` if the query name does not exist. 80 81 Raises ``dns.resolver.YXDOMAIN`` if the query name is too long after 82 DNAME substitution. 83 84 Raises ``dns.resolver.NoAnswer`` if *raise_on_no_answer* is 85 ``True`` and the query name exists but has no RRset of the 86 desired type and class. 87 88 Raises ``dns.resolver.NoNameservers`` if no non-broken 89 nameservers are available to answer the question. 90 91 Returns a ``dns.resolver.Answer`` instance. 92 93 """ 94 95 resolution = dns.resolver._Resolution(self, qname, rdtype, rdclass, tcp, 96 raise_on_no_answer, search) 97 if not backend: 98 backend = dns.asyncbackend.get_default_backend() 99 start = time.time() 100 while True: 101 (request, answer) = resolution.next_request() 102 # Note we need to say "if answer is not None" and not just 103 # "if answer" because answer implements __len__, and python 104 # will call that. We want to return if we have an answer 105 # object, including in cases where its length is 0. 106 if answer is not None: 107 # cache hit! 108 return answer 109 done = False 110 while not done: 111 (nameserver, port, tcp, backoff) = resolution.next_nameserver() 112 if backoff: 113 await backend.sleep(backoff) 114 timeout = self._compute_timeout(start, lifetime) 115 try: 116 if dns.inet.is_address(nameserver): 117 if tcp: 118 response = await _tcp(request, nameserver, 119 timeout, port, 120 source, source_port, 121 backend=backend) 122 else: 123 response = await _udp(request, nameserver, 124 timeout, port, 125 source, source_port, 126 raise_on_truncation=True, 127 backend=backend) 128 else: 129 # We don't do DoH yet. 130 raise NotImplementedError 131 except Exception as ex: 132 (_, done) = resolution.query_result(None, ex) 133 continue 134 (answer, done) = resolution.query_result(response, None) 135 # Note we need to say "if answer is not None" and not just 136 # "if answer" because answer implements __len__, and python 137 # will call that. We want to return if we have an answer 138 # object, including in cases where its length is 0. 139 if answer is not None: 140 return answer 141 142 async def query(self, *args, **kwargs): 143 # We have to define something here as we don't want to inherit the 144 # parent's query(). 145 raise NotImplementedError 146 147 async def resolve_address(self, ipaddr, *args, **kwargs): 148 """Use an asynchronous resolver to run a reverse query for PTR 149 records. 150 151 This utilizes the resolve() method to perform a PTR lookup on the 152 specified IP address. 153 154 *ipaddr*, a ``str``, the IPv4 or IPv6 address you want to get 155 the PTR record for. 156 157 All other arguments that can be passed to the resolve() function 158 except for rdtype and rdclass are also supported by this 159 function. 160 161 """ 162 163 return await self.resolve(dns.reversename.from_address(ipaddr), 164 rdtype=dns.rdatatype.PTR, 165 rdclass=dns.rdataclass.IN, 166 *args, **kwargs) 167 168default_resolver = None 169 170 171def get_default_resolver(): 172 """Get the default asynchronous resolver, initializing it if necessary.""" 173 if default_resolver is None: 174 reset_default_resolver() 175 return default_resolver 176 177 178def reset_default_resolver(): 179 """Re-initialize default asynchronous resolver. 180 181 Note that the resolver configuration (i.e. /etc/resolv.conf on UNIX 182 systems) will be re-read immediately. 183 """ 184 185 global default_resolver 186 default_resolver = Resolver() 187 188 189async def resolve(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, 190 tcp=False, source=None, raise_on_no_answer=True, 191 source_port=0, search=None, backend=None): 192 """Query nameservers asynchronously to find the answer to the question. 193 194 This is a convenience function that uses the default resolver 195 object to make the query. 196 197 See ``dns.asyncresolver.Resolver.resolve`` for more information on the 198 parameters. 199 """ 200 201 return await get_default_resolver().resolve(qname, rdtype, rdclass, tcp, 202 source, raise_on_no_answer, 203 source_port, search, backend) 204 205 206async def resolve_address(ipaddr, *args, **kwargs): 207 """Use a resolver to run a reverse query for PTR records. 208 209 See ``dns.asyncresolver.Resolver.resolve_address`` for more 210 information on the parameters. 211 """ 212 213 return await get_default_resolver().resolve_address(ipaddr, *args, **kwargs) 214 215 216async def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, 217 resolver=None, backend=None): 218 """Find the name of the zone which contains the specified name. 219 220 *name*, an absolute ``dns.name.Name`` or ``str``, the query name. 221 222 *rdclass*, an ``int``, the query class. 223 224 *tcp*, a ``bool``. If ``True``, use TCP to make the query. 225 226 *resolver*, a ``dns.asyncresolver.Resolver`` or ``None``, the 227 resolver to use. If ``None``, the default resolver is used. 228 229 *backend*, a ``dns.asyncbackend.Backend``, or ``None``. If ``None``, 230 the default, then dnspython will use the default backend. 231 232 Raises ``dns.resolver.NoRootSOA`` if there is no SOA RR at the DNS 233 root. (This is only likely to happen if you're using non-default 234 root servers in your network and they are misconfigured.) 235 236 Returns a ``dns.name.Name``. 237 """ 238 239 if isinstance(name, str): 240 name = dns.name.from_text(name, dns.name.root) 241 if resolver is None: 242 resolver = get_default_resolver() 243 if not name.is_absolute(): 244 raise NotAbsolute(name) 245 while True: 246 try: 247 answer = await resolver.resolve(name, dns.rdatatype.SOA, rdclass, 248 tcp, backend=backend) 249 if answer.rrset.name == name: 250 return name 251 # otherwise we were CNAMEd or DNAMEd and need to look higher 252 except (NXDOMAIN, NoAnswer): 253 pass 254 try: 255 name = name.parent() 256 except dns.name.NoParent: # pragma: no cover 257 raise NoRootSOA 258