#!/usr/bin/env python2 # pcepdump - Display messages from PCCs # # Copyright (c) 2012,2013 Cisco Systems, Inc. and others. All rights reserved. # # This program and the accompanying materials are made available under the # terms of the Eclipse Public License v1.0 which accompanies this distribution, # and is available at http://www.eclipse.org/legal/epl-v10.html # import sys import datetime # Temporary hack sys.path.append('..') import argparse import logging import code import pcepy.peer as _P import pcepy.session as _S import pcepy.message as _M from pcepy.message import object as _O _LOGGER = logging.getLogger('pcepy.peer') DEFAULT_IP = '0.0.0.0' KEEPALIVE = 30 class PcepDumper(_P.Context): """A manager for a PCEP connection. PcepDumper can be PCC or PCE and it main function it to listen in and print incoming messages. It aslo provide interface for sending messages. This class should not be used directly, use one of subclasses instead. """ def __init__(self, config, loglevel): super(PcepDumper, self).__init__(config) self.logger = logging.getLogger() handler = logging.StreamHandler() handler.setFormatter( logging.Formatter( "%(asctime)s: %(levelname)s;%(message)s", "%Y-%m-%d %H:%M:%S", ) ) self.logger.addHandler(handler) self.loglevel = loglevel self.enabled = True @property def loglevel(self): return self.logger.getEffectiveLevel() @loglevel.setter def loglevel(self, level): "Set the log level of root logger" self.logger.setLevel(level) def shutdown(self): self.logger.info("Bringing PCE down") self.peer.shutdown() self.bus.stop() def exit(self): if not self.peer: return 0 try: self.shutdown() if self.peer.active: self.logger.info('Waiting on bus stop') self.bus.join() self.logger.info("All connections are down.") return 0 except Exception as err: self.logger.exception("Clean shutdown failed: %s" % err) return 1 def on_message(self, session, eventargs): # Get date and time dt = datetime.datetime.now() # Dont print KeepAlive messages in quiet mode if self.loglevel == logging.WARN and \ isinstance(eventargs['message'], _M.Keepalive): return else: print("=======\n%s: Message on %s:\n%s\n=======" % ( dt.strftime("%Y-%m-%d %H:%M:%S"), eventargs['session'], eventargs['message'].show( { 'msg_sep':'\n', 'obj_sep':'\n', 'item_sep':'\n', 'data':'\n' }, prefix='' ) )) class ManualOpener(_P.base.Opener): def on_connect(self, peer, eventargs): # Copy paste from peer.base.Opener, only difference is that we don't # send Open message session = eventargs['session'] if session.is_server(): return # TODO: duplicate sessions -> send err 9 close session[_P.base.Opener.STATE_STATE] = _P.base.Opener.OS_OPENWAIT session[_P.base.Opener.STATE_PCEPTYPE] = \ self._get_pceptype(peer, session) session[_P.base.Opener.STATE_SESSIONID] = \ self._get_sessionid(peer, session) session[_P.base.Keeper.STATE_KEEPALIVE] = self._get_keepalive(peer, session) session[_P.base.Keeper.STATE_DEADTIMER] = self._get_deadtimer(peer, session) session[_P.base.Opener.STATE_OPENWAIT] = \ _P.base.resolve_timeout(_P.base.Opener.OPENWAIT) def on_message(self, peer, eventargs): session = eventargs['session'] message = eventargs['message'] state = session[_P.base.Opener.STATE_STATE] is_open = isinstance(message, _M.Open) if is_open: open = message.get(_O.Open) if state == _P.base.Opener.OS_OPENWAIT: if not open: _LOGGER.error('Open message without Open object received') return session[_P.base.Opener.STATE_REMOTE_OPEN] = open accept_open = self._accept_open(peer, session, open) if accept_open is True: del session[_P.base.Opener.STATE_OPENWAIT] session[_P.base.Opener.STATE_REMOTE_OK] = True session[_P.base.Opener.STATE_STATE] = \ _P.base.Opener.OS_KEEPWAIT else: _LOGGER.error('Open message received in %s state' % state) elif isinstance(message, _M.Keepalive): if state == _P.base.Opener.OS_KEEPWAIT: del session[_P.base.Opener.STATE_KEEPWAIT] self.ka_rec = True elif state != _P.base.Opener.OS_UP: _LOGGER.error('Keepalive message received in %s state' % state) def send_open(self, keepalive, deadtimer, session_id): send(_M.Open(_O.Open( keealive=keepalive, deaedtimer=deadtimer, session_id=session_id ))) self.open_sended = True def send_keepalive(self, peer, session): send(_M.Keepalive()) self._session_open(peer, session) session[_P.base.Opener.STATE_STATE] = _P.base.Opener.OS_UP class PccDumper(PcepDumper): """ PCC manager. See PcepDumper for more info. """ def __init__(self, config, loglevel, manual_open): super(PccDumper, self).__init__(config, loglevel) self.peer = _P.Pcc('PDE', self) self.peer.add_handler(self) self.role = _S.Node.ROLE_PCC if manual_open: self.peer.remove_handler(_P.base.Opener) self.opener = ManualOpener() self.peer.add_handler(self.opener) def send_open(keepalive=30, deadtimer=120, session_id=1): self.opener.send_open(keepalive, deadtimer, session_id) def send_keepalive(): self.opener.send_keepalive(self.peer, self.peer.sessions[0]) self.send_open = send_open self.send_keepalive = send_keepalive def start(self, address, port): self.logger.info("Starting dumper on %s:%s" % (address, port)) address = self.address_from(address) local = '::' if self.address_is_ipv6(address) else '0.0.0.0' self.peer_node = self.get_node(self.role, 'DumpNode', self.address_from(local) ) self.mark_node = self.get_node(self.role, 'MarkNode', self.address_from(address), port ) self.peer.create_session(self.peer_node, self.mark_node) if not self.bus.is_alive(): self.bus.start() def send_open(self, keepalive=30, deadtimer=120, session_id=1): send(_M.Open(_O.Open( keepalive=keepalive, deadtimer=deadtimer, session_id=session_id ))) self.opened = True def send_keepalive(self): send(_M.Keepalive()) class PceDumper(PcepDumper): """ PCE manager. See PcepDumper for more info. """ def __init__(self, config, loglevel): super(PceDumper, self).__init__(config, loglevel) self.peer = _P.Pce('PDE', self) self.peer.add_handler(self) self.role = _S.Node.ROLE_PCE def start(self, address, port): self.logger.info("Starting dumper on %s:%s" % (address, port)) address = self.address_from(address) self.peer_node = self.get_node(self.role, 'DumpNode', address, port) self.peer.create_server(self.peer_node) if not self.bus.is_alive(): self.bus.start() def ipaddr_port_type(data): try: ip, port = data.split('@') except ValueError: ip = data port = _S.PCEP_PORT try: ip = _P.Context.address_from(ip) except ValueError: raise argparse.ArgumentTypeError('Invalid IP address: %s' % ip) try: port = int(port) except ValueError: raise argparse.ArgumentTypeError('Invalid port: %s' % port) return ip, port def exit(): sys.exit(dumper.exit()) def process_arguments(arguments): """ Initialize command line arguments parser. """ parser = argparse.ArgumentParser( description='Command line utility for running PCE or PCC and printing \ incomming messages', version='%(prog)s 0.1 Copyright (c) 2012,2013 Cisco Systems, Inc. and others. All rights reserved.', usage='%(prog)s [OPTION]...', ) group_type = parser.add_argument_group() group_type.add_argument( '-c', '--pcc', action='store_const', dest='pcc', const=True, default=False, help='Enable PCC mode (PCE is default)', ) group_parameters = parser.add_argument_group() group_parameters.add_argument( '-a', '--address', action='store', type=ipaddr_port_type, dest='ip_port', default=(_P.Context.address_from(DEFAULT_IP), _S.PCEP_PORT), metavar='IP@PORT', help='the ip address and port to which is this server bound' ) group_parameters.add_argument( '-i', '--id', action='store', type=int, dest=_P.base.Opener.CONFIG_NODE_ID, metavar='ID', help='the node ID for PCE', ) group_parameters.add_argument( '-k', '-ka', '--keepalive', action='store', type=int, dest=_P.base.Keeper.CONFIG_KEEPALIVE, default=KEEPALIVE, metavar='TIMEOUT', help='in seconds, value of the desired KeepAlive timer' ) group_parameters.add_argument( '-d', '--deadtimer', action='store', type=int, dest=_P.base.Keeper.CONFIG_DEADTIMER, default=None, metavar='TIMEOUT', help='in seconds, value of the desired deadtimer' ) group_open = parser.add_argument_group() group_open.add_argument( '-o', '--open', action='store_const', const=True, dest='open', default=False, help='Force manual session opening (pcc only)' ) group_mode = parser.add_argument_group() # TODO: Maybe raise error when specified more of these? # Now, last one specified is used. group_mode.add_argument( '--stateful', action='store_const', const=_P.base.Opener.PCEPTYPE_STATEFUL, dest=_P.base.Opener.CONFIG_PCEPTYPE, default=_P.base.Opener.PCEPTYPE_STATELESS, help='passive stateful' ) group_mode.add_argument( '--active', action='store_const', const=_P.base.Opener.PCEPTYPE_STATEFULA, dest=_P.base.Opener.CONFIG_PCEPTYPE, default=_P.base.Opener.PCEPTYPE_STATELESS, help='active stateful' ) group_mode.add_argument( '--versioned', action='store', dest=_P.base.Opener.CONFIG_DB_VERSION, type=int, default=0, metavar='VERSION', help='version number for versioned stateful' ) group_output = parser.add_argument_group() group_output.add_argument( '--quiet', action='store_const', const=logging.WARN, dest='loglevel', default=logging.INFO, help='Suppres WARN log level (INFO is default)', ) group_output.add_argument( '--debug', action='store_const', const=logging.DEBUG, dest='loglevel', default=logging.INFO, help='Suppres DEBUG log level (INFO is default)', ) # Parse arguments and create dict parsed_args = vars(parser.parse_args(arguments)) # If keepalive timer was set and deadtimer was not, reset it to # 4 times keepalive (max value is 255) parsed_args[_P.base.Keeper.CONFIG_DEADTIMER] = \ parsed_args[_P.base.Keeper.CONFIG_DEADTIMER]or \ parsed_args[_P.base.Keeper.CONFIG_KEEPALIVE] * 4 if \ parsed_args[_P.base.Keeper.CONFIG_KEEPALIVE] * 4 < 255 else \ 255 # Print warning, if user forces deadtimer other than keepalive * 4 if parsed_args[_P.base.Keeper.CONFIG_DEADTIMER] != \ parsed_args[_P.base.Keeper.CONFIG_KEEPALIVE] * 4: sys.stderr.write('Warrning: deadtimer (%s) should be 4 times bigger '\ 'than keepalive (%s)\n' \ % (parsed_args[_P.base.Keeper.CONFIG_DEADTIMER], parsed_args[_P.base.Keeper.CONFIG_KEEPALIVE])) # Set stateful type if versioned and stateless if parsed_args[_P.base.Opener.CONFIG_DB_VERSION] and \ parsed_args[_P.base.Opener.CONFIG_PCEPTYPE] == \ _P.base.Opener.PCEPTYPE_STATELESS: parsed_args[_P.base.Opener.CONFIG_PCEPTYPE] = \ _P.base.Opener.PCEPTYPE_STATEFUL return parsed_args def send(msg, session=0): """ UI function for messages sending. """ try: dumper.peer.sessions[session].send(msg) except(IndexError): sys.stderr.write('Could not send message, ' + \ 'sessions %s doesn\'t exists\n' % session) def quit(): exit() if __name__ == '__main__': args = process_arguments(sys.argv[1:]) # Determine role (PCE or PCC) role = _S.Node.ROLE_PCC if args['pcc'] else _S.Node.ROLE_PCE del args['pcc'] # Log level log_level = args['loglevel'] del args['loglevel'] manual_open = args['open'] del args['open'] # Additional arguments are used for config construction config_session = _P.pce.Pce.CONFIG_SESSION_CONFIG if \ role == _S.Node.ROLE_PCE else _P.pcc.Pcc.CONFIG_SESSION_CONFIG config = { config_session: { key : value for key, value in args.iteritems() if \ value is not None }, } # Create and start dumper if role == _S.Node.ROLE_PCC: dumper = PccDumper(config, log_level, manual_open) else: dumper = PceDumper(config, log_level) dumper.start(args['ip_port'][0], args['ip_port'][1]) code.interact(banner=None, local=globals()) sys.exit(dumper.exit()) print "Done."