#! /usr/bin/env python ## PyRT: Python Routeing Toolkit ## MRTd module: provides MRT writing/reading using protocol ## parsers from appropriate module. ## Copyright (C) 2001 Richard Mortier , Sprint ATL ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License as ## published by the Free Software Foundation; either version 2 of the ## License, or (at your option) any later version. ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ## General Public License for more details. ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA ## 02111-1307 USA # # $Id: mrtd.py,v 1.24 2002/02/26 01:57:03 mort Exp $ # import os, time, struct, getopt, sys, bgp, isis, math, pprint from mutils import * #------------------------------------------------------------------------------- DEFAULT_FILE = "/tmp/mrtd.mrtd" DEFAULT_SIZE = 50*1024*1024 MIN_FILE_SZ = 50*1024 BUF_SZ = 8192*8 INDENT = " " COMMON_HDR_LEN = 12 TABLE_DUMP_HDR_LEN = 4 BGP_SUBTYPE_HDR_LEN = 12 BGP4MP_SUBTYPE_HDR_LEN = 16 BGP4PY_SUBTYPE_HDR_LEN = 20 ISIS_SUBTYPE_HDR_LEN = 0 ISIS2_SUBTYPE_HDR_LEN = 4 ################################################################################ DLIST = [] # # MRTD values # MSG_TYPES = { 0L: "NULL", 1L: "START", # sender is starting up 2L: "DIE", # receiver should shutdown 3L: "I_AM_DEAD", # sender is shutting down 4L: "PEER_DOWN", # sender's peer is down 5L: "PROTOCOL_BGP", 6L: "PROTOCOL_RIP", 7L: "PROTOCOL_IDRP", 8L: "PROTOCOL_RIPNG", 9L: "PROTOCOL_BGP4PLUS", 10L: "PROTOCOL_BGP4PLUS_01", 11L: "PROTOCOL_OSPF2", 12L: "TABLE_DUMP", # routing table dump 16L: "PROTOCOL_BGP4MP", # Zebra BGP4 17L: "PROTOCOL_BGP4PY", # PyRT BGP4 (ext. time stamp) 32L: "PROTOCOL_ISIS", # ISIS 33L: "PROTOCOL_ISIS2", # ISIS + ext. time stamp } DLIST = DLIST + [MSG_TYPES] TABLE_DUMP_SUBTYPES = bgp.AFI_TYPES DLIST = DLIST + [TABLE_DUMP_SUBTYPES] BGP_SUBTYPES = { 0L: "NULL", 1L: "UPDATE", # raw update 2L: "PREF_UPDATE", # (T,L,V) prefs. followed by raw update 3L: "STATE_CHANGE", # state change 4L: "SYNC", # XXX RMM XXX apparently bogo-extensions for some of RIS data? # See, eg., updates.20000814.1631 5L: "BOGO_RIS_EXTN_1", 7L: "BOGO_RIS_EXTN_2", # XXX RMM XXX extensions for other raw messages 129L: "OPEN", 131L: "NOTIFICATION", 132L: "KEEPALIVE", 133L: "ROUTE_REFRESH", } DLIST = DLIST + [BGP_SUBTYPES] BGP4MP_SUBTYPES = { 0L: "STATE_CHANGE", 1L: "MESSAGE", 2L: "ENTRY", 3L: "SNAPSHOT" } DLIST = DLIST + [BGP4MP_SUBTYPES] BGP4PY_SUBTYPES = { 0L: "STATE_CHANGE", 1L: "MESSAGE", 2L: "ENTRY", 3L: "SNAPSHOT" } DLIST = DLIST + [BGP4PY_SUBTYPES] ISIS_SUBTYPES = isis.MSG_TYPES DLIST = DLIST + [ISIS_SUBTYPES] # # GNU Zebra dump specific values # ZEBRA_STATES = { 1L: "IDLE", 2L: "CONNECT", 3L: "ACTIVE", 4L: "OPENSENT", 5L: "OPENCONFIRM", 6L: "ESTABLISHED" } DLIST = DLIST + [ZEBRA_STATES] ZEBRA_EVENTS = { 1L: "BGP_START", 2L: "BGP_STOP", 3L: "BGP_TRANS_CONN_OPEN", 4L: "BGP_TRANS_CONN_CLOSED", 5L: "BGP_TRANS_CONN_OPEN_FAILED", 6L: "BGP_TRANS_FATAL_ERROR", 7L: "CONNECTRETRY_TIMER_EXPIRED", 8L: "HOLD_TIMER_EXPIRED", 9L: "KEEPALIVE_TIMER_EXPIRED", 10L: "RCV_OPEN_MSG", 11L: "RCV_KEEPALIVE_MSG", 12L: "RCV_UPDATE_MSG", 13L: "RCV_NOTIFICATION_MSG" } DLIST = DLIST + [ZEBRA_EVENTS] #------------------------------------------------------------------------------- for d in DLIST: for k in d.keys(): d[ d[k] ] = k ################################################################################ def parseBgp4mpMrtHdr(hdr, verbose=1, level=0): src_as, dst_as, ifc, afi, src_ip, dst_ip = struct.unpack(">HHHH LL", hdr) if verbose > 0: if afi == bgp.AFI_TYPES["IP"]: print level*INDENT + "AS(src): %d, AS(dst): %d" %\ (src_as, dst_as) print level*INDENT + "ifc idx: %d, AFI: %s" %\ (ifc, bgp.AFI_TYPES[afi]) print level*INDENT + "IP(src): %s, IP(dst): %s" %\ (id2str(src_ip), id2str(dst_ip)) else: print INDENT*level + "[ UNKNOWN ADDRESS FAMILY", `afi`, "]" return src_as, dst_as, ifc, afi, src_ip, dst_ip #------------------------------------------------------------------------------- def parseBgp4pyMrtHdr(hdr, verbose=1, level=0): src_as, dst_as, ifc, afi, src_ip, dst_ip, ts_frac =\ struct.unpack(">HHHH LLL", hdr) if verbose > 0: if afi == bgp.AFI_TYPES["IP"]: print level*INDENT + "AS(src): %d, AS(dst): %d" %\ (src_as, dst_as) print level*INDENT + "ifc idx: %d, AFI: %s" %\ (ifc, bgp.AFI_TYPES[afi]) print level*INDENT + "IP(src): %s, IP(dst): %s" %\ (id2str(src_ip), id2str(dst_ip)) else: print INDENT*LEVEL + "[ UNKNOWN ADDRESS FAMILY", `afi`, "]" return src_as, dst_as, ifc, afi, src_ip, dst_ip, ts_frac ################################################################################ class EOFExc(Exception): pass class ParseExc(Exception): pass #------------------------------------------------------------------------------- class Mrtd: _extn_fmt = ".%Y-%m-%d_%H.%M.%S" def __init__(self, file_pfx=DEFAULT_FILE, file_mode="w+b", file_size=None, mrt_type=None, msg_src=None): self._mrt_type = mrt_type self._msg_src = msg_src # message source object, typed by mrt_type self._file_pfx = file_pfx if not mrt_type: self._file_name = file_pfx else: self._file_name = file_pfx +\ time.strftime(Mrtd._extn_fmt, time.gmtime()) self._file_size = file_size self._file_mode = file_mode self._of = open(self._file_name, file_mode) self._read = "" #--------------------------------------------------------------------------- def close(self): # XXX RMM XXX this should possibly be __del__() method? self._of.flush() self._of.close() def write(self, msg): if self._of.tell() + len(msg) > self._file_size: self._of.close() self._file_name = self._file_pfx +\ time.strftime(Mrtd._extn_fmt, time.gmtime()) self._of = open(self._file_name, self._file_mode) self._of.write(msg) self._of.flush() def read(self): if len(self._read) < COMMON_HDR_LEN: self._read = self._read + self._of.read(BUF_SZ) if len(self._read) < COMMON_HDR_LEN: raise EOFExc ptime, ptype, psubtype, plen =\ struct.unpack(">LHHL", self._read[:COMMON_HDR_LEN]) plen = int(plen) phdr = self._read[:COMMON_HDR_LEN] self._read = self._read[COMMON_HDR_LEN:] if len(self._read) < plen: self._read = self._read + self._of.read(BUF_SZ) if len(self._read) < plen: raise EOFExc pdata = self._read[:plen] self._read = self._read[plen:] return (ptime, ptype, psubtype, plen, phdr, pdata) def parse(self, msg, verbose=1, level=0): (ptime, ptype, psubtype, plen, phdr, pdata) = msg if verbose > 1: print prtbin(level*INDENT, phdr) if verbose > 0: print level*INDENT + "[ " + time.ctime(ptime) + " ]" print level*INDENT + "MRT packet: len: %d, type: %s, subtype:" %\ (plen, MSG_TYPES[ptype]), try: if ptype == MSG_TYPES["PROTOCOL_BGP4MP"]: print BGP4MP_SUBTYPES[psubtype] elif ptype == MSG_TYPES["PROTOCOL_BGP4PY"]: print BGP4MP_SUBTYPES[psubtype] elif ptype == MSG_TYPES["PROTOCOL_BGP"]: print BGP_SUBTYPES[psubtype] elif ptype == MSG_TYPES["PROTOCOL_ISIS"]: print ISIS_SUBTYPES[psubtype] elif ptype == MSG_TYPES["PROTOCOL_ISIS2"]: print ISIS_SUBTYPES[psubtype] elif ptype == MSG_TYPES["TABLE_DUMP"]: print TABLE_DUMP_SUBTYPES[psubtype] except (KeyError): if verbose: print level*INDENT +\ '[ *** Unsupported subtype: %d *** ]' % psubtype return None if ptype == MSG_TYPES["PROTOCOL_BGP"]: rv = self.parseBgpMsg(psubtype, plen, pdata, verbose, level+1) elif ptype == MSG_TYPES["PROTOCOL_BGP4MP"]: rv = self.parseBgp4mpMsg(psubtype, plen, pdata, verbose, level+1) elif ptype == MSG_TYPES["PROTOCOL_BGP4PY"]: rv = self.parseBgp4pyMsg(psubtype, plen, pdata, verbose, level+1) elif ptype == MSG_TYPES["PROTOCOL_ISIS"]: rv = self.parseIsisMsg(plen, pdata, verbose, level+1) elif ptype == MSG_TYPES["PROTOCOL_ISIS2"]: rv = self.parseIsis2Msg(plen, pdata, verbose, level+1) elif ptype == MSG_TYPES["TABLE_DUMP"]: rv = self.parseTableDump(psubtype, plen, pdata, verbose, level+1) else: rv = {"T": None, "L": 0, "V": None, "H": {"TIME":0L}} if verbose: print level*INDENT +\ "[ *** Unsupported message type [ '%s' ] *** ]" %\ self._file_name print (level+1)*INDENT + 'time:', time.ctime(ptime) print (level+1)*INDENT + 'header:', str2hex(phdr) print (level+1)*INDENT + 'data:', str2hex(pdata) rv["H"]["TIME"] = rv["H"]["TIME"] + ptime if verbose: print (level+1)*INDENT +\ "extended timestamp: %f\n" % rv["H"]["TIME"] return rv def mkHdr(self, subtype, msg_len): ts = time.time() hdr = struct.pack(">LHHL", int(ts), self._mrt_type, subtype, msg_len) return (ts, hdr) #--------------------------------------------------------------------------- def writeBgpMsg(self, msg_type, msg_len, msg): subtype = BGP_SUBTYPES[bgp.MSG_TYPES[msg_type]] (ts, hdr) = self.mkHdr(subtype, msg_len+BGP_SUBTYPE_HDR_LEN) src_as = self._msg_src._bgp_peer_as src_ip = self._msg_src._bgp_peer_id dst_as = self._msg_src._bgp_as dst_ip = self._msg_src._bgp_id msg = struct.pack(">%ds HLHL %ds" % (COMMON_HDR_LEN, msg_len), hdr, src_as, src_ip, dst_as, dst_ip, msg) self.write(msg) def parseBgpMsg(self, psubtype, plen, pdata, verbose=1, level=0): rv = { "T": MSG_TYPES["PROTOCOL_BGP"], "ST": psubtype, "L": plen, "H": { 'TIME': 0L }, "V": {} } if psubtype in (BGP_SUBTYPES['BOGO_RIS_EXTN_1'], BGP_SUBTYPES['BOGO_RIS_EXTN_2']): print INDENT*level + '[ *** skipping *** ]' return rv if verbose > 1: print prtbin(level*INDENT, pdata[:BGP_SUBTYPE_HDR_LEN]) try: src_as, src_ip, dst_as, dst_ip =\ struct.unpack(">HLHL", pdata[:BGP_SUBTYPE_HDR_LEN]) rv["H"]["SRC_AS"] = src_as rv["H"]["SRC_IP"] = src_ip rv["H"]["DST_AS"] = dst_as rv["H"]["DST_IP"] = dst_ip except (struct.error): print INDENT*level + '[ *** struct error: bogus RIS data?! *** ]' if psubtype == BGP_SUBTYPES['STATE_CHANGE']: src, dst = struct.unpack(">HH", pdata[-4:]) print INDENT*level +\ 'state change: %s -> %s' %\ (ZEBRA_STATES[src], ZEBRA_STATES[dst]) return rv if verbose > 0: print level*INDENT + "IP(src): %s, AS(src): %d" %\ (id2str(src_ip), src_as) print level*INDENT + "IP(dst): %s, AS(dst): %d" %\ (id2str(dst_ip), dst_as) pdata = pdata[BGP_SUBTYPE_HDR_LEN:] if pdata[0:len(bgp.BGP_MARKER)] != bgp.BGP_MARKER: raise ParseExc else: msg_len, msg_type =\ struct.unpack(">HB", pdata[len(bgp.BGP_MARKER): len(bgp.BGP_MARKER)+3]) if verbose > 1: print prtbin(level*INDENT, pdata[:bgp.BGP_HDR_LEN]) if verbose > 0: print level*INDENT + "BGP message type: %s len=%d" %\ (bgp.MSG_TYPES[msg_type], msg_len) rv["V"] = bgp.parseBgpPdu(msg_type, msg_len, pdata, verbose, level+1) return rv #--------------------------------------------------------------------------- # NOTE: Bgp4mp is essentially GNU Zebra specific; as of version 0.89 and # 0.91a, Zebra's bgpd appears to have a couple of bugs wrt. dumping MRT # traces -- workarounds to patch things up before calling into the bgp # module are below. def writeBgp4mpMsg(self, ptype, plen, pkt): subtype = BGP4MP_SUBTYPES["MESSAGE"] (ts, hdr) = self.mkHdr(subtype, plen+BGP4MP_SUBTYPE_HDR_LEN) src_as = self._msg_src._bgp_peer_as dst_as = self._msg_src._bgp_as src_ip = self._msg_src._bgp_peer_id dst_ip = self._msg_src._bgp_id msg = struct.pack(">%ds HHHHLL %ds" % (COMMON_HDR_LEN, plen), hdr, src_as, dst_as, 0, bgp.AFI_TYPES["IP"], src_ip, dst_ip, pkt) self.write(msg) def parseBgp4mpMsg(self, psubtype, plen, pdata, verbose=1, level=0): rv = { "T": MSG_TYPES["PROTOCOL_BGP4MP"], "ST": psubtype, "L": plen, "H": { "TIME": 0L }, "V": {} } if psubtype == BGP4MP_SUBTYPES["STATE_CHANGE"]: if verbose > 1: print prtbin(level*INDENT, pdata) # XXX HACK get occasional 8 byte packets dumped, which I don't # _believe_ to be valid; seem to have a "valid" state changes in # though. Bizarre. if plen == 8: if verbose > 0: print level*INDENT +\ "[ *** BOGUS 8 byte PAYLOAD PACKET *** ]" pdata = pdata[4:] else: src_as, dst_as, ifc, afi, src_ip, dst_ip =\ parseBgp4mpMrtHdr(pdata[0:BGP4MP_SUBTYPE_HDR_LEN], verbose, level) rv["H"]["SRC_AS"] = src_as rv["H"]["DST_AS"] = dst_as rv["H"]["SRC_IP"] = src_ip rv["H"]["DST_IP"] = dst_ip rv["H"]["IFC"] = ifc rv["H"]["AFI"] = afi pdata = pdata[BGP4MP_SUBTYPE_HDR_LEN:] start_st, end_st = struct.unpack(">HH", pdata) rv["V"] = (start_st, end_st) if verbose > 0: print level*INDENT + "%s -> %s\n" %\ (ZEBRA_STATES[start_st], ZEBRA_STATES[end_st]) elif psubtype == BGP4MP_SUBTYPES["MESSAGE"]: # XXX HACK similarly, get either (a) 4 null bytes instead of MRT # header, or (b) bogus MRT header for subtype MESSAGE. Skip them. if pdata[0:4+bgp.BGP_MARKER_LEN] == ("\000\000\000\000" + bgp.BGP_MARKER): if verbose > 1: print prtbin(level*INDENT, pdata[:4]) if verbose > 0: print level*INDENT + "[ *** BOGUS NULL MRT HEADER *** ]" pdata = pdata[4:] else: if verbose > 1: print prtbin(level*INDENT, pdata[:BGP4MP_SUBTYPE_HDR_LEN]) src_as, dst_as, ifc, afi, src_ip, dst_ip =\ parseBgp4mpMrtHdr(pdata[0:BGP4MP_SUBTYPE_HDR_LEN], verbose, level) rv["H"]["SRC_AS"] = src_as rv["H"]["DST_AS"] = dst_as rv["H"]["SRC_IP"] = src_ip rv["H"]["DST_IP"] = dst_ip rv["H"]["IFC"] = ifc rv["H"]["AFI"] = afi pdata = pdata[BGP4MP_SUBTYPE_HDR_LEN:] msg_len, msg_type =\ struct.unpack(">HB", pdata[bgp.BGP_MARKER_LEN:bgp.BGP_HDR_LEN]) rv["V"] = bgp.parseBgpPdu(msg_type, msg_len, pdata, verbose, level) else: print level*INDENT + "[ *** SUBTYPE: %d NOT PARSED *** ]" % psubtype return rv #--------------------------------------------------------------------------- def writeBgp4pyMsg(self, ptype, plen, pkt): subtype = BGP4PY_SUBTYPES["MESSAGE"] (ts, hdr) = self.mkHdr(subtype, plen+BGP4PY_SUBTYPE_HDR_LEN) src_as = self._msg_src._bgp_peer_as dst_as = self._msg_src._bgp_as src_ip = self._msg_src._bgp_peer_id dst_ip = self._msg_src._bgp_id (ts_frac, ts_int) = math.modf(ts) msg = struct.pack(">%ds HH HH LLL %ds" % (COMMON_HDR_LEN, plen), hdr, src_as, dst_as, 0, bgp.AFI_TYPES["IP"], src_ip, dst_ip, ts_frac*1000000, pkt) self.write(msg) def parseBgp4pyMsg(self, psubtype, plen, pdata, verbose=1, level=0): rv = { "T": MSG_TYPES["PROTOCOL_BGP4PY"], "ST": psubtype, "L": plen, "H": { "TIME": 0L }, "V": {} } if verbose > 1: print prtbin(level*INDENT, pdata[:BGP4PY_SUBTYPE_HDR_LEN]) src_as, dst_as, ifc, afi, src_ip, dst_ip, ts_frac =\ parseBgp4pyMrtHdr(pdata[0:BGP4PY_SUBTYPE_HDR_LEN], verbose, level) rv["H"]["SRC_AS"] = src_as rv["H"]["DST_AS"] = dst_as rv["H"]["SRC_IP"] = src_ip rv["H"]["DST_IP"] = dst_ip rv["H"]["IFC"] = ifc rv["H"]["AFI"] = afi rv["H"]["TIME"] = ts_frac*0.000001 pdata = pdata[BGP4PY_SUBTYPE_HDR_LEN:] if psubtype == BGP4PY_SUBTYPES["STATE_CHANGE"]: start_st, end_st = struct.unpack(">HH", pdata) rv["V"] = (start_st, end_st) if verbose > 0: print level*INDENT + "%s -> %s\n" %\ (ZEBRA_STATES[start_st], ZEBRA_STATES[end_st]) elif psubtype == BGP4PY_SUBTYPES["MESSAGE"]: msg_len, msg_type =\ struct.unpack(">HB", pdata[bgp.BGP_MARKER_LEN:bgp.BGP_HDR_LEN]) rv["V"] = bgp.parseBgpPdu(msg_type, msg_len, pdata, verbose, level) else: print level*INDENT + "[ *** SUBTYPE: %d NOT PARSED *** ]" % psubtype return rv #--------------------------------------------------------------------------- def writeIsisMsg(self, ptype, plen, pkt): (ts, hdr) = self.mkHdr(ptype, plen+ISIS_SUBTYPE_HDR_LEN) msg = struct.pack(">%ds %ds" % (len(hdr), len(pkt)), hdr, pkt) self.write(msg) def parseIsisMsg(self, plen, pdata, verbose=1, level=0): rv = { "T": MSG_TYPES["PROTOCOL_ISIS"], "ST": 0L, "L": plen, "H": { "TIME": 0L }, "V": {} } rv["V"].update(isis.parseIsisMsg(plen, pdata, verbose, level)) return rv #--------------------------------------------------------------------------- def writeIsis2Msg(self, ptype, plen, pkt): (ts, hdr) = self.mkHdr(ptype, plen+ISIS2_SUBTYPE_HDR_LEN) (ts_frac, ts_int) = math.modf(ts) msg = struct.pack(">%ds L %ds" % (COMMON_HDR_LEN, len(pkt)), hdr, ts_frac*1000000, pkt) self.write(msg) def parseIsis2Msg(self, plen, pdata, verbose=1, level=0): rv = { "T": MSG_TYPES["PROTOCOL_ISIS2"], "ST": 0L, "L": plen, "H": { "TIME": 0L }, "V": {} } (ts_frac, ) = struct.unpack(">L", pdata[:ISIS2_SUBTYPE_HDR_LEN]) rv["H"]["TIME"] = ts_frac*0.000001 rv["V"].update(isis.parseIsisMsg(plen, pdata[ISIS2_SUBTYPE_HDR_LEN:], verbose, level)) return rv #--------------------------------------------------------------------------- def parseTableDump(self, psubtype, plen, pdata, verbose=1, level=0): rv = { "T": MSG_TYPES["TABLE_DUMP"], "ST": psubtype, "L": plen, "H": { "TIME": 0L }, "V": [] } if verbose > 1: print prtbin(level*INDENT, pdata[:TABLE_DUMP_HDR_LEN]) view, seqno = struct.unpack(">HH", pdata[:TABLE_DUMP_HDR_LEN]) rv["H"]["VIEW"] = view rv["H"]["SEQNO"] = seqno if verbose: print INDENT*level + "view: %d, seqno: %d" % (view, seqno) pdata = pdata[TABLE_DUMP_HDR_LEN:] while len(pdata): erv = bgp.parseTableEntry(plen, pdata, verbose, level) pdata = pdata[erv["L"]:] rv["V"].append(erv) return rv #--------------------------------------------------------------------------- ################################################################################ if __name__ == "__main__": VERBOSE = 1 file_name = DEFAULT_FILE file_size = -1 #--------------------------------------------------------------------------- def usage(): print """Usage: %s [ options ]: -h|--help : Help -q|--quiet : Be quiet -v|--verbose : Be verbose -f|--file : Set file name to parse (def: %s) -z|--file-size : Set size of output file(s)""" %\ (os.path.basename(sys.argv[0]), DEFAULT_FILE) sys.exit(0) #--------------------------------------------------------------------------- if len(sys.argv) < 2: usage() try: try: opts, args = getopt.getopt(sys.argv[1:], "hqvf:z:", ("help", "quiet", "verbose", "file=", "size=" )) except (getopt.error): usage() for (x, y) in opts: if x in ('-h', '--help'): usage() elif x in ('-q', '--quiet'): VERBOSE = 0 elif x in ('-v', '--verbose'): VERBOSE = 2 elif x in ('-f', '--file'): file_name = y elif x in ('-z', '--size'): file_size = string.atof(y) else: usage() #----------------------------------------------------------------------- mrt = Mrtd(file_name, "rb", file_size) while 1: rv = mrt.parse(mrt.read(), VERBOSE) except (EOFExc): print "End of file" except (KeyboardInterrupt): print "Interrupted" mrt.close() sys.exit(0) ################################################################################ ################################################################################