OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.names.test.test_rootresolve -*- | |
2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
3 # See LICENSE for details. | |
4 | |
5 | |
6 """ | |
7 Resolver implementation for querying successive authoritative servers to | |
8 lookup a record, starting from the root nameservers. | |
9 | |
10 @author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>} | |
11 | |
12 todo:: | |
13 robustify it | |
14 break discoverAuthority into several smaller functions | |
15 documentation | |
16 """ | |
17 | |
18 from __future__ import generators | |
19 | |
20 import sys | |
21 | |
22 from twisted.python import log | |
23 from twisted.internet import defer | |
24 from twisted.names import dns | |
25 from twisted.names import common | |
26 | |
27 def retry(t, p, *args): | |
28 assert t, "Timeout is required" | |
29 t = list(t) | |
30 def errback(failure): | |
31 failure.trap(defer.TimeoutError) | |
32 if not t: | |
33 return failure | |
34 return p.query(timeout=t.pop(0), *args | |
35 ).addErrback(errback | |
36 ) | |
37 return p.query(timeout=t.pop(0), *args | |
38 ).addErrback(errback | |
39 ) | |
40 | |
41 class _DummyController: | |
42 def messageReceived(self, *args): | |
43 pass | |
44 | |
45 class Resolver(common.ResolverBase): | |
46 def __init__(self, hints): | |
47 common.ResolverBase.__init__(self) | |
48 self.hints = hints | |
49 | |
50 def _lookup(self, name, cls, type, timeout): | |
51 d = discoverAuthority(name, self.hints | |
52 ).addCallback(self.discoveredAuthority, name, cls, type, timeout | |
53 ) | |
54 return d | |
55 | |
56 def discoveredAuthority(self, auth, name, cls, type, timeout): | |
57 from twisted.names import client | |
58 q = dns.Query(name, type, cls) | |
59 r = client.Resolver(servers=[(auth, dns.PORT)]) | |
60 d = r.queryUDP([q], timeout) | |
61 d.addCallback(r.filterAnswers) | |
62 return d | |
63 | |
64 def lookupNameservers(host, atServer, p=None): | |
65 # print 'Nameserver lookup for', host, 'at', atServer, 'with', p | |
66 if p is None: | |
67 p = dns.DNSDatagramProtocol(_DummyController()) | |
68 p.noisy = False | |
69 return retry( | |
70 (1, 3, 11, 45), # Timeouts | |
71 p, # Protocol instance | |
72 (atServer, dns.PORT), # Server to query | |
73 [dns.Query(host, dns.NS, dns.IN)] # Question to ask | |
74 ) | |
75 | |
76 def lookupAddress(host, atServer, p=None): | |
77 # print 'Address lookup for', host, 'at', atServer, 'with', p | |
78 if p is None: | |
79 p = dns.DNSDatagramProtocol(_DummyController()) | |
80 p.noisy = False | |
81 return retry( | |
82 (1, 3, 11, 45), # Timeouts | |
83 p, # Protocol instance | |
84 (atServer, dns.PORT), # Server to query | |
85 [dns.Query(host, dns.A, dns.IN)] # Question to ask | |
86 ) | |
87 | |
88 def extractAuthority(msg, cache): | |
89 records = msg.answers + msg.authority + msg.additional | |
90 nameservers = [r for r in records if r.type == dns.NS] | |
91 | |
92 # print 'Records for', soFar, ':', records | |
93 # print 'NS for', soFar, ':', nameservers | |
94 | |
95 if not nameservers: | |
96 return None, nameservers | |
97 if not records: | |
98 raise IOError("No records") | |
99 for r in records: | |
100 if r.type == dns.A: | |
101 cache[str(r.name)] = r.payload.dottedQuad() | |
102 for r in records: | |
103 if r.type == dns.NS: | |
104 if str(r.payload.name) in cache: | |
105 return cache[str(r.payload.name)], nameservers | |
106 for addr in records: | |
107 if addr.type == dns.A and addr.name == r.name: | |
108 return addr.payload.dottedQuad(), nameservers | |
109 return None, nameservers | |
110 | |
111 def discoverAuthority(host, roots, cache=None, p=None): | |
112 if cache is None: | |
113 cache = {} | |
114 | |
115 rootAuths = list(roots) | |
116 | |
117 parts = host.rstrip('.').split('.') | |
118 parts.reverse() | |
119 | |
120 authority = rootAuths.pop() | |
121 | |
122 soFar = '' | |
123 for part in parts: | |
124 soFar = part + '.' + soFar | |
125 # print '///////', soFar, authority, p | |
126 msg = defer.waitForDeferred(lookupNameservers(soFar, authority, p)) | |
127 yield msg | |
128 msg = msg.getResult() | |
129 | |
130 newAuth, nameservers = extractAuthority(msg, cache) | |
131 | |
132 if newAuth is not None: | |
133 # print "newAuth is not None" | |
134 authority = newAuth | |
135 else: | |
136 if nameservers: | |
137 r = str(nameservers[0].payload.name) | |
138 # print 'Recursively discovering authority for', r | |
139 authority = defer.waitForDeferred(discoverAuthority(r, roots, ca
che, p)) | |
140 yield authority | |
141 authority = authority.getResult() | |
142 # print 'Discovered to be', authority, 'for', r | |
143 ## else: | |
144 ## # print 'Doing address lookup for', soFar, 'at', authority | |
145 ## msg = defer.waitForDeferred(lookupAddress(soFar, authority, p)
) | |
146 ## yield msg | |
147 ## msg = msg.getResult() | |
148 ## records = msg.answers + msg.authority + msg.additional | |
149 ## addresses = [r for r in records if r.type == dns.A] | |
150 ## if addresses: | |
151 ## authority = addresses[0].payload.dottedQuad() | |
152 ## else: | |
153 ## raise IOError("Resolution error") | |
154 # print "Yielding authority", authority | |
155 yield authority | |
156 | |
157 discoverAuthority = defer.deferredGenerator(discoverAuthority) | |
158 | |
159 def makePlaceholder(deferred, name): | |
160 def placeholder(*args, **kw): | |
161 deferred.addCallback(lambda r: getattr(r, name)(*args, **kw)) | |
162 return deferred | |
163 return placeholder | |
164 | |
165 class DeferredResolver: | |
166 def __init__(self, resolverDeferred): | |
167 self.waiting = [] | |
168 resolverDeferred.addCallback(self.gotRealResolver) | |
169 | |
170 def gotRealResolver(self, resolver): | |
171 w = self.waiting | |
172 self.__dict__ = resolver.__dict__ | |
173 self.__class__ = resolver.__class__ | |
174 for d in w: | |
175 d.callback(resolver) | |
176 | |
177 def __getattr__(self, name): | |
178 if name.startswith('lookup') or name in ('getHostByName', 'query'): | |
179 self.waiting.append(defer.Deferred()) | |
180 return makePlaceholder(self.waiting[-1], name) | |
181 raise AttributeError(name) | |
182 | |
183 def bootstrap(resolver): | |
184 """Lookup the root nameserver addresses using the given resolver | |
185 | |
186 Return a Resolver which will eventually become a C{root.Resolver} | |
187 instance that has references to all the root servers that we were able | |
188 to look up. | |
189 """ | |
190 domains = [chr(ord('a') + i) for i in range(13)] | |
191 # f = lambda r: (log.msg('Root server address: ' + str(r)), r)[1] | |
192 f = lambda r: r | |
193 L = [resolver.getHostByName('%s.root-servers.net' % d).addCallback(f) for d
in domains] | |
194 d = defer.DeferredList(L) | |
195 d.addCallback(lambda r: Resolver([e[1] for e in r if e[0]])) | |
196 return DeferredResolver(d) | |
197 | |
198 if __name__ == '__main__': | |
199 if len(sys.argv) < 2: | |
200 print 'Specify a domain' | |
201 else: | |
202 log.startLogging(sys.stdout) | |
203 from twisted.names.client import ThreadedResolver | |
204 r = bootstrap(ThreadedResolver()) | |
205 d = r.lookupAddress(sys.argv[1]) | |
206 d.addCallbacks(log.msg, log.err).addBoth(lambda _: reactor.stop()) | |
207 from twisted.internet import reactor | |
208 reactor.run() | |
OLD | NEW |