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