1 # -*- test-case-name: twisted.names.test.test_rootresolve -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 Resolver implementation for querying successive authoritative servers to
7 lookup a record, starting from the root nameservers.
18 from twisted.python.failure import Failure
19 from twisted.internet import defer
20 from twisted.names import dns, common, error
23 def retry(t, p, *args):
25 Issue a query one or more times.
27 This function is deprecated. Use one of the resolver classes for retry
28 logic, or implement it yourself.
31 "twisted.names.root.retry is deprecated since Twisted 10.0. Use a "
32 "Resolver object for retry logic.", category=DeprecationWarning,
35 assert t, "Timeout is required"
38 failure.trap(defer.TimeoutError)
41 return p.query(timeout=t.pop(0), *args
44 return p.query(timeout=t.pop(0), *args
50 class _DummyController:
52 A do-nothing DNS controller. This is useful when all messages received
53 will be responses to previously issued queries. Anything else received
56 def messageReceived(self, *args):
61 class Resolver(common.ResolverBase):
63 L{Resolver} implements recursive lookup starting from a specified list of
66 @ivar hints: A C{list} of C{str} giving the dotted quad representation
67 of IP addresses of root servers at which to begin resolving names.
69 @ivar _maximumQueries: A C{int} giving the maximum number of queries
70 which will be attempted to resolve a single name.
72 @ivar _reactor: A L{IReactorTime} and L{IReactorUDP} provider to use to
73 bind UDP ports and manage timeouts.
75 def __init__(self, hints, maximumQueries=10, reactor=None):
76 common.ResolverBase.__init__(self)
78 self._maximumQueries = maximumQueries
79 self._reactor = reactor
84 Return a list of two-tuples representing the addresses of the root
85 servers, as defined by C{self.hints}.
87 return [(ip, dns.PORT) for ip in self.hints]
90 def _query(self, query, servers, timeout, filter):
92 Issue one query and return a L{Deferred} which fires with its response.
94 @param query: The query to issue.
95 @type query: L{dns.Query}
97 @param servers: The servers which might have an answer for this
99 @type servers: L{list} of L{tuple} of L{str} and L{int}
101 @param timeout: A timeout on how long to wait for the response.
102 @type timeout: L{tuple} of L{int}
104 @param filter: A flag indicating whether to filter the results. If
105 C{True}, the returned L{Deferred} will fire with a three-tuple of
106 lists of L{RRHeaders} (like the return value of the I{lookup*}
107 methods of L{IResolver}. IF C{False}, the result will be a
109 @type filter: L{bool}
111 @return: A L{Deferred} which fires with the response or a timeout
115 from twisted.names import client
116 r = client.Resolver(servers=servers, reactor=self._reactor)
117 d = r.queryUDP([query], timeout)
119 d.addCallback(r.filterAnswers)
123 def _lookup(self, name, cls, type, timeout):
125 Implement name lookup by recursively discovering the authoritative
126 server for the name and then asking it, starting at one of the servers
130 # A series of timeouts for semi-exponential backoff, summing to an
131 # arbitrary total of 60 seconds.
132 timeout = (1, 3, 11, 45)
133 return self._discoverAuthority(
134 dns.Query(name, type, cls), self._roots(), timeout,
135 self._maximumQueries)
138 def _discoverAuthority(self, query, servers, timeout, queriesLeft):
140 Issue a query to a server and follow a delegation if necessary.
142 @param query: The query to issue.
143 @type query: L{dns.Query}
145 @param servers: The servers which might have an answer for this
147 @type servers: L{list} of L{tuple} of L{str} and L{int}
149 @param timeout: A C{tuple} of C{int} giving the timeout to use for this
152 @param queriesLeft: A C{int} giving the number of queries which may
153 yet be attempted to answer this query before the attempt will be
156 @return: A L{Deferred} which fires with a three-tuple of lists of
157 L{RRHeaders} giving the response, or with a L{Failure} if there is
158 a timeout or response error.
160 # Stop now if we've hit the query limit.
163 error.ResolverError("Query limit reached without result"))
165 d = self._query(query, servers, timeout, False)
167 self._discoveredAuthority, query, timeout, queriesLeft - 1)
171 def _discoveredAuthority(self, response, query, timeout, queriesLeft):
173 Interpret the response to a query, checking for error codes and
174 following delegations if necessary.
176 @param response: The L{Message} received in response to issuing C{query}.
177 @type response: L{Message}
179 @param query: The L{dns.Query} which was issued.
180 @type query: L{dns.Query}.
182 @param timeout: The timeout to use if another query is indicated by
184 @type timeout: L{tuple} of L{int}
186 @param queriesLeft: A C{int} giving the number of queries which may
187 yet be attempted to answer this query before the attempt will be
190 @return: A L{Failure} indicating a response error, a three-tuple of
191 lists of L{RRHeaders} giving the response to C{query} or a
192 L{Deferred} which will fire with one of those.
194 if response.rCode != dns.OK:
195 return Failure(self.exceptionForCode(response.rCode)(response))
197 # Turn the answers into a structure that's a little easier to work with.
199 for answer in response.answers:
200 records.setdefault(answer.name, []).append(answer)
202 def findAnswerOrCName(name, type, cls):
204 for record in records.get(name, []):
205 if record.cls == cls:
206 if record.type == type:
208 elif record.type == dns.CNAME:
210 # If there were any CNAME records, return the last one. There's
211 # only supposed to be zero or one, though.
220 record = findAnswerOrCName(name, query.type, query.cls)
222 if name == query.name:
223 # If there's no answer for the original name, then this may
224 # be a delegation. Code below handles it.
227 # Try to resolve the CNAME with another query.
228 d = self._discoverAuthority(
229 dns.Query(str(name), query.type, query.cls),
230 self._roots(), timeout, queriesLeft)
231 # We also want to include the CNAME in the ultimate result,
232 # otherwise this will be pretty confusing.
233 def cbResolved((answers, authority, additional)):
234 answers.insert(0, previous)
235 return (answers, authority, additional)
236 d.addCallback(cbResolved)
238 elif record.type == query.type:
244 # It's a CNAME record. Try to resolve it from the records
245 # in this response with another iteration around the loop.
246 if record.payload.name in seen:
247 raise error.ResolverError("Cycle in CNAME processing")
248 name = record.payload.name
251 # Build a map to use to convert NS names into IP addresses.
253 for rr in response.additional:
255 addresses[str(rr.name)] = rr.payload.dottedQuad()
259 for rr in response.authority:
260 if rr.type == dns.NS:
261 ns = str(rr.payload.name)
263 hints.append((addresses[ns], dns.PORT))
267 return self._discoverAuthority(
268 query, hints, timeout, queriesLeft)
270 d = self.lookupAddress(traps[0], timeout)
272 lambda (answers, authority, additional):
273 answers[0].payload.dottedQuad())
275 lambda hint: self._discoverAuthority(
276 query, [(hint, dns.PORT)], timeout, queriesLeft - 1))
279 return Failure(error.ResolverError(
280 "Stuck at response without answers or delegation"))
283 def discoveredAuthority(self, auth, name, cls, type, timeout):
285 'twisted.names.root.Resolver.discoveredAuthority is deprecated since '
286 'Twisted 10.0. Use twisted.names.client.Resolver directly, instead.',
287 category=DeprecationWarning, stacklevel=2)
288 from twisted.names import client
289 q = dns.Query(name, type, cls)
290 r = client.Resolver(servers=[(auth, dns.PORT)])
291 d = r.queryUDP([q], timeout)
292 d.addCallback(r.filterAnswers)
297 def lookupNameservers(host, atServer, p=None):
299 'twisted.names.root.lookupNameservers is deprecated since Twisted '
300 '10.0. Use twisted.names.root.Resolver.lookupNameservers instead.',
301 category=DeprecationWarning, stacklevel=2)
302 # print 'Nameserver lookup for', host, 'at', atServer, 'with', p
304 p = dns.DNSDatagramProtocol(_DummyController())
307 (1, 3, 11, 45), # Timeouts
308 p, # Protocol instance
309 (atServer, dns.PORT), # Server to query
310 [dns.Query(host, dns.NS, dns.IN)] # Question to ask
313 def lookupAddress(host, atServer, p=None):
315 'twisted.names.root.lookupAddress is deprecated since Twisted '
316 '10.0. Use twisted.names.root.Resolver.lookupAddress instead.',
317 category=DeprecationWarning, stacklevel=2)
318 # print 'Address lookup for', host, 'at', atServer, 'with', p
320 p = dns.DNSDatagramProtocol(_DummyController())
323 (1, 3, 11, 45), # Timeouts
324 p, # Protocol instance
325 (atServer, dns.PORT), # Server to query
326 [dns.Query(host, dns.A, dns.IN)] # Question to ask
329 def extractAuthority(msg, cache):
331 'twisted.names.root.extractAuthority is deprecated since Twisted '
332 '10.0. Please inspect the Message object directly.',
333 category=DeprecationWarning, stacklevel=2)
334 records = msg.answers + msg.authority + msg.additional
335 nameservers = [r for r in records if r.type == dns.NS]
337 # print 'Records for', soFar, ':', records
338 # print 'NS for', soFar, ':', nameservers
341 return None, nameservers
343 raise IOError("No records")
346 cache[str(r.name)] = r.payload.dottedQuad()
349 if str(r.payload.name) in cache:
350 return cache[str(r.payload.name)], nameservers
352 if addr.type == dns.A and addr.name == r.name:
353 return addr.payload.dottedQuad(), nameservers
354 return None, nameservers
356 def discoverAuthority(host, roots, cache=None, p=None):
358 'twisted.names.root.discoverAuthority is deprecated since Twisted '
359 '10.0. Use twisted.names.root.Resolver.lookupNameservers instead.',
360 category=DeprecationWarning, stacklevel=4)
365 rootAuths = list(roots)
367 parts = host.rstrip('.').split('.')
370 authority = rootAuths.pop()
374 soFar = part + '.' + soFar
375 # print '///////', soFar, authority, p
376 msg = defer.waitForDeferred(lookupNameservers(soFar, authority, p))
378 msg = msg.getResult()
380 newAuth, nameservers = extractAuthority(msg, cache)
382 if newAuth is not None:
383 # print "newAuth is not None"
387 r = str(nameservers[0].payload.name)
388 # print 'Recursively discovering authority for', r
389 authority = defer.waitForDeferred(discoverAuthority(r, roots, cache, p))
391 authority = authority.getResult()
392 # print 'Discovered to be', authority, 'for', r
394 ## # print 'Doing address lookup for', soFar, 'at', authority
395 ## msg = defer.waitForDeferred(lookupAddress(soFar, authority, p))
397 ## msg = msg.getResult()
398 ## records = msg.answers + msg.authority + msg.additional
399 ## addresses = [r for r in records if r.type == dns.A]
401 ## authority = addresses[0].payload.dottedQuad()
403 ## raise IOError("Resolution error")
404 # print "Yielding authority", authority
407 discoverAuthority = defer.deferredGenerator(discoverAuthority)
409 def makePlaceholder(deferred, name):
410 def placeholder(*args, **kw):
411 deferred.addCallback(lambda r: getattr(r, name)(*args, **kw))
415 class DeferredResolver:
416 def __init__(self, resolverDeferred):
418 resolverDeferred.addCallback(self.gotRealResolver)
420 def gotRealResolver(self, resolver):
422 self.__dict__ = resolver.__dict__
423 self.__class__ = resolver.__class__
427 def __getattr__(self, name):
428 if name.startswith('lookup') or name in ('getHostByName', 'query'):
429 self.waiting.append(defer.Deferred())
430 return makePlaceholder(self.waiting[-1], name)
431 raise AttributeError(name)
433 def bootstrap(resolver):
434 """Lookup the root nameserver addresses using the given resolver
436 Return a Resolver which will eventually become a C{root.Resolver}
437 instance that has references to all the root servers that we were able
440 domains = [chr(ord('a') + i) for i in range(13)]
441 # f = lambda r: (log.msg('Root server address: ' + str(r)), r)[1]
443 L = [resolver.getHostByName('%s.root-servers.net' % d).addCallback(f) for d in domains]
444 d = defer.DeferredList(L)
445 d.addCallback(lambda r: Resolver([e[1] for e in r if e[0]]))
446 return DeferredResolver(d)