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

Source Code for Module dns.resolver

  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   
 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 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, unicode)): 573 rdtype = dns.rdatatype.from_text(rdtype) 574 if isinstance(rdclass, (str, unicode)): 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, 597 algorithm=self.keyalgorithm) 598 request.use_edns(self.edns, self.ednsflags, self.payload) 599 response = None 600 # 601 # make a copy of the servers list so we can alter it later. 602 # 603 nameservers = self.nameservers[:] 604 backoff = 0.10 605 while response is None: 606 if len(nameservers) == 0: 607 raise NoNameservers 608 for nameserver in nameservers[:]: 609 timeout = self._compute_timeout(start) 610 try: 611 if tcp: 612 response = dns.query.tcp(request, nameserver, 613 timeout, self.port, 614 source=source) 615 else: 616 response = dns.query.udp(request, nameserver, 617 timeout, self.port, 618 source=source) 619 except (socket.error, dns.exception.Timeout): 620 # 621 # Communication failure or timeout. Go to the 622 # next server 623 # 624 response = None 625 continue 626 except dns.query.UnexpectedSource: 627 # 628 # Who knows? Keep going. 629 # 630 response = None 631 continue 632 except dns.exception.FormError: 633 # 634 # We don't understand what this server is 635 # saying. Take it out of the mix and 636 # continue. 637 # 638 nameservers.remove(nameserver) 639 response = None 640 continue 641 rcode = response.rcode() 642 if rcode == dns.rcode.NOERROR or \ 643 rcode == dns.rcode.NXDOMAIN: 644 break 645 # 646 # We got a response, but we're not happy with the 647 # rcode in it. Remove the server from the mix if 648 # the rcode isn't SERVFAIL. 649 # 650 if rcode != dns.rcode.SERVFAIL: 651 nameservers.remove(nameserver) 652 response = None 653 if not response is None: 654 break 655 # 656 # All nameservers failed! 657 # 658 if len(nameservers) > 0: 659 # 660 # But we still have servers to try. Sleep a bit 661 # so we don't pound them! 662 # 663 timeout = self._compute_timeout(start) 664 sleep_time = min(timeout, backoff) 665 backoff *= 2 666 time.sleep(sleep_time) 667 if response.rcode() == dns.rcode.NXDOMAIN: 668 continue 669 all_nxdomain = False 670 break 671 if all_nxdomain: 672 raise NXDOMAIN 673 answer = Answer(qname, rdtype, rdclass, response) 674 if self.cache: 675 self.cache.put((qname, rdtype, rdclass), answer) 676 return answer
677
678 - def use_tsig(self, keyring, keyname=None, 679 algorithm=dns.tsig.default_algorithm):
680 """Add a TSIG signature to the query. 681 682 @param keyring: The TSIG keyring to use; defaults to None. 683 @type keyring: dict 684 @param keyname: The name of the TSIG key to use; defaults to None. 685 The key must be defined in the keyring. If a keyring is specified 686 but a keyname is not, then the key used will be the first key in the 687 keyring. Note that the order of keys in a dictionary is not defined, 688 so applications should supply a keyname when a keyring is used, unless 689 they know the keyring contains only one key. 690 @param algorithm: The TSIG key algorithm to use. The default 691 is dns.tsig.default_algorithm. 692 @type algorithm: string""" 693 self.keyring = keyring 694 if keyname is None: 695 self.keyname = self.keyring.keys()[0] 696 else: 697 self.keyname = keyname 698 self.keyalgorithm = algorithm
699
700 - def use_edns(self, edns, ednsflags, payload):
701 """Configure Edns. 702 703 @param edns: The EDNS level to use. The default is -1, no Edns. 704 @type edns: int 705 @param ednsflags: The EDNS flags 706 @type ednsflags: int 707 @param payload: The EDNS payload size. The default is 0. 708 @type payload: int""" 709 710 if edns is None: 711 edns = -1 712 self.edns = edns 713 self.ednsflags = ednsflags 714 self.payload = payload
715 716 default_resolver = None 717
718 -def get_default_resolver():
719 """Get the default resolver, initializing it if necessary.""" 720 global default_resolver 721 if default_resolver is None: 722 default_resolver = Resolver() 723 return default_resolver
724
725 -def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, 726 tcp=False, source=None):
727 """Query nameservers to find the answer to the question. 728 729 This is a convenience function that uses the default resolver 730 object to make the query. 731 @see: L{dns.resolver.Resolver.query} for more information on the 732 parameters.""" 733 return get_default_resolver().query(qname, rdtype, rdclass, tcp, source)
734
735 -def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None):
736 """Find the name of the zone which contains the specified name. 737 738 @param name: the query name 739 @type name: absolute dns.name.Name object or string 740 @param rdclass: The query class 741 @type rdclass: int 742 @param tcp: use TCP to make the query (default is False). 743 @type tcp: bool 744 @param resolver: the resolver to use 745 @type resolver: dns.resolver.Resolver object or None 746 @rtype: dns.name.Name""" 747 748 if isinstance(name, (str, unicode)): 749 name = dns.name.from_text(name, dns.name.root) 750 if resolver is None: 751 resolver = get_default_resolver() 752 if not name.is_absolute(): 753 raise NotAbsolute(name) 754 while 1: 755 try: 756 answer = resolver.query(name, dns.rdatatype.SOA, rdclass, tcp) 757 if answer.rrset.name == name: 758 return name 759 # otherwise we were CNAMEd or DNAMEd and need to look higher 760 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): 761 pass 762 try: 763 name = name.parent() 764 except dns.name.NoParent: 765 raise NoRootSOA
766