#!/usr/local/bin/python

import getopt
import os
import rrdtool
import sys
import string

#
# flow-rpt2rrd - convert flow-report output to rrd format
#

# TODO
#  work with time-series flow-report output
#  relax 5 minute sample requirement.

#
# load key file
#
def load_keyfile(fname):
  keys = []
  f = open(fname, "r")
  line = f.readline().strip()
  while line:
    if line[0:1] == '#':
      line = f.readline()
      continue
    keys.append(line)
    line = f.readline().strip()
  f.close()
  return string.join(keys, ',')

#
# Class: ftsym
#   load a symbol table from a file in value symbol format, provide
#   access methods findbyname and findbyval
#
class ftsym:

#
# load symbols
#
  def __init__(self,field):
    self.sv = {}
    self.vs = {}
    __symbol_lookup = { 'ip-source-port' : 'tcp-port.sym',
                      'ip-destination-port' : 'tcp-port.sym',
                      'ip-protocol' : 'ip-prot.sym',
                      'source-as' : 'asn.sym',
                      'destination-as' : 'asn.sym',
                      'source-tag' : 'tag.sym',
                      'destination-tag' : 'tag.sym',
                      'ip-address-type' : 'ip-type.sym',
                    }
    fname = "/usr/local/netflow/var/sym/%s" % __symbol_lookup[field]
    f = open(fname, "r")
    line = f.readline().strip()
    while line:
      (v,s) = line.split();
      self.vs[v] = s
      self.sv[s] = v
      line = f.readline().strip()
    f.close()

#
# access by name, return value.  If the name does not exist return the name.
#
  def findbyname(self, name):
    return self.sv.get(name,name)

#
# access by value, return name.  If the value does not exist return the value.
#
  def findbyval(self, val):
    return self.vs.get(val,val)


#
# Class: ftrpt2rrd
#
#   Read in output of flow-report, make suitable for rrd
#
#   pickfields - pick flows,octets,packets for inclusion into new rrd
#   pickkeys - pick keys for inclusion into new rrds.
#   mapsym() - replace key values with symbols
#   setrrd() - set rrd params
#   convert(stream) - convert to rrd format
#
class ftrpt2rrd:
#
#
#
  def __init__(self):
    # not in data area
    self.debug = 0
    self.verbose = 0
    self.in_data = 0
    self.use_key_names = {}
    self.use_key_names_special = {}
    self.use_key_names_total = 0
    self.use_fields = {'flows' : 1, 'octets' : 1, 'packets' : 1}
    self.mapsym = 0
    self.rrd_5min = 0
    self.rrd_30min = 0
    self.rrd_2hr = 0
    self.rrd_1day = 0
    self.rrd_path = '.'
    self.rrd_postfix = ''
    self.field_names = {}
    self.field_names2 = {}
    self.field_total = 0
    self.field_keys = {}
    self.field_vals = {}
    self.start_time = 0
    self.sym = {}
    self.records_processed = 0

  def set_use_fields(self,f):
    self.use_fields = {}
    for i in string.split(f, ','):
      self.use_fields[i] = 1
    
  def set_use_key_names(self,f):
    self.use_key_names = {}
    self.use_key_names_special = {}
    for i in string.split(f, ','):
      if i[:6] == 'total_':
        self.use_key_names_special[i] = 1
      else:
        self.use_key_names[i] = 1
        self.use_key_names_total += 1

  def set_mapsym(self):
    self.mapsym = 1

  def set_debug(self, debug):
    self.debug = debug

  def set_verbose(self, verbose):
    self.verbose = verbose

  def setrrd(self, storage, path, postfix):
    (self.rrd_5min, self.rrd_30min, self.rrd_2hr, self.rrd_1day) = \
      string.split(storage,':')
    self.rrd_path = path
    self.rrd_postfix = postfix

  def update_rrd(self, key, vals, use_fields_index):

    # / in the key maps to - for files
    key = key.replace('/','-')

    # open an rrd, it it doesn't exist create it.
    rrdFile = "%s/%s%s.rrd" % (self.rrd_path, key, self.rrd_postfix)

    # exists?
    if not os.access(rrdFile, os.F_OK):

      print "Creating RRD", rrdFile

      rrdParams = []
      t = str(int(self.start_time) - 300)
      rrdParams.append('--start')
      rrdParams.append(t)
      for i in use_fields_index.keys():
        rrdParams.append("DS:%s:ABSOLUTE:600:U:U" % use_fields_index[i])
      if (self.rrd_5min):
        rrdParams.append('RRA:AVERAGE:0.5:1:%s' % self.rrd_5min)
        rrdParams.append('RRA:MAX:0.5:1:%s' % self.rrd_5min)
      if (self.rrd_30min):
        rrdParams.append('RRA:AVERAGE:0.5:6:%s' % self.rrd_30min)
        rrdParams.append('RRA:MAX:0.5:6:%s' % self.rrd_30min)
      if (self.rrd_2hr):
        rrdParams.append('RRA:AVERAGE:0.5:24:%s' % self.rrd_2hr)
        rrdParams.append('RRA:MAX:0.5:24:%s' % self.rrd_2hr)
      if (self.rrd_1day):
        rrdParams.append('RRA:AVERAGE:0.5:288:%s' % self.rrd_1day)
        rrdParams.append('RRA:MAX:0.5:288:%s' % self.rrd_1day)

      rrdtool.create(rrdFile, *rrdParams)
      if self.debug:
        print >>sys.stderr, string.join(rrdParams,' ')

    # foreach value

    update = self.start_time
    for i in use_fields_index.keys():
      update = "%s:%s" % (update,vals[i])
    if self.debug:
      print >>sys.stderr, "update", update

    if (self.verbose):
      print "Updating RRD", rrdFile

    rrdtool.update(rrdFile,update)
    
#
#
#
  def convert(self, f):

    # first line
    line = f.readline().strip()
  
    while line:
  
      # report data starts after recn comment
      if (not self.in_data) :

        if line[:13] == '# first-flow:':
          self.start_time = (string.split(line[14:]))[0]

        # handle the totals record differently
        if line[:53] == '# rec1: records,ignores,flows,octets,packets,duration':
          tmp = string.split(line[8:], ',')
          line = f.readline().strip()
          tmp_use_fields_index = {}
          tmp_splt = string.split(line, ',')
          x = 0
          ds = 0
          for i in tmp:
            if self.use_key_names_special.get("total_%s" % i,0):
              tmp_use_fields_index[x] = i
              ds = ds + 1
            x = x + 1
          if ds:
            self.update_rrd('totals', tmp_splt, tmp_use_fields_index)
          del tmp_splt, tmp_use_fields_index, i, x, ds
          continue
    
        if line[:6] == '# recn':
          self.in_data = 1
    
          # foreach element in field names
          for i in string.split(line[8:],','):
    
            # remove key designators
            if i[-1:] == '*':
              i = i[:-1]
              self.field_keys[self.field_total] = 1
            else:
              self.field_vals[self.field_total] = 1

            # store the field names
            self.field_names[self.field_total] = i
            self.field_names2[i] = self.field_total

            self.field_total += 1

          # start time must be set by now
          if (self.start_time == 0):
            raise ValueError, "Start time not found, make sure flow-report is including the header"

          # load symbol tables
          if self.mapsym == 1:
            for i in self.field_keys.keys():
              self.sym[i] = ftsym(self.field_names[i])

          # convert use_fields to use_fields_index for easier access
          self.use_fields_index = {}
          for i in self.use_fields.keys():
            if self.use_fields[i] and self.field_names2.get(i,'x') != 'x':
              self.use_fields_index[self.field_names2[i]] = i

      else :
    
        # if in the data area and not a comment, store it
        if self.in_data and line [:1] != '#':
    
          splt = string.split(line, ',')

          # combine the key fields to form one key
          k = ''
          for i in self.field_keys.keys():
            # try a symbol table lookup
            if self.mapsym == 1:
              t = self.sym[i].findbyval(splt[i])
            else:
              t = splt[i]
            k = "%s-%s" % (k, t)

          # done if all entries in key_names list have been stored.
          if self.use_key_names_total:
            if self.records_processed == self.use_key_names_total:
              break

          # if set, only allow specified keys
          if self.use_key_names.get(k[1:],0) == 0:
            line = f.readline().strip()
            continue

          # mark this key as processed
          self.use_key_names[k[1:]] |= 2

          self.records_processed += 1

          self.update_rrd(k[1:], splt, self.use_fields_index)

      # next line
      line = f.readline().strip()

    # keys which were not available in the report also need to be
    # updated with 0 values.
    for i in xrange(len(splt)):
      splt[i] = 0
    for x in self.use_key_names.keys():
      if not (self.use_key_names[x] & 2):
        self.update_rrd(x, splt, self.use_fields_index)

#
# main
#
   
(opts,rags) = getopt.getopt(sys.argv[1:], "dhk:K:f:np:P:r:v")

# mrtg defaults
opt_rrd_storage = "600:600:600:732"
opt_keys = ''
opt_fields = 'flows,octets,packets'
opt_names = 0
opt_rrd_path = './'
opt_keyfile = ''
opt_debug = 0
opt_rrd_postfix = ''
opt_verbose = 0

for o,v in opts:

  if o == '-d':
    opt_debug = 1
  elif o == '-k':
    opt_keys = v
  elif o == '-K':
    opt_keys = load_keyfile(v)
  elif o == '-f':
    opt_fields = v
  elif o == '-n':
    opt_names = 1
  elif o == '-p':
     opt_rrd_path = v
  elif o == '-P':
     opt_rrd_postfix = v
  elif o == '-r':
    opt_rrd_storage = v
  elif o == '-v':
    opt_verbose = 1
  elif o == '-h':
    print "Usage: flow-rpt2rrd [-nv] [-k keys] [-K keyfile] [-f fields]"
    print "                    [-p rrd_path] [-P fname_postfix]"
    print "                    [-r rrd_storage 5_min:30_min:2_hr:1_day ]"
    sys.exit(0)

if opt_keys == '':
  print >>sys.stderr, "Keys must be defined with -k or -K."
  sys.exit(1)

ftrrd = ftrpt2rrd()
if (opt_names == 1):
  ftrrd.set_mapsym()
ftrrd.setrrd(opt_rrd_storage, opt_rrd_path, opt_rrd_postfix)
ftrrd.set_use_key_names(opt_keys)
ftrrd.set_use_fields(opt_fields)
ftrrd.set_debug(opt_debug)
ftrrd.set_verbose(opt_verbose)
ftrrd.convert(sys.stdin)

