~jk0/nova/xs-ipv6

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/names/root.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.names.test.test_rootresolve -*-
 
2
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
"""
 
6
Resolver implementation for querying successive authoritative servers to
 
7
lookup a record, starting from the root nameservers.
 
8
 
 
9
@author: Jp Calderone
 
10
 
 
11
todo::
 
12
    robustify it
 
13
    documentation
 
14
"""
 
15
 
 
16
import warnings
 
17
 
 
18
from twisted.python.failure import Failure
 
19
from twisted.internet import defer
 
20
from twisted.names import dns, common, error
 
21
 
 
22
 
 
23
def retry(t, p, *args):
 
24
    """
 
25
    Issue a query one or more times.
 
26
 
 
27
    This function is deprecated.  Use one of the resolver classes for retry
 
28
    logic, or implement it yourself.
 
29
    """
 
30
    warnings.warn(
 
31
        "twisted.names.root.retry is deprecated since Twisted 10.0.  Use a "
 
32
        "Resolver object for retry logic.", category=DeprecationWarning,
 
33
        stacklevel=2)
 
34
 
 
35
    assert t, "Timeout is required"
 
36
    t = list(t)
 
37
    def errback(failure):
 
38
        failure.trap(defer.TimeoutError)
 
39
        if not t:
 
40
            return failure
 
41
        return p.query(timeout=t.pop(0), *args
 
42
            ).addErrback(errback
 
43
            )
 
44
    return p.query(timeout=t.pop(0), *args
 
45
        ).addErrback(errback
 
46
        )
 
47
 
 
48
 
 
49
 
 
50
class _DummyController:
 
51
    """
 
52
    A do-nothing DNS controller.  This is useful when all messages received
 
53
    will be responses to previously issued queries.  Anything else received
 
54
    will be ignored.
 
55
    """
 
56
    def messageReceived(self, *args):
 
57
        pass
 
58
 
 
59
 
 
60
 
 
61
class Resolver(common.ResolverBase):
 
62
    """
 
63
    L{Resolver} implements recursive lookup starting from a specified list of
 
64
    root servers.
 
65
 
 
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.
 
68
 
 
69
    @ivar _maximumQueries: A C{int} giving the maximum number of queries
 
70
        which will be attempted to resolve a single name.
 
71
 
 
72
    @ivar _reactor: A L{IReactorTime} and L{IReactorUDP} provider to use to
 
73
        bind UDP ports and manage timeouts.
 
74
    """
 
75
    def __init__(self, hints, maximumQueries=10, reactor=None):
 
76
        common.ResolverBase.__init__(self)
 
77
        self.hints = hints
 
78
        self._maximumQueries = maximumQueries
 
79
        self._reactor = reactor
 
80
 
 
81
 
 
82
    def _roots(self):
 
83
        """
 
84
        Return a list of two-tuples representing the addresses of the root
 
85
        servers, as defined by C{self.hints}.
 
86
        """
 
87
        return [(ip, dns.PORT) for ip in self.hints]
 
88
 
 
89
 
 
90
    def _query(self, query, servers, timeout, filter):
 
91
        """
 
92
        Issue one query and return a L{Deferred} which fires with its response.
 
93
 
 
94
        @param query: The query to issue.
 
95
        @type query: L{dns.Query}
 
96
 
 
97
        @param servers: The servers which might have an answer for this
 
98
            query.
 
99
        @type servers: L{list} of L{tuple} of L{str} and L{int}
 
100
 
 
101
        @param timeout: A timeout on how long to wait for the response.
 
102
        @type timeout: L{tuple} of L{int}
 
103
 
 
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
 
108
            L{Message} instance.
 
109
        @type filter: L{bool}
 
110
 
 
111
        @return: A L{Deferred} which fires with the response or a timeout
 
112
            error.
 
113
        @rtype: L{Deferred}
 
114
        """
 
115
        from twisted.names import client
 
116
        r = client.Resolver(servers=servers, reactor=self._reactor)
 
117
        d = r.queryUDP([query], timeout)
 
118
        if filter:
 
119
            d.addCallback(r.filterAnswers)
 
120
        return d
 
121
 
 
122
 
 
123
    def _lookup(self, name, cls, type, timeout):
 
124
        """
 
125
        Implement name lookup by recursively discovering the authoritative
 
126
        server for the name and then asking it, starting at one of the servers
 
127
        in C{self.hints}.
 
128
        """
 
129
        if timeout is None:
 
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)
 
136
 
 
137
 
 
138
    def _discoverAuthority(self, query, servers, timeout, queriesLeft):
 
139
        """
 
140
        Issue a query to a server and follow a delegation if necessary.
 
141
 
 
142
        @param query: The query to issue.
 
143
        @type query: L{dns.Query}
 
144
 
 
145
        @param servers: The servers which might have an answer for this
 
146
            query.
 
147
        @type servers: L{list} of L{tuple} of L{str} and L{int}
 
148
 
 
149
        @param timeout: A C{tuple} of C{int} giving the timeout to use for this
 
150
            query.
 
151
 
 
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
 
154
            abandoned.
 
155
 
 
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.
 
159
        """
 
160
        # Stop now if we've hit the query limit.
 
161
        if queriesLeft <= 0:
 
162
            return Failure(
 
163
                error.ResolverError("Query limit reached without result"))
 
164
 
 
165
        d = self._query(query, servers, timeout, False)
 
166
        d.addCallback(
 
167
            self._discoveredAuthority, query, timeout, queriesLeft - 1)
 
168
        return d
 
169
 
 
170
 
 
171
    def _discoveredAuthority(self, response, query, timeout, queriesLeft):
 
172
        """
 
173
        Interpret the response to a query, checking for error codes and
 
174
        following delegations if necessary.
 
175
 
 
176
        @param response: The L{Message} received in response to issuing C{query}.
 
177
        @type response: L{Message}
 
178
 
 
179
        @param query: The L{dns.Query} which was issued.
 
180
        @type query: L{dns.Query}.
 
181
 
 
182
        @param timeout: The timeout to use if another query is indicated by
 
183
            this response.
 
184
        @type timeout: L{tuple} of L{int}
 
185
 
 
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
 
188
            abandoned.
 
189
 
 
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.
 
193
        """
 
194
        if response.rCode != dns.OK:
 
195
            return Failure(self.exceptionForCode(response.rCode)(response))
 
196
 
 
197
        # Turn the answers into a structure that's a little easier to work with.
 
198
        records = {}
 
199
        for answer in response.answers:
 
200
            records.setdefault(answer.name, []).append(answer)
 
201
 
 
202
        def findAnswerOrCName(name, type, cls):
 
203
            cname = None
 
204
            for record in records.get(name, []):
 
205
                if record.cls ==  cls:
 
206
                    if record.type == type:
 
207
                        return record
 
208
                    elif record.type == dns.CNAME:
 
209
                        cname = record
 
210
            # If there were any CNAME records, return the last one.  There's
 
211
            # only supposed to be zero or one, though.
 
212
            return cname
 
213
 
 
214
        seen = set()
 
215
        name = query.name
 
216
        record = None
 
217
        while True:
 
218
            seen.add(name)
 
219
            previous = record
 
220
            record = findAnswerOrCName(name, query.type, query.cls)
 
221
            if record is None:
 
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.
 
225
                    break
 
226
                else:
 
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)
 
237
                    return d
 
238
            elif record.type == query.type:
 
239
                return (
 
240
                    response.answers,
 
241
                    response.authority,
 
242
                    response.additional)
 
243
            else:
 
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
 
249
 
 
250
 
 
251
        # Build a map to use to convert NS names into IP addresses.
 
252
        addresses = {}
 
253
        for rr in response.additional:
 
254
            if rr.type == dns.A:
 
255
                addresses[str(rr.name)] = rr.payload.dottedQuad()
 
256
 
 
257
        hints = []
 
258
        traps = []
 
259
        for rr in response.authority:
 
260
            if rr.type == dns.NS:
 
261
                ns = str(rr.payload.name)
 
262
                if ns in addresses:
 
263
                    hints.append((addresses[ns], dns.PORT))
 
264
                else:
 
265
                    traps.append(ns)
 
266
        if hints:
 
267
            return self._discoverAuthority(
 
268
                query, hints, timeout, queriesLeft)
 
269
        elif traps:
 
270
            d = self.lookupAddress(traps[0], timeout)
 
271
            d.addCallback(
 
272
                lambda (answers, authority, additional):
 
273
                    answers[0].payload.dottedQuad())
 
274
            d.addCallback(
 
275
                lambda hint: self._discoverAuthority(
 
276
                    query, [(hint, dns.PORT)], timeout, queriesLeft - 1))
 
277
            return d
 
278
        else:
 
279
            return Failure(error.ResolverError(
 
280
                    "Stuck at response without answers or delegation"))
 
281
 
 
282
 
 
283
    def discoveredAuthority(self, auth, name, cls, type, timeout):
 
284
        warnings.warn(
 
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)
 
293
        return d
 
294
 
 
295
 
 
296
 
 
297
def lookupNameservers(host, atServer, p=None):
 
298
    warnings.warn(
 
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
 
303
    if p is None:
 
304
        p = dns.DNSDatagramProtocol(_DummyController())
 
305
        p.noisy = False
 
306
    return retry(
 
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
 
311
    )
 
312
 
 
313
def lookupAddress(host, atServer, p=None):
 
314
    warnings.warn(
 
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
 
319
    if p is None:
 
320
        p = dns.DNSDatagramProtocol(_DummyController())
 
321
        p.noisy = False
 
322
    return retry(
 
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
 
327
    )
 
328
 
 
329
def extractAuthority(msg, cache):
 
330
    warnings.warn(
 
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]
 
336
 
 
337
    # print 'Records for', soFar, ':', records
 
338
    # print 'NS for', soFar, ':', nameservers
 
339
 
 
340
    if not nameservers:
 
341
        return None, nameservers
 
342
    if not records:
 
343
        raise IOError("No records")
 
344
    for r in records:
 
345
        if r.type == dns.A:
 
346
            cache[str(r.name)] = r.payload.dottedQuad()
 
347
    for r in records:
 
348
        if r.type == dns.NS:
 
349
            if str(r.payload.name) in cache:
 
350
                return cache[str(r.payload.name)], nameservers
 
351
    for addr in records:
 
352
        if addr.type == dns.A and addr.name == r.name:
 
353
            return addr.payload.dottedQuad(), nameservers
 
354
    return None, nameservers
 
355
 
 
356
def discoverAuthority(host, roots, cache=None, p=None):
 
357
    warnings.warn(
 
358
        'twisted.names.root.discoverAuthority is deprecated since Twisted '
 
359
        '10.0.  Use twisted.names.root.Resolver.lookupNameservers instead.',
 
360
        category=DeprecationWarning, stacklevel=4)
 
361
 
 
362
    if cache is None:
 
363
        cache = {}
 
364
 
 
365
    rootAuths = list(roots)
 
366
 
 
367
    parts = host.rstrip('.').split('.')
 
368
    parts.reverse()
 
369
 
 
370
    authority = rootAuths.pop()
 
371
 
 
372
    soFar = ''
 
373
    for part in parts:
 
374
        soFar = part + '.' + soFar
 
375
        # print '///////',  soFar, authority, p
 
376
        msg = defer.waitForDeferred(lookupNameservers(soFar, authority, p))
 
377
        yield msg
 
378
        msg = msg.getResult()
 
379
 
 
380
        newAuth, nameservers = extractAuthority(msg, cache)
 
381
 
 
382
        if newAuth is not None:
 
383
            # print "newAuth is not None"
 
384
            authority = newAuth
 
385
        else:
 
386
            if nameservers:
 
387
                r = str(nameservers[0].payload.name)
 
388
                # print 'Recursively discovering authority for', r
 
389
                authority = defer.waitForDeferred(discoverAuthority(r, roots, cache, p))
 
390
                yield authority
 
391
                authority = authority.getResult()
 
392
                # print 'Discovered to be', authority, 'for', r
 
393
##            else:
 
394
##                # print 'Doing address lookup for', soFar, 'at', authority
 
395
##                msg = defer.waitForDeferred(lookupAddress(soFar, authority, p))
 
396
##                yield msg
 
397
##                msg = msg.getResult()
 
398
##                records = msg.answers + msg.authority + msg.additional
 
399
##                addresses = [r for r in records if r.type == dns.A]
 
400
##                if addresses:
 
401
##                    authority = addresses[0].payload.dottedQuad()
 
402
##                else:
 
403
##                    raise IOError("Resolution error")
 
404
    # print "Yielding authority", authority
 
405
    yield authority
 
406
 
 
407
discoverAuthority = defer.deferredGenerator(discoverAuthority)
 
408
 
 
409
def makePlaceholder(deferred, name):
 
410
    def placeholder(*args, **kw):
 
411
        deferred.addCallback(lambda r: getattr(r, name)(*args, **kw))
 
412
        return deferred
 
413
    return placeholder
 
414
 
 
415
class DeferredResolver:
 
416
    def __init__(self, resolverDeferred):
 
417
        self.waiting = []
 
418
        resolverDeferred.addCallback(self.gotRealResolver)
 
419
 
 
420
    def gotRealResolver(self, resolver):
 
421
        w = self.waiting
 
422
        self.__dict__ = resolver.__dict__
 
423
        self.__class__ = resolver.__class__
 
424
        for d in w:
 
425
            d.callback(resolver)
 
426
 
 
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)
 
432
 
 
433
def bootstrap(resolver):
 
434
    """Lookup the root nameserver addresses using the given resolver
 
435
 
 
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
 
438
    to look up.
 
439
    """
 
440
    domains = [chr(ord('a') + i) for i in range(13)]
 
441
    # f = lambda r: (log.msg('Root server address: ' + str(r)), r)[1]
 
442
    f = lambda r: r
 
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)