RPKI Engine 1.0

adns.py (3465)

Go to the documentation of this file.
00001 """
00002 Basic asynchronous DNS code, using asyncore and Bob Halley's excellent
00003 dnspython package.
00004 
00005 $Id: adns.py 3465 2010-10-07 00:59:39Z 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 ## @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:
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()
 All Classes Namespaces Files Functions Variables