1 # Copyright (C) 2003-2007, 2009 Nominum, Inc.
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.
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.
18 @var default_resolver: The default resolver object
19 @type default_resolver: dns.resolver.Resolver object"""
33 if sys.platform == 'win32':
36 class NXDOMAIN(dns.exception.DNSException):
37 """The query name does not exist."""
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.
44 Timeout = dns.exception.Timeout
46 class NoAnswer(dns.exception.DNSException):
47 """The response did not contain an answer to the question."""
50 class NoNameservers(dns.exception.DNSException):
51 """No non-broken nameservers are available to answer the query."""
54 class NotAbsolute(dns.exception.DNSException):
55 """Raised if an absolute domain name is required but a relative name
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!"""
66 """DNS stub resolver answer
68 Instances of this class bundle up the result of a successful DNS
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
77 Note that CNAMEs or DNAMEs in the response may mean that answer
78 node's name might not be the query name.
80 @ivar qname: The query name
81 @type qname: dns.name.Name object
82 @ivar rdtype: The query type
84 @ivar rdclass: The query class
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)
93 def __init__(self, qname, rdtype, rdclass, response):
96 self.rdclass = rdclass
97 self.response = response
100 for count in xrange(0, 15):
102 rrset = response.find_rrset(response.answer, qname,
104 if min_ttl == -1 or rrset.ttl < min_ttl:
108 if rdtype != dns.rdatatype.CNAME:
110 crrset = response.find_rrset(response.answer,
114 if min_ttl == -1 or crrset.ttl < min_ttl:
126 self.expiration = time.time() + min_ttl
128 def __getattr__(self, attr):
130 return self.rrset.name
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
140 raise AttributeError, attr
143 return len(self.rrset)
146 return iter(self.rrset)
148 def __getitem__(self, i):
151 def __delitem__(self, i):
154 def __getslice__(self, i, j):
155 return self.rrset[i:j]
157 def __delslice__(self, i, j):
161 """Simple DNS answer cache.
163 @ivar data: A dictionary of cached data
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
170 @type next_cleaning: float
173 def __init__(self, cleaning_interval=300.0):
174 """Initialize a DNS cache.
176 @param cleaning_interval: the number of seconds between periodic
177 cleanings. The default is 300.0
178 @type cleaning_interval: float.
182 self.cleaning_interval = cleaning_interval
183 self.next_cleaning = time.time() + self.cleaning_interval
185 def maybe_clean(self):
186 """Clean the cache if it's time to do so."""
189 if self.next_cleaning <= now:
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:
197 self.next_cleaning = now + self.cleaning_interval
200 """Get the answer associated with I{key}. Returns None if
201 no answer is cached for 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
209 v = self.data.get(key)
210 if v is None or v.expiration <= time.time():
214 def put(self, key, value):
215 """Associate key and value in the cache.
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
224 self.data[key] = value
226 def flush(self, key=None):
229 If I{key} is specified, only that item is flushed. Otherwise
230 the entire cache is flushed.
232 @param key: the key to flush
233 @type key: (dns.name.Name, int, int) tuple or None
237 if self.data.has_key(key):
241 self.next_cleaning = time.time() + self.cleaning_interval
243 class Resolver(object):
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.
257 @ivar timeout: The number of seconds to wait for a response from a
258 server, before timing out.
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
263 @type lifetime: float
264 @ivar keyring: The TSIG keyring to use. The default is None.
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.
270 @ivar ednsflags: The EDNS flags
272 @ivar payload: The EDNS payload size. The default is 0.
274 @ivar cache: The cache to use. The default is None.
275 @type cache: dns.resolver.Cache object
277 def __init__(self, filename='/etc/resolv.conf', configure=True):
278 """Initialize a resolver instance.
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"""
292 if sys.platform == 'win32':
295 self.read_resolv_conf(filename)
298 """Reset all resolver configuration to the defaults."""
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 = []
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):
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']
332 if len(l) == 0 or l[0] == '#' or l[0] == ';':
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))
347 if len(self.nameservers) == 0:
348 self.nameservers.append('127.0.0.1')
350 def _determine_split_char(self, entry):
352 # The windows registry irritatingly changes the list element
353 # delimiter in between ' ' and ',' (and vice-versa) in various
354 # versions of windows.
356 if entry.find(' ') >= 0:
358 elif entry.find(',') >= 0:
361 # probably a singleton; treat as a space-separated list.
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)
372 if not ns in self.nameservers:
373 self.nameservers.append(ns)
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))
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
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))
390 def _config_win32_fromkey(self, key):
391 """Extract DNS info from a registry key."""
393 servers, rtype = _winreg.QueryValueEx(key, 'NameServer')
397 self._config_win32_nameservers(servers)
399 dom, rtype = _winreg.QueryValueEx(key, 'Domain')
401 self._config_win32_domain(servers)
406 servers, rtype = _winreg.QueryValueEx(key, 'DhcpNameServer')
410 self._config_win32_nameservers(servers)
412 dom, rtype = _winreg.QueryValueEx(key, 'DhcpDomain')
414 self._config_win32_domain(servers)
418 search, rtype = _winreg.QueryValueEx(key, 'SearchList')
422 self._config_win32_search(search)
424 def read_registry(self):
425 """Extract resolver configuration from the Windows registry."""
426 lm = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE)
431 tcp_params = _winreg.OpenKey(lm,
432 r'SYSTEM\CurrentControlSet'
433 r'\Services\Tcpip\Parameters')
435 except EnvironmentError:
437 tcp_params = _winreg.OpenKey(lm,
438 r'SYSTEM\CurrentControlSet'
439 r'\Services\VxD\MSTCP')
441 self._config_win32_fromkey(tcp_params)
445 interfaces = _winreg.OpenKey(lm,
446 r'SYSTEM\CurrentControlSet'
447 r'\Services\Tcpip\Parameters'
453 guid = _winreg.EnumKey(interfaces, i)
455 key = _winreg.OpenKey(interfaces, guid)
456 if not self._win32_is_nic_enabled(lm, guid, key):
459 self._config_win32_fromkey(key)
462 except EnvironmentError:
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.
473 # (Code contributed by Paul Marks, thanks!)
476 # This hard-coded location seems to be consistent, at least
477 # from Windows 2000 through Vista.
478 connection_key = _winreg.OpenKey(
480 r'SYSTEM\CurrentControlSet\Control\Network'
481 r'\{4D36E972-E325-11CE-BFC1-08002BE10318}'
482 r'\%s\Connection' % guid)
485 # The PnpInstanceID points to a key inside Enum
486 (pnp_id, ttype) = _winreg.QueryValueEx(
487 connection_key, 'PnpInstanceID')
489 if ttype != _winreg.REG_SZ:
492 device_key = _winreg.OpenKey(
493 lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id)
496 # Get ConfigFlags for this device
497 (flags, ttype) = _winreg.QueryValueEx(
498 device_key, 'ConfigFlags')
500 if ttype != _winreg.REG_DWORD:
503 # Based on experimentation, bit 0x1 indicates that the
504 # device is disabled.
505 return not (flags & 0x1)
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.
518 (nte, ttype) = _winreg.QueryValueEx(interface_key,
520 return nte is not None
524 def _compute_timeout(self, start):
528 # Time going backwards is bad. Just give up.
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.
535 duration = now - start
536 if duration >= self.lifetime:
538 return min(self.lifetime - duration, self.timeout)
540 def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
542 """Query nameservers to find the answer to the question.
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.
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).
557 @rtype: dns.resolver.Answer instance
558 @raises Timeout: no answers could be found in the specified lifetime
559 @raises NXDOMAIN: the query name does not exist
560 @raises NoAnswer: the response did not contain an answer
561 @raises NoNameservers: no non-broken nameservers are available to
562 answer the question."""
564 if isinstance(qname, (str, unicode)):
565 qname = dns.name.from_text(qname, None)
566 if isinstance(rdtype, str):
567 rdtype = dns.rdatatype.from_text(rdtype)
568 if isinstance(rdclass, str):
569 rdclass = dns.rdataclass.from_text(rdclass)
571 if qname.is_absolute():
572 qnames_to_try.append(qname)
575 qnames_to_try.append(qname.concatenate(dns.name.root))
577 for suffix in self.search:
578 qnames_to_try.append(qname.concatenate(suffix))
580 qnames_to_try.append(qname.concatenate(self.domain))
583 for qname in qnames_to_try:
585 answer = self.cache.get((qname, rdtype, rdclass))
588 request = dns.message.make_query(qname, rdtype, rdclass)
589 if not self.keyname is None:
590 request.use_tsig(self.keyring, self.keyname)
591 request.use_edns(self.edns, self.ednsflags, self.payload)
594 # make a copy of the servers list so we can alter it later.
596 nameservers = self.nameservers[:]
598 while response is None:
599 if len(nameservers) == 0:
601 for nameserver in nameservers[:]:
602 timeout = self._compute_timeout(start)
605 response = dns.query.tcp(request, nameserver,
608 response = dns.query.udp(request, nameserver,
610 except (socket.error, dns.exception.Timeout):
612 # Communication failure or timeout. Go to the
617 except dns.query.UnexpectedSource:
619 # Who knows? Keep going.
623 except dns.exception.FormError:
625 # We don't understand what this server is
626 # saying. Take it out of the mix and
629 nameservers.remove(nameserver)
632 rcode = response.rcode()
633 if rcode == dns.rcode.NOERROR or \
634 rcode == dns.rcode.NXDOMAIN:
637 # We got a response, but we're not happy with the
638 # rcode in it. Remove the server from the mix if
639 # the rcode isn't SERVFAIL.
641 if rcode != dns.rcode.SERVFAIL:
642 nameservers.remove(nameserver)
644 if not response is None:
647 # All nameservers failed!
649 if len(nameservers) > 0:
651 # But we still have servers to try. Sleep a bit
652 # so we don't pound them!
654 timeout = self._compute_timeout(start)
655 sleep_time = min(timeout, backoff)
657 time.sleep(sleep_time)
658 if response.rcode() == dns.rcode.NXDOMAIN:
664 answer = Answer(qname, rdtype, rdclass, response)
666 self.cache.put((qname, rdtype, rdclass), answer)
669 def use_tsig(self, keyring, keyname=None):
670 """Add a TSIG signature to the query.
672 @param keyring: The TSIG keyring to use; defaults to None.
674 @param keyname: The name of the TSIG key to use; defaults to None.
675 The key must be defined in the keyring. If a keyring is specified
676 but a keyname is not, then the key used will be the first key in the
677 keyring. Note that the order of keys in a dictionary is not defined,
678 so applications should supply a keyname when a keyring is used, unless
679 they know the keyring contains only one key."""
680 self.keyring = keyring
682 self.keyname = self.keyring.keys()[0]
684 self.keyname = keyname
686 def use_edns(self, edns, ednsflags, payload):
689 @param edns: The EDNS level to use. The default is -1, no Edns.
691 @param ednsflags: The EDNS flags
693 @param payload: The EDNS payload size. The default is 0.
694 @type payload: int"""
699 self.ednsflags = ednsflags
700 self.payload = payload
702 default_resolver = None
704 def get_default_resolver():
705 """Get the default resolver, initializing it if necessary."""
706 global default_resolver
707 if default_resolver is None:
708 default_resolver = Resolver()
709 return default_resolver
711 def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN,
713 """Query nameservers to find the answer to the question.
715 This is a convenience function that uses the default resolver
716 object to make the query.
717 @see: L{dns.resolver.Resolver.query} for more information on the
719 return get_default_resolver().query(qname, rdtype, rdclass, tcp)
721 def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None):
722 """Find the name of the zone which contains the specified name.
724 @param name: the query name
725 @type name: absolute dns.name.Name object or string
726 @param rdclass: The query class
728 @param tcp: use TCP to make the query (default is False).
730 @param resolver: the resolver to use
731 @type resolver: dns.resolver.Resolver object or None
732 @rtype: dns.name.Name"""
734 if isinstance(name, (str, unicode)):
735 name = dns.name.from_text(name, dns.name.root)
737 resolver = get_default_resolver()
738 if not name.is_absolute():
739 raise NotAbsolute, name
742 answer = resolver.query(name, dns.rdatatype.SOA, rdclass, tcp)
744 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
747 except dns.name.NoParent: