1# Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc. 2# 3# Permission to use, copy, modify, and distribute this software and its 4# documentation for any purpose with or without fee is hereby granted, 5# provided that the above copyright notice and this permission notice 6# appear in all copies. 7# 8# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 9# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 11# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 14# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 16"""DNS stub resolver. 17 18@var default_resolver: The default resolver object 19@type default_resolver: dns.resolver.Resolver object""" 20 21import socket 22import sys 23import time 24 25import dns.exception 26import dns.message 27import dns.name 28import dns.query 29import dns.rcode 30import dns.rdataclass 31import dns.rdatatype 32 33if sys.platform == 'win32': 34 import _winreg 35 36class NXDOMAIN(dns.exception.DNSException): 37 """The query name does not exist.""" 38 pass 39 40# The definition of the Timeout exception has moved from here to the 41# dns.exception module. We keep dns.resolver.Timeout defined for 42# backwards compatibility. 43 44Timeout = dns.exception.Timeout 45 46class NoAnswer(dns.exception.DNSException): 47 """The response did not contain an answer to the question.""" 48 pass 49 50class NoNameservers(dns.exception.DNSException): 51 """No non-broken nameservers are available to answer the query.""" 52 pass 53 54class NotAbsolute(dns.exception.DNSException): 55 """Raised if an absolute domain name is required but a relative name 56 was provided.""" 57 pass 58 59class NoRootSOA(dns.exception.DNSException): 60 """Raised if for some reason there is no SOA at the root name. 61 This should never happen!""" 62 pass 63 64 65class Answer(object): 66 """DNS stub resolver answer 67 68 Instances of this class bundle up the result of a successful DNS 69 resolution. 70 71 For convenience, the answer object implements much of the sequence 72 protocol, forwarding to its rrset. E.g. "for a in answer" is 73 equivalent to "for a in answer.rrset", "answer[i]" is equivalent 74 to "answer.rrset[i]", and "answer[i:j]" is equivalent to 75 "answer.rrset[i:j]". 76 77 Note that CNAMEs or DNAMEs in the response may mean that answer 78 node's name might not be the query name. 79 80 @ivar qname: The query name 81 @type qname: dns.name.Name object 82 @ivar rdtype: The query type 83 @type rdtype: int 84 @ivar rdclass: The query class 85 @type rdclass: int 86 @ivar response: The response message 87 @type response: dns.message.Message object 88 @ivar rrset: The answer 89 @type rrset: dns.rrset.RRset object 90 @ivar expiration: The time when the answer expires 91 @type expiration: float (seconds since the epoch) 92 """ 93 def __init__(self, qname, rdtype, rdclass, response): 94 self.qname = qname 95 self.rdtype = rdtype 96 self.rdclass = rdclass 97 self.response = response 98 min_ttl = -1 99 rrset = None 100 for count in xrange(0, 15): 101 try: 102 rrset = response.find_rrset(response.answer, qname, 103 rdclass, rdtype) 104 if min_ttl == -1 or rrset.ttl < min_ttl: 105 min_ttl = rrset.ttl 106 break 107 except KeyError: 108 if rdtype != dns.rdatatype.CNAME: 109 try: 110 crrset = response.find_rrset(response.answer, 111 qname, 112 rdclass, 113 dns.rdatatype.CNAME) 114 if min_ttl == -1 or crrset.ttl < min_ttl: 115 min_ttl = crrset.ttl 116 for rd in crrset: 117 qname = rd.target 118 break 119 continue 120 except KeyError: 121 raise NoAnswer 122 raise NoAnswer 123 if rrset is None: 124 raise NoAnswer 125 self.rrset = rrset 126 self.expiration = time.time() + min_ttl 127 128 def __getattr__(self, attr): 129 if attr == 'name': 130 return self.rrset.name 131 elif attr == 'ttl': 132 return self.rrset.ttl 133 elif attr == 'covers': 134 return self.rrset.covers 135 elif attr == 'rdclass': 136 return self.rrset.rdclass 137 elif attr == 'rdtype': 138 return self.rrset.rdtype 139 else: 140 raise AttributeError(attr) 141 142 def __len__(self): 143 return len(self.rrset) 144 145 def __iter__(self): 146 return iter(self.rrset) 147 148 def __getitem__(self, i): 149 return self.rrset[i] 150 151 def __delitem__(self, i): 152 del self.rrset[i] 153 154 def __getslice__(self, i, j): 155 return self.rrset[i:j] 156 157 def __delslice__(self, i, j): 158 del self.rrset[i:j] 159 160class Cache(object): 161 """Simple DNS answer cache. 162 163 @ivar data: A dictionary of cached data 164 @type data: dict 165 @ivar cleaning_interval: The number of seconds between cleanings. The 166 default is 300 (5 minutes). 167 @type cleaning_interval: float 168 @ivar next_cleaning: The time the cache should next be cleaned (in seconds 169 since the epoch.) 170 @type next_cleaning: float 171 """ 172 173 def __init__(self, cleaning_interval=300.0): 174 """Initialize a DNS cache. 175 176 @param cleaning_interval: the number of seconds between periodic 177 cleanings. The default is 300.0 178 @type cleaning_interval: float. 179 """ 180 181 self.data = {} 182 self.cleaning_interval = cleaning_interval 183 self.next_cleaning = time.time() + self.cleaning_interval 184 185 def maybe_clean(self): 186 """Clean the cache if it's time to do so.""" 187 188 now = time.time() 189 if self.next_cleaning <= now: 190 keys_to_delete = [] 191 for (k, v) in self.data.iteritems(): 192 if v.expiration <= now: 193 keys_to_delete.append(k) 194 for k in keys_to_delete: 195 del self.data[k] 196 now = time.time() 197 self.next_cleaning = now + self.cleaning_interval 198 199 def get(self, key): 200 """Get the answer associated with I{key}. Returns None if 201 no answer is cached for the key. 202 @param key: the key 203 @type key: (dns.name.Name, int, int) tuple whose values are the 204 query name, rdtype, and rdclass. 205 @rtype: dns.resolver.Answer object or None 206 """ 207 208 self.maybe_clean() 209 v = self.data.get(key) 210 if v is None or v.expiration <= time.time(): 211 return None 212 return v 213 214 def put(self, key, value): 215 """Associate key and value in the cache. 216 @param key: the key 217 @type key: (dns.name.Name, int, int) tuple whose values are the 218 query name, rdtype, and rdclass. 219 @param value: The answer being cached 220 @type value: dns.resolver.Answer object 221 """ 222 223 self.maybe_clean() 224 self.data[key] = value 225 226 def flush(self, key=None): 227 """Flush the cache. 228 229 If I{key} is specified, only that item is flushed. Otherwise 230 the entire cache is flushed. 231 232 @param key: the key to flush 233 @type key: (dns.name.Name, int, int) tuple or None 234 """ 235 236 if not key is None: 237 if self.data.has_key(key): 238 del self.data[key] 239 else: 240 self.data = {} 241 self.next_cleaning = time.time() + self.cleaning_interval 242 243class Resolver(object): 244 """DNS stub resolver 245 246 @ivar domain: The domain of this host 247 @type domain: dns.name.Name object 248 @ivar nameservers: A list of nameservers to query. Each nameserver is 249 a string which contains the IP address of a nameserver. 250 @type nameservers: list of strings 251 @ivar search: The search list. If the query name is a relative name, 252 the resolver will construct an absolute query name by appending the search 253 names one by one to the query name. 254 @type search: list of dns.name.Name objects 255 @ivar port: The port to which to send queries. The default is 53. 256 @type port: int 257 @ivar timeout: The number of seconds to wait for a response from a 258 server, before timing out. 259 @type timeout: float 260 @ivar lifetime: The total number of seconds to spend trying to get an 261 answer to the question. If the lifetime expires, a Timeout exception 262 will occur. 263 @type lifetime: float 264 @ivar keyring: The TSIG keyring to use. The default is None. 265 @type keyring: dict 266 @ivar keyname: The TSIG keyname to use. The default is None. 267 @type keyname: dns.name.Name object 268 @ivar keyalgorithm: The TSIG key algorithm to use. The default is 269 dns.tsig.default_algorithm. 270 @type keyalgorithm: string 271 @ivar edns: The EDNS level to use. The default is -1, no Edns. 272 @type edns: int 273 @ivar ednsflags: The EDNS flags 274 @type ednsflags: int 275 @ivar payload: The EDNS payload size. The default is 0. 276 @type payload: int 277 @ivar cache: The cache to use. The default is None. 278 @type cache: dns.resolver.Cache object 279 """ 280 def __init__(self, filename='/etc/resolv.conf', configure=True): 281 """Initialize a resolver instance. 282 283 @param filename: The filename of a configuration file in 284 standard /etc/resolv.conf format. This parameter is meaningful 285 only when I{configure} is true and the platform is POSIX. 286 @type filename: string or file object 287 @param configure: If True (the default), the resolver instance 288 is configured in the normal fashion for the operating system 289 the resolver is running on. (I.e. a /etc/resolv.conf file on 290 POSIX systems and from the registry on Windows systems.) 291 @type configure: bool""" 292 293 self.reset() 294 if configure: 295 if sys.platform == 'win32': 296 self.read_registry() 297 elif filename: 298 self.read_resolv_conf(filename) 299 300 def reset(self): 301 """Reset all resolver configuration to the defaults.""" 302 self.domain = \ 303 dns.name.Name(dns.name.from_text(socket.gethostname())[1:]) 304 if len(self.domain) == 0: 305 self.domain = dns.name.root 306 self.nameservers = [] 307 self.search = [] 308 self.port = 53 309 self.timeout = 2.0 310 self.lifetime = 30.0 311 self.keyring = None 312 self.keyname = None 313 self.keyalgorithm = dns.tsig.default_algorithm 314 self.edns = -1 315 self.ednsflags = 0 316 self.payload = 0 317 self.cache = None 318 319 def read_resolv_conf(self, f): 320 """Process f as a file in the /etc/resolv.conf format. If f is 321 a string, it is used as the name of the file to open; otherwise it 322 is treated as the file itself.""" 323 if isinstance(f, str) or isinstance(f, unicode): 324 try: 325 f = open(f, 'r') 326 except IOError: 327 # /etc/resolv.conf doesn't exist, can't be read, etc. 328 # We'll just use the default resolver configuration. 329 self.nameservers = ['127.0.0.1'] 330 return 331 want_close = True 332 else: 333 want_close = False 334 try: 335 for l in f: 336 if len(l) == 0 or l[0] == '#' or l[0] == ';': 337 continue 338 tokens = l.split() 339 if len(tokens) == 0: 340 continue 341 if tokens[0] == 'nameserver': 342 self.nameservers.append(tokens[1]) 343 elif tokens[0] == 'domain': 344 self.domain = dns.name.from_text(tokens[1]) 345 elif tokens[0] == 'search': 346 for suffix in tokens[1:]: 347 self.search.append(dns.name.from_text(suffix)) 348 finally: 349 if want_close: 350 f.close() 351 if len(self.nameservers) == 0: 352 self.nameservers.append('127.0.0.1') 353 354 def _determine_split_char(self, entry): 355 # 356 # The windows registry irritatingly changes the list element 357 # delimiter in between ' ' and ',' (and vice-versa) in various 358 # versions of windows. 359 # 360 if entry.find(' ') >= 0: 361 split_char = ' ' 362 elif entry.find(',') >= 0: 363 split_char = ',' 364 else: 365 # probably a singleton; treat as a space-separated list. 366 split_char = ' ' 367 return split_char 368 369 def _config_win32_nameservers(self, nameservers): 370 """Configure a NameServer registry entry.""" 371 # we call str() on nameservers to convert it from unicode to ascii 372 nameservers = str(nameservers) 373 split_char = self._determine_split_char(nameservers) 374 ns_list = nameservers.split(split_char) 375 for ns in ns_list: 376 if not ns in self.nameservers: 377 self.nameservers.append(ns) 378 379 def _config_win32_domain(self, domain): 380 """Configure a Domain registry entry.""" 381 # we call str() on domain to convert it from unicode to ascii 382 self.domain = dns.name.from_text(str(domain)) 383 384 def _config_win32_search(self, search): 385 """Configure a Search registry entry.""" 386 # we call str() on search to convert it from unicode to ascii 387 search = str(search) 388 split_char = self._determine_split_char(search) 389 search_list = search.split(split_char) 390 for s in search_list: 391 if not s in self.search: 392 self.search.append(dns.name.from_text(s)) 393 394 def _config_win32_fromkey(self, key): 395 """Extract DNS info from a registry key.""" 396 try: 397 servers, rtype = _winreg.QueryValueEx(key, 'NameServer') 398 except WindowsError: 399 servers = None 400 if servers: 401 self._config_win32_nameservers(servers) 402 try: 403 dom, rtype = _winreg.QueryValueEx(key, 'Domain') 404 if dom: 405 self._config_win32_domain(dom) 406 except WindowsError: 407 pass 408 else: 409 try: 410 servers, rtype = _winreg.QueryValueEx(key, 'DhcpNameServer') 411 except WindowsError: 412 servers = None 413 if servers: 414 self._config_win32_nameservers(servers) 415 try: 416 dom, rtype = _winreg.QueryValueEx(key, 'DhcpDomain') 417 if dom: 418 self._config_win32_domain(dom) 419 except WindowsError: 420 pass 421 try: 422 search, rtype = _winreg.QueryValueEx(key, 'SearchList') 423 except WindowsError: 424 search = None 425 if search: 426 self._config_win32_search(search) 427 428 def read_registry(self): 429 """Extract resolver configuration from the Windows registry.""" 430 lm = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) 431 want_scan = False 432 try: 433 try: 434 # XP, 2000 435 tcp_params = _winreg.OpenKey(lm, 436 r'SYSTEM\CurrentControlSet' 437 r'\Services\Tcpip\Parameters') 438 want_scan = True 439 except EnvironmentError: 440 # ME 441 tcp_params = _winreg.OpenKey(lm, 442 r'SYSTEM\CurrentControlSet' 443 r'\Services\VxD\MSTCP') 444 try: 445 self._config_win32_fromkey(tcp_params) 446 finally: 447 tcp_params.Close() 448 if want_scan: 449 interfaces = _winreg.OpenKey(lm, 450 r'SYSTEM\CurrentControlSet' 451 r'\Services\Tcpip\Parameters' 452 r'\Interfaces') 453 try: 454 i = 0 455 while True: 456 try: 457 guid = _winreg.EnumKey(interfaces, i) 458 i += 1 459 key = _winreg.OpenKey(interfaces, guid) 460 if not self._win32_is_nic_enabled(lm, guid, key): 461 continue 462 try: 463 self._config_win32_fromkey(key) 464 finally: 465 key.Close() 466 except EnvironmentError: 467 break 468 finally: 469 interfaces.Close() 470 finally: 471 lm.Close() 472 473 def _win32_is_nic_enabled(self, lm, guid, interface_key): 474 # Look in the Windows Registry to determine whether the network 475 # interface corresponding to the given guid is enabled. 476 # 477 # (Code contributed by Paul Marks, thanks!) 478 # 479 try: 480 # This hard-coded location seems to be consistent, at least 481 # from Windows 2000 through Vista. 482 connection_key = _winreg.OpenKey( 483 lm, 484 r'SYSTEM\CurrentControlSet\Control\Network' 485 r'\{4D36E972-E325-11CE-BFC1-08002BE10318}' 486 r'\%s\Connection' % guid) 487 488 try: 489 # The PnpInstanceID points to a key inside Enum 490 (pnp_id, ttype) = _winreg.QueryValueEx( 491 connection_key, 'PnpInstanceID') 492 493 if ttype != _winreg.REG_SZ: 494 raise ValueError 495 496 device_key = _winreg.OpenKey( 497 lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id) 498 499 try: 500 # Get ConfigFlags for this device 501 (flags, ttype) = _winreg.QueryValueEx( 502 device_key, 'ConfigFlags') 503 504 if ttype != _winreg.REG_DWORD: 505 raise ValueError 506 507 # Based on experimentation, bit 0x1 indicates that the 508 # device is disabled. 509 return not (flags & 0x1) 510 511 finally: 512 device_key.Close() 513 finally: 514 connection_key.Close() 515 except (EnvironmentError, ValueError): 516 # Pre-vista, enabled interfaces seem to have a non-empty 517 # NTEContextList; this was how dnspython detected enabled 518 # nics before the code above was contributed. We've retained 519 # the old method since we don't know if the code above works 520 # on Windows 95/98/ME. 521 try: 522 (nte, ttype) = _winreg.QueryValueEx(interface_key, 523 'NTEContextList') 524 return nte is not None 525 except WindowsError: 526 return False 527 528 def _compute_timeout(self, start): 529 now = time.time() 530 if now < start: 531 if start - now > 1: 532 # Time going backwards is bad. Just give up. 533 raise Timeout 534 else: 535 # Time went backwards, but only a little. This can 536 # happen, e.g. under vmware with older linux kernels. 537 # Pretend it didn't happen. 538 now = start 539 duration = now - start 540 if duration >= self.lifetime: 541 raise Timeout 542 return min(self.lifetime - duration, self.timeout) 543 544 def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, 545 tcp=False, source=None): 546 """Query nameservers to find the answer to the question. 547 548 The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects 549 of the appropriate type, or strings that can be converted into objects 550 of the appropriate type. E.g. For I{rdtype} the integer 2 and the 551 the string 'NS' both mean to query for records with DNS rdata type NS. 552 553 @param qname: the query name 554 @type qname: dns.name.Name object or string 555 @param rdtype: the query type 556 @type rdtype: int or string 557 @param rdclass: the query class 558 @type rdclass: int or string 559 @param tcp: use TCP to make the query (default is False). 560 @type tcp: bool 561 @param source: bind to this IP address (defaults to machine default IP). 562 @type source: IP address in dotted quad notation 563 @rtype: dns.resolver.Answer instance 564 @raises Timeout: no answers could be found in the specified lifetime 565 @raises NXDOMAIN: the query name does not exist 566 @raises NoAnswer: the response did not contain an answer 567 @raises NoNameservers: no non-broken nameservers are available to 568 answer the question.""" 569 570 if isinstance(qname, (str, unicode)): 571 qname = dns.name.from_text(qname, None) 572 if isinstance(rdtype, str): 573 rdtype = dns.rdatatype.from_text(rdtype) 574 if isinstance(rdclass, str): 575 rdclass = dns.rdataclass.from_text(rdclass) 576 qnames_to_try = [] 577 if qname.is_absolute(): 578 qnames_to_try.append(qname) 579 else: 580 if len(qname) > 1: 581 qnames_to_try.append(qname.concatenate(dns.name.root)) 582 if self.search: 583 for suffix in self.search: 584 qnames_to_try.append(qname.concatenate(suffix)) 585 else: 586 qnames_to_try.append(qname.concatenate(self.domain)) 587 all_nxdomain = True 588 start = time.time() 589 for qname in qnames_to_try: 590 if self.cache: 591 answer = self.cache.get((qname, rdtype, rdclass)) 592 if answer: 593 return answer 594 request = dns.message.make_query(qname, rdtype, rdclass) 595 if not self.keyname is None: 596 request.use_tsig(self.keyring, self.keyname, self.keyalgorithm) 597 request.use_edns(self.edns, self.ednsflags, self.payload) 598 response = None 599 # 600 # make a copy of the servers list so we can alter it later. 601 # 602 nameservers = self.nameservers[:] 603 backoff = 0.10 604 while response is None: 605 if len(nameservers) == 0: 606 raise NoNameservers 607 for nameserver in nameservers[:]: 608 timeout = self._compute_timeout(start) 609 try: 610 if tcp: 611 response = dns.query.tcp(request, nameserver, 612 timeout, self.port, 613 source=source) 614 else: 615 response = dns.query.udp(request, nameserver, 616 timeout, self.port, 617 source=source) 618 except (socket.error, dns.exception.Timeout): 619 # 620 # Communication failure or timeout. Go to the 621 # next server 622 # 623 response = None 624 continue 625 except dns.query.UnexpectedSource: 626 # 627 # Who knows? Keep going. 628 # 629 response = None 630 continue 631 except dns.exception.FormError: 632 # 633 # We don't understand what this server is 634 # saying. Take it out of the mix and 635 # continue. 636 # 637 nameservers.remove(nameserver) 638 response = None 639 continue 640 rcode = response.rcode() 641 if rcode == dns.rcode.NOERROR or \ 642 rcode == dns.rcode.NXDOMAIN: 643 break 644 # 645 # We got a response, but we're not happy with the 646 # rcode in it. Remove the server from the mix if 647 # the rcode isn't SERVFAIL. 648 # 649 if rcode != dns.rcode.SERVFAIL: 650 nameservers.remove(nameserver) 651 response = None 652 if not response is None: 653 break 654 # 655 # All nameservers failed! 656 # 657 if len(nameservers) > 0: 658 # 659 # But we still have servers to try. Sleep a bit 660 # so we don't pound them! 661 # 662 timeout = self._compute_timeout(start) 663 sleep_time = min(timeout, backoff) 664 backoff *= 2 665 time.sleep(sleep_time) 666 if response.rcode() == dns.rcode.NXDOMAIN: 667 continue 668 all_nxdomain = False 669 break 670 if all_nxdomain: 671 raise NXDOMAIN 672 answer = Answer(qname, rdtype, rdclass, response) 673 if self.cache: 674 self.cache.put((qname, rdtype, rdclass), answer) 675 return answer 676 677 def use_tsig(self, keyring, keyname=None, 678 algorithm=dns.tsig.default_algorithm): 679 """Add a TSIG signature to the query. 680 681 @param keyring: The TSIG keyring to use; defaults to None. 682 @type keyring: dict 683 @param keyname: The name of the TSIG key to use; defaults to None. 684 The key must be defined in the keyring. If a keyring is specified 685 but a keyname is not, then the key used will be the first key in the 686 keyring. Note that the order of keys in a dictionary is not defined, 687 so applications should supply a keyname when a keyring is used, unless 688 they know the keyring contains only one key. 689 @param algorithm: The TSIG key algorithm to use. The default 690 is dns.tsig.default_algorithm. 691 @type algorithm: string""" 692 self.keyring = keyring 693 if keyname is None: 694 self.keyname = self.keyring.keys()[0] 695 else: 696 self.keyname = keyname 697 self.keyalgorithm = algorithm 698 699 def use_edns(self, edns, ednsflags, payload): 700 """Configure Edns. 701 702 @param edns: The EDNS level to use. The default is -1, no Edns. 703 @type edns: int 704 @param ednsflags: The EDNS flags 705 @type ednsflags: int 706 @param payload: The EDNS payload size. The default is 0. 707 @type payload: int""" 708 709 if edns is None: 710 edns = -1 711 self.edns = edns 712 self.ednsflags = ednsflags 713 self.payload = payload 714 715default_resolver = None 716 717def get_default_resolver(): 718 """Get the default resolver, initializing it if necessary.""" 719 global default_resolver 720 if default_resolver is None: 721 default_resolver = Resolver() 722 return default_resolver 723 724def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, 725 tcp=False, source=None): 726 """Query nameservers to find the answer to the question. 727 728 This is a convenience function that uses the default resolver 729 object to make the query. 730 @see: L{dns.resolver.Resolver.query} for more information on the 731 parameters.""" 732 return get_default_resolver().query(qname, rdtype, rdclass, tcp, source) 733 734def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None): 735 """Find the name of the zone which contains the specified name. 736 737 @param name: the query name 738 @type name: absolute dns.name.Name object or string 739 @param rdclass: The query class 740 @type rdclass: int 741 @param tcp: use TCP to make the query (default is False). 742 @type tcp: bool 743 @param resolver: the resolver to use 744 @type resolver: dns.resolver.Resolver object or None 745 @rtype: dns.name.Name""" 746 747 if isinstance(name, (str, unicode)): 748 name = dns.name.from_text(name, dns.name.root) 749 if resolver is None: 750 resolver = get_default_resolver() 751 if not name.is_absolute(): 752 raise NotAbsolute(name) 753 while 1: 754 try: 755 answer = resolver.query(name, dns.rdatatype.SOA, rdclass, tcp) 756 return name 757 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): 758 try: 759 name = name.parent() 760 except dns.name.NoParent: 761 raise NoRootSOA 762