00001 """
00002 Basic asynchronous DNS code, using asyncore and Bob Halley's excellent
00003 dnspython package.
00004
00005 $Id: adns.py 3449 2010-09-16 21:30:30Z sra $
00006
00007 Copyright (C) 2010 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
00050
00051
00052 resolver = dns.resolver.Resolver()
00053 if resolver.cache is None:
00054 resolver.cache = dns.resolver.Cache()
00055
00056
00057
00058
00059
00060
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:
00069 pass
00070 try:
00071 nameservers.append((socket.AF_INET6, dns.ipv6.inet_aton(ns)))
00072 continue
00073 except:
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()