#!/usr/bin/env python # $Id$ # # Copyright (C) 2014 Dragon Research Labs ("DRL") # Portions copyright (C) 2009-2013 Internet Systems Consortium ("ISC") # # 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 AND ISC DISCLAIM ALL # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL DRL OR # ISC 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. import asyncore import bisect import glob import logging import os import shutil import sqlite3 import subprocess import sys import time import rpki.POW import rpki.oids import rpki.rtr.channels import rpki.rtr.client import rpki.rtr.generator import rpki.rtr.pdus import rpki.rtr.server from rpki.rtr.channels import Timestamp class ReplayClock(object): """ Internal clock for replaying a set of rpki-rtr database files. This class replaces the normal on-disk serial number mechanism with an in-memory version based on pre-computed data. DO NOT USE THIS IN PRODUCTION. You have been warned. """ def __init__(self): self.timestamps = dict((v, sorted(set(Timestamp(int(f.split(".")[0])) for f in glob.iglob("*.ax.v%d" % v)))) for v in rpki.rtr.pdus.PDU.version_map) self.epoch = min(t[0] for t in self.timestamps.itervalues()) self.offset = self.epoch - Timestamp.now() self.nonce = rpki.rtr.generator.AXFRSet.new_nonce(0) def __nonzero__(self): return sum(len(t) for t in self.timestamps.itervalues()) > 0 def now(self): now = Timestamp.now(self.offset) return now def read_current(self, version): now = self.now() if version is None: return self.epoch, self.nonce while len(self.timestamps[version]) > 1 and now >= self.timestamps[version][1]: del self.timestamps[version][0] return self.timestamps[version][0], self.nonce def siesta(self): try: when = min(t[1] for t in self.timestamps.itervalues() if len(t) > 1) except ValueError: return None now = self.now() if now < when: return when - now else: return 1 def server_main(args): """ Reply rpki-data from a historical database. This is a clone of server_main() which replaces the external serial number updates triggered via the kickme channel by cronjob_main with an internal clocking mechanism to replay historical test data. DO NOT USE THIS IN PRODUCTION. You have been warned. """ logger = logging.LoggerAdapter(logging.root, dict(connection = rpki.rtr.server._hostport_tag())) logger.debug("[Starting]") if args.rpki_rtr_dir: try: os.chdir(args.rpki_rtr_dir) except OSError, e: sys.exit(e) # Yes, this really does replace a global function defined in another # module with a bound method to our clock object. Fun stuff, huh? clock = ReplayClock() rpki.rtr.server.read_current = clock.read_current try: server = rpki.rtr.server.ServerChannel(logger = logger, refresh = args.refresh, retry = args.retry, expire = args.expire) old_serial = server.get_serial() logger.debug("[Starting at serial %d (%s)]", old_serial, old_serial) while clock: new_serial = server.get_serial() if old_serial != new_serial: logger.debug("[Serial bumped from %d (%s) to %d (%s)]", old_serial, old_serial, new_serial, new_serial) server.notify(force = True) old_serial = new_serial asyncore.loop(timeout = clock.siesta(), count = 1) except KeyboardInterrupt: sys.exit(0) # Splice our extensions into server rpki.rtr.server.server_main = server_main # And run the program import rpki.rtr.main rpki.rtr.main.main()