| # Copyright (C) 2003-2007, 2009, 2010 Nominum, Inc. |
| # |
| # Permission to use, copy, modify, and distribute this software and its |
| # documentation for any purpose with or without fee is hereby granted, |
| # provided that the above copyright notice and this permission notice |
| # appear in all copies. |
| # |
| # THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES |
| # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR |
| # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
| # OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| |
| """DNS stub resolver. |
| |
| @var default_resolver: The default resolver object |
| @type default_resolver: dns.resolver.Resolver object""" |
| |
| import socket |
| import sys |
| import time |
| |
| import dns.exception |
| import dns.message |
| import dns.name |
| import dns.query |
| import dns.rcode |
| import dns.rdataclass |
| import dns.rdatatype |
| |
| if sys.platform == 'win32': |
| import _winreg |
| |
| class NXDOMAIN(dns.exception.DNSException): |
| """The query name does not exist.""" |
| pass |
| |
| # The definition of the Timeout exception has moved from here to the |
| # dns.exception module. We keep dns.resolver.Timeout defined for |
| # backwards compatibility. |
| |
| Timeout = dns.exception.Timeout |
| |
| class NoAnswer(dns.exception.DNSException): |
| """The response did not contain an answer to the question.""" |
| pass |
| |
| class NoNameservers(dns.exception.DNSException): |
| """No non-broken nameservers are available to answer the query.""" |
| pass |
| |
| class NotAbsolute(dns.exception.DNSException): |
| """Raised if an absolute domain name is required but a relative name |
| was provided.""" |
| pass |
| |
| class NoRootSOA(dns.exception.DNSException): |
| """Raised if for some reason there is no SOA at the root name. |
| This should never happen!""" |
| pass |
| |
| |
| class Answer(object): |
| """DNS stub resolver answer |
| |
| Instances of this class bundle up the result of a successful DNS |
| resolution. |
| |
| For convenience, the answer object implements much of the sequence |
| protocol, forwarding to its rrset. E.g. "for a in answer" is |
| equivalent to "for a in answer.rrset", "answer[i]" is equivalent |
| to "answer.rrset[i]", and "answer[i:j]" is equivalent to |
| "answer.rrset[i:j]". |
| |
| Note that CNAMEs or DNAMEs in the response may mean that answer |
| node's name might not be the query name. |
| |
| @ivar qname: The query name |
| @type qname: dns.name.Name object |
| @ivar rdtype: The query type |
| @type rdtype: int |
| @ivar rdclass: The query class |
| @type rdclass: int |
| @ivar response: The response message |
| @type response: dns.message.Message object |
| @ivar rrset: The answer |
| @type rrset: dns.rrset.RRset object |
| @ivar expiration: The time when the answer expires |
| @type expiration: float (seconds since the epoch) |
| """ |
| def __init__(self, qname, rdtype, rdclass, response): |
| self.qname = qname |
| self.rdtype = rdtype |
| self.rdclass = rdclass |
| self.response = response |
| min_ttl = -1 |
| rrset = None |
| for count in xrange(0, 15): |
| try: |
| rrset = response.find_rrset(response.answer, qname, |
| rdclass, rdtype) |
| if min_ttl == -1 or rrset.ttl < min_ttl: |
| min_ttl = rrset.ttl |
| break |
| except KeyError: |
| if rdtype != dns.rdatatype.CNAME: |
| try: |
| crrset = response.find_rrset(response.answer, |
| qname, |
| rdclass, |
| dns.rdatatype.CNAME) |
| if min_ttl == -1 or crrset.ttl < min_ttl: |
| min_ttl = crrset.ttl |
| for rd in crrset: |
| qname = rd.target |
| break |
| continue |
| except KeyError: |
| raise NoAnswer |
| raise NoAnswer |
| if rrset is None: |
| raise NoAnswer |
| self.rrset = rrset |
| self.expiration = time.time() + min_ttl |
| |
| def __getattr__(self, attr): |
| if attr == 'name': |
| return self.rrset.name |
| elif attr == 'ttl': |
| return self.rrset.ttl |
| elif attr == 'covers': |
| return self.rrset.covers |
| elif attr == 'rdclass': |
| return self.rrset.rdclass |
| elif attr == 'rdtype': |
| return self.rrset.rdtype |
| else: |
| raise AttributeError(attr) |
| |
| def __len__(self): |
| return len(self.rrset) |
| |
| def __iter__(self): |
| return iter(self.rrset) |
| |
| def __getitem__(self, i): |
| return self.rrset[i] |
| |
| def __delitem__(self, i): |
| del self.rrset[i] |
| |
| def __getslice__(self, i, j): |
| return self.rrset[i:j] |
| |
| def __delslice__(self, i, j): |
| del self.rrset[i:j] |
| |
| class Cache(object): |
| """Simple DNS answer cache. |
| |
| @ivar data: A dictionary of cached data |
| @type data: dict |
| @ivar cleaning_interval: The number of seconds between cleanings. The |
| default is 300 (5 minutes). |
| @type cleaning_interval: float |
| @ivar next_cleaning: The time the cache should next be cleaned (in seconds |
| since the epoch.) |
| @type next_cleaning: float |
| """ |
| |
| def __init__(self, cleaning_interval=300.0): |
| """Initialize a DNS cache. |
| |
| @param cleaning_interval: the number of seconds between periodic |
| cleanings. The default is 300.0 |
| @type cleaning_interval: float. |
| """ |
| |
| self.data = {} |
| self.cleaning_interval = cleaning_interval |
| self.next_cleaning = time.time() + self.cleaning_interval |
| |
| def maybe_clean(self): |
| """Clean the cache if it's time to do so.""" |
| |
| now = time.time() |
| if self.next_cleaning <= now: |
| keys_to_delete = [] |
| for (k, v) in self.data.iteritems(): |
| if v.expiration <= now: |
| keys_to_delete.append(k) |
| for k in keys_to_delete: |
| del self.data[k] |
| now = time.time() |
| self.next_cleaning = now + self.cleaning_interval |
| |
| def get(self, key): |
| """Get the answer associated with I{key}. Returns None if |
| no answer is cached for the key. |
| @param key: the key |
| @type key: (dns.name.Name, int, int) tuple whose values are the |
| query name, rdtype, and rdclass. |
| @rtype: dns.resolver.Answer object or None |
| """ |
| |
| self.maybe_clean() |
| v = self.data.get(key) |
| if v is None or v.expiration <= time.time(): |
| return None |
| return v |
| |
| def put(self, key, value): |
| """Associate key and value in the cache. |
| @param key: the key |
| @type key: (dns.name.Name, int, int) tuple whose values are the |
| query name, rdtype, and rdclass. |
| @param value: The answer being cached |
| @type value: dns.resolver.Answer object |
| """ |
| |
| self.maybe_clean() |
| self.data[key] = value |
| |
| def flush(self, key=None): |
| """Flush the cache. |
| |
| If I{key} is specified, only that item is flushed. Otherwise |
| the entire cache is flushed. |
| |
| @param key: the key to flush |
| @type key: (dns.name.Name, int, int) tuple or None |
| """ |
| |
| if not key is None: |
| if self.data.has_key(key): |
| del self.data[key] |
| else: |
| self.data = {} |
| self.next_cleaning = time.time() + self.cleaning_interval |
| |
| class Resolver(object): |
| """DNS stub resolver |
| |
| @ivar domain: The domain of this host |
| @type domain: dns.name.Name object |
| @ivar nameservers: A list of nameservers to query. Each nameserver is |
| a string which contains the IP address of a nameserver. |
| @type nameservers: list of strings |
| @ivar search: The search list. If the query name is a relative name, |
| the resolver will construct an absolute query name by appending the search |
| names one by one to the query name. |
| @type search: list of dns.name.Name objects |
| @ivar port: The port to which to send queries. The default is 53. |
| @type port: int |
| @ivar timeout: The number of seconds to wait for a response from a |
| server, before timing out. |
| @type timeout: float |
| @ivar lifetime: The total number of seconds to spend trying to get an |
| answer to the question. If the lifetime expires, a Timeout exception |
| will occur. |
| @type lifetime: float |
| @ivar keyring: The TSIG keyring to use. The default is None. |
| @type keyring: dict |
| @ivar keyname: The TSIG keyname to use. The default is None. |
| @type keyname: dns.name.Name object |
| @ivar keyalgorithm: The TSIG key algorithm to use. The default is |
| dns.tsig.default_algorithm. |
| @type keyalgorithm: string |
| @ivar edns: The EDNS level to use. The default is -1, no Edns. |
| @type edns: int |
| @ivar ednsflags: The EDNS flags |
| @type ednsflags: int |
| @ivar payload: The EDNS payload size. The default is 0. |
| @type payload: int |
| @ivar cache: The cache to use. The default is None. |
| @type cache: dns.resolver.Cache object |
| """ |
| def __init__(self, filename='/etc/resolv.conf', configure=True): |
| """Initialize a resolver instance. |
| |
| @param filename: The filename of a configuration file in |
| standard /etc/resolv.conf format. This parameter is meaningful |
| only when I{configure} is true and the platform is POSIX. |
| @type filename: string or file object |
| @param configure: If True (the default), the resolver instance |
| is configured in the normal fashion for the operating system |
| the resolver is running on. (I.e. a /etc/resolv.conf file on |
| POSIX systems and from the registry on Windows systems.) |
| @type configure: bool""" |
| |
| self.reset() |
| if configure: |
| if sys.platform == 'win32': |
| self.read_registry() |
| elif filename: |
| self.read_resolv_conf(filename) |
| |
| def reset(self): |
| """Reset all resolver configuration to the defaults.""" |
| self.domain = \ |
| dns.name.Name(dns.name.from_text(socket.gethostname())[1:]) |
| if len(self.domain) == 0: |
| self.domain = dns.name.root |
| self.nameservers = [] |
| self.search = [] |
| self.port = 53 |
| self.timeout = 2.0 |
| self.lifetime = 30.0 |
| self.keyring = None |
| self.keyname = None |
| self.keyalgorithm = dns.tsig.default_algorithm |
| self.edns = -1 |
| self.ednsflags = 0 |
| self.payload = 0 |
| self.cache = None |
| |
| def read_resolv_conf(self, f): |
| """Process f as a file in the /etc/resolv.conf format. If f is |
| a string, it is used as the name of the file to open; otherwise it |
| is treated as the file itself.""" |
| if isinstance(f, str) or isinstance(f, unicode): |
| try: |
| f = open(f, 'r') |
| except IOError: |
| # /etc/resolv.conf doesn't exist, can't be read, etc. |
| # We'll just use the default resolver configuration. |
| self.nameservers = ['127.0.0.1'] |
| return |
| want_close = True |
| else: |
| want_close = False |
| try: |
| for l in f: |
| if len(l) == 0 or l[0] == '#' or l[0] == ';': |
| continue |
| tokens = l.split() |
| if len(tokens) == 0: |
| continue |
| if tokens[0] == 'nameserver': |
| self.nameservers.append(tokens[1]) |
| elif tokens[0] == 'domain': |
| self.domain = dns.name.from_text(tokens[1]) |
| elif tokens[0] == 'search': |
| for suffix in tokens[1:]: |
| self.search.append(dns.name.from_text(suffix)) |
| finally: |
| if want_close: |
| f.close() |
| if len(self.nameservers) == 0: |
| self.nameservers.append('127.0.0.1') |
| |
| def _determine_split_char(self, entry): |
| # |
| # The windows registry irritatingly changes the list element |
| # delimiter in between ' ' and ',' (and vice-versa) in various |
| # versions of windows. |
| # |
| if entry.find(' ') >= 0: |
| split_char = ' ' |
| elif entry.find(',') >= 0: |
| split_char = ',' |
| else: |
| # probably a singleton; treat as a space-separated list. |
| split_char = ' ' |
| return split_char |
| |
| def _config_win32_nameservers(self, nameservers): |
| """Configure a NameServer registry entry.""" |
| # we call str() on nameservers to convert it from unicode to ascii |
| nameservers = str(nameservers) |
| split_char = self._determine_split_char(nameservers) |
| ns_list = nameservers.split(split_char) |
| for ns in ns_list: |
| if not ns in self.nameservers: |
| self.nameservers.append(ns) |
| |
| def _config_win32_domain(self, domain): |
| """Configure a Domain registry entry.""" |
| # we call str() on domain to convert it from unicode to ascii |
| self.domain = dns.name.from_text(str(domain)) |
| |
| def _config_win32_search(self, search): |
| """Configure a Search registry entry.""" |
| # we call str() on search to convert it from unicode to ascii |
| search = str(search) |
| split_char = self._determine_split_char(search) |
| search_list = search.split(split_char) |
| for s in search_list: |
| if not s in self.search: |
| self.search.append(dns.name.from_text(s)) |
| |
| def _config_win32_fromkey(self, key): |
| """Extract DNS info from a registry key.""" |
| try: |
| servers, rtype = _winreg.QueryValueEx(key, 'NameServer') |
| except WindowsError: |
| servers = None |
| if servers: |
| self._config_win32_nameservers(servers) |
| try: |
| dom, rtype = _winreg.QueryValueEx(key, 'Domain') |
| if dom: |
| self._config_win32_domain(dom) |
| except WindowsError: |
| pass |
| else: |
| try: |
| servers, rtype = _winreg.QueryValueEx(key, 'DhcpNameServer') |
| except WindowsError: |
| servers = None |
| if servers: |
| self._config_win32_nameservers(servers) |
| try: |
| dom, rtype = _winreg.QueryValueEx(key, 'DhcpDomain') |
| if dom: |
| self._config_win32_domain(dom) |
| except WindowsError: |
| pass |
| try: |
| search, rtype = _winreg.QueryValueEx(key, 'SearchList') |
| except WindowsError: |
| search = None |
| if search: |
| self._config_win32_search(search) |
| |
| def read_registry(self): |
| """Extract resolver configuration from the Windows registry.""" |
| lm = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) |
| want_scan = False |
| try: |
| try: |
| # XP, 2000 |
| tcp_params = _winreg.OpenKey(lm, |
| r'SYSTEM\CurrentControlSet' |
| r'\Services\Tcpip\Parameters') |
| want_scan = True |
| except EnvironmentError: |
| # ME |
| tcp_params = _winreg.OpenKey(lm, |
| r'SYSTEM\CurrentControlSet' |
| r'\Services\VxD\MSTCP') |
| try: |
| self._config_win32_fromkey(tcp_params) |
| finally: |
| tcp_params.Close() |
| if want_scan: |
| interfaces = _winreg.OpenKey(lm, |
| r'SYSTEM\CurrentControlSet' |
| r'\Services\Tcpip\Parameters' |
| r'\Interfaces') |
| try: |
| i = 0 |
| while True: |
| try: |
| guid = _winreg.EnumKey(interfaces, i) |
| i += 1 |
| key = _winreg.OpenKey(interfaces, guid) |
| if not self._win32_is_nic_enabled(lm, guid, key): |
| continue |
| try: |
| self._config_win32_fromkey(key) |
| finally: |
| key.Close() |
| except EnvironmentError: |
| break |
| finally: |
| interfaces.Close() |
| finally: |
| lm.Close() |
| |
| def _win32_is_nic_enabled(self, lm, guid, interface_key): |
| # Look in the Windows Registry to determine whether the network |
| # interface corresponding to the given guid is enabled. |
| # |
| # (Code contributed by Paul Marks, thanks!) |
| # |
| try: |
| # This hard-coded location seems to be consistent, at least |
| # from Windows 2000 through Vista. |
| connection_key = _winreg.OpenKey( |
| lm, |
| r'SYSTEM\CurrentControlSet\Control\Network' |
| r'\{4D36E972-E325-11CE-BFC1-08002BE10318}' |
| r'\%s\Connection' % guid) |
| |
| try: |
| # The PnpInstanceID points to a key inside Enum |
| (pnp_id, ttype) = _winreg.QueryValueEx( |
| connection_key, 'PnpInstanceID') |
| |
| if ttype != _winreg.REG_SZ: |
| raise ValueError |
| |
| device_key = _winreg.OpenKey( |
| lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id) |
| |
| try: |
| # Get ConfigFlags for this device |
| (flags, ttype) = _winreg.QueryValueEx( |
| device_key, 'ConfigFlags') |
| |
| if ttype != _winreg.REG_DWORD: |
| raise ValueError |
| |
| # Based on experimentation, bit 0x1 indicates that the |
| # device is disabled. |
| return not (flags & 0x1) |
| |
| finally: |
| device_key.Close() |
| finally: |
| connection_key.Close() |
| except (EnvironmentError, ValueError): |
| # Pre-vista, enabled interfaces seem to have a non-empty |
| # NTEContextList; this was how dnspython detected enabled |
| # nics before the code above was contributed. We've retained |
| # the old method since we don't know if the code above works |
| # on Windows 95/98/ME. |
| try: |
| (nte, ttype) = _winreg.QueryValueEx(interface_key, |
| 'NTEContextList') |
| return nte is not None |
| except WindowsError: |
| return False |
| |
| def _compute_timeout(self, start): |
| now = time.time() |
| if now < start: |
| if start - now > 1: |
| # Time going backwards is bad. Just give up. |
| raise Timeout |
| else: |
| # Time went backwards, but only a little. This can |
| # happen, e.g. under vmware with older linux kernels. |
| # Pretend it didn't happen. |
| now = start |
| duration = now - start |
| if duration >= self.lifetime: |
| raise Timeout |
| return min(self.lifetime - duration, self.timeout) |
| |
| def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, |
| tcp=False, source=None): |
| """Query nameservers to find the answer to the question. |
| |
| The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects |
| of the appropriate type, or strings that can be converted into objects |
| of the appropriate type. E.g. For I{rdtype} the integer 2 and the |
| the string 'NS' both mean to query for records with DNS rdata type NS. |
| |
| @param qname: the query name |
| @type qname: dns.name.Name object or string |
| @param rdtype: the query type |
| @type rdtype: int or string |
| @param rdclass: the query class |
| @type rdclass: int or string |
| @param tcp: use TCP to make the query (default is False). |
| @type tcp: bool |
| @param source: bind to this IP address (defaults to machine default IP). |
| @type source: IP address in dotted quad notation |
| @rtype: dns.resolver.Answer instance |
| @raises Timeout: no answers could be found in the specified lifetime |
| @raises NXDOMAIN: the query name does not exist |
| @raises NoAnswer: the response did not contain an answer |
| @raises NoNameservers: no non-broken nameservers are available to |
| answer the question.""" |
| |
| if isinstance(qname, (str, unicode)): |
| qname = dns.name.from_text(qname, None) |
| if isinstance(rdtype, str): |
| rdtype = dns.rdatatype.from_text(rdtype) |
| if isinstance(rdclass, str): |
| rdclass = dns.rdataclass.from_text(rdclass) |
| qnames_to_try = [] |
| if qname.is_absolute(): |
| qnames_to_try.append(qname) |
| else: |
| if len(qname) > 1: |
| qnames_to_try.append(qname.concatenate(dns.name.root)) |
| if self.search: |
| for suffix in self.search: |
| qnames_to_try.append(qname.concatenate(suffix)) |
| else: |
| qnames_to_try.append(qname.concatenate(self.domain)) |
| all_nxdomain = True |
| start = time.time() |
| for qname in qnames_to_try: |
| if self.cache: |
| answer = self.cache.get((qname, rdtype, rdclass)) |
| if answer: |
| return answer |
| request = dns.message.make_query(qname, rdtype, rdclass) |
| if not self.keyname is None: |
| request.use_tsig(self.keyring, self.keyname, self.keyalgorithm) |
| request.use_edns(self.edns, self.ednsflags, self.payload) |
| response = None |
| # |
| # make a copy of the servers list so we can alter it later. |
| # |
| nameservers = self.nameservers[:] |
| backoff = 0.10 |
| while response is None: |
| if len(nameservers) == 0: |
| raise NoNameservers |
| for nameserver in nameservers[:]: |
| timeout = self._compute_timeout(start) |
| try: |
| if tcp: |
| response = dns.query.tcp(request, nameserver, |
| timeout, self.port, |
| source=source) |
| else: |
| response = dns.query.udp(request, nameserver, |
| timeout, self.port, |
| source=source) |
| except (socket.error, dns.exception.Timeout): |
| # |
| # Communication failure or timeout. Go to the |
| # next server |
| # |
| response = None |
| continue |
| except dns.query.UnexpectedSource: |
| # |
| # Who knows? Keep going. |
| # |
| response = None |
| continue |
| except dns.exception.FormError: |
| # |
| # We don't understand what this server is |
| # saying. Take it out of the mix and |
| # continue. |
| # |
| nameservers.remove(nameserver) |
| response = None |
| continue |
| rcode = response.rcode() |
| if rcode == dns.rcode.NOERROR or \ |
| rcode == dns.rcode.NXDOMAIN: |
| break |
| # |
| # We got a response, but we're not happy with the |
| # rcode in it. Remove the server from the mix if |
| # the rcode isn't SERVFAIL. |
| # |
| if rcode != dns.rcode.SERVFAIL: |
| nameservers.remove(nameserver) |
| response = None |
| if not response is None: |
| break |
| # |
| # All nameservers failed! |
| # |
| if len(nameservers) > 0: |
| # |
| # But we still have servers to try. Sleep a bit |
| # so we don't pound them! |
| # |
| timeout = self._compute_timeout(start) |
| sleep_time = min(timeout, backoff) |
| backoff *= 2 |
| time.sleep(sleep_time) |
| if response.rcode() == dns.rcode.NXDOMAIN: |
| continue |
| all_nxdomain = False |
| break |
| if all_nxdomain: |
| raise NXDOMAIN |
| answer = Answer(qname, rdtype, rdclass, response) |
| if self.cache: |
| self.cache.put((qname, rdtype, rdclass), answer) |
| return answer |
| |
| def use_tsig(self, keyring, keyname=None, |
| algorithm=dns.tsig.default_algorithm): |
| """Add a TSIG signature to the query. |
| |
| @param keyring: The TSIG keyring to use; defaults to None. |
| @type keyring: dict |
| @param keyname: The name of the TSIG key to use; defaults to None. |
| The key must be defined in the keyring. If a keyring is specified |
| but a keyname is not, then the key used will be the first key in the |
| keyring. Note that the order of keys in a dictionary is not defined, |
| so applications should supply a keyname when a keyring is used, unless |
| they know the keyring contains only one key. |
| @param algorithm: The TSIG key algorithm to use. The default |
| is dns.tsig.default_algorithm. |
| @type algorithm: string""" |
| self.keyring = keyring |
| if keyname is None: |
| self.keyname = self.keyring.keys()[0] |
| else: |
| self.keyname = keyname |
| self.keyalgorithm = algorithm |
| |
| def use_edns(self, edns, ednsflags, payload): |
| """Configure Edns. |
| |
| @param edns: The EDNS level to use. The default is -1, no Edns. |
| @type edns: int |
| @param ednsflags: The EDNS flags |
| @type ednsflags: int |
| @param payload: The EDNS payload size. The default is 0. |
| @type payload: int""" |
| |
| if edns is None: |
| edns = -1 |
| self.edns = edns |
| self.ednsflags = ednsflags |
| self.payload = payload |
| |
| default_resolver = None |
| |
| def get_default_resolver(): |
| """Get the default resolver, initializing it if necessary.""" |
| global default_resolver |
| if default_resolver is None: |
| default_resolver = Resolver() |
| return default_resolver |
| |
| def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, |
| tcp=False, source=None): |
| """Query nameservers to find the answer to the question. |
| |
| This is a convenience function that uses the default resolver |
| object to make the query. |
| @see: L{dns.resolver.Resolver.query} for more information on the |
| parameters.""" |
| return get_default_resolver().query(qname, rdtype, rdclass, tcp, source) |
| |
| def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None): |
| """Find the name of the zone which contains the specified name. |
| |
| @param name: the query name |
| @type name: absolute dns.name.Name object or string |
| @param rdclass: The query class |
| @type rdclass: int |
| @param tcp: use TCP to make the query (default is False). |
| @type tcp: bool |
| @param resolver: the resolver to use |
| @type resolver: dns.resolver.Resolver object or None |
| @rtype: dns.name.Name""" |
| |
| if isinstance(name, (str, unicode)): |
| name = dns.name.from_text(name, dns.name.root) |
| if resolver is None: |
| resolver = get_default_resolver() |
| if not name.is_absolute(): |
| raise NotAbsolute(name) |
| while 1: |
| try: |
| answer = resolver.query(name, dns.rdatatype.SOA, rdclass, tcp) |
| return name |
| except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): |
| try: |
| name = name.parent() |
| except dns.name.NoParent: |
| raise NoRootSOA |