diff options
Diffstat (limited to 'rpki/rpki_rtr')
-rw-r--r-- | rpki/rpki_rtr/client.py | 30 | ||||
-rw-r--r-- | rpki/rpki_rtr/generator.py | 52 | ||||
-rw-r--r-- | rpki/rpki_rtr/main.py | 86 | ||||
-rw-r--r-- | rpki/rpki_rtr/server.py | 69 |
4 files changed, 121 insertions, 116 deletions
diff --git a/rpki/rpki_rtr/client.py b/rpki/rpki_rtr/client.py index 8143e1df..0ba3688f 100644 --- a/rpki/rpki_rtr/client.py +++ b/rpki/rpki_rtr/client.py @@ -479,35 +479,7 @@ class ClientChannel(rpki.rpki_rtr.channels.PDUChannel): def client_main(args): """ - Toy client, intended only for debugging. - - This program takes one or more arguments. The first argument - determines what kind of connection it should open to the server, the - remaining arguments are connection details specific to this - particular type of connection. - - If the first argument is "loopback", the client will run a copy of - the server directly in a subprocess, and communicate with it via a - PF_UNIX socket pair. This sub-mode takes no further arguments. - - If the first argument is "ssh", the client will attempt to run ssh - in as subprocess to connect to the server using the ssh subsystem - mechanism as specified for this protocol. The remaining arguments - should be a hostname (or IP address in a form acceptable to ssh) and - a TCP port number. - - If the first argument is "tcp", the client will attempt to open a - direct (and completely insecure!) TCP connection to the server. - The remaining arguments should be a hostname (or IP address) and - a TCP port number. - - If the first argument is "tls", the client will attempt to open a - TLS connection to the server. The remaining arguments should be a - hostname (or IP address) and a TCP port number. - - An optional final name is the name of a file containing a SQLite - database in which to store the received table. If specified, this - database will be created if missing. + Test client, intended primarily for debugging. """ logging.debug("[Startup]") diff --git a/rpki/rpki_rtr/generator.py b/rpki/rpki_rtr/generator.py index 5ef2c3dc..2d8f3f76 100644 --- a/rpki/rpki_rtr/generator.py +++ b/rpki/rpki_rtr/generator.py @@ -23,8 +23,9 @@ Database generator for RPKI-RTR server (RFC 6810 et sequalia). import os import sys import glob -import base64 import socket +import base64 +import random import logging import subprocess import rpki.POW @@ -337,7 +338,20 @@ class AXFRSet(PDUSet): if i != self.filename(): os.unlink(i) - def mark_current(self): + @staticmethod + def new_nonce(force_zero_nonce): + """ + Create and return a new nonce value. + """ + + if force_zero_nonce: + return 0 + try: + return int(random.SystemRandom().getrandbits(16)) + except NotImplementedError: + return int(random.getrandbits(16)) + + def mark_current(self, force_zero_nonce = False): """ Save current serial number and nonce, creating new nonce if necessary. Creating a new nonce triggers cleanup of old state, as @@ -348,7 +362,7 @@ class AXFRSet(PDUSet): old_serial, nonce = rpki.rpki_rtr.server.read_current(self.version) if old_serial is None or self.seq_ge(old_serial, self.serial): logging.debug("Creating new nonce and deleting stale data") - nonce = rpki.rpki_rtr.server.new_nonce() + nonce = self.new_nonce(force_zero_nonce) self.destroy_old_data() rpki.rpki_rtr.server.write_current(self.serial, nonce, self.version) @@ -465,22 +479,13 @@ def kick_all(serial): def cronjob_main(args): """ - Run this mode right after rcynic to do the real work of groveling - through the ROAs that rcynic collects and translating that data into - the form used in the rpki-router protocol. This mode prepares both - full dumps (AXFR) and incremental dumps against a specific prior - version (IXFR). [Terminology here borrowed from DNS, as is much of - the protocol design.] Finally, this mode kicks any active servers, - so that they can notify their clients that a new version is - available. - - Run this in the directory where you want to write its output files, - which should also be the directory in which you run this program in - --server mode. - - This mode takes one argument on the command line, which specifies - the directory name of rcynic's authenticated output tree (normally - $somewhere/rcynic-data/authenticated/). + Run this right after running rcynic to wade through the ROAs and + router certificates that rcynic collects and translate that data + into the form used in the rpki-router protocol. Output is an + updated database containing both full dumps (AXFR) and incremental + dumps against a specific prior version (IXFR). After updating the + database, kicks any active servers, so that they can notify their + clients that a new version is available. """ if args.rpki_rtr_dir: @@ -514,7 +519,7 @@ def cronjob_main(args): for axfr in glob.iglob("*.ax.v%d" % version): if axfr != pdus.filename(): pdus.save_ixfr(rpki.rpki_rtr.generator.AXFRSet.load(axfr)) - pdus.mark_current() + pdus.mark_current(args.force_zero_nonce) logging.debug("# New serial is %d (%s)", pdus.serial, pdus.serial) @@ -531,11 +536,7 @@ def cronjob_main(args): def show_main(args): """ - Display dumps created by --cronjob mode in textual form. - Intended only for debugging. - - This mode takes no command line arguments. Run it in the directory - where you ran --cronjob mode. + Display current rpki-rtr server database in textual form. """ if args.rpki_rtr_dir: @@ -564,6 +565,7 @@ def argparse_setup(subparsers): subparser.set_defaults(func = cronjob_main, default_log_to = "syslog") subparser.add_argument("--scan-roas", help = "specify an external scan_roas program") subparser.add_argument("--scan-routercerts", help = "specify an external scan_routercerts program") + subparser.add_argument("--force_zero_nonce", action = "store_true", help = "force nonce value of zero") subparser.add_argument("rcynic_dir", help = "directory containing validated rcynic output tree") subparser.add_argument("rpki_rtr_dir", nargs = "?", help = "directory containing RPKI-RTR database") diff --git a/rpki/rpki_rtr/main.py b/rpki/rpki_rtr/main.py new file mode 100644 index 00000000..9415f270 --- /dev/null +++ b/rpki/rpki_rtr/main.py @@ -0,0 +1,86 @@ +# $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. + +""" +RPKI-Router protocol implementation. See RFC 6810 et sequalia in fine +RFC and Internet-Draft repositories near you. +""" + +import os +import sys +import time +import logging +import logging.handlers +import argparse + +from rpki.rpki_rtr.server import argparse_setup as argparse_setup_server +from rpki.rpki_rtr.client import argparse_setup as argparse_setup_client +from rpki.rpki_rtr.generator import argparse_setup as argparse_setup_generator + +class Formatter(logging.Formatter): + + converter = time.gmtime + + def __init__(self, debug, fmt, datefmt): + self.debug = debug + super(Formatter, self).__init__(fmt, datefmt) + + def format(self, record): + if getattr(record, "connection", None) is None: + record.connection = "" + return super(Formatter, self).format(record) + + def formatException(self, ei): + if self.debug: + return super(Formatter, self).formatException(ei) + else: + return str(ei[1]) + +def main(): + + os.environ["TZ"] = "UTC" + time.tzset() + + argparser = argparse.ArgumentParser(description = __doc__) + argparser.add_argument("--debug", action = "store_true", help = "debugging mode") + argparser.add_argument("--log-level", default = logging.DEBUG, + choices = ("debug", "info", "warning", "error", "critical"), + type = lambda s: int(getattr(logging, s.upper()))) + argparser.add_argument("--log-to", + choices = ("syslog", "stderr")) + subparsers = argparser.add_subparsers(title = "Commands", metavar = "", dest = "mode") + argparse_setup_server(subparsers) + argparse_setup_client(subparsers) + argparse_setup_generator(subparsers) + args = argparser.parse_args() + + fmt = "rpki-rtr/" + args.mode + "%(connection)s[%(process)d] %(message)s" + + if (args.log_to or args.default_log_to) == "stderr": + handler = logging.StreamHandler() + fmt = "%(asctime)s " + fmt + elif os.path.exists("/dev/log"): + handler = logging.handlers.SysLogHandler("/dev/log") + else: + handler = logging.handlers.SysLogHandler() + + handler.setFormatter(Formatter(args.debug, fmt, "%Y-%m-%dT%H:%M:%SZ")) + logging.root.addHandler(handler) + logging.root.setLevel(args.log_level) + + return args.func(args) diff --git a/rpki/rpki_rtr/server.py b/rpki/rpki_rtr/server.py index cd687ad2..f23ef30e 100644 --- a/rpki/rpki_rtr/server.py +++ b/rpki/rpki_rtr/server.py @@ -24,7 +24,6 @@ import os import sys import errno import socket -import random import logging import asyncore import rpki.POW @@ -188,19 +187,6 @@ def write_current(serial, nonce, version): os.rename(tmpfn, curfn) -def new_nonce(force_zero_nonce = False): - """ - Create and return a new nonce value. - """ - - if force_zero_nonce: - return 0 - try: - return int(random.SystemRandom().getrandbits(16)) - except NotImplementedError: - return int(random.getrandbits(16)) - - class FileProducer(object): """ File-based producer object for asynchat. @@ -477,32 +463,9 @@ def server_main(args): """ Implement the server side of the rpkk-router protocol. Other than one PF_UNIX socket inode, this doesn't write anything to disk, so it - can be run with minimal privileges. Most of the hard work has - already been done in --cronjob mode, so all that this mode has to do - is serve up the results. - - In production use this server should run under sshd. The subsystem - mechanism in sshd does not allow us to pass arguments on the command - line, so setting this up might require a wrapper script, but in - production use you will probably want to lock down the public key - used to authenticate the ssh session so that it can only run this - one command, in which case you can just specify the full command - including any arguments in the authorized_keys file. - - Unless you do something special, sshd will have this program running - in whatever it thinks is the home directory associated with the - username given in the ssh prototocol setup, so it may be easiest to - set this up so that the home directory sshd puts this program into - is the one where --cronjob left its files for this mode to pick up. - - This mode must be run in the directory where you ran --cronjob mode. - - This mode takes one optional argument: if provided, the argument is - the name of a directory to which the program should chdir() on - startup; this may simplify setup when running under inetd. - - The server is event driven, so everything interesting happens in the - channel classes. + can be run with minimal privileges. Most of the work has already + been done by the database generator, so all this server has to do is + pass the results along to a client. """ logger = logging.LoggerAdapter(logging.root, dict(connection = _hostport_tag())) @@ -515,9 +478,6 @@ def server_main(args): except OSError, e: sys.exit(e) - if args.force_zero_nonce: - logger.warning("--force_zero_nonce not implemented at the moment, ignoring") - kickme = None try: server = rpki.rpki_rtr.server.ServerChannel(logger = logger) @@ -532,18 +492,10 @@ def server_main(args): def listener_main(args): """ - Simple plain-TCP listener. Listens on a specified TCP port, upon - receiving a connection, forks the process and starts child executing - at server_main(). - - First argument (required) is numeric port number. - - Second argument (optional) is directory, like --server. - - NB: plain-TCP is completely insecure. We only implement this - because it's all that the routers currently support. In theory, we - will all be running TCP-AO in the future, at which point this will - go away. + Totally insecure TCP listener for rpki-rtr protocol. We only + implement this because it's all that the routers currently support. + In theory, we will all be running TCP-AO in the future, at which + point this listener will go away or become a TCP-AO listener. """ # Perhaps we should daemonize? Deal with that later. @@ -579,12 +531,6 @@ def listener_main(args): os.dup2(s.fileno(), 1) # pylint: disable=E1103 s.close() #os.closerange(3, os.sysconf("SC_OPEN_MAX")) - # - logging.warning("Should be reconfiguring logging here, but we're lame") - #global log_tag - #log_tag = "rtr-origin/server" + rpki.rpki_rtr.server.hostport_tag() - #syslog.closelog() - #syslog.openlog(log_tag, syslog.LOG_PID, syslog_facility) server_main(()) sys.exit() else: @@ -608,7 +554,6 @@ def argparse_setup(subparsers): subparser = subparsers.add_parser("server", description = server_main.__doc__, help = "RPKI-RTR protocol server") subparser.set_defaults(func = server_main, default_log_to = "syslog") - subparser.add_argument("--force_zero_nonce", action = "store_true", help = "force nonce value of zero") subparser.add_argument("rpki_rtr_dir", nargs = "?", help = "directory containing RPKI-RTR database") subparser = subparsers.add_parser("listener", description = listener_main.__doc__, |