1
# -*- test-case-name: twisted.names.test.test_rootresolve -*-
2
# Copyright (c) 2001-2009 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)