Package dns :: Module resolver
[hide private]
[frames] | no frames]

Source Code for Module dns.resolver

  1  # Copyright (C) 2003-2007, 2009 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   
 21  import socket 
 22  import sys 
 23  import time 
 24   
 25  import dns.exception 
 26  import dns.message 
 27  import dns.name 
 28  import dns.query 
 29  import dns.rcode 
 30  import dns.rdataclass 
 31  import dns.rdatatype 
 32   
 33  if sys.platform == 'win32': 
 34      import _winreg 
 35   
36 -class 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 44 Timeout = dns.exception.Timeout 45
46 -class NoAnswer(dns.exception.DNSException):
47 """The response did not contain an answer to the question.""" 48 pass
49
50 -class NoNameservers(dns.exception.DNSException):
51 """No non-broken nameservers are available to answer the query.""" 52 pass
53
54 -class NotAbsolute(dns.exception.DNSException):
55 """Raised if an absolute domain name is required but a relative name 56 was provided.""" 57 pass
58
59 -class 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
65 -class 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
160 -class 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
243 -class 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 edns: The EDNS level to use. The default is -1, no Edns. 269 @type edns: int 270 @ivar ednsflags: The EDNS flags 271 @type ednsflags: int 272 @ivar payload: The EDNS payload size. The default is 0. 273 @type payload: int 274 @ivar cache: The cache to use. The default is None. 275 @type cache: dns.resolver.Cache object 276 """
277 - def __init__(self, filename='/etc/resolv.conf', configure=True):
278 """Initialize a resolver instance. 279 280 @param filename: The filename of a configuration file in 281 standard /etc/resolv.conf format. This parameter is meaningful 282 only when I{configure} is true and the platform is POSIX. 283 @type filename: string or file object 284 @param configure: If True (the default), the resolver instance 285 is configured in the normal fashion for the operating system 286 the resolver is running on. (I.e. a /etc/resolv.conf file on 287 POSIX systems and from the registry on Windows systems.) 288 @type configure: bool""" 289 290 self.reset() 291 if configure: 292 if sys.platform == 'win32': 293 self.read_registry() 294 elif filename: 295 self.read_resolv_conf(filename)
296
297 - def reset(self):
298 """Reset all resolver configuration to the defaults.""" 299 self.domain = \ 300 dns.name.Name(dns.name.from_text(socket.gethostname())[1:]) 301 if len(self.domain) == 0: 302 self.domain = dns.name.root 303 self.nameservers = [] 304 self.search = [] 305 self.port = 53 306 self.timeout = 2.0 307 self.lifetime = 30.0 308 self.keyring = None 309 self.keyname = None 310 self.edns = -1 311 self.ednsflags = 0 312 self.payload = 0 313 self.cache = None
314
315 - def read_resolv_conf(self, f):
316 """Process f as a file in the /etc/resolv.conf format. If f is 317 a string, it is used as the name of the file to open; otherwise it 318 is treated as the file itself.""" 319 if isinstance(f, str) or isinstance(f, unicode): 320 try: 321 f = open(f, 'r') 322 except IOError: 323 # /etc/resolv.conf doesn't exist, can't be read, etc. 324 # We'll just use the default resolver configuration. 325 self.nameservers = ['127.0.0.1'] 326 return 327 want_close = True 328 else: 329 want_close = False 330 try: 331 for l in f: 332 if len(l) == 0 or l[0] == '#' or l[0] == ';': 333 continue 334 tokens = l.split() 335 if len(tokens) == 0: 336 continue 337 if tokens[0] == 'nameserver': 338 self.nameservers.append(tokens[1]) 339 elif tokens[0] == 'domain': 340 self.domain = dns.name.from_text(tokens[1]) 341 elif tokens[0] == 'search': 342 for suffix in tokens[1:]: 343 self.search.append(dns.name.from_text(suffix)) 344 finally: 345 if want_close: 346 f.close() 347 if len(self.nameservers) == 0: 348 self.nameservers.append('127.0.0.1')
349
350 - def _determine_split_char(self, entry):
351 # 352 # The windows registry irritatingly changes the list element 353 # delimiter in between ' ' and ',' (and vice-versa) in various 354 # versions of windows. 355 # 356 if entry.find(' ') >= 0: 357 split_char = ' ' 358 elif entry.find(',') >= 0: 359 split_char = ',' 360 else: 361 # probably a singleton; treat as a space-separated list. 362 split_char = ' ' 363 return split_char
364
365 - def _config_win32_nameservers(self, nameservers):
366 """Configure a NameServer registry entry.""" 367 # we call str() on nameservers to convert it from unicode to ascii 368 nameservers = str(nameservers) 369 split_char = self._determine_split_char(nameservers) 370 ns_list = nameservers.split(split_char) 371 for ns in ns_list: 372 if not ns in self.nameservers: 373 self.nameservers.append(ns)
374
375 - def _config_win32_domain(self, domain):
376 """Configure a Domain registry entry.""" 377 # we call str() on domain to convert it from unicode to ascii 378 self.domain = dns.name.from_text(str(domain))
379
380 - def _config_win32_search(self, search):
381 """Configure a Search registry entry.""" 382 # we call str() on search to convert it from unicode to ascii 383 search = str(search) 384 split_char = self._determine_split_char(search) 385 search_list = search.split(split_char) 386 for s in search_list: 387 if not s in self.search: 388 self.search.append(dns.name.from_text(s))
389
390 - def _config_win32_fromkey(self, key):
391 """Extract DNS info from a registry key.""" 392 try: 393 servers, rtype = _winreg.QueryValueEx(key, 'NameServer') 394 except WindowsError: 395 servers = None 396 if servers: 397 self._config_win32_nameservers(servers) 398 try: 399 dom, rtype = _winreg.QueryValueEx(key, 'Domain') 400 if dom: 401 self._config_win32_domain(dom) 402 except WindowsError: 403 pass 404 else: 405 try: 406 servers, rtype = _winreg.QueryValueEx(key, 'DhcpNameServer') 407 except WindowsError: 408 servers = None 409 if servers: 410 self._config_win32_nameservers(servers) 411 try: 412 dom, rtype = _winreg.QueryValueEx(key, 'DhcpDomain') 413 if dom: 414 self._config_win32_domain(dom) 415 except WindowsError: 416 pass 417 try: 418 search, rtype = _winreg.QueryValueEx(key, 'SearchList') 419 except WindowsError: 420 search = None 421 if search: 422 self._config_win32_search(search)
423
424 - def read_registry(self):
425 """Extract resolver configuration from the Windows registry.""" 426 lm = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) 427 want_scan = False 428 try: 429 try: 430 # XP, 2000 431 tcp_params = _winreg.OpenKey(lm, 432 r'SYSTEM\CurrentControlSet' 433 r'\Services\Tcpip\Parameters') 434 want_scan = True 435 except EnvironmentError: 436 # ME 437 tcp_params = _winreg.OpenKey(lm, 438 r'SYSTEM\CurrentControlSet' 439 r'\Services\VxD\MSTCP') 440 try: 441 self._config_win32_fromkey(tcp_params) 442 finally: 443 tcp_params.Close() 444 if want_scan: 445 interfaces = _winreg.OpenKey(lm, 446 r'SYSTEM\CurrentControlSet' 447 r'\Services\Tcpip\Parameters' 448 r'\Interfaces') 449 try: 450 i = 0 451 while True: 452 try: 453 guid = _winreg.EnumKey(interfaces, i) 454 i += 1 455 key = _winreg.OpenKey(interfaces, guid) 456 if not self._win32_is_nic_enabled(lm, guid, key): 457 continue 458 try: 459 self._config_win32_fromkey(key) 460 finally: 461 key.Close() 462 except EnvironmentError: 463 break 464 finally: 465 interfaces.Close() 466 finally: 467 lm.Close()
468
469 - def _win32_is_nic_enabled(self, lm, guid, interface_key):
470 # Look in the Windows Registry to determine whether the network 471 # interface corresponding to the given guid is enabled. 472 # 473 # (Code contributed by Paul Marks, thanks!) 474 # 475 try: 476 # This hard-coded location seems to be consistent, at least 477 # from Windows 2000 through Vista. 478 connection_key = _winreg.OpenKey( 479 lm, 480 r'SYSTEM\CurrentControlSet\Control\Network' 481 r'\{4D36E972-E325-11CE-BFC1-08002BE10318}' 482 r'\%s\Connection' % guid) 483 484 try: 485 # The PnpInstanceID points to a key inside Enum 486 (pnp_id, ttype) = _winreg.QueryValueEx( 487 connection_key, 'PnpInstanceID') 488 489 if ttype != _winreg.REG_SZ: 490 raise ValueError 491 492 device_key = _winreg.OpenKey( 493 lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id) 494 495 try: 496 # Get ConfigFlags for this device 497 (flags, ttype) = _winreg.QueryValueEx( 498 device_key, 'ConfigFlags') 499 500 if ttype != _winreg.REG_DWORD: 501 raise ValueError 502 503 # Based on experimentation, bit 0x1 indicates that the 504 # device is disabled. 505 return not (flags & 0x1) 506 507 finally: 508 device_key.Close() 509 finally: 510 connection_key.Close() 511 except (EnvironmentError, ValueError): 512 # Pre-vista, enabled interfaces seem to have a non-empty 513 # NTEContextList; this was how dnspython detected enabled 514 # nics before the code above was contributed. We've retained 515 # the old method since we don't know if the code above works 516 # on Windows 95/98/ME. 517 try: 518 (nte, ttype) = _winreg.QueryValueEx(interface_key, 519 'NTEContextList') 520 return nte is not None 521 except WindowsError: 522 return False
523
524 - def _compute_timeout(self, start):
525 now = time.time() 526 if now < start: 527 if start - now > 1: 528 # Time going backwards is bad. Just give up. 529 raise Timeout 530 else: 531 # Time went backwards, but only a little. This can 532 # happen, e.g. under vmware with older linux kernels. 533 # Pretend it didn't happen. 534 now = start 535 duration = now - start 536 if duration >= self.lifetime: 537 raise Timeout 538 return min(self.lifetime - duration, self.timeout)
539
540 - def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, 541 tcp=False, source=None):
542 """Query nameservers to find the answer to the question. 543 544 The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects 545 of the appropriate type, or strings that can be converted into objects 546 of the appropriate type. E.g. For I{rdtype} the integer 2 and the 547 the string 'NS' both mean to query for records with DNS rdata type NS. 548 549 @param qname: the query name 550 @type qname: dns.name.Name object or string 551 @param rdtype: the query type 552 @type rdtype: int or string 553 @param rdclass: the query class 554 @type rdclass: int or string 555 @param tcp: use TCP to make the query (default is False). 556 @type tcp: bool 557 @param source: bind to this IP address (defaults to machine default IP). 558 @type source: IP address in dotted quad notation 559 @rtype: dns.resolver.Answer instance 560 @raises Timeout: no answers could be found in the specified lifetime 561 @raises NXDOMAIN: the query name does not exist 562 @raises NoAnswer: the response did not contain an answer 563 @raises NoNameservers: no non-broken nameservers are available to 564 answer the question.""" 565 566 if isinstance(qname, (str, unicode)): 567 qname = dns.name.from_text(qname, None) 568 if isinstance(rdtype, str): 569 rdtype = dns.rdatatype.from_text(rdtype) 570 if isinstance(rdclass, str): 571 rdclass = dns.rdataclass.from_text(rdclass) 572 qnames_to_try = [] 573 if qname.is_absolute(): 574 qnames_to_try.append(qname) 575 else: 576 if len(qname) > 1: 577 qnames_to_try.append(qname.concatenate(dns.name.root)) 578 if self.search: 579 for suffix in self.search: 580 qnames_to_try.append(qname.concatenate(suffix)) 581 else: 582 qnames_to_try.append(qname.concatenate(self.domain)) 583 all_nxdomain = True 584 start = time.time() 585 for qname in qnames_to_try: 586 if self.cache: 587 answer = self.cache.get((qname, rdtype, rdclass)) 588 if answer: 589 return answer 590 request = dns.message.make_query(qname, rdtype, rdclass) 591 if not self.keyname is None: 592 request.use_tsig(self.keyring, self.keyname) 593 request.use_edns(self.edns, self.ednsflags, self.payload) 594 response = None 595 # 596 # make a copy of the servers list so we can alter it later. 597 # 598 nameservers = self.nameservers[:] 599 backoff = 0.10 600 while response is None: 601 if len(nameservers) == 0: 602 raise NoNameservers 603 for nameserver in nameservers[:]: 604 timeout = self._compute_timeout(start) 605 try: 606 if tcp: 607 response = dns.query.tcp(request, nameserver, 608 timeout, self.port, 609 source=source) 610 else: 611 response = dns.query.udp(request, nameserver, 612 timeout, self.port, 613 source=source) 614 except (socket.error, dns.exception.Timeout): 615 # 616 # Communication failure or timeout. Go to the 617 # next server 618 # 619 response = None 620 continue 621 except dns.query.UnexpectedSource: 622 # 623 # Who knows? Keep going. 624 # 625 response = None 626 continue 627 except dns.exception.FormError: 628 # 629 # We don't understand what this server is 630 # saying. Take it out of the mix and 631 # continue. 632 # 633 nameservers.remove(nameserver) 634 response = None 635 continue 636 rcode = response.rcode() 637 if rcode == dns.rcode.NOERROR or \ 638 rcode == dns.rcode.NXDOMAIN: 639 break 640 # 641 # We got a response, but we're not happy with the 642 # rcode in it. Remove the server from the mix if 643 # the rcode isn't SERVFAIL. 644 # 645 if rcode != dns.rcode.SERVFAIL: 646 nameservers.remove(nameserver) 647 response = None 648 if not response is None: 649 break 650 # 651 # All nameservers failed! 652 # 653 if len(nameservers) > 0: 654 # 655 # But we still have servers to try. Sleep a bit 656 # so we don't pound them! 657 # 658 timeout = self._compute_timeout(start) 659 sleep_time = min(timeout, backoff) 660 backoff *= 2 661 time.sleep(sleep_time) 662 if response.rcode() == dns.rcode.NXDOMAIN: 663 continue 664 all_nxdomain = False 665 break 666 if all_nxdomain: 667 raise NXDOMAIN 668 answer = Answer(qname, rdtype, rdclass, response) 669 if self.cache: 670 self.cache.put((qname, rdtype, rdclass), answer) 671 return answer
672
673 - def use_tsig(self, keyring, keyname=None):
674 """Add a TSIG signature to the query. 675 676 @param keyring: The TSIG keyring to use; defaults to None. 677 @type keyring: dict 678 @param keyname: The name of the TSIG key to use; defaults to None. 679 The key must be defined in the keyring. If a keyring is specified 680 but a keyname is not, then the key used will be the first key in the 681 keyring. Note that the order of keys in a dictionary is not defined, 682 so applications should supply a keyname when a keyring is used, unless 683 they know the keyring contains only one key.""" 684 self.keyring = keyring 685 if keyname is None: 686 self.keyname = self.keyring.keys()[0] 687 else: 688 self.keyname = keyname
689
690 - def use_edns(self, edns, ednsflags, payload):
691 """Configure Edns. 692 693 @param edns: The EDNS level to use. The default is -1, no Edns. 694 @type edns: int 695 @param ednsflags: The EDNS flags 696 @type ednsflags: int 697 @param payload: The EDNS payload size. The default is 0. 698 @type payload: int""" 699 700 if edns is None: 701 edns = -1 702 self.edns = edns 703 self.ednsflags = ednsflags 704 self.payload = payload
705 706 default_resolver = None 707
708 -def get_default_resolver():
709 """Get the default resolver, initializing it if necessary.""" 710 global default_resolver 711 if default_resolver is None: 712 default_resolver = Resolver() 713 return default_resolver
714
715 -def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, 716 tcp=False, source=None):
717 """Query nameservers to find the answer to the question. 718 719 This is a convenience function that uses the default resolver 720 object to make the query. 721 @see: L{dns.resolver.Resolver.query} for more information on the 722 parameters.""" 723 return get_default_resolver().query(qname, rdtype, rdclass, tcp, source)
724
725 -def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None):
726 """Find the name of the zone which contains the specified name. 727 728 @param name: the query name 729 @type name: absolute dns.name.Name object or string 730 @param rdclass: The query class 731 @type rdclass: int 732 @param tcp: use TCP to make the query (default is False). 733 @type tcp: bool 734 @param resolver: the resolver to use 735 @type resolver: dns.resolver.Resolver object or None 736 @rtype: dns.name.Name""" 737 738 if isinstance(name, (str, unicode)): 739 name = dns.name.from_text(name, dns.name.root) 740 if resolver is None: 741 resolver = get_default_resolver() 742 if not name.is_absolute(): 743 raise NotAbsolute, name 744 while 1: 745 try: 746 answer = resolver.query(name, dns.rdatatype.SOA, rdclass, tcp) 747 return name 748 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): 749 try: 750 name = name.parent() 751 except dns.name.NoParent: 752 raise NoRootSOA
753