source: trunk/server/lib/dnspython/dns/resolver.py

Last change on this file was 745, checked in by Silvan Scherrer, 13 years ago

Samba Server: updated trunk to 3.6.0

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