RPKI Engine
1.0
|
00001 """ 00002 Basic asynchronous DNS code, using asyncore and Bob Halley's excellent 00003 dnspython package. 00004 00005 $Id: adns.py 4014 2011-10-05 16:30:24Z sra $ 00006 00007 Copyright (C) 2010--2011 Internet Systems Consortium ("ISC") 00008 00009 Permission to use, copy, modify, and distribute this software for any 00010 purpose with or without fee is hereby granted, provided that the above 00011 copyright notice and this permission notice appear in all copies. 00012 00013 THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH 00014 REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 00015 AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, 00016 INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 00017 LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 00018 OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 00019 PERFORMANCE OF THIS SOFTWARE. 00020 00021 Portions copyright (C) 2003--2007, 2009, 2010 Nominum, Inc. 00022 00023 Permission to use, copy, modify, and distribute this software and its 00024 documentation for any purpose with or without fee is hereby granted, 00025 provided that the above copyright notice and this permission notice 00026 appear in all copies. 00027 00028 THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES 00029 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 00030 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR 00031 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 00032 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 00033 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 00034 OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 00035 """ 00036 00037 import asyncore, socket, time, sys 00038 import rpki.async, rpki.sundial, rpki.log 00039 00040 try: 00041 import dns.resolver, dns.rdatatype, dns.rdataclass, dns.name, dns.message 00042 import dns.inet, dns.exception, dns.query, dns.rcode, dns.ipv4, dns.ipv6 00043 except ImportError: 00044 if __name__ == "__main__": 00045 sys.exit("DNSPython not available, skipping rpki.adns unit test") 00046 else: 00047 raise 00048 00049 ## @var resolver 00050 # Resolver object, shared by everything using this module 00051 00052 resolver = dns.resolver.Resolver() 00053 if resolver.cache is None: 00054 resolver.cache = dns.resolver.Cache() 00055 00056 ## @var nameservers 00057 # Nameservers from resolver.nameservers converted to (af, address) 00058 # pairs. The latter turns out to be a more useful form for us to use 00059 # internally, because it simplifies the checks we need to make upon 00060 # packet receiption. 00061 00062 nameservers = [] 00063 00064 for ns in resolver.nameservers: 00065 try: 00066 nameservers.append((socket.AF_INET, dns.ipv4.inet_aton(ns))) 00067 continue 00068 except Exception: 00069 pass 00070 try: 00071 nameservers.append((socket.AF_INET6, dns.ipv6.inet_aton(ns))) 00072 continue 00073 except Exception: 00074 pass 00075 rpki.log.error("Couldn't parse nameserver address %r" % ns) 00076 00077 class dispatcher(asyncore.dispatcher): 00078 """ 00079 Basic UDP socket reader for use with asyncore. 00080 """ 00081 00082 def __init__(self, cb, eb, af, bufsize = 65535): 00083 asyncore.dispatcher.__init__(self) 00084 self.cb = cb 00085 self.eb = eb 00086 self.af = af 00087 self.bufsize = bufsize 00088 self.create_socket(af, socket.SOCK_DGRAM) 00089 00090 def handle_read(self): 00091 """ 00092 Receive a packet, hand it off to query class callback. 00093 """ 00094 wire, from_address = self.recvfrom(self.bufsize) 00095 self.cb(self.af, from_address[0], from_address[1], wire) 00096 00097 def handle_error(self): 00098 """ 00099 Pass errors to query class errback. 00100 """ 00101 self.eb(sys.exc_info()[1]) 00102 00103 def handle_connect(self): 00104 """ 00105 Quietly ignore UDP "connection" events. 00106 """ 00107 pass 00108 00109 def writable(self): 00110 """ 00111 We don't need to hear about UDP socket becoming writable. 00112 """ 00113 return False 00114 00115 00116 class query(object): 00117 """ 00118 Simplified (no search paths) asynchronous adaptation of 00119 dns.resolver.Resolver.query() (q.v.). 00120 """ 00121 00122 def __init__(self, cb, eb, qname, qtype = dns.rdatatype.A, qclass = dns.rdataclass.IN): 00123 if isinstance(qname, (str, unicode)): 00124 qname = dns.name.from_text(qname) 00125 if isinstance(qtype, str): 00126 qtype = dns.rdatatype.from_text(qtype) 00127 if isinstance(qclass, str): 00128 qclass = dns.rdataclass.from_text(qclass) 00129 assert qname.is_absolute() 00130 self.cb = cb 00131 self.eb = eb 00132 self.qname = qname 00133 self.qtype = qtype 00134 self.qclass = qclass 00135 self.start = time.time() 00136 rpki.async.defer(self.go) 00137 00138 def go(self): 00139 """ 00140 Start running the query. Check our cache before doing network 00141 query; if we find an answer there, just return it. Otherwise 00142 start the network query. 00143 """ 00144 if resolver.cache: 00145 answer = resolver.cache.get((self.qname, self.qtype, self.qclass)) 00146 else: 00147 answer = None 00148 if answer: 00149 self.cb(self, answer) 00150 else: 00151 self.timer = rpki.async.timer() 00152 self.sockets = {} 00153 self.request = dns.message.make_query(self.qname, self.qtype, self.qclass) 00154 if resolver.keyname is not None: 00155 self.request.use_tsig(resolver.keyring, resolver.keyname, resolver.keyalgorithm) 00156 self.request.use_edns(resolver.edns, resolver.ednsflags, resolver.payload) 00157 self.response = None 00158 self.backoff = 0.10 00159 self.nameservers = nameservers[:] 00160 self.loop1() 00161 00162 def loop1(self): 00163 """ 00164 Outer loop. If we haven't got a response yet and still have 00165 nameservers to check, start inner loop. Otherwise, we're done. 00166 """ 00167 self.timer.cancel() 00168 if self.response is None and self.nameservers: 00169 self.iterator = rpki.async.iterator(self.nameservers[:], self.loop2, self.done2) 00170 else: 00171 self.done1() 00172 00173 def loop2(self, iterator, nameserver): 00174 """ 00175 Inner loop. Send query to next nameserver in our list, unless 00176 we've hit the overall timeout for this query. 00177 """ 00178 self.timer.cancel() 00179 try: 00180 timeout = resolver._compute_timeout(self.start) 00181 except dns.resolver.Timeout, e: 00182 self.lose(e) 00183 else: 00184 af, addr = nameserver 00185 if af not in self.sockets: 00186 self.sockets[af] = dispatcher(self.socket_cb, self.socket_eb, af) 00187 self.sockets[af].sendto(self.request.to_wire(), 00188 (dns.inet.inet_ntop(af, addr), resolver.port)) 00189 self.timer.set_handler(self.socket_timeout) 00190 self.timer.set_errback(self.socket_eb) 00191 self.timer.set(rpki.sundial.timedelta(seconds = timeout)) 00192 00193 def socket_timeout(self): 00194 """ 00195 No answer from nameserver, move on to next one (inner loop). 00196 """ 00197 self.response = None 00198 self.iterator() 00199 00200 def socket_eb(self, e): 00201 """ 00202 UDP socket signaled error. If it really is some kind of socket 00203 error, handle as if we've timed out on this nameserver; otherwise, 00204 pass error back to caller. 00205 """ 00206 self.timer.cancel() 00207 if isinstance(e, socket.error): 00208 self.response = None 00209 self.iterator() 00210 else: 00211 self.lose(e) 00212 00213 def socket_cb(self, af, from_host, from_port, wire): 00214 """ 00215 Received a packet that might be a DNS message. If it doesn't look 00216 like it came from one of our nameservers, just drop it and leave 00217 the timer running. Otherwise, try parsing it: if it's an answer, 00218 we're done, otherwise handle error appropriately and move on to 00219 next nameserver. 00220 """ 00221 sender = (af, dns.inet.inet_pton(af, from_host)) 00222 if from_port != resolver.port or sender not in self.nameservers: 00223 return 00224 self.timer.cancel() 00225 try: 00226 self.response = dns.message.from_wire(wire, keyring = self.request.keyring, request_mac = self.request.mac, one_rr_per_rrset = False) 00227 except dns.exception.FormError: 00228 self.nameservers.remove(sender) 00229 else: 00230 rcode = self.response.rcode() 00231 if rcode in (dns.rcode.NOERROR, dns.rcode.NXDOMAIN): 00232 self.done1() 00233 return 00234 if rcode != dns.rcode.SERVFAIL: 00235 self.nameservers.remove(sender) 00236 self.response = None 00237 self.iterator() 00238 00239 def done2(self): 00240 """ 00241 Done with inner loop. If we still haven't got an answer and 00242 haven't (yet?) eliminated all of our nameservers, wait a little 00243 while before starting the cycle again, unless we've hit the 00244 timeout threshold for the whole query. 00245 """ 00246 if self.response is None and self.nameservers: 00247 try: 00248 delay = rpki.sundial.timedelta(seconds = min(resolver._compute_timeout(self.start), self.backoff)) 00249 self.backoff *= 2 00250 self.timer.set_handler(self.loop1) 00251 self.timer.set_errback(self.lose) 00252 self.timer.set(delay) 00253 except dns.resolver.Timeout, e: 00254 self.lose(e) 00255 else: 00256 self.loop1() 00257 00258 def cleanup(self): 00259 """ 00260 Shut down our timer and sockets. 00261 """ 00262 self.timer.cancel() 00263 for s in self.sockets.itervalues(): 00264 s.close() 00265 00266 def lose(self, e): 00267 """ 00268 Something bad happened. Clean up, then pass error back to caller. 00269 """ 00270 self.cleanup() 00271 self.eb(self, e) 00272 00273 def done1(self): 00274 """ 00275 Done with outer loop. If we got a useful answer, cache it, then 00276 pass it back to caller; if we got an error, pass the appropriate 00277 exception back to caller. 00278 """ 00279 self.cleanup() 00280 try: 00281 if not self.nameservers: 00282 raise dns.resolver.NoNameservers 00283 if self.response.rcode() == dns.rcode.NXDOMAIN: 00284 raise dns.resolver.NXDOMAIN 00285 answer = dns.resolver.Answer(self.qname, self.qtype, self.qclass, self.response) 00286 if resolver.cache: 00287 resolver.cache.put((self.qname, self.qtype, self.qclass), answer) 00288 self.cb(self, answer) 00289 except (rpki.async.ExitNow, SystemExit): 00290 raise 00291 except Exception, e: 00292 self.lose(e) 00293 00294 class getaddrinfo(object): 00295 00296 typemap = { dns.rdatatype.A : socket.AF_INET, 00297 dns.rdatatype.AAAA : socket.AF_INET6 } 00298 00299 def __init__(self, cb, eb, host, address_families = typemap.values()): 00300 self.cb = cb 00301 self.eb = eb 00302 self.host = host 00303 self.result = [] 00304 self.queries = [query(self.done, self.lose, host, qtype) 00305 for qtype in self.typemap 00306 if self.typemap[qtype] in address_families] 00307 00308 def done(self, q, answer): 00309 if answer is not None: 00310 for a in answer: 00311 self.result.append((self.typemap[a.rdtype], a.address)) 00312 self.queries.remove(q) 00313 if not self.queries: 00314 self.cb(self.result) 00315 00316 def lose(self, q, e): 00317 if isinstance(e, dns.resolver.NoAnswer): 00318 self.done(q, None) 00319 else: 00320 for q in self.queries: 00321 q.cleanup() 00322 self.eb(e) 00323 00324 if __name__ == "__main__": 00325 00326 rpki.log.use_syslog = False 00327 print "Some adns tests may take a minute or two, please be patient" 00328 00329 class test_getaddrinfo(object): 00330 00331 def __init__(self, qname): 00332 self.qname = qname 00333 getaddrinfo(self.done, self.lose, qname) 00334 00335 def done(self, result): 00336 print "getaddrinfo(%s) returned: %s" % ( 00337 self.qname, 00338 ", ".join(str(r) for r in result)) 00339 00340 def lose(self, e): 00341 print "getaddrinfo(%s) failed: %r" % (self.qname, e) 00342 00343 class test_query(object): 00344 00345 def __init__(self, qname, qtype = dns.rdatatype.A, qclass = dns.rdataclass.IN): 00346 self.qname = qname 00347 self.qtype = qtype 00348 self.qclass = qclass 00349 query(self.done, self.lose, qname, qtype = qtype, qclass = qclass) 00350 00351 def done(self, q, result): 00352 print "query(%s, %s, %s) returned: %s" % ( 00353 self.qname, 00354 dns.rdatatype.to_text(self.qtype), 00355 dns.rdataclass.to_text(self.qclass), 00356 ", ".join(str(r) for r in result)) 00357 00358 def lose(self, q, e): 00359 print "getaddrinfo(%s, %s, %s) failed: %r" % ( 00360 self.qname, 00361 dns.rdatatype.to_text(self.qtype), 00362 dns.rdataclass.to_text(self.qclass), 00363 e) 00364 00365 if True: 00366 for qtype in (dns.rdatatype.A, dns.rdatatype.AAAA, dns.rdatatype.HINFO): 00367 test_query("subvert-rpki.hactrn.net", qtype) 00368 test_query("nonexistant.rpki.net") 00369 test_query("subvert-rpki.hactrn.net", qclass = dns.rdataclass.CH) 00370 00371 for host in ("subvert-rpki.hactrn.net", "nonexistant.rpki.net"): 00372 test_getaddrinfo(host) 00373 00374 rpki.async.event_loop()