/* 1620, Mon 23 Nov 98

   NF_FWD.C:  Packet forwarder for NetFlow data

   Copyright (C) 1998-2002 by Nevil Brownlee,
   CAIDA | University of Auckland */

/*
 * $Log: nf_fwd.c,v $
 * Revision 1.1.4.1.2.10  2002/02/23 01:57:34  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.4.1.2.8  2001/05/24 02:19:57  nevil
 * LfapMet implemented by Remco Poortinga.
 * MinPDUs implemented by Nevil.
 *
 * Revision 1.1.4.1.2.6  2000/08/08 19:44:55  nevil
 * 44b8 release
 *
 * Revision 1.1.4.1.2.4  2000/06/06 03:38:22  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.4.1.2.1  2000/01/12 02:57:13  nevil
 * Implement 'packet pair matched' turnaroundtime distribution attributes.
 * Fix ASN-related bugs in NeTraMet, distribution-related bugs in fd_filter.
 *
 * Revision 1.1.4.1  1999/10/03 21:06:26  nevil
 * *** empty log message ***
 *
 * Revision 1.1.2.4  1999/09/22 05:38:44  nevil
 * Improve code to work properly on 64-bit machines
 * - Add OS=ALPHA handling to configure.in
 * - Clean up the Alpha compiler warnings
 * - Change all the snmp-related code to use Bit32 instead of unsigned long
 *
 * Revision 1.1.2.3  1999/09/14 00:45:30  nevil
 * 4.3 Release ..
 *  - Implement -D option to run NeTraMet as a daemon
 *  - Tidy up the on-line help displays
 *
 * Revision 1.1.2.2  1999/01/08 01:31:56  nevil
 * Implementation of TCP attributes, part 3
 *
 * Revision 1.1.2.1  1998/12/22 23:59:00  nevil
 * Add new program to distribution
 *
 */

#if HAVE_CONFIG_H
#include <ntm_conf.h>
#endif

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#if HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/utsname.h>

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

#define MXINTERFACES  4  /* Max nbr of interfaces to meter */

#include "ausnmp.h"      /* Bit8, Bit16, .. */
#include "flowdata.h"    /* Cisco's file */

#define SZ_IFNAME  64
struct interface_info {
   u_char nbr;  /* Interface number (for Surce/DestInterface attributes) */
   char name[SZ_IFNAME+1];  /* Interface name, e.g. nf0 */
   int fd;
   struct sockaddr_in from;
   char *nf_buf;
   };
struct interface_info *pci[MXINTERFACES];
int n_interfaces;  /* Nbr of active interfaces */

int sd;  /* Target socket */
struct sockaddr_in to;
char to_host[SZ_IFNAME], to_port[SZ_IFNAME];
int pkts = 1;

int display_enabled, kb_enabled;

void interface_read(struct interface_info *pi)
{
   int flen, j;
   char msg[60];

   flen = sizeof(pi->from);
   if (recvfrom(pi->fd, pi->nf_buf, MAX_FLOW_PAK_SIZE, 0, 
         (struct sockaddr *)&pi->from, &flen) < 0) {
      printf("nf_read(%s): recvfrom failed\n", pi->name);
      return;
      }
   if (display_enabled && --pkts == 0) {
      pkts = 10;
      printf("+");  fflush(stdout);
      }

   sendto(sd, pi->nf_buf, MAX_FLOW_PAK_SIZE, 0,
      (struct sockaddr *)&to, sizeof(to));

   }

int init_interface(struct interface_info *pi)
{
   int soc;
   struct sockaddr_in socadr;
   struct in_addr netid;
   ushort port;

   if (pi->name[0] == '\0') strcpy(pi->name, "9996");
   port = atoi(pi->name);

   /* Open a UDP port from the NetFlow router */
   socadr.sin_family = AF_INET;
   socadr.sin_addr.s_addr = htonl(INADDR_ANY);
   socadr.sin_port = htons(port);
   netid.s_addr = socadr.sin_addr.s_addr;

   if ((soc = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
      printf("interface %d: couldn't get socket, errno %d\n",
         pi->nbr, (errno));
      return 0;  /* Fail */
      }
   /* Bind the socket */
   if (bind(soc, (struct sockaddr *)&socadr, sizeof(socadr)) < 0) {
      printf("interface %d: couldn't bind socket, errno %d\n",
         pi->nbr, (errno));
      return 0;  /* Fail */
      }

   sprintf(pi->name, "udp-%u", port);
   pi->fd = soc;

   pi->nf_buf = (char *)malloc(MAX_FLOW_PAK_SIZE);
   return 1;  /* OK */
   }


char *strmov(char *d, char *s)
{
   while (*s != '\0') *d++ = *s++;
   return d;
   }

/* Help screen.  Add any new command line options here */
void print_help(void)
{
   fprintf(stderr, "\nUsage: NetFwd [OPTION]...\n");
   fprintf(stderr, 
      "A redirector/consolidator for NetFlow data streams:\n\n"
      "  -t HST \t Host to send packets to\n"
      "  -p PRT \t Port nbr to send packets to (default 9996)\n"
      "\n"
      "  -i IFN \t UDP port to read from (maximum of 4)\n"
      "\n"
      "  -k     \t Disable keyboard input processing\n"
      "  -s     \t Disable screen display\n"
      "\n"
      "     >>> Hit ESC CR to terminate NetFwd while it's running <<<\n"
      "\n"
      "Report bugs to netramet@auckland.ac.nz\n");
   }

int main(int argc, char *argv[])
{
   int arg,c;
   char *ap;
   struct utsname name;
   struct hostent *hp;
   char interface_name[64] = "";

   if (argc < 2) {  /* Help Screen when called with no args */
      print_help();
      exit(-1);
      }

   display_enabled = kb_enabled = 1;  /* Enabled by default */
   for (n_interfaces = 0;  n_interfaces != MXINTERFACES; ++n_interfaces)
      pci[n_interfaces] = NULL;
   n_interfaces = 0;
   to_host[0] = to_port[0] = '\0';
   for (c = 0, arg = 1; arg < argc; ++arg) {
      if (argv[arg][0] == '-') {
         ap = argv[arg]+2;
	 switch (argv[arg][1]) {
	 case 'i':  /* -i name of interface to monitor */
	    if (*ap == '\0') ap = argv[++arg];
            pci[n_interfaces] = (struct interface_info *)calloc(
               sizeof(struct interface_info), 1);
            strncpy(pci[n_interfaces]->name,ap,SZ_IFNAME);
            pci[n_interfaces]->name[SZ_IFNAME] = '\0';
	    pci[n_interfaces]->nbr = n_interfaces+1;  /* 1-org */
            ++n_interfaces;
	    break;
	 case 'k':
	    kb_enabled = 0;  /* -k to disable keyboard */
	    break;
	 case 'p':
	   if (*ap == '\0') ap = argv[++arg];  /* 'to' Port nbr */
            strncpy(to_port,ap,SZ_IFNAME);
            break;
	 case 's':
	    display_enabled = 0;  /* -s to disable screen */
	    break;
	 case 't':
	   if (*ap == '\0') ap = argv[++arg];  /* 'To' host */
            strncpy(to_host,ap,SZ_IFNAME);
            break;
	 default:
	    printf("Invalid option: -%c\n", argv[arg][1]);
	    exit(0);
	    }
	 }
      }

   /* Set up connection to target host */
   if (to_host[0] == '\0') {
      printf("No target host specified <<<\n");
      print_help();
      exit(-2);
      }
   hp = gethostbyname(to_host);
   if (hp == NULL) {
      printf("Unknown host %s <<<\n", to_host);	
      exit(-3);
      }
   to.sin_family = AF_INET;
   memcpy((char *)&to.sin_addr.s_addr, hp->h_addr, hp->h_length);

   to.sin_port = htons(to_port[0] == '\0' ? 9996 : atoi(to_port));

   sd = socket(AF_INET, SOCK_DGRAM, 0);
   if (sd < 0){
      perror("socket");
      exit(1);
      }
   if (connect(sd, (struct sockaddr *)&to, sizeof(to)) != 0) {
      perror("connect");
      exit(2);
      }


   if (n_interfaces == 0) {
      pci[0] = 
         (struct interface_info *)calloc(sizeof(struct interface_info),1);
      pci[0]->nbr = n_interfaces = 1;
      }

   uname(&name);
   if (display_enabled) printf("Running on %s", name.nodename);
   ap = interface_name;
   for (c = 0; c != n_interfaces; ++c) {
      if (!init_interface(pci[c])) exit(-4);
      if (c != 0) ap = strmov(ap,", ");
      ap = strmov(ap,pci[c]->name);
      *ap = '\0';
      if (display_enabled) printf(", port %s",pci[c]->name);
      }
   if (display_enabled) printf("\n");
   receive(sd,n_interfaces);
   }

handle_keyboard()
{
   char kb_buf[25];
   int ch;
   fgets(kb_buf, sizeof kb_buf, stdin);
   if ((ch = kb_buf[0]) == 27) {  /* ESC */
      printf("Shutting down\n");
      exit(0);
      }
   switch (tolower(ch)) {
   case 'v':
      printf("NetFwd version v" ver_str "\n");
      break;
      }
   }

receive(int sd,int n_interfaces)
{
   fd_set fdset;
   int j, width, count;
   struct timeval t;

   width = 0;  /* Determine size of fd_set */
   for (j = 0; j != n_interfaces; ++j)
      if (pci[j]->fd > width) width = pci[j]->fd;
   ++width;

   for (;;) {
      FD_ZERO(&fdset);
      for (j = 0; j != n_interfaces; ++j) FD_SET(pci[j]->fd,&fdset);
      if (kb_enabled) FD_SET(0, &fdset);  /* stdin */
      t.tv_sec = 0;  t.tv_usec = 250000;  /* 0.25 seconds */
      count = select(width, &fdset, 0, 0, &t);
      if (count > 0) {
         for (j = 0; j != n_interfaces; ++j) {
            if (FD_ISSET(pci[j]->fd, &fdset))
               interface_read(pci[j]);
	    }
         if (FD_ISSET(0, &fdset))  /* stdin */
            handle_keyboard();
         }
      else switch (count) {
      case 0:
	 break;
      case -1:
	 if (errno == EINTR) continue;
	 else perror("select");
	 return -1;
      default:
	 printf("select returned %d\n", count);
	 return -1;
	 }
      }
   }
