//===========================================================================
//  $Name: cflowd-2-1-b1 $
//  $Id: flowwatch.cc,v 1.14 2000/10/24 16:18:55 dwm Exp $
//===========================================================================
//  CAIDA Copyright Notice
//
//  By accessing this software, cflowd++, you are duly informed
//  of and agree to be bound by the conditions described below in this
//  notice:
//
//  This software product, cflowd++, is developed by Daniel W. McRobb, and
//  copyrighted(C) 1998 by the University of California, San Diego
//  (UCSD), with all rights reserved.  UCSD administers the CAIDA grant,
//  NCR-9711092, under which part of this code was developed.
//
//  There is no charge for cflowd++ software. You can redistribute it
//  and/or modify it under the terms of the GNU General Public License,
//  v.  2 dated June 1991 which is incorporated by reference herein.
//  cflowd++ is distributed WITHOUT ANY WARRANTY, IMPLIED OR EXPRESS, OF
//  MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE or that the use
//  of it will not infringe on any third party's intellectual property
//  rights.
//
//  You should have received a copy of the GNU GPL along with cflowd++.
//  Copies can also be obtained from:
//
//    http://www.gnu.org/copyleft/gpl.html
//
//  or by writing to:
//
//    University of California, San Diego
//
//    SDSC/CAIDA
//    9500 Gilman Dr., MS-0505
//    La Jolla, CA 92093 - 0505  USA
//
//  Or contact:
//
//    info@caida.org
//===========================================================================

extern "C" {
#include <sys/types.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
  
#include "caida_t.h"
  
}

#include <fstream.h>

#include "CflowdFlowFilter.hh"
#include "CflowdConfig.hh"
#include "CflowdConfigLex.hh"
#include "CflowdRawFlowConverter.hh"
#include "CflowdPacketQueue.hh"
#include "CflowdVersion.hh"
#include "Signal.hh"

extern int errno;

static const string          rcsid = "@(#) $Name: cflowd-2-1-b1 $ $Id: flowwatch.cc,v 1.14 2000/10/24 16:18:55 dwm Exp $";
static const CflowdVersion   g_cflowdVersion(rcsid);
static CflowdConfig          g_cflowdConfig;
static CflowdPacketQueue     g_packetQueue;

//-------------------------------------------------------------------------
//                      void Usage(const char *argv0)                      
//.........................................................................
//  
//-------------------------------------------------------------------------
void Usage(const char *argv0)
{
  cerr << "usage: " << argv0
       << " [-v] [-c configFile] [-o outFile] filterExpression" << endl;
  cerr << "  filterExpression is evaluated for a match on each flow entry."
       << endl << endl;

  cerr << " Possible flow entry field operands:" << endl
       << "   router srcaddr dstaddr inputif outputif srcport dstport"
       << " pkts bytes" << endl
       << "   nexthop starttime endtime protocol tos srcas dstas"
       << " srcmask dstmask" << endl
       << "   tcpflags enginetype engineid all now"
       << endl << endl;

  cerr << " Possible operators:" << endl
       << "   <  <=  ==  !=  >=  >  &&  ||  &  |  !  *  /  +  -  %  (  )"
       << endl << endl;

  cerr << " Example:" << endl
       << "   flowwatch '(protocol == 6) && (dstas == 2056) &&"
       << " ((tcpflags & 0x02) == 0x02))'" << endl;
  
  return;
}

//-------------------------------------------------------------------------
//   static void FixFlowAsNumbers(CflowdRawFlow & flow, uint16_t localAS)  
//.........................................................................
//  Given a raw flow and the local AS of the Cisco that sent the flow,
//  try to determin if the flow was sourced from or destined for the
//  local AS and fix zeros in the corresponding AS fields.
//-------------------------------------------------------------------------
static void FixFlowAsNumbers(CflowdRawFlow & flow, uint16_t localAS)
{
  if (localAS == 0)
    return;
  
  const CflowdRawFlow::index_type  k_srcFields =
    CflowdRawFlow::k_srcAsMask|CflowdRawFlow::k_srcMaskLenMask;

  if ((flow.Index() & k_srcFields) == k_srcFields) {
    if (flow.SrcAs() == 0 && flow.SrcMaskLen() != 0) {
      flow.SrcAs(localAS);
    }
  }
  
  const CflowdRawFlow::index_type  k_dstFields =
    CflowdRawFlow::k_dstAsMask|CflowdRawFlow::k_dstMaskLenMask;
  if ((flow.Index() & k_dstFields) == k_dstFields) {
    if ((flow.DstAs() == 0) && flow.DstMaskLen() != 0) {
      flow.DstAs(localAS);
    }
  }
  return;
}

static int UpdateFlowsWithV8Data(CflowdCiscoMap::iterator ciscoMapIter,
                                 CflowdRawFlow *flows,
                                 int & flowArrNum,
                                 CiscoFlowHeaderV8_t *hdrPtrV8,
                                 const char *pktPtr)
{
  CiscoFlowEntryV8AsAggV2_t            *entryPtrV8AsAggV2;
  CiscoFlowEntryV8ProtocolPortAggV2_t  *entryPtrV8ProtocolPortAggV2;
  CiscoFlowEntryV8NetMatrixAggV2_t     *entryPtrV8NetMatrixAggV2;
  int                                   flowNum;
  uint16_t                              numFlows = ntohs(hdrPtrV8->count);
  ipv4addr_t                            ciscoAddr = (*ciscoMapIter).first;
  
  switch (hdrPtrV8->agg_method) {
    case k_CiscoV8FlowExportASAggType:
      //  AS aggregation
      if (numFlows > k_maxFlowsPerV8AsAggPacket) {
        fprintf(stderr,
               "[E] too many flow in v8 AS agg packet! (%d, max %d) {%s:%d}\n",
               numFlows,k_maxFlowsPerV8AsAggPacket,__FILE__,__LINE__);
        return(-1);
      }
      entryPtrV8AsAggV2 =
        (CiscoFlowEntryV8AsAggV2_t *)(&pktPtr[0] +
                                      sizeof(CiscoFlowHeaderV8_t));
      for (flowNum = 0; flowNum < ntohs(hdrPtrV8->count); flowNum++) {
        flows[flowArrNum].Init(ciscoAddr,hdrPtrV8,entryPtrV8AsAggV2);
        entryPtrV8AsAggV2++;
        flowArrNum++;
      }
      
      break;

    case k_CiscoV8FlowExportProtocolPortAggType:
      //  protocol/port aggregation
      if (numFlows > k_maxFlowsPerV8ProtocolPortAggPacket) {
        fprintf(stderr,
               "[E] too many flow in protocol/port agg packet!"
               " (%d, max %d) {%s:%d}\n",
               numFlows,k_maxFlowsPerV8ProtocolPortAggPacket,
               __FILE__,__LINE__);
        return(-1);
      }
      entryPtrV8ProtocolPortAggV2 =
        (CiscoFlowEntryV8ProtocolPortAggV2_t *)(&pktPtr[0] +
                                                sizeof(CiscoFlowHeaderV8_t));
      for (flowNum = 0; flowNum < ntohs(hdrPtrV8->count); flowNum++) {
        flows[flowArrNum].Init(ciscoAddr,hdrPtrV8,entryPtrV8ProtocolPortAggV2);
        entryPtrV8ProtocolPortAggV2++;
        flowArrNum++;
      }
      break;

    case k_CiscoV8FlowExportNetMatrixAggType:
      //  network prefix matrix aggregation
      if (numFlows > k_maxFlowsPerV8NetMatrixAggPacket) {
        fprintf(stderr,
               "[E] too many flow in net agg packet! (%hu, max %hu) {%s:%d}\n",
               numFlows,k_maxFlowsPerV8NetMatrixAggPacket,__FILE__,__LINE__);
        return(-1);
      }
      entryPtrV8NetMatrixAggV2 =
        (CiscoFlowEntryV8NetMatrixAggV2_t *)(&pktPtr[0] +
                                             sizeof(CiscoFlowHeaderV8_t));
      for (flowNum = 0; flowNum < ntohs(hdrPtrV8->count); flowNum++) {
        flows[flowArrNum].Init(ciscoAddr,hdrPtrV8,entryPtrV8NetMatrixAggV2);
        FixFlowAsNumbers(flows[flowArrNum],
                         ((*ciscoMapIter).second)->LocalAS());
        entryPtrV8NetMatrixAggV2++;
        flowArrNum++;
      }
      break;

    default:
      break;
  }

  return(0);
}

//-------------------------------------------------------------------------
//                     int main(int argc, char *argv[])                    
//.........................................................................
//  
//-------------------------------------------------------------------------
int main(int argc, char *argv[])
{
  struct sockaddr_un   servSockAddr;
  int                  servSockAddrLen;
  int                  sockFd;
  char                *configFilename = NULL;
  extern int           optind;
  extern char         *optarg;
  int                  optChar;
  CflowdFlowFilter     flowFilter;
  ofstream            *outStream = (ofstream *)0;
  Signal               sigQuit(SIGQUIT);
  
  if (argc < 2) {
    Usage(argv[0]);
    exit(1);
  }

  while ((optChar = getopt(argc,argv,"c:o:v")) != EOF) {
    switch (optChar) {
      case 'c':
        configFilename = (char *)strdup(optarg);
        break;
      case 'o':
        outStream = new ofstream(optarg);
        if (!outStream || !(*outStream)) {
          cerr << "failed to open output file '" << optarg << "': "
               << strerror(errno) << " {" << __FILE__ << ":"
               << __LINE__ << "}" << endl;
          exit(1);
        }
        break;
      case 'v':
        cerr << g_cflowdVersion.Name() << "  " << g_cflowdVersion.Id() << endl;
        exit(0);
        break;
      case '?':
      default:
        Usage(argv[0]);
        exit(1);
        break;
    }
  }
  if (configFilename == NULL) {
    configFilename =
      (char *)strdup(CflowdConfig::k_defaultCflowdConfigFile.c_str());
  }
  
  if (LoadConfigFile(configFilename,g_cflowdConfig) < 0) {
    cerr << "failed to load config file '" << configFilename << "': "
         << strerror(errno) << endl;
    Usage(argv[0]);
    exit(1);
  }

  if (g_packetQueue.Open(configFilename,g_cflowdConfig.PacketBufSize()) < 0) {
    cerr << "[A] failed to open packet queue!  Exiting." << endl;
    exit(1);
  }

  if (flowFilter.Compile(argv[argc-1]) < 0) {
    Usage(argv[0]);
    exit(1);
  }
  
  g_packetQueue.GetLock();
  
  for (;;) {
    uint16_t      exportVersion;
    ipv4addr_t    ciscoAddr;
    const char   *pktPtr;
    int           pktNum, flowNum, flowArrNum;
    int           numPackets, numFlows;

    CflowdCiscoMap::iterator    ciscoMapIter;
    CiscoFlowHeaderV1_t        *hdrPtrV1;
    CiscoFlowEntryV1_t         *entryPtrV1;
    CiscoFlowHeaderV5_t        *hdrPtrV5;
    CiscoFlowEntryV5_t         *entryPtrV5;
    CiscoFlowHeaderV6_t        *hdrPtrV6;
    CiscoFlowEntryV6_t         *entryPtrV6;
    CiscoFlowHeaderV8_t        *hdrPtrV8;

    numPackets = g_packetQueue.NumPackets();
    CflowdRawFlow flows[numPackets * k_maxFlowsPerAnyPacket];
    flowArrNum = 0;

    for (pktNum = 0; pktNum < numPackets; pktNum++) {
      //  get reference to next packet.
      pktPtr = g_packetQueue.GetPacket(ciscoAddr);
      ciscoMapIter = g_cflowdConfig.CiscoMap().find(ciscoAddr);
      if (ciscoMapIter == g_cflowdConfig.CiscoMap().end()) {
        struct in_addr  addrIn;
        addrIn.s_addr = ciscoAddr;
        cerr << "[E] received packet from a source for which"
             << " we're not configured (" << inet_ntoa(addrIn) << ")" << endl;
        continue;
      }
    
      //  get the flow-export version of the packet
      exportVersion = ntohs(*(uint16_t *)pktPtr);
      
      //  and deal with the packet
      switch (exportVersion) {
        case 1:
          hdrPtrV1 = (CiscoFlowHeaderV1_t *)(&pktPtr[0]);
          entryPtrV1 = (CiscoFlowEntryV1_t *)(&pktPtr[0] +
                                              sizeof(CiscoFlowHeaderV1_t));
          for (flowNum = 0; flowNum < ntohs(hdrPtrV1->count); flowNum++) {
            flows[flowArrNum].Init(ciscoAddr,hdrPtrV1,entryPtrV1);
            entryPtrV5++;
            flowArrNum++;
          }
          break;
        case 5:
          hdrPtrV5 = (CiscoFlowHeaderV5_t *)(&pktPtr[0]);
          entryPtrV5 = (CiscoFlowEntryV5_t *)(&pktPtr[0] +
                                              sizeof(CiscoFlowHeaderV5_t));
          for (flowNum = 0; flowNum < ntohs(hdrPtrV5->count); flowNum++) {
            flows[flowArrNum].Init(ciscoAddr,hdrPtrV5,entryPtrV5);
            FixFlowAsNumbers(flows[flowArrNum],
                             ((*ciscoMapIter).second)->LocalAS());
            entryPtrV5++;
            flowArrNum++;
          }
          break;
        case 6:
          hdrPtrV6 = (CiscoFlowHeaderV6_t *)(&pktPtr[0]);
          entryPtrV6 = (CiscoFlowEntryV6_t *)(&pktPtr[0] +
                                              sizeof(CiscoFlowHeaderV6_t));
          for (flowNum = 0; flowNum < ntohs(hdrPtrV6->count); flowNum++) {
            flows[flowArrNum].Init(ciscoAddr,hdrPtrV6,entryPtrV6);
            FixFlowAsNumbers(flows[flowArrNum],
                             ((*ciscoMapIter).second)->LocalAS());
            entryPtrV5++;
            flowArrNum++;
          }
          break;
        case 8:
          hdrPtrV8 = (CiscoFlowHeaderV8_t *)(&pktPtr[0]);
          UpdateFlowsWithV8Data(ciscoMapIter,flows,flowArrNum,
                                hdrPtrV8,pktPtr);
          break;
          
        default:
          break;
      }
    }
    g_packetQueue.ReleaseLock();
    
    numFlows = flowArrNum;
    for (flowNum = 0; flowNum < numFlows; flowNum++) {
      if (flowFilter.Matches(&flows[flowNum])) {
        if (outStream != ((ofstream *)0)) {
          sigQuit.Block();
          flows[flowNum].Write(*outStream);
          sigQuit.Unblock();
        }
        else {
          cout << flows[flowNum];
        }
      }
    }
    g_packetQueue.ToggleBuffers(false);
  }
  
}
