/* 1500, Fri 21 May 99

  1220, Wed 19 Aug 98  OCxMON changes, MCI, Reston
  1530, Tue 21 Jul 98  New 'high-speed' WATTCP, meter v4.2
  1330, Wed 30 Apr 97  Nevil at ISMA-2, SDSC, San Diego
  2245, Thu  5 Dec 96  First OC3MON version, MCI, Reston

NeTraMet outer block, moved into OCxMON environment.

OCxMON collects tracerecs (one for the first cell of each
packet), and passes them to routines in fs2flows.c.
This module provides the same interface calls as fs2flows.

For OCxMON, we use only a single Ethernet interface, which
handles IP traffic to/from the meter.  It doesn't do any
monitoring of the Ethernet!
*/

/* 
 * $Log: meter_oc.c,v $
 * Revision 1.1.1.2.2.2  2000/01/12 02:57:11  nevil
 * Implement 'packet pair matched' turnaroundtime distribution attributes.
 * Fix ASN-related bugs in NeTraMet, distribution-related bugs in fd_filter.
 *
 * Revision 1.1.1.2.2.1  1999/11/29 00:17:27  nevil
 * Make changes to support NetBSD on an Alpha (see version.history for details)
 *
 * Revision 1.1.1.2  1999/10/03 21:06:25  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.5  1999/09/29 02:42:48  nevil
 * Fix problems discovered on PC meters
 * - Use NETRAMET compile-time variable to leave out C++ declarations
 * - Clean up mistakes in porting OCX_BGP code from meter_pc to meter_oc
 *
 * Revision 1.1.1.1.2.4  1999/09/24 02:58:40  nevil
 * Polish up code to get rid of warning messages from Borland (DOS) compiler.
 * Make manager PeerAddress buffers NSAP_ADDR_LEN bytes long.
 * Add asn_lookup variable - only call FindSubnet if ruleset uses ASNs.
 *
 * Revision 1.1.1.1.2.1  1999/01/08 01:31:55  nevil
 * Implementation of TCP attributes, part 3
 *
 * Revision 1.1.1.1  1998/11/16 03:57:29  nevil
 * Import of NeTraMet 4.3b3
 *
 * Revision 1.1.1.1  1998/11/16 03:22:01  nevil
 * Import of release 4.3b3
 *
 * Revision 1.1.1.1.2.2  1998/11/11 23:50:04  nevil
 * OC3 card swaps ATM header - we must swap it back
 *
 * Revision 1.1.3.1.2.2  1998/10/19 22:32:47  nevil
 * Meter improvements, mostly arising from developments for the
 * OCxMON meter.  These are documented in notes_oc.txt
 *
 * Revision 1.1.3.1.2.1  1998/10/18 20:51:24  nevil
 * Added Nicolai's patches, some 'tidying up' of the source
 *
 * Revision 1.1.3.1  1998/10/13 02:48:39  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.4  1998/07/21 05:31:06  rtfm
 * Three new parameters for init_meter (Joel)
 *
 * Revision 1.3  1998/05/20 04:02:28  rtfm
 * Declare startup = 1; otherwise meter doesn't start properly
 *
 * Copyright (C) 1992-2000 by Nevil Brownlee,
 * ITSS Technology Ddevelopment,  The University of Auckland
 */

#include <sys/types.h>
#include <dos.h>
#include <conio.h>
#include <alloc.h>
#include <ctype.h>


#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <float.h>              /* DBL_MIN DBL_MAX */

#if !defined(DOS)
#   include <sys/time.h>
#   include <sys/resource.h>
#   include <netinet/in.h>
#else
#   include "..\wattcp\tcp.h"
#endif

#if !defined(DOS)
#   include "types.h"
#else
#   include "..\types\types.h"     /* includes constant.h */
#   include "..\intel\timestmp.h"
#   include "..\intel\byteswap.h"
#endif

#include "..\integrat\trace.h"     /* Must be included before util.h */
#include "..\integrat\fs2flows.h"  /* ASSUME_ETHERTYPE_IS_IP, fn prototypes */

#include "..\integrat\readbgp.h"   /* Added Thu 20 Aug 98 */
#include "..\integrat\subnet.h"


#define EXTSNMP  /* Declare au_snmp_port */
#include "ausnmp.h"

#include "..\snmplib\asn1.h"
#include "..\snmplib\snmp.h"
#include "..\snmplib\snmpimpl.h"
#include "..\snmplib\snmpapi.h"

#define PKTSNAP
#include "pktsnap.h"
#include "flowhash.h"
#include "met_vars.h"

extern char version_descr[];  /* In met_vars.c */
extern char *communities[];  /* In snmp/snmpagnt.c */

extern int errno;
int snmp_dump_packet = 0;

udp_Socket udpsock;
static udp_Socket *snmp_sp;
int status;  /* For socket */
Bit32 gethostid(void);  /* In src\pcbsd.c */
void init_snmp(void);  /* In snmp\snmpapi.c */

unsigned char half_scale = 0;

void zero_pkt_stats(void);  /* meter_oc function prototypes */
void receive(void);
void meter_snmp_read(int length);


u_int active_feed = 1;
PentiumClock active_alarm;


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

struct interface_info {
   u_char nbr;  /* Interface number (for Surce/DestInterface attributes) */
   char name[20];  /* Interface name, e.g. nf0 */
   u_int SampleRate,  /* 1 = count every packet on this interface */
      sample_count;
   u_int ipackets;  /* Total packets since last stats reset */
   u_int idrops;  /* Dropped packets since last stats reset */
   };
struct interface_info pci[MXINTERFACES];

int n_interfaces = 2;  /* Nbr of active interfaces */
char interface_name[10] = "ocxa";  /* Device name for metered interface */


// none of this code gets compiled in
// if it's isn't going to be called
//
#if DO_FOREGROUND_STATS

void usage(char *prog)
{
   fprintf(stderr, "Usage: %s -u mxrules [-f mxflows]\n", prog);
   fprintf(stderr, "   -w wrcom [-r rdcom]\n");
   }


void bump_noflowpkts(int if_nbr)
{  }

void startup_screen()
{
   char cbuf[60];
   sclear();
   scpos(0,0);  printf(version_descr);
   scpos(0,1);  printf("Running on %s",inet_ntoa(cbuf,gethostid()));
   }
   
// Called once (on startup).  Passes command line in
//    (OCxMON doesn't have any command-line parameters)
//
int flows_init(
   int argc,
   char *argv[],
   PentiumClock *keystroke_interval,
   PentiumClock *query_interval,
   PentiumClock *active_interval,
   int *want_facility_loopback,
   int *want_rx,
   int *want_tx
   )
{
   int arg, sample_rate, bgp_result;
   char cbuf[20], *ap;
   struct en_info *enp;

   if (argc < 2) {
      usage(argv[0]);
      return 1;
      }

    /* Last three options added by Joel, June 98.
       Following code from integrat\fs2flows.c */

    /* Capture module should enable RX but not TX on all cards */
    *want_rx = TRUE;
    *want_tx = FALSE;

    /* Capture module should enable facility loopback on all cards */
    *want_facility_loopback = TRUE;

   scinit();  sclear();  /* Set screen size parameters */
   display_enabled = kb_enabled = 1;  /* Enabled by default */
   chart_interface = 0;
   mxflows = DFMXFLOWS;  mxrules = DFMXRULES;
   mxstreams = DFMXSTREAMS;  mxstr_blocks = DFMXSTRBLOCKS;
   mxdistribs = DFMXDISTRIBS;  mxdistevents = DFMXDISTEVENTS;
   mxpktdatas = DFMXPKTDATS;
   /* Default (CMU) communities are:  0 = "public", 1 = "proxy",
     2 = "private", 3 = "regional", 4 = "core"
      We only allow "public" and "private" by default */
   communities[1] = communities[3] = communities[4] = "";

   n_interfaces = 2;  /* For OCxMON */
   memset(pci, 0, sizeof(pci));
   pci[0].nbr = 1;  pci[1].nbr = 2;
   pci[0].SampleRate = pci[1].SampleRate = 1;
   pci[0].sample_count = pci[1].sample_count = 1;

   for (arg = 1; arg < argc; ++arg) {
      if (argv[arg][0] == '-') {
         ap = argv[arg]+2;
         switch (argv[arg][1]) {
         case 'a':
            if (*ap == '\0') ap = argv[++arg];
            mxpktdatas = atoi(ap);
            break;
	 case 'b':
	    if (*ap == '\0') ap = argv[++arg];
	    mxstr_blocks = atoi(ap);
	    break;
         case 'd':
            snmp_dump_packet++;
            break;
         case 'e':
            if (*ap == '\0') ap = argv[++arg];
            mxdistevents = atoi(ap);
            break;
         case 'f':
            if (*ap == '\0') ap = argv[++arg];
            mxflows = atoi(ap);
            break;
         case 'k':
            kb_enabled = 0;  /* -k to disable keyboard */
            break;
         case 'm':
            if (*ap == '\0') ap = argv[++arg];
            au_snmp_port = atoi(ap);
            break;
         case 'n':
            if (*ap == '\0') ap = argv[++arg];
            sample_rate = atoi(ap);
            if (sample_rate < 1) sample_rate = 1;
            pci[0].SampleRate = pci[1].SampleRate = sample_rate;
            break;
#if NF_OCX_BGP
	 case 'o':
	    use_owner_asns++;
	    break;
#endif
         case 'r':
            if (*ap == '\0') ap = argv[++arg];
            communities[0] = ap;  /* -r to set read community */
            break;
         case 's':
            display_enabled = 0;  /* -s to disable screen */
            break;
         case 't':
            if (*ap == '\0') ap = argv[++arg];
            mxstreams = atoi(ap);
            break;
         case 'u':
	    if (*ap == '\0') ap = argv[++arg];
	    mxrules = atoi(ap);
	    break;
 	 case 'v':
            if (*ap == '\0') ap = argv[++arg];
            mxdistribs = atoi(ap);
	    break;
         case 'w':
            if (*ap == '\0') ap = argv[++arg];
            communities[2] = ap;  /* -w to set write community */
            break;
         default:
            printf("Invalid option: -%c\n", argv[arg][1]);
            return 1;  /* Error */
            }
         }
      }

   snmp_sp = &udpsock;  /* myport        hisadr hisport */
   if (!udp_open( snmp_sp, SNMP_PORT,  0, 0,        0 )) {
      puts("udp_open failed!\n");
      return 1;  /* Error */
      }

   init_snmp();

#if NF_OCX_BGP
   InitSubnet();
   bgp_result = read_bgp_file("bgp.txt");
   if (bgp_result) {
      printf(">>>> read_bgp_file() returned %d <<<<", bgp_result);
      exit(99);
      }
#endif

   init_monitor(1);

   // Set an "alarm" interval when we will
   // query the statistics
   //
   active_interval->put_seconds(1.0);     /* For housekeeping */
   query_interval->put_seconds(0.05);     /* For checks on WATTCP */
   keystroke_interval->put_seconds(1.0);  /* For checks on keyboard */
   return 0;
   }


// Clears all statistics after being queried remotely
// or during initialization of module
//
// Note that we do not touch the report timestamp
// so that querying will not delay timing out a flow
//
int flows_clear(void)
{
   return 0;
   }

Bit32 nbytes;  /* npackets declared in pktsnap.h */

/* Dummies for debug functions in integrat\aal5.c 
   The real ones send debug info to a TCP socket */

int check_packet( void *sock, struct cellhashrec *hr )
{ return 0; }
void dump_packet( void *sock, struct cellhashrec *hr )
{ }

/* flows_per_packet() (from integrat\fs2flows.c) is called once per
   packet by flows_per_block(), after it has reassembled the packet
   from its cells.
   
   Constants (e.g. LINK_HAS_LLC_SNAP) defined in integrat\constant.h
   Offsets into the packet header are defined in integrat\trace.h
   */


int flows_per_packet(cellhashrec *chp, Bit8 interface)
{
   struct interface_info *pcip;
   struct pkt pp;
   Bit16 ethertype, lsap;
   int icmp2bytes;
#if NF_OCX_BGP
   Subnet *my_subnet;
   Bit32 my_address;
   Bit16 my_ASN;
#endif

   pcip = &pci[interface];
   if (--pcip->sample_count == 0) {  /* Process only 1 pkt per SampleRate */
      pcip->sample_count = pcip->SampleRate;

      memset(&pp, 0, sizeof(pp));
      pp.Low.Interface = pp.High.Interface = interface+1;  /* 1-org */
      pp.High.AdjType = AT_AAL5;  /* ATM over AAL5 */

#if LINK_HAS_LLC_SNAP
      lsap = chp->tr_lsap;
      if (lsap == LSAP_SNAP) {
         ethertype = ntohs(chp->tr_etype);
         if (ethertype == ETHERTYPE_IP) {
            pp.PeerAddrType = AT_IP4;
            pp.p_len = ntohs(chp->tr_len);  /* Length from IP header */
            }
         else {  /* We don't know the length for other packet types !!! */
            pp.PeerAddrType = AT_OTHER;
            pp.p_len = 0;
            }
         }
#else  /* Assume IP */
      lsap = 0xAAAA;  ethertype = 0x0800;
      pp.PeerAddrType = AT_IP4;
      pp.p_len = ntohs(chp->tr_len);
#endif

      ++npackets;  nbytes += pp.p_len;

      *(Bit32 *)&pp.Low.PeerAddress = chp->tr_src;
      *(Bit32 *)&pp.High.PeerAddress = chp->tr_dst;

      if ((pp.TransAddrType = chp->tr_proto) == PT_ICMP) {
         /* ICMP type and class are the first two bytes 
            after the IP header */
         icmp2bytes = chp->tr_sport;
         pp.Low.TransAddress[1] = icmp2bytes >> 8;
         pp.High.TransAddress[1] = icmp2bytes &&0xFF;
         }
      else {
         *(Bit16 *)&pp.Low.TransAddress = chp->tr_sport;
         *(Bit16 *)&pp.High.TransAddress = chp->tr_dport;
         }

#if NF_OCX_BGP
      if ((my_subnet = FindSubnet(ntohl(chp->tr_src))) != NULL) {
         my_ASN = use_owner_asns ? my_subnet->src_as : my_subnet->exit_as;
         pp.Low.nf_ASN[0] = my_ASN >> 8;
         pp.Low.nf_ASN[1] = my_ASN & 0xff;
         pp.Low.nf_mask = bits_in_mask(my_subnet->mask);
         }
      if ((my_subnet = FindSubnet(ntohl(chp->tr_dst))) != NULL) {
         my_ASN = use_owner_asns ? my_subnet->src_as : my_subnet->exit_as;
         pp.High.nf_ASN[0] = my_ASN >> 8;
         pp.High.nf_ASN[1] = my_ASN & 0xff;
         pp.High.nf_mask = bits_in_mask(my_subnet->mask);
         }
#endif

#if 0  /* NETFLOW code from meter_ux.c */
      pp.dPkts = fr1->dPkts;
      pp.dOctets = fr1->dOctets;
      pp.dFirst = fr1->First/10 - uptime_delta;  /* Centiseconds */
      pp.dLast = fr1->Last/10 - uptime_delta;

      memset(pp.Low.nf_ASN,0,TRANS_ADDR_LEN);
      memset(pp.High.nf_ASN,0,TRANS_ADDR_LEN);
      pp.Low.nf_mask = pp.High.nf_mask = 0;

      pp.ntm_interface = pi->nbr;  
            /* Remember interface from -i option */
#endif  /* NETFLOW */

      if (adj_reqd) {
            /* Use UNI VPI and VCI as low 3 bytes (4 bytes
               if we assume NNI) of destination MAC address.
               Put it in both source and dest so that rulesets
               which test only one will work as expected */

         *(Bit32 *)&pp.Low.AdjAddress[2] = 
            *(Bit32 *)&pp.High.AdjAddress[2] = 

#if CARD_SWAPS_ATM_HEADER
               NNI_VPI_VCI(htonl(chp->tr_atm.whole));  /* Swap it back! */
#else
               NNI_VPI_VCI(chp->tr_atm.whole);
#endif
         }

      chp->tr_when.strip_bitfields();
         /* Strip timestamp lower bits in place 
            (must do this before calling get_seconds) */
      pp.arrival_time = chp->tr_when.get_seconds();

      pkt_monitor(&pp);
      }

   return 0;
   }


static n_waiting = 0;  /* Chars waiting in snmp socket */

// OCxMON calls this every query_interval seconds, so as to keep wattcp
// alive.  If it gets a non-zero result, it will call flows_query (below)
// to do whatever socket processing is required.
//
// Nonzero return causes main() to ask cards how much of any
// pending block they have filled in, call flows_per_block()
// for those partial blocks, and then call flows_query()
//
int flows_is_query_wanted(void)  /* Interface changed $$$ */
{
   if (!tcp_tick(snmp_sp)) {
      fprintf(stderr,"flows_is_query_wanted(): tcp_tick failed!!\n");
      return 0;
      }
   n_waiting = sock_dataready(snmp_sp);
   return n_waiting != 0;
   }


// Called when program is queried remotely (see above)
//
int flows_query(const char *extra_msg)  /* Interface changed $$$ */
{
   meter_snmp_read(n_waiting);
   sock_close(snmp_sp);
   if (!udp_open(snmp_sp, SNMP_PORT,  0, 0,   0 )) {
      puts("udp_open failed!\n");
      return 1;  /* Error result */
      }
   return 0;  /* Success */
   }




// Called when program is about to exit
//
int flows_cleanup(void)
{
   return 0;
   }



unsigned int startup = 1, p;
int gci;  /* Check this in meter_pc (was char) !!! */
char l_tod_s;  /* Last tod_s displayed on chart */
unsigned int sumx, minx, maxx, samples;  /* Chart-building data */
unsigned long pd,bd;


unsigned int bkgi;  /* Seconds before next run of background process */
#define BKG_INTERVAL  30  /* 30 seconds */


void show_meter_time()
{
   char msg[60];
   sprintf(msg,"%lu seconds since %02d%02d:%02d",
      elapsed_sec, s_tod_h,s_tod_m,s_tod_s);
   display_msg(0,msg);
   }

void zero_pkt_stats()
{
   int j;
   npackets = nbytes = 0L;
   badpackets = nobufpackets = lostpackets  =
      stats_time = t_backlog = spackets = sbytes =
      kilodummypackets = dummypacketrate = 0L;
   mindummyrate = 100000000L;
   pkt_backlog = max_pkt_backlog = max_pkt_rate =
      dummypackets = mdpacketrate = 0;
   clear_pkt_stats = 0;
   }


// Called by main() every active_interval
// OCxMON could use this to send regular data via a
// (possibly tunneled) UDP (possibly multicast) stream
//
// NeTraMet uses it for its 'one-second' processing
//
void flows_send_active(void)
{
   unsigned int x, ax;
   char msg[60];
   struct mgr_rec *mip;
   struct rdr_rec *cip;

   ++elapsed_sec;  set_tod();
   if (startup) {  /* Running now, initialise counters etc. */
      clear_pkt_stats = 1;
      minx = 30000;  sumx = samples = maxx = 0;
      spackets = npackets = 0;  sbytes = nbytes = 0;
      l_tod_s = -1;
      gci = gc_interval;  /* Garbage collect interval */
      bkgi = BKG_INTERVAL;  /* Background interval */
      gc_f = mxflows/100;  /* Flow indexes to check */
      if (gc_f < 4) gc_f = 4;
      startup = 0;

      startup_screen();
      scpos(0,scr_mtrow);
         printf("Room for %d flows, sizeof(flow) = %d",
            mxflows, sizeof(struct flow));
      }
   else {
#ifdef NEW_ATR
      check_rates(uptime_s());
#endif
      if (--gci == 0) {
         garbage_collect(1);  /* Routine incremental collection */
         gci = gc_interval;
         }
      if (--bkgi == 0) {  /* Check % of flows active */
         p = (unsigned long)active_flows()*100/mxflows;
         if (display_enabled) {
            scpos(12,3);  printf("a=%3u",p);
            }
         if (p > FloodMark && FloodMode != TV_TRUE &&
               FloodMark > 0 && FloodMark < 100) {
            FloodMode = TV_TRUE;  /* Stop creating new flow records */
            display_msg(1,"Meter in Flood mode");
            }
         else for (x = 0; x != sizeof(mi)/sizeof(struct mgr_rec); ++x) {
            mip = &mi[x];
            if (mip->mi_Status != RS_ACTIVE
                  || mip->mi_RunningStandby == TV_TRUE)
               continue;
            else if (p > mip->mi_HighWaterMark &&
              mip->mi_HighWaterMark > 0 &&
                  mip->mi_HighWaterMark < 100 &&
                  mip->mi_StandbyRuleSet != 0 &&
                  mip->mi_StandbyRuleSet != mip->mi_CurrentRuleSet &&
                  (mip->mi_StandbyRuleSet != 0 &&
                     ri[mip->mi_StandbyRuleSet-1].ri_Status == RS_ACTIVE)
                  ) {
               open_rule_set(mip->mi_CurrentRuleSet, 0);  /* Close */
               open_rule_set(mip->mi_StandbyRuleSet, 1);  /* Open */
               mip->mi_RunningStandby = TV_TRUE;
               sprintf(msg,"Manager %d running Standby", x+1);
               display_msg(1,msg);
               }
            else if (p > HighWaterMark*2/3)
           more_garbage();
            }
         pd = uptime_cs();
         for (x = 0; x != sizeof(ci)/sizeof(struct rdr_rec); ++x) {
            cip = &ci[x];
            if (cip->ci_Status != RS_ACTIVE || cip->ci_Timeout == 0)
               continue;
            if ((pd - cip->ci_LastTime)/100 > cip->ci_Timeout)
               cip->ci_Status = RS_UNUSED;  /* Timed out */
            }
         bkgi = BKG_INTERVAL;
         }

      pd = (npackets-spackets)*pci[0].SampleRate;
      bd = (nbytes-sbytes)*pci[0].SampleRate;
      if (display_enabled) {
/*         scpos(0,3);
         printf("q=%4d",pkt_backlog);  ?? */
         scpos(23,3);
         printf("%02d%02d:%02d", tod_h,tod_m,tod_s);
         scpos(0,5);
/*         x = util_pc(pd,bd);  /* % utilisation ?? */

         x = bd / 125000;  /* Mbps */
         printf("p=%6lu, b=%10lu, u=%4d", pd,bd, x);
         sumx += x;  ++samples;
         if (x < minx) minx = x;
         if (x > maxx) maxx = x;
         if (tod_s != l_tod_s && tod_s%10 == 0) {
            if (samples) {  /* 30% in col 79 */
               ax = (sumx+(samples>>1))/samples;
               if (half_scale)
                     chart(scr_chrtcol,0, scr_lcol,scr_lrow,
                  minx>>1,ax>>1,maxx>>1);
               else chart(scr_chrtcol,0, scr_lcol,scr_lrow,
                  minx,ax,maxx);
               l_tod_s = tod_s;
               }
            minx = 30000;  sumx = samples = maxx = 0;
            }
         }

      }
   if (clear_pkt_stats) zero_pkt_stats();
   else {
      ++stats_time;
      if (pd > max_pkt_rate) max_pkt_rate = pd;
      t_backlog += (unsigned long)pkt_backlog;
      if (pkt_backlog > max_pkt_backlog)
         max_pkt_backlog = pkt_backlog;
      if (dummypacketrate < mindummyrate) {
         mindummyrate = dummypacketrate;  mdpacketrate = pd;
         }
      }

   pkt_backlog = 0;  dummypacketrate = 0;
   spackets = npackets;  sbytes = nbytes;
   }



Bit32 snmp_peer_addr;

void meter_snmp_read(int length)
{
   int out_length;
   Bit8 packet[1520], outpacket[1550];  /* Leave some space just in case */
   int count;
   struct sockaddr from;
   int fromlen = sizeof(struct sockaddr);
   char snmp_peer_name[30];  /* Name of host which sent the snmp request */
   getpeername((tcp_Socket *)snmp_sp, &from,&fromlen);
   snmp_peer_addr = from.s_ip;

   sock_read(snmp_sp, packet,length);

   if (snmp_dump_packet) {
      inet_ntoa(snmp_peer_name,from.s_ip);
      printf("received %d bytes from %s:\n", length, snmp_peer_name);
      for (count = 0; count < length; count++) {
         printf("%02X ", packet[count]);
         if ((count % 16) == 15) printf("\n");
         else if ((count % 4) == 3) printf(" ");
         }
      printf("\n\n");
      }
   out_length = 1500;
   if (snmp_agent_parse(packet, length, outpacket, &out_length, from.s_ip)) {
      if (snmp_dump_packet) {
         printf("sent %d bytes to %s:\n", out_length, snmp_peer_name);
         for (count = 0; count < out_length; count++) {
            printf("%02X ", outpacket[count]);
            if ((count % 16) == 15) printf("\n");
            }
         printf("\n\n");
         }
      sock_write(snmp_sp, (char *)outpacket, out_length);
      }
   }

void bgp_lookup_test()
{
   Subnet *my_subnet;
   Bit32 my_address;
   char my_address_string[50];
   char temp[20];

   strcpy(my_address_string,"130.216.4.32");

   /* convert the dotted-decimal string into binary */
   my_address = inet_addr("130.216.4.32");

   /* find the subnet record that corresponds to the IP address */
   my_subnet = FindSubnet(my_address);
   if (!my_subnet) {
      fprintf( stdout, "IP=%s not found in routing table\n",
         inet_ntoa(temp,my_address) );
//           continue;
      }
   else {
      fprintf( stdout, "\nIP=%s, AS_path='%s'\n",
         my_subnet->as_path, my_address_string );
      fprintf( stdout, "net=%s/%d ",
         inet_ntoa(temp,ntohl(my_subnet->addr)),
         bits_in_mask(my_subnet->mask) );
      fprintf( stdout, "nexthop_ip=%s\n",
         inet_ntoa(temp,my_subnet->nexthop_addr) );
      }
   }

void flows_keystroke(int keystroke)
{
   w_clear(0,scr_lrow, scr_chrtcol-1,scr_lrow);  /* Status area */
   scpos(0,scr_lrow);
   switch (keystroke) {
   case 'h':
      half_scale = !half_scale;
      printf("Chart scale 0..%d Mbps",
      half_scale ? 60 : 30);
      break;
   case 'v':
      printf(version_descr);
      break;
   case '=':
      startup_screen();  /* Clear the clutter off the screen */
      break;
#if NF_OCX_BGP
   case '.':  /* Test bgp lookup */
      bgp_lookup_test();
      break;
#endif
   default:
      handle_kb(keystroke);
      break;
      }
   }


#endif  // DO_FOREGROUND_STATS

