#! /usr/bin/env python

##     PyRT: Python Routeing Toolkit

##     Manipulates MRTd dumps, splicing one or more dumps together in
##     time order, and outputting the result given start/end times,
##     and given a certain 'chunk' size.

##     Copyright (C) 2001 Richard Mortier <mort@sprintlabs.com>, 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: table-dump.py,v 1.3 2002/02/26 01:57:03 mort Exp $
#

import time, getopt, sys, string, os, pprint
import mrtd, bgp
from mutils import *

VIEW_NO      = 0x00
STATUS       = 0x01
MINS_TO_SECS = 60

################################################################################

def processEntry(rv):

    def mkStr(attr, aval):
    
        flags = 0
        if aval["FLAGS"]['optional']:
            flags = flags | (1<<7)
        if aval["FLAGS"]['transitive']:
            flags = flags | (1<<6)
        if aval["FLAGS"]['partial']:
            flags = flags | (1<<5)
        if aval["FLAGS"]['extlen']:
            flags = flags | (1<<4)

        rflgs = struct.pack('BB', flags & 0xff, attr & 0xff)
        if   attr == bgp.PATH_ATTRIBUTES["ORIGIN"]:
            rval = struct.pack('B', aval["V"])
                        
        elif attr == bgp.PATH_ATTRIBUTES["AS_PATH"]:
            if aval["V"] != None:
                for pseg in aval["V"]:
                    tl = struct.pack('BB', pseg["T"], pseg["L"])
                    val = ""
                    for v in pseg["V"]:
                        val = struct.pack('>%dsH' % len(val), val, v)
                rval = struct.pack('%ds%ds' % (len(tl), len(val)), tl, val)
            else:
                rval = struct.pack('BB',
                                   bgp.AS_PATH_SEG_TYPES['SEQUENCE'], 0)

        elif attr == bgp.PATH_ATTRIBUTES["NEXT_HOP"]:
            rval = struct.pack('>L', aval["V"])

        elif attr == bgp.PATH_ATTRIBUTES["MULTI_EXIT_DISCRIMINATOR"]:
            rval = struct.pack('>L', aval["V"])
            
        elif attr == bgp.PATH_ATTRIBUTES["LOC_PREF"]:
            rval = struct.pack('>L', aval["V"])

        elif attr == bgp.PATH_ATTRIBUTES["ATOMIC_AGGR"]:
            rval = ""

        elif attr == bgp.PATH_ATTRIBUTES["AGGREGATOR"]:
            rval = struct.pack(">HL", aval["V"][0], aval["V"][1])

        elif attr == bgp.PATH_ATTRIBUTES["COMMUNITY"]:
            rval = ""
            for v in aval["V"]:
                rval = struct.pack('>%ds4s' % len(rval), rval, v)

        elif attr == bgp.PATH_ATTRIBUTES["ORIGINATOR_ID"]:
            rval = struct.pack('>L', aval["V"])

        elif attr == bgp.PATH_ATTRIBUTES["CLUSTER_LIST"]:
            rval = ""
            for v in aval["V"]:
                rval = struct.pack('>%dsL' % len(rval), rval, v)

        else:
            print '[ *** Unsupported attribute:', \
                  bgp.PATH_ATTRIBUTES[attr], '*** ]'
            return None

        if aval["FLAGS"]['extlen']:            
            rlen = struct.pack('>H', len(rval) & 0xffff)
        else:
            rlen = struct.pack('>B', len(rval) & 0xff)

        return struct.pack('%ds%ds%ds' % (len(rflgs), len(rlen), len(rval)),
                           rflgs, rlen, rval)

    msg_tm = rv["H"]["TIME"]
    src_as, src_ip = rv["H"]["SRC_AS"], rv["H"]["SRC_IP"]
    ifc, afi = rv["H"]["IFC"], rv["H"]["AFI"]

    for pfx in rv["V"]["V"]["UNFEASIBLE"]:
        del TABLE[pfx]

    astr = ""
    for attr in rv["V"]["V"]["PATH_ATTRS"].keys():
        rstr = mkStr(attr, rv["V"]["V"]["PATH_ATTRS"][attr])
        astr = struct.pack('%ds%ds' % (len(astr), len(rstr)),
                           astr, rstr)

        
    for pfx in rv["V"]["V"]["FEASIBLE"]:
        TABLE[pfx] = {"TIME"   : msg_tm,
                      "PEER_IP": src_ip,
                      "PEER_AS": src_as,
                      "ATTRS"  : astr,
                      }

#-------------------------------------------------------------------------------

def dumpTable():

    now = time.time()
    date_str = time.strftime(".%Y-%m-%d_%H.%M.%S", time.gmtime(LAST_TM))
    of = open(OUTPUT_F + date_str, 'w+b')

    error('dumping...')

    seq_no = 0
    for (pfx, plen) in TABLE.keys():
        attr_len = len(TABLE[(pfx,plen)]['ATTRS'])
        common_hdr = struct.pack('>HH', VIEW_NO, seq_no)
        entry      = struct.pack('>4sBBLLHH%ds' % attr_len,
                                 pfx, plen, STATUS,
                                 TABLE[(pfx,plen)]['TIME'],
                                 TABLE[(pfx,plen)]['PEER_IP'],
                                 TABLE[(pfx,plen)]['PEER_AS'],
                                 attr_len, TABLE[(pfx,plen)]['ATTRS'])
        
        mrt_hdr = struct.pack('>LHHL',
                              now,
                              mrtd.MSG_TYPES['TABLE_DUMP'],
                              mrtd.TABLE_DUMP_SUBTYPES['IP'],
                              attr_len+\
                              mrtd.TABLE_DUMP_HDR_LEN+\
                              bgp.TABLE_DUMP_ENTRY_HDR_LEN)

        of.write('%s%s%s' % (mrt_hdr, common_hdr, entry))
        seq_no = seq_no + 1

    error('[%d] entries...' % len(TABLE.keys()) )

    of.close()

################################################################################

if __name__ == "__main__":

    VERBOSE  = 1
    START_T  = -1
    INTERVAL = -1
    LAST_TM  = -1

    OUTPUT_F = 'bview'
    TABLE_F = None
    TABLE   = {}
    
    #---------------------------------------------------------------------------

    def usage():

        print """Usage: %s [ options ] <filenames>:
        -h|--help       : Help
        -q|--quiet      : Be quiet
        -v|--verbose    : Be verbose

        -f|--file       : Filename prefix for output
        -s|--start-time : Start time of packets of interest [inclusive]
        -i|--interval   : Table dump invterval (minutes)
        -t|--table      : Initial table [def.: none]""" %\
            (os.path.basename(sys.argv[0]),)
        sys.exit(0)
    
    #---------------------------------------------------------------------------

    if len(sys.argv) < 2:
        usage()
        
    try:
        opts, args =\
              getopt.getopt(sys.argv[1:],
                            "hqvf:s:i:t:",
                            ("help", "quiet", "verbose",
                             "file=", "start-time=", "interval=", "table=" ))
    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'):
            OUTPUT_F = y

        elif x in ('-s', '--start-time'):
            START_T = time.mktime(time.strptime(y))

        elif x in ('-i', '--interval'):
            INTERVAL = string.atol(y) * MINS_TO_SECS

        elif x in ('-t', '--table'):
            TABLE_F = y

        else:
            usage()

    filenames = args
    if not filenames:
        usage()

    #---------------------------------------------------------------------------

    NEXT_DUMP = START_T + INTERVAL

    # seed from dump if required here

    if TABLE_F:
        cnt = 0
        error('[ %s ] initializing table...' % TABLE_F)
        try:
            mrt = mrtd.Mrtd(TABLE_F, "rb")
            while 1:
                rv = mrt.parse(mrt.read(), VERBOSE)
                cnt = cnt + 1
                if rv["T"] == mrtd.MSG_TYPES["TABLE_DUMP"]:
                    for v in rv["V"]:
                        pfx = v["V"]["PREFIX"]
                        entry = { "TIME"   : v["V"]["UPTIME"],
                                  "PEER_IP": v["V"]["PEER_IP"],
                                  "PEER_AS": v["V"]["PEER_AS"],
                                  "ATTRS"  : v["V"]["ATTRS"],
                                  }

                        TABLE[pfx] = entry
        except (mrtd.EOFExc):
            error("end of file: %u messages\n" % cnt)
        except (KeyboardInterrupt):
            error("interrupted: %u messages\n" % cnt)
        mrt.close()
        error('done\n')

    print 'init entries:', `len(TABLE.keys())`

    # process UPDATE files
    
    for fn in filenames:
        cnt = 0
        try:
            error('[ %s ] parsing...' % fn)
            mrt = mrtd.Mrtd(fn, "rb")
            while 1:
                msg = mrt.read()
                cnt = cnt + 1
                if (START_T < 0) or (msg[0] >= START_T):
                    rv = mrt.parse(msg, VERBOSE)
                    if ((rv["T"] == mrtd.MSG_TYPES["PROTOCOL_BGP"] and
                         rv["ST"] == mrtd.BGP_SUBTYPES["UPDATE"])
                        or
                        (rv["T"] == mrtd.MSG_TYPES["PROTOCOL_BGP4MP"] and
                         rv["ST"] == mrtd.BGP4MP_SUBTYPES["MESSAGE"] and
                         rv["V"]["T"] == bgp.MSG_TYPES["UPDATE"])
                        or
                        (rv["T"] == mrtd.MSG_TYPES["PROTOCOL_BGP4PY"] and
                         rv["ST"] == mrtd.BGP4MP_SUBTYPES["MESSAGE"] and
                         rv["V"]["T"] == bgp.MSG_TYPES["UPDATE"])
                        ):

                        processEntry(rv)

                        LAST_TM = msg[0]
                        if (LAST_TM > NEXT_DUMP and NEXT_DUMP > START_T):
                            dumpTable()
                            NEXT_DUMP = NEXT_DUMP + INTERVAL

        except (mrtd.EOFExc):
            error("end of file: %u messages..." % cnt)
        except (KeyboardInterrupt):
            error("interrupted: %u messages..." % cnt)
        error('done\n')
        mrt.close()

    # do a final table dump

    dumpTable()
    
    sys.exit(0)

################################################################################
################################################################################


syntax highlighted by Code2HTML, v. 0.9.1