/* 1500, Fri 21 May 99

   METER_PC.C:  The AU accounting meter mainline;
		   based on CMU's snmpd,
		   PCIP's packet monitoring
		   and Waterloo's TCP/IP.

   Copyright (C) 1992-2002 by Nevil Brownlee,
   ITSS Technology Development,  The University of Auckland */

/* 
 * $Log: meter_pc.c,v $
 * Revision 1.1.1.2.2.8  2002/02/23 01:57:32  nevil
 * Moving srl examples to examples/ directory.  Modified examples/Makefile.in
 *
 * Revision 1.1.1.2.2.6  2001/05/24 02:19:55  nevil
 * LfapMet implemented by Remco Poortinga.
 * MinPDUs implemented by Nevil.
 *
 * Revision 1.1.1.2.2.4  2000/06/06 03:38:21  nevil
 * Combine NEW_ATR with TCP_ATR, various bug fixes
 *
 * Revision 1.1.1.2.2.1  2000/01/12 02:57:12  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  1999/10/03 21:06:25  nevil
 * *** empty log message ***
 *
 * Revision 1.1.1.1.2.5  1999/09/29 02:42:49  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:41  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  1998/10/28 20:31:28  nevil
 * Import of NeTraMet 4.3b1
 *
 * Revision 1.1.3.1.2.2  1998/10/19 22:32:48  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:26  nevil
 * Added Nicolai's patches, some 'tidying up' of the source
 *
 * Revision 1.1.3.1  1998/10/13 02:48:40  nevil
 * Import of Nicolai's 4.2.2
 *
 * Revision 1.1  1998/05/07 04:13:44  rtfm
 * Initial revision
 */

#pragma inline

#define UPTIME_DBG    1
#define PKT_DEBUG     0
#define PC_BGP_TEST   0

#define LOW_PRIORITY_ARP  1
#define MIN_IP_BUFFERS   10
   /* Ignore ARP packets if less IP buffers than this */

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

char interface_name[10];  /* Device name for metered interface */

#include <dos.h>
#include <bios.h>
#include <conio.h>
#include <alloc.h>
#include <ctype.h>
#include <errno.h>

#if defined(__LARGE__)
extern unsigned _stklen = 16000;  /* Default stack size is only 4K !! */
#endif

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>

#define EXTSNMP
#include "ausnmp.h"

#include "asn1.h"
#include "..\wattcp\wattcp.h"
#include "snmp.h"
#include "snmpimpl.h"
#include "snmpapi.h"
#include "..\wattcp\pktdrv.h"

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

#if FLAT_MEM  /* Use flat memory for flows */
extern void flat_init(int verbose);  /* In flat.cpp */
#endif

#if NF_OCX_BGP
#include "..\integrat\readbgp.h"
#include "..\integrat\subnet.h"
#endif

extern int errno;
int snmp_dump_packet = 0;

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

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

#if UPTIME_DBG
void print_uptime_log(void);  /* In chart.cpp */
#endif

int n_interfaces, IP_int;

void accept_pkt(int len,
#if OCX_NTM || OC3_NTM
   double at,
#else
   counter64 at,
#endif
   unsigned char huge *buff, int b_len, int if_nbr)
{
   Bit16 ether_type, lsap;
   Bit8 huge *p;
   struct pkt huge *pp;
   int pkt_ix;

   if (--en_table[if_nbr].sample_count == 0) {
      /* Process only 1 pkt per SampleRate */
      en_table[if_nbr].sample_count = en_table[if_nbr].SampleRate;
      ++en_table[if_nbr].npackets;
      en_table[if_nbr].nbytes += len;
      if (buff[12] <= 5) {  /* 802.3 (5FF hex = 1535 decimal) */
         lsap = buff[14] << 8 | buff[15];
         if (lsap == 0xAAAA) {  /* SNAP packet */
            ether_type = buff[20] << 8 | buff[21];
            p = &buff[22];  b_len -= 22;
            }
         else {
            ether_type = 0;
            p = &buff[14];  b_len -= 14;
            }
         }
      else {   /* Blue book */
         lsap = 0;
         ether_type = buff[12] << 8 | buff[13];
         p = &buff[14];  b_len -= 14;
         }
      asm pushf;  /* Save flags (especially int flag) */
      asm cli;    /* Disable interrupts */
         if (free_n_pkt == 0) pkt_ix = 0;
         else {
	    pp = &pkts[pkt_ix = free_head];
            free_head = pp->next;
            --free_n_pkt;
            }
      asm popf;   /* Restore flags */
      if (pkt_ix == 0) {  /* No pkts free, ignore the data */
	 ++lostpackets;
	 ++en_table[if_nbr].lostpackets;
	 }
      else {  /* We have a buffer */
         pp->p_len = len;
         pp->Low.Interface = pp->High.Interface = if_nbr+1;  /* 1-org */
         pp->Low.AdjType = pp->High.AdjType = AT_ETHERNET;
         pp->arrival_time = at;
         if (handle_pkt(pp, ether_type,lsap, buff, p,b_len)) {
            pp->next = 0;
            asm pushf;  /* Save flags (especially int flag) */
            asm cli;    /* Disable interrupts */
               if (proc_n_pkt == 0) proc_head = pkt_ix;
               else pkts[proc_tail].next = pkt_ix;
               proc_tail = pkt_ix;
               ++proc_n_pkt;
            asm popf;   /* Restore flags */
            }
         else {  /* No ruleset wants this packet; just free it */
            asm pushf;  /* Save flags (especially int flag) */
	    asm cli;    /* Disable interrupts */
               if (free_n_pkt == 0) free_head = pkt_ix;
	       else pkts[free_tail].next = pkt_ix;
               free_tail = pkt_ix;
               ++free_n_pkt;
	    asm popf;   /* Restore flags */
            }
	 }
      }
   }

int our_packet(Bit8 far *buff)
{
   if (buff[0] == 0xFF) {  /* Broadcast */
      if (buff[13] == 0x06 && buff[12] == 0x08)  /* ARP */
	 return 1;
      }
   else if (buff[5] == eth_addr[5] && buff[4] == eth_addr[4] &&
	 buff[3] == eth_addr[3] && buff[2] == eth_addr[2] &&
	 buff[1] == eth_addr[1] && buff[0] == eth_addr[0]) {
      if (buff[12] == 0x08 &&
	    (buff[13] == 0x00 || buff[13] == 0x06 )) {  /* IP or ARP */
	 return 1;
	 }
      }
   return 0;  /* Only want our IP & ARP pkts */
   }

#if PKT_DEBUG
Bit8 *last_buf, *buf;
#endif

Bit8 far *pkt_rcv_call1(Bit16 len,  /* Nevil, 15 Jul 96 */
   Bit16 lah_len, Bit8 far *buff, Bit16 if_nbr)
{
   Bit8 far *ipbuf;
   struct pbuf *bp;
   int if_type = en_table[if_nbr].type;

   if (if_type & EN_HP) {
      if (if_type & EN_MT)
	 accept_pkt(len,uptime(), buff,lah_len, if_nbr);
            /* Pass attrib info to meter buffer */
      if (if_type & EN_IP) {
         if (our_packet(buff)) {
#if LOW_PRIORITY_ARP
	    if (buff[13] == 0x06) {  /* ARP */
               if (freeq.len < MIN_IP_BUFFERS) return (Bit8 far *)NULL;
               }             
#endif      
            return get_pktbuf(len);
            }
         }
      return (Bit8 far *)NULL;  /* Don't bother with call2 */
      }
   else {  /* Can't see packet contents until call2 */
#if PKT_DEBUG
      last_buf = get_pktbuf(len);
      if (last_buf == NULL) swrite('#');
      else {
         bp = (struct pbuf *)(last_buf - BUF_HDR_LEN);
# if OCX_NTM || OC3_NTM
         bp->d.arrival_time.d = uptime();
# else         
         bp->d.arrival_time.c = uptime();
# endif
         }
      return last_buf;
#else
      if ((ipbuf = get_pktbuf(len)) == NULL)
         return ipbuf;  /* No free IP buffers */
      bp = (struct pbuf *)(ipbuf - BUF_HDR_LEN);
# if OCX_NTM || OC3_NTM
      bp->d.arrival_time.d = uptime();
# else         
      bp->d.arrival_time.c = uptime();
# endif
      return ipbuf;
#endif
      }
   }

void pkt_rcv_call2(Bit16 len, Bit8 far *buff, Bit16 if_nbr)
{
   struct pbuf *bp = (struct pbuf *)(buff - BUF_HDR_LEN);
   int if_type = en_table[if_nbr].type;
#if PKT_DEBUG
   if (buff != last_buf) swrite('X');
#endif
   if (if_type & EN_HP) {
      bp->state = 2;  /* IP pkt ready for tcp/ip */
      enqueue(&readyq, bp);
      return;
      }
   else {
      if (if_type & EN_MT) {
# if OCX_NTM || OC3_NTM
         accept_pkt(len,bp->d.arrival_time.d, buff,len, if_nbr);
#else         
         accept_pkt(len,bp->d.arrival_time.c, buff,len, if_nbr);
#endif
            /* Pass attribute info to meter buffer */
	 if (if_type & EN_IP) {
            if (our_packet(buff)) {
               bp->state = 2;  /* IP pkt ready for tcp/ip */
               enqueue(&readyq, bp);
               return;
               }
            }
	 }
      else if (if_type & EN_IP) {  /* IP only on this interface */
         bp->state = 2;  /* Unicast/broadcast mode */
         enqueue(&readyq, bp);
         return;
         }
      }
   bp->state = 0;  /* Unicast/broadcast mode */
   enqueue(&freeq, bp);
   }

void au_send_mon(int length, Bit8 far *buffer)
{  /* Invoked by pkt_send in src/pcpkt.c (if monitoring on IP interface) */
   accept_pkt(length,uptime(), buffer,length, IP_int);
      /* Only sending on IP interface */
   }

void bump_noflowpkts(int if_nbr)
{
   ++noflowpackets;
   ++en_table[if_nbr-1].noflowpackets;  /* 1-org */
   }

void startup_screen()
{
   char cbuf[60];
   sclear();
   scpos(0,0);  printf(version_descr);
   scpos(0,1);  printf("Running on %s",inet_ntoa(cbuf,gethostid()));
   }
   
#if PC_BGP_TEST
void bgp_lookup_test(void);
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( htonl(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) );
      }
   }
#endif

int main(int argc, char *argv[])
{
   int arg, a, c, s_n, bgp_result;
   char *ap;
   struct en_info *enp;

   scinit();  sclear();
   maxpkt = 1024;
   s_n = 1;  n_interfaces = 0;  IP_int = -1;
   for (c = 0; c != MAX_PD; ++c) {
      memset(&en_table[c], 0, sizeof(struct en_info));
      en_table[c].sample_count = en_table[c].SampleRate = 1;
      }   
   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] = "";
   for (arg = 1; arg < argc; ++arg) {
      if (argv[arg][0] == '-') {
	 ap = argv[arg]+2;
	 switch (a = 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 'f':
	    if (*ap == '\0') ap = argv[++arg];
	    mxflows = atoi(ap);
	    break;
	 case 'I':  /* IP only, no metering */
 	 case 'i':  /* Metering (and perhaps IP) */
            if (a == 'I') IP_int = n_interfaces;
	    else if (IP_int < 0) IP_int = n_interfaces;
	 case 'h':  /* Metering with high-performance driver */
            c = 0;
            if (*ap == '\0') ap = argv[++arg];
            if (arg != argc) {  /* There is another arg */
               if (*ap == '-') --arg;  /* -h -.. means -h0 */
               else if(isdigit(*ap)) c = atoi(ap);
               }
	    en_table[n_interfaces].int_nbr = c;
	    if (a != 'I') en_table[n_interfaces].type =
	       a == 'h' ? EN_HP|EN_MT : EN_MT;
            else en_table[n_interfaces].type = 0;
            ++n_interfaces;
	    break;
	 case 'k':
	    kb_enabled = 0;  /* -k to disable keyboard */
	    break;
	 case 'l':  /* Use 'Total Length' field from IP datagrams */
	    use_ip_length = 1;
	    break;
	 case 'm':
	    if (*ap == '\0') ap = argv[++arg];
            au_snmp_port = atoi(ap);
            break;
	 case 'n':
	    if (*ap == '\0') ap = argv[++arg];
	    s_n = atoi(ap);
            if (s_n < 1) s_n = 1;
	    break;
#if NF_OCX_BGP
	 case 'o':
	    use_owner_asns++;
	    break;
#endif
	 case 'p':
	    if (*ap == '\0') ap = argv[++arg];
	    maxpkt = atoi(ap);
	    break;
	 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]);
	    exit(0);
	    }
	 }
      }
   if (n_interfaces == 0) {
      en_table[0].type= EN_MT;  n_interfaces =  1;
      }
   if (IP_int == -1) IP_int = 0;
   en_table[IP_int].type |= EN_IP;
   for (c = 0; c != n_interfaces; ++c)
      en_table[c].SampleRate = en_table[c].sample_count = s_n;

   pkts = (struct pkt far *)farmalloc((maxpkt+1) * sizeof(struct pkt));
   pkts[0].next = 0;
   for (c = 1; c <= maxpkt; ++c)  /* pkt[0] used for dummy packets */
      pkts[c].next = c-1;
   free_head = maxpkt;  free_tail = 1;  free_n_pkt = maxpkt;
   proc_head = proc_tail = proc_n_pkt = 0;

   sock_init();
   sprintf(interface_name, "et%d",en_table[0].int_nbr);
   startup_screen();

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

   init_snmp();

#if FLAT_MEM && !(OCX_NTM || OC3_NTM)
   flat_init(1);  /* Verbose flat init (for ntm32) */
# endif
#if NF_OCX_BGP
   InitSubnet();
   bgp_result = read_bgp_file("bgp.txt");
   if (bgp_result) {
      log_msg(LOG_ERR, 1,
         "read_bgp_file failed, bgp_result=%d\n", bgp_result);
      }
#endif

   init_monitor(n_interfaces);
   receive();
   }

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 set_chart_interface(int int_nbr)
{
   chart_interface = int_nbr;
   spackets = en_table[chart_interface].npackets;
   sbytes = en_table[chart_interface].nbytes;
   zero_pkt_stats();
   printf("Charting interface %d", int_nbr+1);
   }

int mxmonpkts = 200;  /* Max nbr packets per pkt_monitor call */
   /* 400 was a little bit too high!  Nevil, 22 Oct 96 */
int bkgi;  /* Seconds before next run of backgound process */
#define BKG_INTERVAL  30  /* 30 seconds */
extern Bit32 pc_noflowpkts;  /* Declared in met_vars.c */

void zero_pkt_stats()
{
   int j;
   struct en_info *eip;
   pc_noflowpkts = noflowpackets;
   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;
   zpackets = en_table[chart_interface].npackets;
   clear_pkt_stats = 0;
   }

void receive()
{
   int startup, ch, gci, p,
      k, pkt_ix, n, x, sumx, minx, maxx, samples, ax;
   char msg[60];
   char l_tod_s;  /* Last tod_s displayed on chart */
   struct pkt huge *pp;
   struct en_info *eip;
   struct mgr_rec *mip;
   struct rdr_rec *cip;
   unsigned long pd,bd;
   long tot = set_timeout(1);
   char huge *cp;
   unsigned char half_scale = 0;

   startup = 1;
   for (;;) {
      sock_tick((sock_type *)s, &status);
      if ((n = sock_dataready((sock_type *)s)) != 0) {
	 meter_snmp_read(n);
	 sock_close((sock_type *)s);
	 if (!udp_open( s, SNMP_PORT,  0, 0,   0 )) {
            puts("udp_open failed!\n");
	    exit(1);
            }
	 }

      if (proc_n_pkt == 0) {  /* No queued packets - build a dummy one */
         cp = (char far *)&pkts[0];
	 for (k = 0; k != sizeof(struct pkt); ++k) cp[k] = 0;
	 pkts[0].PeerAddrType = AT_DUMMY;
	 pkt_monitor(&pkts[0]);
         }
      else {
	 if (proc_n_pkt > pkt_backlog) pkt_backlog = proc_n_pkt;
	 for (k = mxmonpkts; proc_n_pkt != 0 && k != 0; --k) {
	    asm pushf;  /* Save flags (especially int flag) */
	    asm cli;    /* Disable interrupts */
	       pp = &pkts[pkt_ix = proc_head];
	       proc_head = pp->next;
	       --proc_n_pkt;
	    asm popf;   /* Restore flags */
            pkt_monitor(pp);
            asm pushf;  /* Save flags (especially int flag) */
	    asm cli;    /* Disable interrupts */
               if (free_n_pkt == 0) free_head = pkt_ix;
	       else pkts[free_tail].next = pkt_ix;
               free_tail = pkt_ix;
               ++free_n_pkt;
	    asm popf;   /* Restore flags */
	    }
	 }

      if (chk_timeout(tot)) {
	 tot = set_timeout(1);  /* 1 second */
	 ++elapsed_sec;  set_tod();
	 eip = &en_table[chart_interface];
	 if (startup) {  /* Running now, initialise counters etc. */
	    clear_pkt_stats = 1;
	    minx = 100;  sumx = samples = maxx = 0;
	    spackets = eip->npackets;  sbytes= eip->nbytes;
	    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;
	    }
	 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 != 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;
               }               
	    npackets = eip->npackets - zpackets;  /* For 'stats' display */
	    pd = (eip->npackets-spackets)*eip->SampleRate;
	    bd = (eip->nbytes-sbytes)*eip->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 */
	       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 = 100;  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 = eip->npackets;  sbytes = eip->nbytes;

	 if (kb_enabled && kbhit()) {
	    if ((ch = getch()) == 27) {  /* ESC */
	       scpos(0,scr_lrow);
	       printf("\nShutting down\n");
               exit(0);
	       }
	    w_clear(0,scr_lrow, scr_chrtcol-1,scr_lrow);  /* Status area */
            scpos(0,scr_lrow);
	    switch (k = tolower(ch)) {
	    case 'b':
	       printf("bad=%lu, nobuf=%lu, lost=%lu",
		  badpackets,nobufpackets,lostpackets);
	       break;
	    case 'h':
	       half_scale = !half_scale;
	       printf("Chart scale 0..%d \%",
		  half_scale ? 60 : 30);
	       break;
	    case 'i':
	       printf("Ethernet %d, type %d, sw int %d",
		  chart_interface+1,
		  en_table[chart_interface].type,
		  en_table[chart_interface].int_nbr);
	       break;
	    case 'v':
	       printf(version_descr);
	       break;
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	       set_chart_interface(k - '1');
	       break;
	    case '=':
	       startup_screen();  /* Clear the clutter off the screen */
	       break;
            case '\t':
               printf("uptime = %.3f seconds", uptime_cs()/100.0);
               break;
#if UPTIME_DBG
	    case '-':
	       start_uptime_clock();  /* Caution: CLK_386 doesn't use this */
               printf("uptime clock reset");
               break;
            case '`':
	       biostime(1, 1573040L - 91L);  /* Midnight - 5s */
               break;
            case '\\':
               print_uptime_log();
               break;
            case '\'':
//               printf("last_tod = %lu, tod= %lu",
//                  last_tod, *bios_tick_counter);
               break;
#endif
#if PC_BGP_TEST
            case '?':  /* Test bgp lookup */ 
               bgp_lookup_test();
               break;
#endif
            case '.':
	       quack(313);  /* For a debugger breakpoint */
               break;
            default:
	       handle_kb(ch);
	       break;
	       }
	    }
         }
      }

sock_err:
   switch (status) {
   case 1 : /* Foreign host closed */
      exit(0);
   case -1: /* Timeout */
      printf("\nConnection timed out!");
      exit(1);
   default: printf("Aborting");
      exit(1);
      }
   }

static Bit32 snmp_peer_addr;
   
void snmp_error(char *msg)
{
   char snmp_peer_name[30], buf[80];
   inet_ntoa(snmp_peer_name, snmp_peer_addr);
   sprintf(buf, "%s: %s", snmp_peer_name,msg);
   display_msg(1, buf);
   }

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 *)s, &from,&fromlen);
   snmp_peer_addr = from.s_ip;

   sock_read((sock_type *)s, 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");
            else if ((count % 4) == 3) printf(" ");
	    }
         printf("\n\n");
	 }
      sock_write((sock_type *)s, (char *)outpacket, out_length);
      }
   }
