//===========================================================================
//  $Name: cflowd-2-1-b1 $
//  $Id: cflowd.cc,v 1.41 2000/10/24 16:18:54 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 "aclocal.h"
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <fcntl.h>
#ifdef HAVE_SYS_FILIO_H
  #define BSD_COMP 1
  #include <sys/filio.h>
#endif
#ifdef HAVE_SYS_TERMIOS_H
  #include <sys/termios.h>
#endif
#include "caida_t.h"
}

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

#define k_flowsToRead  64

static const string         rcsid = "@(#) $Name: cflowd-2-1-b1 $ $Id: cflowd.cc,v 1.41 2000/10/24 16:18:54 dwm Exp $";
static const CflowdVersion  g_cflowdVersion = CflowdVersion(rcsid);
static CflowdConfig         g_cflowdConfig;
static int                  g_tcpCollSockFd = -1;
static int                  g_tableSockFd = -1;
static CflowdPacketQueue    g_packetQueue;
bool                        g_cflowdDebug = false;
Signal                      g_sigChld(SIGCHLD);
Signal                      g_sigTerm(SIGTERM);

//-------------------------------------------------------------------------
//                         static int UnixListen()                         
//.........................................................................
//  Sets up our UNIX domain socket to listen for local client
//  connections (from cfdases, cfdnets, et. al.).
//-------------------------------------------------------------------------
static int UnixListen()
{
  struct sockaddr_un   tableListenSockAddr;
  int                  tableListenSockAddrLen;
  int                  sockFd;
  int                  reuseAddr = 1;
  
  if ((sockFd = socket(AF_UNIX,SOCK_STREAM,0)) < 0) {
    return(-1);
  }

  unlink(g_cflowdConfig.TableSockFile().c_str());
  
  setsockopt(sockFd,SOL_SOCKET,SO_REUSEADDR,(char *)&reuseAddr,
             sizeof(reuseAddr));
  
  memset(&tableListenSockAddr,0,sizeof(tableListenSockAddr));
  tableListenSockAddr.sun_family = AF_UNIX;
  strcpy(tableListenSockAddr.sun_path,g_cflowdConfig.TableSockFile().c_str());
  tableListenSockAddrLen = strlen(tableListenSockAddr.sun_path) +
                         sizeof(tableListenSockAddr.sun_family) + 1;
  if (bind(sockFd,(struct sockaddr *)&tableListenSockAddr,
           tableListenSockAddrLen) < 0) {
    syslog(LOG_ERR,"[E] bind(%d,%p,%d) failed: %m {%s:%d}",
           sockFd,&tableListenSockAddr,tableListenSockAddrLen,
           __FILE__,__LINE__);
    close(sockFd);
    return(-1);
  }
  listen(sockFd,5);
  g_tableSockFd = sockFd;
  return(sockFd);
}

//-------------------------------------------------------------------------
//          static void ChildHandleLocalSigPipe(int signalNumber)          
//.........................................................................
//  Used to handle a SIGPIPE when we fork() and send data to a local
//  client (cfdases, cfdnets, et. al.).
//-------------------------------------------------------------------------
static void ChildHandleLocalSigPipe(int signalNumber)
{
  syslog(LOG_ERR,"[E] SIGPIPE received.");
  return;
}

//-------------------------------------------------------------------------
//                      static int HandleLocalClient()                     
//.........................................................................
//   Handles a connection from a local client (cfdnets, cfdases,
//   et. al.).  We fork() and send them the data they requested,
//   always returning 0 to the parent process (the child exits after
//   sending or receiving a SIGPIPE).  The parent process will clean
//   up the child process table slot when it receives a SIGCHLD.
//-------------------------------------------------------------------------
static int HandleLocalClient()
{
  CflowdTableRequest        tableRequest;
  int                       acceptFd;
  struct sockaddr_un        clientSockAddr;
  socklen_t                 clientSockAddrLen = sizeof(clientSockAddr);
  CflowdCiscoMap::iterator  ciscoIter;
  
  acceptFd = accept(g_tableSockFd,
                    (struct sockaddr *)&clientSockAddr,&clientSockAddrLen);
  if (acceptFd < 0) {
    syslog(LOG_ERR,"[E] accept(%d,%p,%p) failed: %m {%s:%d}",
           g_tableSockFd,&clientSockAddr,&clientSockAddrLen,__FILE__,__LINE__);
    return(-1);
  }
  if (tableRequest.Read(acceptFd) < 0) {
    syslog(LOG_ERR,"[E] tableRequest.Read(%d) failed {%s:%d}",
           acceptFd,__FILE__,__LINE__);
    close(acceptFd);
    return(-1);
  }

  ciscoIter = g_cflowdConfig.CiscoMap().find(tableRequest.RouterIpAddr());
  if (ciscoIter == g_cflowdConfig.CiscoMap().end()) {
    struct in_addr  routerInAddr;
    routerInAddr.s_addr = tableRequest.RouterIpAddr();
    syslog(LOG_ERR,"[E] client requested data for router %s, for which"
           " we don't have data {%s:%d}",inet_ntoa(routerInAddr),
           __FILE__,__LINE__);
    close(acceptFd);
    return(-1);
  }
  if ((((*ciscoIter).second)->TableIndex() & tableRequest.TableIndex()) !=
      tableRequest.TableIndex()) {
    syslog(LOG_ERR,"[E] client requested data we don't have"
           " (request index %#x, our table index is %#x) {%s:%d}",
           tableRequest.TableIndex(),((*ciscoIter).second)->TableIndex(),
           __FILE__,__LINE__);
    close(acceptFd);
    return(-1);
  }

  //  if we got here, the client requested data we have.
  //  We'll fork, then send them the data.

  if (! ((*ciscoIter).second)->HaveRecentInterfaceInfo()) {
    ((*ciscoIter).second)->GetInterfaceInfo();
  }
                                                         
  int  forkRc;
  
  if ((forkRc = fork()) > 0) {
    //  parent process
    close(acceptFd);
    return(0);
  }
  else {
    if (forkRc < 0) {
      // fork() failed
      syslog(LOG_ERR,"[E] fork() failed! {%s:%d}",__FILE__,__LINE__);
      return(-1);
    }
    else {
      //  child process

      //  Tweak our index for the Cisco to match that of the client request.
      ((*ciscoIter).second)->TableIndex(tableRequest.TableIndex());

      Signal sigPipe(SIGPIPE);
      sigPipe.InstallHandler(ChildHandleLocalSigPipe);

      int  linger = 1;
      
      setsockopt(acceptFd,SOL_SOCKET,SO_LINGER,(char *)&linger,sizeof(linger));
      
      //  write the data to the client.
      if (((*ciscoIter).second)->write(acceptFd) < 0) {
        syslog(LOG_ERR,"[E] (*ciscoIter).second.write(%d) failed {%s:%d}",
               acceptFd,__FILE__,__LINE__);
      }
      //  close the connection and exit
      close(acceptFd);
      exit(0);
    }
  }
  //  UNREACHED
}

//-------------------------------------------------------------------------
//              static void HandleSigChld(int signalNumber)
//.........................................................................
//  Handles a SIGCHLD signal.
//-------------------------------------------------------------------------
static void HandleSigChld(int signalNumber)
{
  int  childExitStatus;
  while (waitpid(-1,&childExitStatus,WNOHANG) > 0)
    ;
  return;
}

//-------------------------------------------------------------------------
//                          static int TcpListen()                         
//.........................................................................
//  Sets up our TCP socket to listen for remote connections from
//  cfdcollect.  Returns the socket descriptor if successful, else
//  returns -1.
//-------------------------------------------------------------------------
static int TcpListen()
{
  int                 sockFd;
  struct sockaddr_in  tcpSockAddr;
  int                 tcpSockAddrLen;
  int                 soReuseAddr = 1;

  //  Create the socket.
  if ((sockFd = socket(AF_INET,SOCK_STREAM,0)) < 0) {
    syslog(LOG_ERR,"[E] socket(AF_INET,SOCK_STREAM,0) failed: %m {%s:%d}",
           __FILE__,__LINE__);
    return(-1);
  }

  //  Set the reuse-address socket option.
  #ifdef SO_REUSEADDR
    setsockopt(sockFd,SOL_SOCKET,SO_REUSEADDR,(char *)&soReuseAddr,
               sizeof(soReuseAddr));
  #endif
    
  //  Bind the socket to our configured TCP collection port.  Note we
  //  wildcard the IP address (listen on all interfaces).
  memset(&tcpSockAddr,0,sizeof(tcpSockAddr));
  tcpSockAddr.sin_addr.s_addr = INADDR_ANY;
  tcpSockAddr.sin_port = htons(g_cflowdConfig.TcpCollectPort());
  tcpSockAddr.sin_family = AF_INET;
  tcpSockAddrLen = sizeof(tcpSockAddr);
  
  if (bind(sockFd,(struct sockaddr *)&tcpSockAddr,tcpSockAddrLen) < 0) {
    syslog(LOG_ERR,"[E] bind(%d,%s:%hu,%d) failed: %m {%s:%d}",
           sockFd,inet_ntoa(tcpSockAddr.sin_addr),
           g_cflowdConfig.TcpCollectPort(),tcpSockAddrLen,__FILE__,__LINE__);
    close(sockFd);
    return(-2);
  }

  //  Listen for connections on the new socket.  
  if (listen(sockFd,5) < 0) {
    syslog(LOG_ERR,"[E] listen(%d,5) failed: %m {%s:%d}",
           __FILE__,__LINE__,sockFd);
    close(sockFd);
    return(-3);
  }

  g_tcpCollSockFd = sockFd;

  return(sockFd);
}

//-------------------------------------------------------------------------
//                       int HandleCollectorClient()                       
//.........................................................................
//  Handle a connection from cfdcollect.
//-------------------------------------------------------------------------
int HandleCollectorClient()
{
  int                       acceptFd;
  CflowdCiscoMap::iterator  ciscoIter;

  struct sockaddr_in        collSockAddr, clientSockAddr;
  socklen_t                 collSockAddrLen = sizeof(collSockAddr);
  socklen_t                 clientSockAddrLen = sizeof(clientSockAddr);
    
  //  Accept the client connection.
  acceptFd = accept(g_tcpCollSockFd,
                    (struct sockaddr *)&collSockAddr,&collSockAddrLen);
  if (acceptFd < 0) {
    syslog(LOG_ERR,"[E] accept(%d,%p,%p) failed: %m {%s:%d}",
           g_tableSockFd,&collSockAddr,&collSockAddrLen,__FILE__,__LINE__);
    return(-1);
  }

  //  Get the address of the remote host.
  if (getpeername(acceptFd,(struct sockaddr *)&clientSockAddr,
                  &clientSockAddrLen) < 0) {
    syslog(LOG_ERR,"[E] getpeername(%d,%p,%p) failed: %m {%s:%d}",
           acceptFd,&clientSockAddr,&clientSockAddrLen,__FILE__,__LINE__);
    syslog(LOG_ERR,"[E] rejecting connection.");
    close(acceptFd);
    return(-1);
  }
      
  //  Check that the remote host is permitted to connect.
  CflowdCollectorMap::iterator  collIter;
  
  collIter =
    g_cflowdConfig.CollectorMap().find(clientSockAddr.sin_addr.s_addr);
  if (collIter == g_cflowdConfig.CollectorMap().end()) {
    //  host not permitted, reject them
    close(acceptFd);
    syslog(LOG_ERR,"[E] host %s is not authorized to connect {%s:%d}",
           inet_ntoa(clientSockAddr.sin_addr),__FILE__,__LINE__);
    return(-1);
  }

  //  We'll fork, then send the data from the child.

  int  forkRc;
  
  if ((forkRc = fork()) < 0) {
      // fork() failed
      syslog(LOG_ERR,"[E] fork() failed! {%s:%d}",__FILE__,__LINE__);
      return(-1);
  }
  
  if (forkRc > 0) {
    // parent process.  Clear our tables.
    close(acceptFd);
    for (ciscoIter = g_cflowdConfig.CiscoMap().begin();
         ciscoIter != g_cflowdConfig.CiscoMap().end(); ciscoIter++) {
      ((*ciscoIter).second)->ClearTableData();
      ((*ciscoIter).second)->HaveRecentInterfaceInfo(false);
    }
    return(0);
  }
  else {
    //  child process
    close(g_tableSockFd);

    // shutdown(acceptFd,0);

    //  Set up our signal handler for SIGPIPE.
    Signal sigPipe(SIGPIPE);
    sigPipe.InstallHandler(ChildHandleLocalSigPipe);

    //  Enable keepalives on the connection.  This should prevent us
    //  from hanging for eons in a blocked write.
    int on = 1;
    if (setsockopt(acceptFd,SOL_SOCKET,SO_KEEPALIVE,
                   (char *)&on,sizeof(on)) < 0) {
      syslog(LOG_WARNING,
             "[W] setsockopt(%d,SOL_SOCKET,SO_KEEPALIVE,%p,%d) failed:"
             " %m {%s:%d}",acceptFd,&on,sizeof(on),__FILE__,__LINE__);
    }

    g_cflowdConfig.CiscoMap().GetInterfaceInfo();
    
    //  Write the data to the client.
    
    if (g_cflowdConfig.CiscoMap().Write(acceptFd) < 0) {
      syslog(LOG_ERR,
             "[E] g_cflowdConfig.CiscoMap().write(%d) failed {%s:%d}",
             acceptFd,__FILE__,__LINE__);
    }
    
    //  Close the connection and exit.
    fsync(acceptFd);
    close(acceptFd);
    
    syslog(LOG_INFO,"[I] sent data to %s:%hu",
           inet_ntoa(clientSockAddr.sin_addr),
           ntohs(clientSockAddr.sin_port));
    
    exit(0);
  }
  //  UNREACHED
}

//-------------------------------------------------------------------------
// static void UpdateSequenceNumber(CflowdCiscoMap::iterator & ciscoMapIter, 
//                                  uint8_t engineId, uint32_t seqNumber,  
//                                  uint16_t flowCount,
//                                  uint8_t aggMethod = 0)                    
//.........................................................................
//  Updates the sequence number for a Cisco flow engine, given an
//  iterator for the CflowdCisco stored in a CflowdCiscoMap, the engine
//  ID from the flow packet, the sequence number from the flow packet
//  and the flow count from the flow packet.
//-------------------------------------------------------------------------
static void UpdateSequenceNumber(CflowdCiscoMap::iterator & ciscoMapIter,
                                 uint8_t engineId, uint32_t seqNumber,
                                 uint16_t flowCount,
                                 uint8_t aggMethod = 0)
{
  CflowdCiscoFlowEngine & engine =
    ((*ciscoMapIter).second)->FlowEngines()[engineId];

  engine.SequenceNumber(seqNumber,flowCount,aggMethod);

  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 UpdateTablesWithV8Data(CflowdCiscoMap::iterator ciscoMapIter,
//                                    CflowdRawFlow *flows,         
//                                    int & flowArrNum,             
//                                    CiscoFlowHeaderV8_t *hdrPtrV8, 
//                                    const char *pktPtr)           
//............................................................................
//  Updates tables using data in a v8 flow-export packet.  Currently we
//  only handle the AS aggregation, protocol/port aggregation and net
//  matrix aggregation methods for v8 flow-export.  Support for the source
//  network aggregation and destination network aggregation will be added
//  in the future.
//----------------------------------------------------------------------------
static int UpdateTablesWithV8Data(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) {
        syslog(LOG_ERR,
               "[E] too many flow in v8 AS agg packet! (%d, max %d) {%s:%d}",
               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) {
        syslog(LOG_ERR,
               "[E] too many flow in protocol/port agg packet!"
               " (%d, max %d) {%s:%d}",
               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) {
        syslog(LOG_ERR,
               "[E] too many flow in net agg packet! (%hu, max %hu) {%s:%d}",
               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);
}

//-------------------------------------------------------------------------
//                        static int UpdateTables()                        
//.........................................................................
//  
//-------------------------------------------------------------------------
static int UpdateTables()
{
  int                         pktNum, flowNum, flowArrNum;
  int                         numPackets, numFlows;
  CflowdRawFlow               flow;
  CflowdCiscoMap::iterator    ciscoMapIter;
  
  uint16_t      exportVersion;
  ipv4addr_t    ciscoAddr;
  const char   *pktPtr;
  
  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;
      syslog(LOG_ERR,"[E] received packet from a source for which"
             " we're not configured (%s)",inet_ntoa(addrIn));
      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);
          entryPtrV1++;
          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++;
        }
        UpdateSequenceNumber(ciscoMapIter,hdrPtrV5->engine_id,
                             ntohl(hdrPtrV5->flow_sequence),
                             ntohs(hdrPtrV5->count),0);
        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());
          entryPtrV6++;
          flowArrNum++;
        }
        UpdateSequenceNumber(ciscoMapIter,hdrPtrV6->engine_id,
                             ntohl(hdrPtrV6->flow_sequence),
                             ntohs(hdrPtrV6->count),0);
        break;
        
      case 8:
        hdrPtrV8 = (CiscoFlowHeaderV8_t *)(&pktPtr[0]);
        UpdateTablesWithV8Data(ciscoMapIter,flows,flowArrNum,hdrPtrV8,pktPtr);
        UpdateSequenceNumber(ciscoMapIter,hdrPtrV8->engine_id,
                             ntohl(hdrPtrV8->flow_sequence),
                             ntohs(hdrPtrV8->count),
                             hdrPtrV8->agg_method);
        break;

      default:
        syslog(LOG_ERR,
               "[E] got version %d flow-export packet?  We don't understand!"
               " {%s:%d}",exportVersion,__FILE__,__LINE__);
        break;
    }
  }
  g_packetQueue.ReleaseLock();
  
  numFlows = flowArrNum;

  for (flowNum = 0; flowNum < numFlows; flowNum++) {
    ciscoMapIter = g_cflowdConfig.CiscoMap().find(flows[flowNum].Router());
    if (ciscoMapIter != g_cflowdConfig.CiscoMap().end()) {
      ((*ciscoMapIter).second)->AddFlow(flows[flowNum]);
    }
  }

  return(numPackets);
}

//-------------------------------------------------------------------------
//                       static void OpenRawFlowLogs()                         
//.........................................................................
//  Opens raw flow files for each Cisco for which we're configured to
//  dump raw flows.
//-------------------------------------------------------------------------
static void OpenRawFlowLogs()
{
  CflowdCiscoMap::iterator    ciscoMapIter;

  for (ciscoMapIter = g_cflowdConfig.CiscoMap().begin();
       ciscoMapIter != g_cflowdConfig.CiscoMap().end();
       ciscoMapIter++) {
    if (((*ciscoMapIter).second)->TableIndex() &
        CflowdCisco::k_cflowdRawFlowMask) {
      ((*ciscoMapIter).second)->CreateFlowLogger(g_cflowdConfig.FlowDirectory(),
                                                 g_cflowdConfig.NumFlowFiles(),
                                                 g_cflowdConfig.FlowFileLength());
    }
  }
  return;
}

//-------------------------------------------------------------------------
//                      static void CloseRawFlowLogs()                     
//.........................................................................
//  Closes all raw flow files.
//-------------------------------------------------------------------------
static void CloseRawFlowLogs()
{
  CflowdCiscoMap::iterator    ciscoMapIter;

  for (ciscoMapIter = g_cflowdConfig.CiscoMap().begin();
       ciscoMapIter != g_cflowdConfig.CiscoMap().end();
       ciscoMapIter++) {
    if (((*ciscoMapIter).second)->TableIndex() &
        CflowdCisco::k_cflowdRawFlowMask) {
      ((*ciscoMapIter).second)->FlowLogger()->Close();
    }
  }
  return;
}

//-------------------------------------------------------------------------
//                              int MainLoop()                             
//.........................................................................
//  
//-------------------------------------------------------------------------
int MainLoop()
{
  vector<CflowdRawFlow>             *newFlowVector;
  vector<CflowdRawFlow>::iterator    flowIter;
  CflowdCiscoMap::iterator           ciscoIter;
  fd_set                             clientFdSet;
  fd_set                             tmpFdSet;
  int                                maxClientFd = 0;
  struct timeval                     mtv;

  g_sigChld.InstallHandler(HandleSigChld);
  
  FD_ZERO(&clientFdSet);

  //  open our named stream socket on which we'll accept 
  //  local client connections (from cfdases, et. al)
  if (UnixListen() >= 0) {
    FD_SET(g_tableSockFd,&clientFdSet);
    if (maxClientFd < g_tableSockFd) {
      maxClientFd = g_tableSockFd;
    }
  }
  else {
    syslog(LOG_ALERT,
           "[A] failed to open named stream socket!  Exiting {%s:%d}",
           __FILE__,__LINE__);
    CloseRawFlowLogs();
    exit(1);
  }
  
  //  open our TCP socket for connections from cfdcollect
  if (TcpListen() >= 0) {
    FD_SET(g_tcpCollSockFd,&clientFdSet);
    if (maxClientFd < g_tcpCollSockFd) {
      maxClientFd = g_tcpCollSockFd;
    }
  }
  else {
    syslog(LOG_ALERT,"[A] failed to open TCP socket! Exiting {%s:%d}",
           __FILE__,__LINE__);
    exit(1);
  }
  
  for (;;) {
    memcpy(&tmpFdSet,&clientFdSet,sizeof(tmpFdSet));
    mtv.tv_sec = 0;
    mtv.tv_usec = 0;

    int  selectRc;
    
    selectRc = select(maxClientFd+1,&tmpFdSet,(fd_set *)NULL,
                      (fd_set *)NULL,&mtv);
    if (selectRc < 0) {
      if (errno != EINTR) {
        syslog(LOG_ERR,"[E] select error: %m {%s:%d}",__FILE__,__LINE__);
      }
      continue;
    }
    else {
      if (FD_ISSET(g_tableSockFd,&tmpFdSet)) {
        HandleLocalClient();
      }
    }

    //  read all the packets, update my tables, then toggle buffers
    g_sigChld.Block();
    UpdateTables();
    g_packetQueue.ToggleBuffers(false);
    g_sigChld.Unblock();

    //  accept and handle cfdcollect connection if one is waiting.
    if (FD_ISSET(g_tcpCollSockFd,&tmpFdSet)) {
      g_sigChld.Block();
      HandleCollectorClient();
      g_sigChld.Unblock();
    }
  }
}

//-------------------------------------------------------------------------
//                         static void Daemonize()                         
//.........................................................................
//  Become a daemon process.
//-------------------------------------------------------------------------
static void Daemonize()
{
  int fd;

  //  fork to run in background, have parent exit
  if (fork() != 0)
    exit(0);

  //  disassociate from process group
  setpgid(0,getpid());
  
  //  ignore terminal I/O signals
  #ifdef SIGTTOU
    signal(SIGTTOU, SIG_IGN);
  #endif

  //  disassociate from control terminal
  if ((fd = open("/dev/tty",O_RDWR)) >= 0) {
    ioctl(fd,TIOCNOTTY,(char *)0);
    close(fd);
  }
  return;
}

//-------------------------------------------------------------------------
//                  static void HandleSigTerm(int sigNum)                  
//.........................................................................
//  SIGTERM handler.
//-------------------------------------------------------------------------
static void HandleSigTerm(int sigNum)
{
  syslog(LOG_INFO,"[I] Received SIGTERM.  Exiting.");
  CloseRawFlowLogs();
  exit(0);
}

//-------------------------------------------------------------------------
//                     int main(int argc, char *argv[])                    
//.........................................................................
//  
//-------------------------------------------------------------------------
int main(int argc, char *argv[])
{
  extern int    optind;
  extern char  *optarg;
  int           optChar;
  char         *configFilename = NULL;
  
  while ((optChar = getopt(argc,argv,"D")) != EOF) {
    switch (optChar) {
      case 'D':
        g_cflowdDebug = true;
        break;
      default:
        break;
    }
  }

  argc -= optind;
  argv += optind;

  if (argc >= 1) {
    configFilename = argv[argc-1];
  }
  else {
    configFilename =
      (char *)strdup(CflowdConfig::k_defaultCflowdConfigFile.c_str());
  }

  Daemonize();

  g_sigTerm.InstallHandler(HandleSigTerm);
  
  LoadConfigFile(configFilename,g_cflowdConfig);
  openlog("cflowd",LOG_PID,g_cflowdConfig.LogFacility());
  syslog(LOG_INFO,"[I] cflowd (version %s) started.",
         g_cflowdVersion.Name().c_str());

  OpenRawFlowLogs();
  
  if (g_packetQueue.Open(configFilename,g_cflowdConfig.PacketBufSize()) < 0) {
    syslog(LOG_ALERT,"[A] failed to open packet queue!  Exiting. {%s:%d}",
           __FILE__,__LINE__);
    exit(1);
  }
  g_packetQueue.GetLock();
  
  MainLoop();
}
