diff options
Diffstat (limited to 'rpki/irdbd.py')
-rw-r--r-- | rpki/irdbd.py | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/rpki/irdbd.py b/rpki/irdbd.py new file mode 100644 index 00000000..41739dc4 --- /dev/null +++ b/rpki/irdbd.py @@ -0,0 +1,266 @@ +# $Id$ +# +# Copyright (C) 2013--2014 Dragon Research Labs ("DRL") +# Portions copyright (C) 2009--2012 Internet Systems Consortium ("ISC") +# Portions copyright (C) 2007--2008 American Registry for Internet Numbers ("ARIN") +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notices and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND DRL, ISC, AND ARIN DISCLAIM ALL +# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL, +# ISC, OR ARIN BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR +# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +""" +IR database daemon. +""" + +import sys +import os +import time +import argparse +import urlparse +import rpki.http +import rpki.config +import rpki.resource_set +import rpki.relaxng +import rpki.exceptions +import rpki.left_right +import rpki.log +import rpki.x509 +import rpki.daemonize + +class main(object): + + def handle_list_resources(self, q_pdu, r_msg): + child = rpki.irdb.Child.objects.get( + issuer__handle__exact = q_pdu.self_handle, + handle = q_pdu.child_handle) + resources = child.resource_bag + r_pdu = rpki.left_right.list_resources_elt() + r_pdu.tag = q_pdu.tag + r_pdu.self_handle = q_pdu.self_handle + r_pdu.child_handle = q_pdu.child_handle + r_pdu.valid_until = child.valid_until.strftime("%Y-%m-%dT%H:%M:%SZ") + r_pdu.asn = resources.asn + r_pdu.ipv4 = resources.v4 + r_pdu.ipv6 = resources.v6 + r_msg.append(r_pdu) + + def handle_list_roa_requests(self, q_pdu, r_msg): + for request in rpki.irdb.ROARequest.objects.raw(""" + SELECT irdb_roarequest.* + FROM irdb_roarequest, irdb_resourceholderca + WHERE irdb_roarequest.issuer_id = irdb_resourceholderca.id + AND irdb_resourceholderca.handle = %s + """, [q_pdu.self_handle]): + prefix_bag = request.roa_prefix_bag + r_pdu = rpki.left_right.list_roa_requests_elt() + r_pdu.tag = q_pdu.tag + r_pdu.self_handle = q_pdu.self_handle + r_pdu.asn = request.asn + r_pdu.ipv4 = prefix_bag.v4 + r_pdu.ipv6 = prefix_bag.v6 + r_msg.append(r_pdu) + + def handle_list_ghostbuster_requests(self, q_pdu, r_msg): + ghostbusters = rpki.irdb.GhostbusterRequest.objects.filter( + issuer__handle__exact = q_pdu.self_handle, + parent__handle__exact = q_pdu.parent_handle) + if ghostbusters.count() == 0: + ghostbusters = rpki.irdb.GhostbusterRequest.objects.filter( + issuer__handle__exact = q_pdu.self_handle, + parent = None) + for ghostbuster in ghostbusters: + r_pdu = rpki.left_right.list_ghostbuster_requests_elt() + r_pdu.tag = q_pdu.tag + r_pdu.self_handle = q_pdu.self_handle + r_pdu.parent_handle = q_pdu.parent_handle + r_pdu.vcard = ghostbuster.vcard + r_msg.append(r_pdu) + + def handle_list_ee_certificate_requests(self, q_pdu, r_msg): + for ee_req in rpki.irdb.EECertificateRequest.objects.filter(issuer__handle__exact = q_pdu.self_handle): + resources = ee_req.resource_bag + r_pdu = rpki.left_right.list_ee_certificate_requests_elt() + r_pdu.tag = q_pdu.tag + r_pdu.self_handle = q_pdu.self_handle + r_pdu.gski = ee_req.gski + r_pdu.valid_until = ee_req.valid_until.strftime("%Y-%m-%dT%H:%M:%SZ") + r_pdu.asn = resources.asn + r_pdu.ipv4 = resources.v4 + r_pdu.ipv6 = resources.v6 + r_pdu.cn = ee_req.cn + r_pdu.sn = ee_req.sn + r_pdu.eku = ee_req.eku + r_pdu.pkcs10 = ee_req.pkcs10 + r_msg.append(r_pdu) + + def handler(self, query, path, cb): + try: + q_pdu = None + r_msg = rpki.left_right.msg.reply() + from django.db import connection + connection.cursor() # Reconnect to mysqld if necessary + self.start_new_transaction() + serverCA = rpki.irdb.ServerCA.objects.get() + rpkid = serverCA.ee_certificates.get(purpose = "rpkid") + try: + q_cms = rpki.left_right.cms_msg(DER = query) + q_msg = q_cms.unwrap((serverCA.certificate, rpkid.certificate)) + self.cms_timestamp = q_cms.check_replay(self.cms_timestamp, path) + if not isinstance(q_msg, rpki.left_right.msg) or not q_msg.is_query(): + raise rpki.exceptions.BadQuery("Unexpected %r PDU" % q_msg) + for q_pdu in q_msg: + self.dispatch(q_pdu, r_msg) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + rpki.log.traceback() + if q_pdu is None: + r_msg.append(rpki.left_right.report_error_elt.from_exception(e)) + else: + r_msg.append(rpki.left_right.report_error_elt.from_exception(e, q_pdu.self_handle, q_pdu.tag)) + irdbd = serverCA.ee_certificates.get(purpose = "irdbd") + cb(200, body = rpki.left_right.cms_msg().wrap(r_msg, irdbd.private_key, irdbd.certificate)) + except (rpki.async.ExitNow, SystemExit): + raise + except Exception, e: + rpki.log.traceback() + cb(500, reason = "Unhandled exception %s: %s" % (e.__class__.__name__, e)) + + def dispatch(self, q_pdu, r_msg): + try: + handler = self.dispatch_vector[type(q_pdu)] + except KeyError: + raise rpki.exceptions.BadQuery("Unexpected %r PDU" % q_pdu) + else: + handler(q_pdu, r_msg) + + def __init__(self, **kwargs): + + global rpki # pylint: disable=W0602 + + os.environ["TZ"] = "UTC" + time.tzset() + + parser = argparse.ArgumentParser(description = __doc__) + parser.add_argument("-c", "--config", + help = "override default location of configuration file") + parser.add_argument("-d", "--debug", action = "store_true", + help = "enable debugging mode") + parser.add_argument("-f", "--foreground", action = "store_true", + help = "do not daemonize") + parser.add_argument("--pidfile", + help = "override default location of pid file") + parser.add_argument("--profile", + help = "enable profiling, saving data to PROFILE") + args = parser.parse_args() + + rpki.log.init("irdbd", use_syslog = not args.debug) + + self.cfg = rpki.config.parser(args.config, "irdbd") + self.cfg.set_global_flags() + + if not args.foreground and not args.debug: + rpki.daemonize.daemon(pidfile = args.pidfile) + + if args.profile: + import cProfile + prof = cProfile.Profile() + try: + prof.runcall(self.main) + finally: + prof.dump_stats(args.profile) + rpki.log.info("Dumped profile data to %s" % args.profile) + else: + self.main() + + def main(self): + + global rpki # pylint: disable=W0602 + from django.conf import settings + + startup_msg = self.cfg.get("startup-message", "") + if startup_msg: + rpki.log.info(startup_msg) + + # Do -not- turn on DEBUG here except for short-lived tests, + # otherwise irdbd will eventually run out of memory and crash. + # + # If you must enable debugging, use django.db.reset_queries() to + # clear the query list manually, but it's probably better just to + # run with debugging disabled, since that's the expectation for + # production code. + # + # https://docs.djangoproject.com/en/dev/faq/models/#why-is-django-leaking-memory + + settings.configure( + DATABASES = { + "default" : { + "ENGINE" : "django.db.backends.mysql", + "NAME" : self.cfg.get("sql-database"), + "USER" : self.cfg.get("sql-username"), + "PASSWORD" : self.cfg.get("sql-password"), + "HOST" : "", + "PORT" : "" }}, + INSTALLED_APPS = ("rpki.irdb",),) + + import rpki.irdb # pylint: disable=W0621 + + # Entirely too much fun with read-only access to transactional databases. + # + # http://stackoverflow.com/questions/3346124/how-do-i-force-django-to-ignore-any-caches-and-reload-data + # http://devblog.resolversystems.com/?p=439 + # http://groups.google.com/group/django-users/browse_thread/thread/e25cec400598c06d + # http://stackoverflow.com/questions/1028671/python-mysqldb-update-query-fails + # http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html + # + # It turns out that MySQL is doing us a favor with this weird + # transactional behavior on read, because without it there's a + # race condition if multiple updates are committed to the IRDB + # while we're in the middle of processing a query. Note that + # proper transaction management by the committers doesn't protect + # us, this is a transactional problem on read. So we need to use + # explicit transaction management. Since irdbd is a read-only + # consumer of IRDB data, this means we need to commit an empty + # transaction at the beginning of processing each query, to reset + # the transaction isolation snapshot. + + import django.db.transaction + self.start_new_transaction = django.db.transaction.commit_manually(django.db.transaction.commit) + + self.dispatch_vector = { + rpki.left_right.list_resources_elt : self.handle_list_resources, + rpki.left_right.list_roa_requests_elt : self.handle_list_roa_requests, + rpki.left_right.list_ghostbuster_requests_elt : self.handle_list_ghostbuster_requests, + rpki.left_right.list_ee_certificate_requests_elt : self.handle_list_ee_certificate_requests} + + try: + self.http_server_host = self.cfg.get("server-host", "") + self.http_server_port = self.cfg.getint("server-port") + except: + # + # Backwards compatibility, remove this eventually. + # + u = urlparse.urlparse(self.cfg.get("http-url")) + if (u.scheme not in ("", "http") or + u.username is not None or + u.password is not None or + u.params or u.query or u.fragment): + raise + self.http_server_host = u.hostname + self.http_server_port = int(u.port) + + self.cms_timestamp = None + + rpki.http.server( + host = self.http_server_host, + port = self.http_server_port, + handlers = self.handler) |