1 # -*- test-case-name: twisted.names.test.test_names -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 Asynchronous client DNS
8 The functions exposed in this module can be used for asynchronous name
9 resolution and dns queries.
11 If you need to create a resolver with specific requirements, such as needing to
12 do queries against a particular host, the L{createResolver} function will
13 return an C{IResolver}.
15 Future plans: Proper nameserver acquisition on Windows/MacOS,
16 better caching, respect timeouts
25 from zope.interface import implements
28 from twisted.python.runtime import platform
29 from twisted.internet import error, defer, protocol, interfaces
30 from twisted.python import log, failure
31 from twisted.python.deprecate import getWarningMethod
32 from twisted.names import dns, common
35 class Resolver(common.ResolverBase):
37 @ivar _waiting: A C{dict} mapping tuple keys of query name/type/class to
38 Deferreds which will be called back with the result of those queries.
39 This is used to avoid issuing the same query more than once in
40 parallel. This is more efficient on the network and helps avoid a
41 "birthday paradox" attack by keeping the number of outstanding requests
42 for a particular query fixed at one instead of allowing the attacker to
43 raise it to an arbitrary number.
45 @ivar _reactor: A provider of L{IReactorTCP}, L{IReactorUDP}, and
46 L{IReactorTime} which will be used to set up network resources and
49 implements(interfaces.IResolver)
61 _lastResolvTime = None
62 _resolvReadInterval = 60
64 def _getProtocol(self):
66 "Resolver.protocol is deprecated; use Resolver.queryUDP instead.",
67 PendingDeprecationWarning,
69 self.protocol = dns.DNSDatagramProtocol(self)
71 protocol = property(_getProtocol)
74 def __init__(self, resolv=None, servers=None, timeout=(1, 3, 11, 45), reactor=None):
76 Construct a resolver which will query domain name servers listed in
77 the C{resolv.conf(5)}-format file given by C{resolv} as well as
78 those in the given C{servers} list. Servers are queried in a
79 round-robin fashion. If given, C{resolv} is periodically checked
80 for modification and re-parsed if it is noticed to have changed.
82 @type servers: C{list} of C{(str, int)} or C{None}
83 @param servers: If not None, interpreted as a list of (host, port)
84 pairs specifying addresses of domain name servers to attempt to use
85 for this lookup. Host addresses should be in IPv4 dotted-quad
86 form. If specified, overrides C{resolv}.
89 @param resolv: Filename to read and parse as a resolver(5)
92 @type timeout: Sequence of C{int}
93 @param timeout: Default number of seconds after which to reissue the
94 query. When the last timeout expires, the query is considered
97 @param reactor: A provider of L{IReactorTime}, L{IReactorUDP}, and
98 L{IReactorTCP} which will be used to establish connections, listen
99 for DNS datagrams, and enforce timeouts. If not provided, the
100 global reactor will be used.
102 @raise ValueError: Raised if no nameserver addresses can be found.
104 common.ResolverBase.__init__(self)
107 from twisted.internet import reactor
108 self._reactor = reactor
110 self.timeout = timeout
115 self.servers = servers
119 if not len(self.servers) and not resolv:
120 raise ValueError, "No nameservers specified"
122 self.factory = DNSClientFactory(self, timeout)
123 self.factory.noisy = 0 # Be quiet by default
125 self.connections = []
130 self.maybeParseConfig()
133 def __getstate__(self):
134 d = self.__dict__.copy()
135 d['connections'] = []
136 d['_parseCall'] = None
140 def __setstate__(self, state):
141 self.__dict__.update(state)
142 self.maybeParseConfig()
145 def maybeParseConfig(self):
146 if self.resolv is None:
147 # Don't try to parse it, don't set up a call loop
151 resolvConf = file(self.resolv)
153 if e.errno == errno.ENOENT:
154 # Missing resolv.conf is treated the same as an empty resolv.conf
159 mtime = os.fstat(resolvConf.fileno()).st_mtime
160 if mtime != self._lastResolvTime:
161 log.msg('%s changed, reparsing' % (self.resolv,))
162 self._lastResolvTime = mtime
163 self.parseConfig(resolvConf)
165 # Check again in a little while
166 self._parseCall = self._reactor.callLater(
167 self._resolvReadInterval, self.maybeParseConfig)
170 def parseConfig(self, resolvConf):
174 if L.startswith('nameserver'):
175 resolver = (L.split()[1], dns.PORT)
176 servers.append(resolver)
177 log.msg("Resolver added %r to server list" % (resolver,))
178 elif L.startswith('domain'):
180 self.domain = L.split()[1]
184 elif L.startswith('search'):
186 self.search = L.split()[1:]
191 servers.append(('127.0.0.1', dns.PORT))
192 self.dynServers = servers
195 def pickServer(self):
197 Return the address of a nameserver.
199 TODO: Weight servers for response time so faster ones can be
202 if not self.servers and not self.dynServers:
204 serverL = len(self.servers)
205 dynL = len(self.dynServers)
208 self.index %= (serverL + dynL)
209 if self.index < serverL:
210 return self.servers[self.index]
212 return self.dynServers[self.index - serverL]
215 def _connectedProtocol(self):
217 Return a new L{DNSDatagramProtocol} bound to a randomly selected port
220 if 'protocol' in self.__dict__:
221 # Some code previously asked for or set the deprecated `protocol`
222 # attribute, so it probably expects that object to be used for
223 # queries. Give it back and skip the super awesome source port
224 # randomization logic. This is actually a really good reason to
225 # remove this deprecated backward compatibility as soon as
228 proto = dns.DNSDatagramProtocol(self)
231 self._reactor.listenUDP(dns.randomSource(), proto)
232 except error.CannotListenError:
238 def connectionMade(self, protocol):
240 Called by associated L{dns.DNSProtocol} instances when they connect.
242 self.connections.append(protocol)
243 for (d, q, t) in self.pending:
244 self.queryTCP(q, t).chainDeferred(d)
248 def connectionLost(self, protocol):
250 Called by associated L{dns.DNSProtocol} instances when they disconnect.
252 if protocol in self.connections:
253 self.connections.remove(protocol)
256 def messageReceived(self, message, protocol, address = None):
257 log.msg("Unexpected message (%d) received from %r" % (message.id, address))
260 def _query(self, *args):
262 Get a new L{DNSDatagramProtocol} instance from L{_connectedProtocol},
263 issue a query to it using C{*args}, and arrange for it to be
264 disconnected from its transport after the query completes.
266 @param *args: Positional arguments to be passed to
267 L{DNSDatagramProtocol.query}.
269 @return: A L{Deferred} which will be called back with the result of the
272 protocol = self._connectedProtocol()
273 d = protocol.query(*args)
274 def cbQueried(result):
275 protocol.transport.stopListening()
281 def queryUDP(self, queries, timeout = None):
283 Make a number of DNS queries via UDP.
285 @type queries: A C{list} of C{dns.Query} instances
286 @param queries: The queries to make.
288 @type timeout: Sequence of C{int}
289 @param timeout: Number of seconds after which to reissue the query.
290 When the last timeout expires, the query is considered failed.
293 @raise C{twisted.internet.defer.TimeoutError}: When the query times
297 timeout = self.timeout
299 addresses = self.servers + list(self.dynServers)
301 return defer.fail(IOError("No domain name servers available"))
303 # Make sure we go through servers in the list in the order they were
307 used = addresses.pop()
308 d = self._query(used, queries, timeout[0])
309 d.addErrback(self._reissue, addresses, [used], queries, timeout)
313 def _reissue(self, reason, addressesLeft, addressesUsed, query, timeout):
314 reason.trap(dns.DNSQueryTimeoutError)
316 # If there are no servers left to be tried, adjust the timeout
317 # to the next longest timeout period and move all the
318 # "used" addresses back to the list of addresses to try.
319 if not addressesLeft:
320 addressesLeft = addressesUsed
321 addressesLeft.reverse()
323 timeout = timeout[1:]
325 # If all timeout values have been used this query has failed. Tell the
326 # protocol we're giving up on it and return a terminal timeout failure
329 return failure.Failure(defer.TimeoutError(query))
331 # Get an address to try. Take it out of the list of addresses
332 # to try and put it ino the list of already tried addresses.
333 address = addressesLeft.pop()
334 addressesUsed.append(address)
336 # Issue a query to a server. Use the current timeout. Add this
337 # function as a timeout errback in case another retry is required.
338 d = self._query(address, query, timeout[0], reason.value.id)
339 d.addErrback(self._reissue, addressesLeft, addressesUsed, query, timeout)
343 def queryTCP(self, queries, timeout = 10):
345 Make a number of DNS queries via TCP.
347 @type queries: Any non-zero number of C{dns.Query} instances
348 @param queries: The queries to make.
350 @type timeout: C{int}
351 @param timeout: The number of seconds after which to fail.
355 if not len(self.connections):
356 address = self.pickServer()
358 return defer.fail(IOError("No domain name servers available"))
360 self._reactor.connectTCP(host, port, self.factory)
361 self.pending.append((defer.Deferred(), queries, timeout))
362 return self.pending[-1][0]
364 return self.connections[0].query(queries, timeout)
367 def filterAnswers(self, message):
369 Extract results from the given message.
371 If the message was truncated, re-attempt the query over TCP and return
372 a Deferred which will fire with the results of that query.
374 If the message's result code is not L{dns.OK}, return a Failure
375 indicating the type of error which occurred.
377 Otherwise, return a three-tuple of lists containing the results from
378 the answers section, the authority section, and the additional section.
381 return self.queryTCP(message.queries).addCallback(self.filterAnswers)
382 if message.rCode != dns.OK:
383 return failure.Failure(self.exceptionForCode(message.rCode)(message))
384 return (message.answers, message.authority, message.additional)
387 def _lookup(self, name, cls, type, timeout):
389 Build a L{dns.Query} for the given parameters and dispatch it via UDP.
391 If this query is already outstanding, it will not be re-issued.
392 Instead, when the outstanding query receives a response, that response
393 will be re-used for this query as well.
399 @return: A L{Deferred} which fires with a three-tuple giving the
400 answer, authority, and additional sections of the response or with
401 a L{Failure} if the response code is anything other than C{dns.OK}.
403 key = (name, type, cls)
404 waiting = self._waiting.get(key)
406 self._waiting[key] = []
407 d = self.queryUDP([dns.Query(name, type, cls)], timeout)
408 def cbResult(result):
409 for d in self._waiting.pop(key):
412 d.addCallback(self.filterAnswers)
420 # This one doesn't ever belong on UDP
421 def lookupZone(self, name, timeout = 10):
423 Perform an AXFR request. This is quite different from usual
424 DNS requests. See http://cr.yp.to/djbdns/axfr-notes.html for
427 address = self.pickServer()
429 return defer.fail(IOError('No domain name servers available'))
432 controller = AXFRController(name, d)
433 factory = DNSClientFactory(controller, timeout)
434 factory.noisy = False #stfu
436 connector = self._reactor.connectTCP(host, port, factory)
437 controller.timeoutCall = self._reactor.callLater(
438 timeout or 10, self._timeoutZone, d, controller,
439 connector, timeout or 10)
440 return d.addCallback(self._cbLookupZone, connector)
442 def _timeoutZone(self, d, controller, connector, seconds):
443 connector.disconnect()
444 controller.timeoutCall = None
445 controller.deferred = None
446 d.errback(error.TimeoutError("Zone lookup timed out after %d seconds" % (seconds,)))
448 def _cbLookupZone(self, result, connector):
449 connector.disconnect()
450 return (result, [], [])
453 class AXFRController:
456 def __init__(self, name, deferred):
458 self.deferred = deferred
462 def connectionMade(self, protocol):
463 # dig saids recursion-desired to 0, so I will too
464 message = dns.Message(protocol.pickID(), recDes=0)
465 message.queries = [dns.Query(self.name, dns.AXFR, dns.IN)]
466 protocol.writeMessage(message)
469 def connectionLost(self, protocol):
470 # XXX Do something here - see #3428
474 def messageReceived(self, message, protocol):
475 # Caveat: We have to handle two cases: All records are in 1
476 # message, or all records are in N messages.
478 # According to http://cr.yp.to/djbdns/axfr-notes.html,
479 # 'authority' and 'additional' are always empty, and only
480 # 'answers' is present.
481 self.records.extend(message.answers)
485 if self.records[0].type == dns.SOA:
487 self.soa = self.records[0]
488 if len(self.records) > 1 and self.records[-1].type == dns.SOA:
489 #print "It's the second SOA! We're done."
490 if self.timeoutCall is not None:
491 self.timeoutCall.cancel()
492 self.timeoutCall = None
493 if self.deferred is not None:
494 self.deferred.callback(self.records)
499 from twisted.internet.base import ThreadedResolver as _ThreadedResolverImpl
501 class ThreadedResolver(_ThreadedResolverImpl):
502 def __init__(self, reactor=None):
504 from twisted.internet import reactor
505 _ThreadedResolverImpl.__init__(self, reactor)
507 "twisted.names.client.ThreadedResolver is deprecated since "
508 "Twisted 9.0, use twisted.internet.base.ThreadedResolver "
510 category=DeprecationWarning, stacklevel=2)
512 class DNSClientFactory(protocol.ClientFactory):
513 def __init__(self, controller, timeout = 10):
514 self.controller = controller
515 self.timeout = timeout
518 def clientConnectionLost(self, connector, reason):
522 def buildProtocol(self, addr):
523 p = dns.DNSProtocol(self.controller)
529 def createResolver(servers=None, resolvconf=None, hosts=None):
531 Create and return a Resolver.
533 @type servers: C{list} of C{(str, int)} or C{None}
535 @param servers: If not C{None}, interpreted as a list of domain name servers
536 to attempt to use. Each server is a tuple of address in C{str} dotted-quad
537 form and C{int} port number.
539 @type resolvconf: C{str} or C{None}
540 @param resolvconf: If not C{None}, on posix systems will be interpreted as
541 an alternate resolv.conf to use. Will do nothing on windows systems. If
542 C{None}, /etc/resolv.conf will be used.
544 @type hosts: C{str} or C{None}
545 @param hosts: If not C{None}, an alternate hosts file to use. If C{None}
546 on posix systems, /etc/hosts will be used. On windows, C:\windows\hosts
551 from twisted.names import resolve, cache, root, hosts as hostsModule
552 if platform.getType() == 'posix':
553 if resolvconf is None:
554 resolvconf = '/etc/resolv.conf'
557 theResolver = Resolver(resolvconf, servers)
558 hostResolver = hostsModule.Resolver(hosts)
561 hosts = r'c:\windows\hosts'
562 from twisted.internet import reactor
563 bootstrap = _ThreadedResolverImpl(reactor)
564 hostResolver = hostsModule.Resolver(hosts)
565 theResolver = root.bootstrap(bootstrap)
567 L = [hostResolver, cache.CacheResolver(), theResolver]
568 return resolve.ResolverChain(L)
573 Get a Resolver instance.
575 Create twisted.names.client.theResolver if it is C{None}, and then return
581 if theResolver is None:
583 theResolver = createResolver()
585 theResolver = createResolver(servers=[('127.0.0.1', 53)])
588 def getHostByName(name, timeout=None, effort=10):
590 Resolve a name to a valid ipv4 or ipv6 address.
592 Will errback with C{DNSQueryTimeoutError} on a timeout, C{DomainError} or
593 C{AuthoritativeDomainError} (or subclasses) on other errors.
596 @param name: DNS name to resolve.
598 @type timeout: Sequence of C{int}
599 @param timeout: Number of seconds after which to reissue the query.
600 When the last timeout expires, the query is considered failed.
603 @param effort: How many times CNAME and NS records to follow while
608 return getResolver().getHostByName(name, timeout, effort)
610 def lookupAddress(name, timeout=None):
612 Perform an A record lookup.
615 @param name: DNS name to resolve.
617 @type timeout: Sequence of C{int}
618 @param timeout: Number of seconds after which to reissue the query.
619 When the last timeout expires, the query is considered failed.
623 return getResolver().lookupAddress(name, timeout)
625 def lookupIPV6Address(name, timeout=None):
627 Perform an AAAA record lookup.
630 @param name: DNS name to resolve.
632 @type timeout: Sequence of C{int}
633 @param timeout: Number of seconds after which to reissue the query.
634 When the last timeout expires, the query is considered failed.
638 return getResolver().lookupIPV6Address(name, timeout)
640 def lookupAddress6(name, timeout=None):
642 Perform an A6 record lookup.
645 @param name: DNS name to resolve.
647 @type timeout: Sequence of C{int}
648 @param timeout: Number of seconds after which to reissue the query.
649 When the last timeout expires, the query is considered failed.
653 return getResolver().lookupAddress6(name, timeout)
655 def lookupMailExchange(name, timeout=None):
657 Perform an MX record lookup.
660 @param name: DNS name to resolve.
662 @type timeout: Sequence of C{int}
663 @param timeout: Number of seconds after which to reissue the query.
664 When the last timeout expires, the query is considered failed.
668 return getResolver().lookupMailExchange(name, timeout)
670 def lookupNameservers(name, timeout=None):
672 Perform an NS record lookup.
675 @param name: DNS name to resolve.
677 @type timeout: Sequence of C{int}
678 @param timeout: Number of seconds after which to reissue the query.
679 When the last timeout expires, the query is considered failed.
683 return getResolver().lookupNameservers(name, timeout)
685 def lookupCanonicalName(name, timeout=None):
687 Perform a CNAME record lookup.
690 @param name: DNS name to resolve.
692 @type timeout: Sequence of C{int}
693 @param timeout: Number of seconds after which to reissue the query.
694 When the last timeout expires, the query is considered failed.
698 return getResolver().lookupCanonicalName(name, timeout)
700 def lookupMailBox(name, timeout=None):
702 Perform an MB record lookup.
705 @param name: DNS name to resolve.
707 @type timeout: Sequence of C{int}
708 @param timeout: Number of seconds after which to reissue the query.
709 When the last timeout expires, the query is considered failed.
713 return getResolver().lookupMailBox(name, timeout)
715 def lookupMailGroup(name, timeout=None):
717 Perform an MG record lookup.
720 @param name: DNS name to resolve.
722 @type timeout: Sequence of C{int}
723 @param timeout: Number of seconds after which to reissue the query.
724 When the last timeout expires, the query is considered failed.
728 return getResolver().lookupMailGroup(name, timeout)
730 def lookupMailRename(name, timeout=None):
732 Perform an MR record lookup.
735 @param name: DNS name to resolve.
737 @type timeout: Sequence of C{int}
738 @param timeout: Number of seconds after which to reissue the query.
739 When the last timeout expires, the query is considered failed.
743 return getResolver().lookupMailRename(name, timeout)
745 def lookupPointer(name, timeout=None):
747 Perform a PTR record lookup.
750 @param name: DNS name to resolve.
752 @type timeout: Sequence of C{int}
753 @param timeout: Number of seconds after which to reissue the query.
754 When the last timeout expires, the query is considered failed.
758 return getResolver().lookupPointer(name, timeout)
760 def lookupAuthority(name, timeout=None):
762 Perform an SOA record lookup.
765 @param name: DNS name to resolve.
767 @type timeout: Sequence of C{int}
768 @param timeout: Number of seconds after which to reissue the query.
769 When the last timeout expires, the query is considered failed.
773 return getResolver().lookupAuthority(name, timeout)
775 def lookupNull(name, timeout=None):
777 Perform a NULL record lookup.
780 @param name: DNS name to resolve.
782 @type timeout: Sequence of C{int}
783 @param timeout: Number of seconds after which to reissue the query.
784 When the last timeout expires, the query is considered failed.
788 return getResolver().lookupNull(name, timeout)
790 def lookupWellKnownServices(name, timeout=None):
792 Perform a WKS record lookup.
795 @param name: DNS name to resolve.
797 @type timeout: Sequence of C{int}
798 @param timeout: Number of seconds after which to reissue the query.
799 When the last timeout expires, the query is considered failed.
803 return getResolver().lookupWellKnownServices(name, timeout)
805 def lookupService(name, timeout=None):
807 Perform an SRV record lookup.
810 @param name: DNS name to resolve.
812 @type timeout: Sequence of C{int}
813 @param timeout: Number of seconds after which to reissue the query.
814 When the last timeout expires, the query is considered failed.
818 return getResolver().lookupService(name, timeout)
820 def lookupHostInfo(name, timeout=None):
822 Perform a HINFO record lookup.
825 @param name: DNS name to resolve.
827 @type timeout: Sequence of C{int}
828 @param timeout: Number of seconds after which to reissue the query.
829 When the last timeout expires, the query is considered failed.
833 return getResolver().lookupHostInfo(name, timeout)
835 def lookupMailboxInfo(name, timeout=None):
837 Perform an MINFO record lookup.
840 @param name: DNS name to resolve.
842 @type timeout: Sequence of C{int}
843 @param timeout: Number of seconds after which to reissue the query.
844 When the last timeout expires, the query is considered failed.
848 return getResolver().lookupMailboxInfo(name, timeout)
850 def lookupText(name, timeout=None):
852 Perform a TXT record lookup.
855 @param name: DNS name to resolve.
857 @type timeout: Sequence of C{int}
858 @param timeout: Number of seconds after which to reissue the query.
859 When the last timeout expires, the query is considered failed.
863 return getResolver().lookupText(name, timeout)
865 def lookupSenderPolicy(name, timeout=None):
867 Perform a SPF record lookup.
870 @param name: DNS name to resolve.
872 @type timeout: Sequence of C{int}
873 @param timeout: Number of seconds after which to reissue the query.
874 When the last timeout expires, the query is considered failed.
878 return getResolver().lookupSenderPolicy(name, timeout)
880 def lookupResponsibility(name, timeout=None):
882 Perform an RP record lookup.
885 @param name: DNS name to resolve.
887 @type timeout: Sequence of C{int}
888 @param timeout: Number of seconds after which to reissue the query.
889 When the last timeout expires, the query is considered failed.
893 return getResolver().lookupResponsibility(name, timeout)
895 def lookupAFSDatabase(name, timeout=None):
897 Perform an AFSDB record lookup.
900 @param name: DNS name to resolve.
902 @type timeout: Sequence of C{int}
903 @param timeout: Number of seconds after which to reissue the query.
904 When the last timeout expires, the query is considered failed.
908 return getResolver().lookupAFSDatabase(name, timeout)
910 def lookupZone(name, timeout=None):
912 Perform an AXFR record lookup.
915 @param name: DNS name to resolve.
917 @type timeout: C{int}
918 @param timeout: When this timeout expires, the query is considered failed.
922 # XXX: timeout here is not a list of ints, it is a single int.
923 return getResolver().lookupZone(name, timeout)
925 def lookupAllRecords(name, timeout=None):
930 @param name: DNS name to resolve.
932 @type timeout: Sequence of C{int}
933 @param timeout: Number of seconds after which to reissue the query.
934 When the last timeout expires, the query is considered failed.
938 return getResolver().lookupAllRecords(name, timeout)
942 def lookupNamingAuthorityPointer(name, timeout=None):
947 @param name: DNS name to resolve.
949 @type timeout: Sequence of C{int}
950 @param timeout: Number of seconds after which to reissue the query.
951 When the last timeout expires, the query is considered failed.
955 return getResolver().lookupNamingAuthorityPointer(name, timeout)