dnspython: Merge in new upstream.
[Samba/gebeck_regimport.git] / lib / dnspython / dns / resolver.py
blob30977f3a8bbb98164be057ce1952d426620317ce
1 # Copyright (C) 2003-2007, 2009, 2010 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.
16 """DNS stub resolver.
18 @var default_resolver: The default resolver object
19 @type default_resolver: dns.resolver.Resolver object"""
21 import socket
22 import sys
23 import time
25 import dns.exception
26 import dns.message
27 import dns.name
28 import dns.query
29 import dns.rcode
30 import dns.rdataclass
31 import dns.rdatatype
33 if sys.platform == 'win32':
34 import _winreg
36 class NXDOMAIN(dns.exception.DNSException):
37 """The query name does not exist."""
38 pass
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."""
48 pass
50 class NoNameservers(dns.exception.DNSException):
51 """No non-broken nameservers are available to answer the query."""
52 pass
54 class NotAbsolute(dns.exception.DNSException):
55 """Raised if an absolute domain name is required but a relative name
56 was provided."""
57 pass
59 class NoRootSOA(dns.exception.DNSException):
60 """Raised if for some reason there is no SOA at the root name.
61 This should never happen!"""
62 pass
64 class NoMetaqueries(dns.exception.DNSException):
65 """Metaqueries are not allowed."""
66 pass
69 class Answer(object):
70 """DNS stub resolver answer
72 Instances of this class bundle up the result of a successful DNS
73 resolution.
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]".
81 Note that CNAMEs or DNAMEs in the response may mean that answer
82 node's name might not be the query name.
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
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)
146 def __len__(self):
147 return len(self.rrset)
149 def __iter__(self):
150 return iter(self.rrset)
152 def __getitem__(self, i):
153 return self.rrset[i]
155 def __delitem__(self, i):
156 del self.rrset[i]
158 def __getslice__(self, i, j):
159 return self.rrset[i:j]
161 def __delslice__(self, i, j):
162 del self.rrset[i:j]
164 class Cache(object):
165 """Simple DNS answer cache.
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
177 def __init__(self, cleaning_interval=300.0):
178 """Initialize a DNS cache.
180 @param cleaning_interval: the number of seconds between periodic
181 cleanings. The default is 300.0
182 @type cleaning_interval: float.
185 self.data = {}
186 self.cleaning_interval = cleaning_interval
187 self.next_cleaning = time.time() + self.cleaning_interval
189 def maybe_clean(self):
190 """Clean the cache if it's time to do so."""
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
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
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
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
227 self.maybe_clean()
228 self.data[key] = value
230 def flush(self, key=None):
231 """Flush the cache.
233 If I{key} is specified, only that item is flushed. Otherwise
234 the entire cache is flushed.
236 @param key: the key to flush
237 @type key: (dns.name.Name, int, int) tuple or None
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
247 class Resolver(object):
248 """DNS stub resolver
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
284 def __init__(self, filename='/etc/resolv.conf', configure=True):
285 """Initialize a resolver instance.
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"""
297 self.reset()
298 if configure:
299 if sys.platform == 'win32':
300 self.read_registry()
301 elif filename:
302 self.read_resolv_conf(filename)
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
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')
358 def _determine_split_char(self, entry):
360 # The windows registry irritatingly changes the list element
361 # delimiter in between ' ' and ',' (and vice-versa) in various
362 # versions of windows.
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
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)
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))
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))
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)
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()
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.
481 # (Code contributed by Paul Marks, thanks!)
483 try:
484 # This hard-coded location seems to be consistent, at least
485 # from Windows 2000 through Vista.
486 connection_key = _winreg.OpenKey(
488 r'SYSTEM\CurrentControlSet\Control\Network'
489 r'\{4D36E972-E325-11CE-BFC1-08002BE10318}'
490 r'\%s\Connection' % guid)
492 try:
493 # The PnpInstanceID points to a key inside Enum
494 (pnp_id, ttype) = _winreg.QueryValueEx(
495 connection_key, 'PnpInstanceID')
497 if ttype != _winreg.REG_SZ:
498 raise ValueError
500 device_key = _winreg.OpenKey(
501 lm, r'SYSTEM\CurrentControlSet\Enum\%s' % pnp_id)
503 try:
504 # Get ConfigFlags for this device
505 (flags, ttype) = _winreg.QueryValueEx(
506 device_key, 'ConfigFlags')
508 if ttype != _winreg.REG_DWORD:
509 raise ValueError
511 # Based on experimentation, bit 0x1 indicates that the
512 # device is disabled.
513 return not (flags & 0x1)
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
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)
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.
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.
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."""
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
609 # make a copy of the servers list so we can alter it later.
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):
629 # Communication failure or timeout. Go to the
630 # next server
632 response = None
633 continue
634 except dns.query.UnexpectedSource:
636 # Who knows? Keep going.
638 response = None
639 continue
640 except dns.exception.FormError:
642 # We don't understand what this server is
643 # saying. Take it out of the mix and
644 # continue.
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
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.
658 if rcode != dns.rcode.SERVFAIL:
659 nameservers.remove(nameserver)
660 response = None
661 if not response is None:
662 break
664 # All nameservers failed!
666 if len(nameservers) > 0:
668 # But we still have servers to try. Sleep a bit
669 # so we don't pound them!
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
686 def use_tsig(self, keyring, keyname=None,
687 algorithm=dns.tsig.default_algorithm):
688 """Add a TSIG signature to the query.
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
708 def use_edns(self, edns, ednsflags, payload):
709 """Configure Edns.
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"""
718 if edns is None:
719 edns = -1
720 self.edns = edns
721 self.ednsflags = ednsflags
722 self.payload = payload
724 default_resolver = None
726 def 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
733 def 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.
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)
743 def zone_for_name(name, rdclass=dns.rdataclass.IN, tcp=False, resolver=None):
744 """Find the name of the zone which contains the specified name.
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"""
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