/* Copyright 1984 by the Massachusetts Institute of Technology */
/* See permission and disclaimer notice in file notice.h */
#include	"notice.h"

/*
 *------------------------------------------------------------------
 *
 * $Source: /u1/jis/gw/cgw/src/gw/in/RCS/egp.c,v $
 * $Revision: 1.9 $
 * $Date: 89/05/08 19:49:51 $
 * $State: Exp $
 * $Author: jis $
 * $Locker:  $
 *
 * $Log:	egp.c,v $
 * Revision 1.9  89/05/08  19:49:51  jis
 * Skip over bogus (class "D") addresses in incoming packets rather
 * then discard the entire packet (and lose the routes after the bogus route).
 * 
 * Revision 1.8  88/07/26  19:54:50  jon
 * Use the metric information returned by the routing restrictions routines.
 * 
 * Revision 1.7  88/07/24  17:38:04  jon
 * Make use of routing restriction routines for incoming routes only.
 * 
 * Revision 1.6  88/06/08  15:41:29  jon
 * Use the new test_gateway_flag instead of conditional compilation based on
 * TESTGW ...
 * 
 * Revision 1.5  88/06/05  23:14:24  jon
 * Don't update primary flag on a same gateway update to prevent route
 * flapping if the third (non-primary) peer dosn't aggree.
 * 
 * Revision 1.4  88/05/30  20:32:08  jon
 * Adds new status commands and primary/non-primary peers for trying to 
 * make better routing decisions due to the way BBN has set things up.
 * 
 * Revision 1.3  87/07/15  00:55:11  jon
 * Registers egpflg with log_register_flag
 * 
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_egp_c = "$Header: egp.c,v 1.9 89/05/08 19:49:51 jis Exp $";
#endif	lint

#include	<types.h>
#include	<sys.h>
#include	"../src/const.h"
#include	"../src/defs.h"
#include	"../src/macs.h"
#include	"../src/net.h"
#include	"../src/ext.h"
#include        "../opcon/opcon.h"
#include	"in.h"
#include	"inparam.h"
#include	"inext.h"
#include	"inrte.h"

#include	"inpro.h"
#include	"egp.h"

long	egptime;		/* Base time for chk of rcv too many
				   neighbor acqs or hellos */
long	egpsleep;		/* No. seconds between egpjob wakeups.
				   Time computed when neigh. (re)acquired 
				   or dropped */
ext long systod;		/* system clock (in seconds) */

/* Logging definitions */
#undef MSGFLG
#define MSGFLG egpflg
ext bitf egpflg;		/* Message flag for EGP */

/* Declarations for configuration info. */
ext unsw nneigh;
ext unsw nmetrics;

ext struct egpnetinfo egp_net_info[];
ext struct egpnet egp_nets[];

/* Egp flags controlling how egp routes get put into our routing table */
ext bitf egp_control_flag;
static bitf *egp_control_flag_addr_table[1];

#define NEGPFLAGNAMES 2
static char *egp_control_flag_bit_names[] = {
  "trust_primary",
  "extra_hop",
  NULL,
};

#define EGP_FLAG_TRUST_PRIMARY   01
#define EGP_FLAG_FLUSH_EXTRA_HOP 02
void egp_status_command(), egp_flags_command();

#define EGP_CMD_STATS 1
#define EGP_CMD_STATUS 2
#define EGP_CMD_CLEAR 3

static subcommand egp_scmd[] = {
    { "stats", "Print egp stats", egp_status_command, EGP_CMD_STATS },
    { "status", "Print peer status", egp_status_command, EGP_CMD_STATUS },
    { "clear", "Clear egp stats counters", egp_status_command, EGP_CMD_CLEAR},
    { "flags", "Examine/modify control flags", egp_flags_command, 0 },
    { NULL, NULL, NULL, 0 },
};

command egp_cmd =
    { NULL, "egp", "Egp control/status", egp_scmd };

/* Possibly interesting stats */
unsl egp_route_updates = 0;
unsl egp_metric_updates = 0;
unsl egp_extra_hop_updates = 0;
unsl egp_route_timeouts = 0;
unsl egp_routes_ignored = 0;
	
static char egpnonet[] = "EGP no route to %d.%d.%d.%d\n";
static char egpunreach[] = "EGP neighbor %d.%d.%d.%d unreachable\n";
static char egpfrg[] =	"EGP received fragmented packet from %d.%d.%d.%d\n";
static char egplverr[] = "EGP bad packet length or version from %d.%d.%d.%d  %d %d\n";
static char egprerr[] =	"EGP received error packet from %d.%d.%d.%d  %d %d %d\n";
static char egprbad[] =	"EGP received bad packet from %d.%d.%d.%d  %d %d %d\n";
static char egpbpint[] = "EGP bad poll interval from %d.%d.%d.%d  %d\n";
static char egpnobuf[] = "EGP can't allocate packet buffer  code %d\n";
static char egpserr[] =	"EGP send error packet to %d.%d.%d.%d  %d %d\n";
static char egpmrcv[] =	"EGP received max hello or acq from %d.%d.%d.%d  %d %d\n";
static char egpbslp[] =	"EGP bad sleep time %d\n";
static char inrtabov[] = "IN route A/B/C net table overflow\n";
static char egpupdsg[] = "EGP update %d.%d.%d.%d --> %d.%d.%d.%d:  %d to %d\n";
static char egpupdng[] =
"EGP update %d.%d.%d.%d --> %d.%d.%d.%d: %d to %d.%d.%d.%d: %d\n";

/* The major EGP state changes and initializations occur when the
 gateway first comes up (EGP), when a neighbor goes into state
 NEIGHBOR (egpstngh), and when a neighbor goes into state UNACQUIRED
 (egpstunacq).
*/
EGP()
{
    void	egpjob();
    void	inager();
    reg	egpng	*ngp;
    reg	word	nn;
    long	time;
    unsw	tmp;
    reg	net	*netp;
    iprte	*ipr;
    
    egpsleep = ACQINT1;
    time = systod;
    egptime = time;

    log_register_flag ("egp", &MSGFLG);
    opcon_register (&egp_cmd);

    egp_control_flag_addr_table[0] = &egp_control_flag;

    for (nn = 0, ngp = &egpngh[0]; nn < nneigh; nn++, ngp++) {
	ngp->ng_state = UNACQUIRED;
	ngp->ng_flags = egpnn[nn].egp_ntype;
	ngp->ng_addr.i_long = mkina(&egpnn[nn])->i_long;
	ngp->ng_id++;
	ngp->ng_retry = 0;
	ngp->ng_hint = ACQINT1;
	ngp->ng_htime = time;
	ngp->ng_racq = 0;
	ngp->ng_rpint = POLLINT;

/* Allow neighbor on non-attached net */
	ipr = ipr_lookup(ngp->ng_addr);
	if ((ipr == NULL) || ((netp = ipr->ipr_netp) == NULL)) {
	    niflog (L_CHECK)
	      dolog(egpnonet, mksina(&ngp->ng_addr));
	    ngp->ng_state = EMPTY;
	}
	else
	  ngp->ng_myaddr.i_long =
	    mkina((iniatlst[netp->n_net]->inia_addr))->i_long;
    }

    stime(egpjob, &egpngh[0], 15, EGPPRI);
    itime(inager, 0, 5 * 60, EGPPRI);
}

/* egpstngh  go into nghbr state, init most vars
*/
egpstngh(ngp, egp)
reg	egpng	*ngp;
reg	egpkt	*egp;
{
    long	time;

    time = systod;

    ngp->ng_state = NEIGHBOR;
    ngp->ng_flags &= (COREGW | PRIMARY);
    ngp->ng_id++;
    ngp->ng_retry = 0;
    ngp->ng_hello = 0;
    ngp->ng_heardme = 0;
    ngp->ng_rhello = 0;
    ngp->ng_spid++;
    ngp->ng_resnd = 0;
    ngp->ng_rercv = 0;
    ngp->ng_rpid = 0;
    ngp->ng_rpint = POLLINT;
    ngp->ng_repoll = 0;
    ngp->ng_rtime = time;
    egpsetint(ngp, egp);
}

/* egpsetint  set egp hello and poll
 intervals and times 
*/
egpsetint(ngp, egp)
reg	egpng	*ngp;
egpkt	*egp;
{
    reg	struct egpacq	*egpa;
    reg	word		tmp;
    long		time;

    time = systod;
    egpa = (struct egpacq *) egp;

    tmp = ip2s(egpa->ea_hint);
    if (tmp < 10)
      ngp->ng_hint = 10;
    else
      ngp->ng_hint = tmp;
    ngp->ng_htime = time + ngp->ng_hint;

    tmp = ip2s(egpa->ea_pint);
    if (tmp < 60)
      ngp->ng_spint = 60;
    else
      ngp->ng_spint = tmp;
    ngp->ng_stime = time + ngp->ng_spint;

    egpstime();
    return;
}

/* egpstunacq  go into state UNACQUIRED */
egpstunacq(ngp)
reg	egpng	*ngp;
{
    long	time;
    
    ngp->ng_state = UNACQUIRED;
    ngp->ng_flags &= (COREGW | PRIMARY);
    ngp->ng_id++;
    ngp->ng_retry = 0;
    ngp->ng_hint = ACQINT2;
    time = systod;
    ngp->ng_htime = time + ACQINT2;
    egpstime();
}

egpjob(ngp)
reg egpng	*ngp;
{
    long	time;
    reg	unsw	pktsnt;

    if ((numq(&inxq) >= bufshr) && (nbfree <= buflow)) {
	stime(egpjob, ngp, 2, EGPPRI);
	return;
    }

    time = systod;

    for ( ; ngp < &egpngh[nneigh]; ngp++) {
	pktsnt = 0;

	switch(ngp->ng_state) {
	case UNACQUIRED:
	case ACQUIRE_SENT:
	    if ((time - ngp->ng_htime) < 0) break;
	    ngp->ng_state = ACQUIRE_SENT;
	    if (++ngp->ng_retry > ACQTRY) {
		ngp->ng_hint = ACQINT2;
		ngp->ng_retry = 0;
	    }
	    ngp->ng_htime = time + ngp->ng_hint;
	    egsacq(ngp, NAREQ, 1, ngp->ng_id);
	    pktsnt++;
	    break;

	case NEIGHBOR:			/* Send NR poll or hello */
	    if ((ngp->ng_flags & REACHABLE) && 
		((time - ngp->ng_stime) >= 0)) {
		    /* send NR poll */
		    if (++ngp->ng_resnd < NREPOLL) {
			ngp->ng_stime = time + 30;
			egspoll(ngp);
			pktsnt++;
			break;
		    }
		    else {
			ngp->ng_stime = time + ngp->ng_spint;
			ngp->ng_resnd = 0;
		    }
		}

	    if ((time - ngp->ng_htime) >= 0) {
		/* Send hello */
		ngp->ng_id++;
		
		ngp->ng_hello++;
		if (ngp->ng_flags & REACHABLE) {
		    if (ngp->ng_hello > NHELLO) {
			if (ngp->ng_heardme <= NHEARD_UR) {
			    ngp->ng_flags &= ~REACHABLE;
			    ngp->ng_retry = 0;
			}
			ngp->ng_hello = 1;
			ngp->ng_heardme = 0;
		    }
		}
		else {	/* unreachable */
		    if (ngp->ng_heardme >= NHEARD_R) {
			ngp->ng_flags |= REACHABLE;
			ngp->ng_retry = 0;
			ngp->ng_hello = 1;
			ngp->ng_heardme = 0;
		    }
		    else
		      if (ngp->ng_hello > NHELLO) {
			  if (++ngp->ng_retry > UNREACH) {
			      niflog(L_ERRC)
				dolog(egpunreach, mksina(&ngp->ng_addr));
			      egpcease(0,ngp,5);
			      pktsnt++;
			      break;
			  }
			  ngp->ng_hello = 1;
			  ngp->ng_heardme = 0;
		      }
		}

		ngp->ng_htime = time + ngp->ng_hint;
		egshello(ngp, HELLO);
		pktsnt++;
	    }
	    break;
	    
	case CEASE_SENT:
	    if ((time - ngp->ng_htime) < 0) break;
	    if (++ngp->ng_retry > CEASETRY) {
		egpstunacq(ngp);
		break;
	    }
	    ngp->ng_htime = time + ngp->ng_hint;
	    egsacq(ngp, NACEASE, 0, ngp->ng_id);
	    pktsnt++;
	    break;

	default:
	    break;
	}

	if (pktsnt) {
	    addtsk(syshnd, EGPPRI, egpjob, ++ngp);
	    return;
	}
    }

    if ((time - egptime) >= (RCVMINS*60L)) {
	egptime = time;
	egpchrcv();
    }

    stime(egpjob, &egpngh[0], egpsleep, EGPPRI);
}

/* egpchrcv  chk whether have rcvd too many neighbor acquires
or too many hellos from a neighbor 
*/
egpchrcv()
{
    reg	egpng	*ngp, *lngp;

    for (ngp = &egpngh[0], lngp = &egpngh[nneigh]; ngp < lngp; ngp++) {
	if (ngp->ng_state == NEIGHBOR) {
	    if ( ((ngp->ng_racq & 0377) > NRACQ) ||
		((ngp->ng_rhello & 0377) > NRHELLO)) {
		    niflog(L_ERRU)
		      dolog(egpmrcv, mksina(&ngp->ng_addr),
			    ngp->ng_rhello, ngp->ng_racq);
		    egpcease(0, ngp, 0);
		    ngp->ng_state = BAD;
		}
	    ngp->ng_racq = 0;
	    ngp->ng_rhello = 0;
	}
    }
}

/* Process egp pkt which just arrived */
inegp(iob)
iorb	*iob;
{
    reg	egpng		*ngp;
    reg	inpkt		*pkt;
    reg	egpkt		*egp;
    struct egperr	*egpe;
    egpkt		*ep;
    unss		frgoff, iphlen, egplen;

    pkt = mkin(iob->i_addr);
    frgoff = ip2s(pkt->i_fofflg);
    if (frgoff & (INMFRG|INFOFFM)) {
	niflog(L_ERRC)
	  dolog(egpfrg, mksina(&pkt->i_src));
	freebuf(iob);
	return;
    }
    iphlen = (pkt->i_ihlver & INHLENM) * INBPHW;

    egp = (egpkt *) (((char *) pkt) + iphlen);
    egplen = ip2s(pkt->i_len) - iphlen;

    if ((egp->egp_ver != EGPVER) ||
	(egplen < EGPLEN) ||
	((incksum(egp, egplen) + 1) != 0) ) {
	    egrerr(iob, egp, ERR_BADHDR);
	    return;
	}

    for (ngp = &egpngh[0]; ngp < &egpngh[nneigh]; ngp++) {
	if (ngp->ng_state == EMPTY) continue;

	if (ngp->ng_addr.i_long == pkt->i_src.i_long) {
	    if (ngp->ng_myaddr.i_long != pkt->i_dst.i_long) {
		egrerr(iob, egp, ERR_UNSPEC);
		return;
	    }

	    if (ngp->ng_state == BAD) {
		freebuf(iob);
		return;
	    }

	    switch(egp->egp_type) {
	    case EGPACQ:
		egpacq(ngp, iob, egp, egplen);
		return;
	    case EGPHELLO:
		egphello(ngp, iob, egp);
		return;
	    case EGPNR:
		egpnr(ngp, iob, egp, egplen);
		return;
	    case EGPPOLL:
		egppoll(ngp, iob, egp);
		return;
	    }
	    
	    break;
	}
    }

    switch(egp->egp_type) {
/* Not legit neighbor */
    case EGPACQ:
	switch(egp->egp_code) {
	case NAREQ:
	    egracq(iob, NAREFUS, 4, egp->egp_id);
	    break;
	case NACEASE:
	    egracq(iob, NACACK, 0, egp->egp_id);
	    break;
	default:
	    egrerr(iob, egp, ERR_UNSPEC);
	    break;
	}
	break;
    case EGPNR:
    case EGPHELLO:
	egrerr(iob, egp, ERR_UNSPEC);
	break;
    case EGPPOLL:
	egrerr(iob, egp, ERR_UNSPEC);
	break;
/* May be legit. neighbor */
    case EGPERR:
	niflog (L_ERRC) {
	    egpe = (struct egperr *) egp;
	    ep = (egpkt *)egpe->ee_egphd;
	    dolog(egprerr, mksina(&pkt->i_src),
		  egpe->ee_rsn, ep->egp_type, ep->egp_code);
	}
	freebuf(iob);
	break;
    default:
	egrerr(iob, egp, ERR_UNSPEC);
	break;
    }

    return;
}

/* egpacq  process EGP neigh acq/cease packet */
egpacq(ngp, iob, egp, egplen)
reg	egpng	*ngp;
reg	iorb	*iob;
reg	egpkt	*egp;
	unsw	egplen;
{
    unsw	error;
    
    error = 0;
    switch(egp->egp_code) {
    case NAREQ:			/* Neighbor acquisition request */
	if (egplen < sizeof(struct egpacq)) {
	    error = 1;
	    break;
	}

	niflog(L_TRCP)
	  dolog("EGP NA request from %d.%d.%d.%d replying\n", mksina(&ngp->ng_addr));
	egpstngh(ngp, egp);
	ngp->ng_racq++;

	egracq(iob, NAREPLY, 1, egp->egp_id);
	return;

    case NAREPLY:			/* Neighbor acq. reply */
	if ( (ngp->ng_state == ACQUIRE_SENT) &&
	    (ngp->ng_id == egp->egp_id) &&
	    (egplen >= sizeof(struct egpacq)) ) {
		niflog(L_TRCP)
		  dolog("EGP NA reply from %d.%d.%d.%d\n",
			mksina(&ngp->ng_addr));
		egpstngh(ngp, egp);
	    }
	else
	  error = 1;
	break;

    case NAREFUS:			/* Neighbor acq. refuse */
	if ((ngp->ng_state == ACQUIRE_SENT) &&
	    (ngp->ng_id == egp->egp_id)) {
		niflog(L_TRCP)
		  dolog("EGP NA refuse from %d.%d.%d.%d\n", mksina(&ngp->ng_addr));
		egpstunacq(ngp);
	    }
	else
	  error = 1;
	break;
	
    case NACEASE:			/* Neighbor acq. cease */
	niflog(L_TRCP)
	  dolog("EGP NA cease from %d.%d.%d.%d sending cease ack\n",
		mksina(&ngp->ng_addr));
	egpstunacq(ngp);
	egracq(iob, NACACK, 0, egp->egp_id);
	return;

    case NACACK:			/* Neighbor acq. cease ack */
	if ((ngp->ng_state == CEASE_SENT) &&
	    (ngp->ng_id == egp->egp_id)) {
		niflog(L_TRCP)
		  dolog("EGP NA cease ack from %d.%d.%d.%d\n",
			mksina(&ngp->ng_addr));
		egpstunacq(ngp);
	    }
	else
	  error = 1;
	break;

    default:
	error = 1;
	break;
    }

    if (error)
      niflog(L_ERRC)
	dolog(egprbad, mksina(&ngp->ng_addr), 
	      ngp->ng_state, egp->egp_type, egp->egp_code);
    freebuf(iob);
    return;
}

/* egphello  process EGP hello packet.
*/
egphello(ngp, iob, egp)
reg	egpng	*ngp;
iorb	*iob;
reg	egpkt	*egp;
{
    intf	error;

    error = 0;
    if (ngp->ng_state != NEIGHBOR)
      error = 1;
    else
      switch(egp->egp_code) {
      case HELLO:
	  ngp->ng_rhello++;
	  niflog(L_TRCP)
	    dolog("EGP HELLO from %d.%d.%d.%d replying\n",
		  mksina(&ngp->ng_addr));
	  egrhello(ngp, iob, HEARDU, egp->egp_id);
	  return;

      case HEARDU:
	  niflog(L_TRCP)
	    dolog("EGP IHU from %d.%d.%d.%d\n", mksina(&ngp->ng_addr));
/* Debug
	  if (ngp->ng_id == egp->egp_id) */
	    ngp->ng_heardme++;
	  break;

      default:
	  error = 1;
	  break;
      }

    if (error)
      niflog(L_ERRC)
	dolog(egprbad, mksina(&ngp->ng_addr),
	      ngp->ng_state, egp->egp_type, egp->egp_code);

    freebuf(iob);
    return;
}

/* Process egp NR poll packet.
 Note that don't swab incoming egp_id - just work w. swabbed id
*/
egppoll(ngp, iob, egp)
reg	egpng	*ngp;
iorb	*iob;
reg	egpkt	*egp;
{
    reg	struct egppoll	*nrp;
    unsw		error;
    long		time;

    error = 0;
    nrp = (struct egppoll *) egp;

    if (ngp->ng_state != NEIGHBOR) error = ERR_UNSPEC;
    else if (egp->egp_code != 0) error = ERR_BADHDR;
    else if (nrp->ep_net[0] != ngp->ng_myaddr.i_aaddr.i_anet)
      error = ERR_BADDATA;
    else {
	time = systod;
	if ((time - ngp->ng_rtime) < 0) {
/* My NR message may have been lost */
	    if ((egp->egp_id == ngp->ng_rpid) &&
		(ngp->ng_repoll++ < NREPOLL)) ;
	    else
	      error = ERR_MAXPOLL;
	}
	else {
	    ngp->ng_rpid = egp->egp_id;
	    ngp->ng_repoll = 0;
	    ngp->ng_rtime = time + (60 * ngp->ng_rpint);
	}
    }

    if (error) {
	if (error < 100)
	  egrerr(iob, egp, error);
	else {
	    niflog(L_ERRC)
	      dolog(egprbad, mksina(&ngp->ng_addr),
		    ngp->ng_state, egp->egp_type, egp->egp_code);
	    freebuf(iob);
	}
	return;
    }

    niflog(L_TRCP)
      dolog("EGP POLL from %d.%d.%d.%d replying\n", mksina(&ngp->ng_addr));
    egrnr(ngp, iob, egp->egp_id);
    return;
}

/* Process NR message packet.
*/
egpnr(ngp, iob, egp, egplen)
reg	egpng	*ngp;
iorb	*iob;
reg	egpkt	*egp;
unsw	egplen;
{
    reg	struct egpnr	*nrp;
    unss		error, egpid;
    long		time;
    net                 *netp;

    error = 0;
    egpid = ip2s(egp->egp_id);
    nrp = (struct egpnr *) egp;
    netp = mknet(iob->i_usr2);

    if (ngp->ng_state != NEIGHBOR) error = 100;
    else if (egp->egp_code != 0) error = ERR_BADHDR;
    else if (nrp->en_net[0] != ngp->ng_myaddr.i_aaddr.i_anet)
      error = ERR_BADDATA;
    else if (egp->egp_info & UFLAG) {
	if ((egpid + 1) != ngp->ng_spid) error = 100;
	else if (++ngp->ng_rercv > 1) error = 100;
    }
    else if ((egpid != ngp->ng_spid) ||
	     (ngp->ng_resnd == 0)) error = 100;

    if (error == 0) {
	niflog(L_TRCP)
	  dolog("EGP NR from %d.%d.%d.%d\n", mksina(&ngp->ng_addr));
	error = innupd(nrp, ngp->ng_addr.i_long, ip2s (egp->egp_system),
		       (ngp->ng_flags & COREGW), (ngp->ng_flags & PRIMARY),
		       netp);
	ngp->ng_spid++;
	ngp->ng_resnd = 0;
	ngp->ng_rercv = 0;
	time = systod;
	ngp->ng_stime = time + ngp->ng_spint;
    }

    if (error) {
	if (error < 100) {
	    egrerr(iob, egp, error);
	    return;
	}
	niflog(L_ERRC)
	  dolog(egprbad, mksina(&ngp->ng_addr),
		ngp->ng_state, egp->egp_type, egp->egp_code);
    }

    freebuf(iob);
    return;
}

/* Initiate egp neighbor cease */
egpcease(iob, ngp, rsn)
reg	iorb	*iob;
reg	egpng	*ngp;
unsw	rsn;
{
    long	time;

    if (iob == NULL) 
      egsacq(ngp, NACEASE, rsn, ++ngp->ng_id);
    else
      egracq(iob, NACEASE, rsn, ++ngp->ng_id);

    ngp->ng_state = CEASE_SENT;
    ngp->ng_retry = 0;
    time = systod;
    ngp->ng_htime = time + ngp->ng_hint;
    return;
}

egsacq(ngp, code, info, id)
egpng	*ngp;
word	code, info, id;
{
    reg	iorb	*iob;

    if ((iob = mkiorb(getbuf())) == NULL) {
	niflog(L_ERRC)
	  dolog(egpnobuf, EGPACQ);
	return(FALSE);
    }
	
    mkinpkt(iob, &ngp->ng_myaddr, &ngp->ng_addr, INEGP, 
	    sizeof(struct egpacq));
    mkacq(iob, code, info, id);
    niflog(L_TRCP)
      dolog("EGP snd NA packet %d to %d.%d.%d.%d\n", code, mksina(&ngp->ng_addr));
    inxaddq(iob);
    
    return(TRUE);
}

egracq(iob, code, info, id)
reg	iorb	*iob;
word	code, info, id;
{
    mkrinpkt(iob, sizeof(struct egpacq));
    mkacq(iob, code, info, id);
    inxaddq(iob);
    return;
}

mkacq(iob, code, info, id)
iorb	*iob;
word	code, info, id;
{
    reg	inpkt		*pkt;
    reg	egpkt		*egp;
    reg	struct egpacq	*egpa;

    pkt = mkin(iob->i_addr);
    egp = ((egpkt *) (((char *) pkt) +
		      ((pkt->i_ihlver & INHLENM) * INBPHW)));
    egp->egp_ver = EGPVER;
    egp->egp_type = EGPACQ;
    egp->egp_code = code;
    egp->egp_info = info;
    egp->egp_system = s2ip(mysystem);
    egp->egp_id = id;

    egpa = (struct egpacq *) egp;
    egpa->ea_hint = s2ip(HELLOINT);
    egpa->ea_pint = s2ip((60 * POLLINT));

    egp->egp_chksum = 0;
    egp->egp_chksum = ~incksum(egp, sizeof(struct egpacq));
    return;
}

egshello(ngp, code)
egpng	*ngp;
word	code;
{
    reg	iorb	*iob;
    
    if ((iob = mkiorb(getbuf())) == NULL) {
	niflog(L_ERRC)
	  dolog(egpnobuf, EGPHELLO);
	return(FALSE);
    }
	
    mkinpkt(iob, &ngp->ng_myaddr, &ngp->ng_addr, 
	    INEGP, sizeof(egpkt));
    mkhello(ngp, iob, code, ngp->ng_id);
    niflog(L_TRCP)
      dolog("EGP sending HELLO packet %d to %d.%d.%d.%d\n",
	    code, mksina(&ngp->ng_addr));
    inxaddq(iob);

    return(TRUE);
}
	
egrhello(ngp, iob, code, id)
egpng	*ngp;
reg	iorb	*iob;
word	code, id;
{
    mkrinpkt(iob, sizeof(egpkt));
    mkhello(ngp, iob, code, id);
    inxaddq(iob);
    return;
}

mkhello(ngp, iob, code, id)
reg	egpng	*ngp;
iorb	*iob;
word	code, id;
{
    inpkt		*pkt;
    reg	egpkt		*egp;

    pkt = mkin(iob->i_addr);
    egp = (egpkt *) (((char *) pkt) +
		     ((pkt->i_ihlver & INHLENM) * INBPHW));
    egp->egp_ver = EGPVER;
    egp->egp_type = EGPHELLO;
    egp->egp_code = code;
    egp->egp_info = 1; /* (ngp->ng_flags & REACHABLE) ? 1:2; */
    egp->egp_system = s2ip(mysystem);
    egp->egp_id = id;
    egp->egp_chksum = 0;
    egp->egp_chksum = ~incksum(egp, sizeof(egpkt));

    return;
}

/* egspoll  send NR poll packet */
egspoll(ngp)
reg	egpng	*ngp;
{
    reg	iorb	*iob;

    if ((iob = mkiorb(getbuf())) == NULL) {
	niflog(L_ERRC)
	  dolog(egpnobuf, EGPPOLL);
	return(FALSE);
    }
	
    mkinpkt(iob, &ngp->ng_myaddr, &ngp->ng_addr, INEGP, 
	    sizeof(struct egppoll));
    mkpoll(iob, ngp);
    niflog(L_TRCP)
      dolog("EGP sending POLL to %d.%d.%d.%d\n", mksina(&ngp->ng_addr));
    inxaddq(iob);

    return(TRUE);
}

mkpoll(iob, ngp)
iorb	*iob;
reg	egpng	*ngp;
{
    inpkt		*pkt;
    reg	egpkt		*egp;
    reg	struct egppoll	*nrp;

    pkt = mkin(iob->i_addr);
    egp = (egpkt *) (((char *) pkt) +
		     ((pkt->i_ihlver & INHLENM) * INBPHW));
    egp->egp_ver = EGPVER;
    egp->egp_type = EGPPOLL;
    egp->egp_code = 0;
    egp->egp_info = (ngp->ng_flags & REACHABLE) ? 1:2;
    egp->egp_system = s2ip(mysystem);
    egp->egp_id = s2ip(ngp->ng_spid);

    nrp = (struct egppoll *) egp;
    nrp->ep_bl1 = 0;
    mkina(nrp->ep_net)->i_long = 0;
    nrp->ep_net[0] = ngp->ng_myaddr.i_aaddr.i_anet;

    egp->egp_chksum = 0;
    egp->egp_chksum = ~incksum(egp, sizeof(struct egppoll));

    return;
}


egrerr(iob, egp, error)
iorb	*iob;
egpkt	*egp;
word	error;
{
    niflog(L_ERRC)
      dolog(egpserr, 
	    mksina(&((mkin(iob->i_addr))->i_src)),
	    egp->egp_type, egp->egp_code);

    mkrinpkt(iob, sizeof(struct egperr));
    mkerr(iob, egp, error);
    inxaddq(iob);
    return;
}

mkerr(iob, eegp, error)
iorb	*iob;
reg	char	*eegp;
word	error;
{
    inpkt		*pkt;
    reg	struct egperr	*ep;
    reg	char		*epc;
    char		*epe;

    pkt = mkin(iob->i_addr);
    ep = ((struct egperr *) (((char *) pkt) +
			     ((pkt->i_ihlver & INHLENM) * INBPHW)));

/* output pkt may be same as input so copy error pkt first */
    epc = (char *)ep->ee_egphd;		
    epe = epc + sizeof(ep->ee_egphd);
    while (epc < epe)
      *epc++ = *eegp++;

    ep->ee_pkt.egp_ver = EGPVER;
    ep->ee_pkt.egp_type = EGPERR;
    ep->ee_pkt.egp_code = 0;
    ep->ee_pkt.egp_info = 0;
    ep->ee_pkt.egp_system = s2ip(mysystem);
    ep->ee_pkt.egp_id = 0;

    ep->ee_rsn = error;

    ep->ee_pkt.egp_chksum = 0;
    ep->ee_pkt.egp_chksum = ~incksum(ep, sizeof(struct egperr));
    return;
}

/* egrnr  return NR message packet */
egrnr(ngp, iob, id)
egpng	*ngp;
iorb	*iob;
word	id;
{
    unsw	len;

    len = mknr(ngp, iob, id);
    mkrinpkt(iob, len);
    inxaddq(iob);

    return;
}

/* Should deal with routing restriction stuff in here, but I'm punting for 
   now, since I think we need a better way to decide what to advertise via
   EGP in anycase.  For now the static list will do. -- Jon 7/24/88 */

mknr(ngp, iob, id)
egpng	*ngp;
iorb	*iob;
unsw	id;
{
    inpkt		*pkt;
    reg	egpkt		*egp;
    reg	struct egpnr	*nrp;
    reg	char 		*nrb;
    unsw		len;
    struct egpnet	*en;
    reg int             i,j;

    pkt = mkin(iob->i_addr);
    egp = ((egpkt *) (((char *) pkt) +
		      ((pkt->i_ihlver & INHLENM) * INBPHW)));
    egp->egp_ver = EGPVER;
    egp->egp_type = EGPNR;
    egp->egp_code = 0;
    egp->egp_info = (ngp->ng_flags & REACHABLE) ? 1:2;
    egp->egp_system = s2ip(mysystem);
    egp->egp_id = id;

    nrp = (struct egpnr *) egp;
    mkina(nrp->en_net)->i_long = 0;
    nrp->en_net[0] = ngp->ng_myaddr.i_aaddr.i_anet;	/* shared net */
    nrp->en_igw = 1;
    nrp->en_egw = 0;

/* Mills NR format */
    nrb = (char *) egp + sizeof(struct egpnr);
    *nrb++ = ngp->ng_myaddr.i_aaddr.i_ahsth;	/* my host adr on shared net */
    *nrb++ = ngp->ng_myaddr.i_aaddr.i_ahstm;
    *nrb++ = ngp->ng_myaddr.i_aaddr.i_ahstl;
    *nrb++ = nmetrics;  /* number of distances */

    for (i = 0, en = &egp_nets[0]; i < nmetrics; i++) {
      *nrb++ = egp_net_info[i].n_hops;
      *nrb++ = egp_net_info[i].n_nets;
      for (j = 0; j < egp_net_info[i].n_nets; j++, en++) {
	switch((en->egp_net[0] & CLMSK) >> CLSHFT) {
	case CLA0:
	case CLA1:
	case CLA2:
	case CLA3:
	  *nrb++ = en->egp_net[0];
	  break;
	case CLB0:
	case CLB1:
	  *nrb++ = en->egp_net[0];
	  *nrb++ = en->egp_net[1];
	  break;
	case CLC:
	  *nrb++ = en->egp_net[0];
	  *nrb++ = en->egp_net[1];
	  *nrb++ = en->egp_net[2];
	  break;
	default:
	  bughalt("Bad EGP net");
	}
      }
    }
		
    len = nrb - (char *) egp;

    egp->egp_chksum = 0;
    egp->egp_chksum = ~incksum(egp, len);

    return(len);
}

egpstime()
{
    reg	egpng	*ngp, *ngpl;

    egpsleep = 60;
    for (ngp = &egpngh[0], ngpl = &egpngh[nneigh]; ngp < ngpl; ngp++) {
	switch(ngp->ng_state) {
	case UNACQUIRED:
	case ACQUIRE_SENT:
	case NEIGHBOR:
	case CEASE_SENT:
	    if (ngp->ng_hint < egpsleep)
	      egpsleep = ngp->ng_hint;
	    break;

	default:
	    break;
	}
    }
    
    if (egpsleep < 10) {
	niflog(L_CHECK)
	  dolog(egpbslp, egpsleep);
	egpsleep = 10;
    }
    
    return;
}

/* innupd  pick up routing info from egp NR packet -
 enter into our routing tables if 1) have no info about net 
 or 2) route to net is via nonfixed gateway and new distance is
 less than old distance or info about route is aging (older
 than half age time + 1).  Also update time and distance fields
 for gateways already in table.
*/
innupd(nrp, src_gw, src_autonomous_system, gwtype, primary, netp)
struct egpnr	*nrp;
inaddr          src_gw;
unss            src_autonomous_system;
unsb		gwtype;
unsb            primary;
net             *netp;
{
    reg	unsb	*nrb;
    reg iprte	*gnr;
    char	gw[4];
    inaddr	net_addr;
    unss	class, dist;
    reg	iprte	*inn;
    unsw	n, ngw;
    unsw	nd, ndist;
    unsw	nn, nne;
    int         is_egp_peer(), do_update;
    int         egp_trust_primary, egp_flush_extra_hop;
    ext         bitf test_gateway_flag;
    int         metric;

    egp_trust_primary = (egp_control_flag & EGP_FLAG_TRUST_PRIMARY);
    egp_flush_extra_hop = (egp_control_flag & EGP_FLAG_FLUSH_EXTRA_HOP);

    /* Get net and its net table ptr for gateways listed in routing update
       packet.  Our gateway must have direct interface to this net */
    switch((nrp->en_net[0] & CLMSK) >> CLSHFT) {
    case CLA0:
    case CLA1:
    case CLA2:
    case CLA3:
	class = CLAA;
	gw[0] = nrp->en_net[0];
	gw[1] = gw[2] = gw[3] = 0;
	break;

    case CLB0:
    case CLB1:
	class = CLAB;
	mkina(gw)->i_baddr.i_bnet = mkina(nrp->en_net)->i_baddr.i_bnet;
	mkina(gw)->i_word.i_wordl = 0;
	break;

    case CLC:
	class = CLAC;
	mkina(gw)->i_caddr.i_cneth = mkina(nrp->en_net)->i_caddr.i_cneth;
	mkina(gw)->i_caddr.i_cnetl = mkina(nrp->en_net)->i_caddr.i_cnetl;
	gw[3] = 0;
	break;

    case CLI:
    default:
	return(ERR_BADDATA);
    }

    if ( ((gnr = ipr_lookup(*(long *)(nrp->en_net))) == NULL))
      return(ERR_BADDATA);
    /* only enforce this in production */
    if (!test_gateway_flag)
      if ((gnr->ipr_netp == NULL) || (gnr->ipr_type != T_DIR))
	return (ERR_BADDATA);

    nrb = (unsb *) nrp + sizeof(struct egpnr);
    ngw = (gwtype == COREGW) ?
      (nrp->en_igw + nrp->en_egw) : nrp->en_igw;

    for (n = 0; n < ngw; n++) {
	/* Fill gw host addr */	
	switch(class) {
	case CLAA:	gw[1] = *nrb++;
	case CLAB:	gw[2] = *nrb++;
	case CLAC:	gw[3] = *nrb++;
			break;
	default:
			bughalt("innupd  bad shared net class");
	}

	ndist = *nrb++;
	for (nd = 0; nd < ndist; nd++) {
	    dist = (*nrb++ & 0377);
	    nne = *nrb++;
	    for (nn = 0; nn < nne; nn++) {
		net_addr.i_long = 0;
		switch((*nrb & CLMSK) >> CLSHFT) {
		case CLA0:
		case CLA1:
		case CLA2:
		case CLA3:
		    net_addr.i_aaddr.i_anet = *nrb;
		    nrb++;
		    break;

		case CLB0:
		case CLB1:
		    net_addr.i_baddr.i_bnet = mkina(nrb)->i_baddr.i_bnet;
		    nrb += 2;
		    break;

		case CLC:
		    net_addr.i_caddr.i_cneth = mkina(nrb)->i_caddr.i_cneth;
		    net_addr.i_caddr.i_cnetl = mkina(nrb)->i_caddr.i_cnetl;
		    nrb += 3;
		    break;

	        case CLI:
		    /* This is an illegal address, just skip over it */
		    net_addr.i_caddr.i_cneth = mkina(nrb)->i_caddr.i_cneth;
		    net_addr.i_caddr.i_cnetl = mkina(nrb)->i_caddr.i_cnetl;
		    nrb += 3;

		    niflog(L_ERRC)
		      dolog("EGP ignoring bogus route to %d.%d.%d.%d from %d.%d.%d.%d\n",
			    mksina(&net_addr), mksina(&src_gw));
		    continue;

		default:
		    return(100);
		}

		if (res_ignore_route (F_RES_EGP, net_addr.i_long, 
				      netp->n_net, &metric)) {
		  niflog (L_INFC) 
		    dolog ("EGP ignoring admin prohibited route to %d.%d.%d.%d from %d.%d.%d.%d\n",
			   mksina(&net_addr), mksina(&src_gw));
		  continue;
		}
		  
		/* use the metric with the routing restriction if there
		   was one ... this is somewhat dangerous ... */
		if (metric != 0) dist = metric;

		inn = ipr_make1(net_addr.i_long);
		if (inn == NULL) {
		    niflog(L_ERRU)
		      dolog(inrtabov);
		    continue;
		}

		switch (inn->ipr_type) {
		case T_DIR:
		case T_FIX:
		    break;
		case T_FLT:
		    if (inn->ipr_owner != O_EGP)
		      break;

		    /* same gateway, why not take the new metric? */
		    if (inn->ipr_gw.i_long == mkina(gw)->i_long) {
			niflog(L_TRCO)
			  if (inn->ipr_metric != dist)
			    dolog(egpupdsg,
				  mksina(&inn->ipr_addr), mksina(&inn->ipr_gw),
				  inn->ipr_metric, dist);
			inn->ipr_metric = dist;
			inn->ipr_time = systod;
			inn->ipr_src_gw.i_long = src_gw.i_long;
			inn->ipr_autonomous_system = src_autonomous_system;
			if (primary)
			  inn->ipr_flags |= F_EGP_PRIMARY;
			/* Be sure not to reset primary even if this "update"
			   isn't from a primary.  The worst we pay for this
			   is waiting to half timeout this route if the
			   primary stops chirping it.  The best is we prevent
			   route flapping if a primary and non-primary aggree
			   on a route but another non-primary thinks otherwise.
			   */
			egp_metric_updates++;
			break;
		    }

		    /* this was the old code */
		    if (!egp_trust_primary) {
		      if ((dist <= inn->ipr_metric) ||
			  ((systod - inn->ipr_time) >= (EGP_RTE_TIMEOUT/2))) {
			/* this extra hop stuff is new */
			/* if the new route is through an egp peer don't
			   take it unless the old route is also */
			if (egp_flush_extra_hop && 
			    is_egp_peer (mkina (gw)->i_long) &&
			    !is_egp_peer (inn->ipr_gw.i_long)) {
			  egp_extra_hop_updates++;
			  break;
			}
			niflog(L_TRCO)
			  dolog(egpupdng, mksina(&inn->ipr_addr),
				mksina(&inn->ipr_gw), inn->ipr_metric,
				mksina(gw), dist);
			inn->ipr_netp = gnr->ipr_netp;
			inn->ipr_gw.i_long = mkina(gw)->i_long;
			inn->ipr_metric = dist;
			inn->ipr_time = systod;
			inn->ipr_src_gw.i_long = src_gw.i_long;
			/* below is new */
			inn->ipr_autonomous_system = src_autonomous_system;
			if (primary)
			  inn->ipr_flags |= F_EGP_PRIMARY;
			else
			  inn->ipr_flags &= !F_EGP_PRIMARY;
			egp_route_updates++;
		      }
		    }
		    else { 
		      /* We trust a "primary" egp server more than the metrics.
			 Thank you BBN! */
		      do_update = FALSE;
		      
		      /* update if this info is from a primary or if the old
			 info was from a non-primary and this metric is at
			 least as good */
		      if (primary || (!(inn->ipr_flags & F_EGP_PRIMARY)
				      && (dist <= inn->ipr_metric)))
			do_update = TRUE;

		      /* We want to experiment with avoiding extra hops at 
			 all cost, so take this route if the old route
			 was though an egp peer and this one isn't */
		      if (egp_flush_extra_hop && 
			  is_egp_peer (inn->ipr_gw.i_long) &&
			  !is_egp_peer (mkina (gw)->i_long)) {
			do_update = TRUE;
			egp_extra_hop_updates++;
		      }
		      /* Also if the new route is through an egp peer don't
			 take it unless the old route is also */
		      if (egp_flush_extra_hop && 
			  is_egp_peer (mkina (gw)->i_long) &&
			  !is_egp_peer (inn->ipr_gw.i_long)) {
			do_update = FALSE;
			egp_extra_hop_updates++;
		      }

		      if ((systod - inn->ipr_time) >= (EGP_RTE_TIMEOUT/2)) {
			do_update = TRUE;
			egp_route_timeouts++;
		      }
		      if (do_update) {
			niflog(L_TRCO)
			  dolog(egpupdng, mksina(&inn->ipr_addr),
				mksina(&inn->ipr_gw), inn->ipr_metric,
				mksina(gw), dist);
			inn->ipr_netp = gnr->ipr_netp;
			inn->ipr_gw.i_long = mkina(gw)->i_long;
			inn->ipr_src_gw.i_long = src_gw.i_long;
			inn->ipr_autonomous_system = src_autonomous_system;
			inn->ipr_metric = dist;
			inn->ipr_time = systod;
			if (primary)
			  inn->ipr_flags |= F_EGP_PRIMARY;
			else
			  inn->ipr_flags &= !F_EGP_PRIMARY;
			egp_route_updates++;
		      }
		      else egp_routes_ignored++;
		    }
		    break;

		case T_NONE:
		    inn->ipr_type = T_FLT;
		    inn->ipr_owner = O_EGP;
		    inn->ipr_netp = gnr->ipr_netp;
		    inn->ipr_gw.i_long = mkina(gw)->i_long;
		    inn->ipr_src_gw.i_long = src_gw.i_long;
		    inn->ipr_autonomous_system = src_autonomous_system;
		    inn->ipr_metric = dist;
		    inn->ipr_time = systod;
		    if (primary)
		      inn->ipr_flags |= F_EGP_PRIMARY;
		    else
		      inn->ipr_flags &= !F_EGP_PRIMARY;
		    egp_route_updates++;
		    break;
		}
	    }
	}
    }
    return(0);
}

/* Times out old routes.  This is just something bogus for now.
 * Sometime I'll figure out what really should be here and write
 * it. */
inager(arg)
int arg;
{
    reg iprte **iprp, *ipr;

    for (iprp = &ip_routes[0]; iprp < &ip_routes[HASH_SIZE]; iprp++)
      for (ipr = *iprp; ipr != NULL; ipr = ipr->ipr_link) {
	  if (ipr->ipr_type != T_FLT || ipr->ipr_owner != O_EGP)
	    continue;

	  if ((systod - ipr->ipr_time) > EGP_RTE_TIMEOUT)
	    ipr->ipr_metric = 255;
      }
}

/* Return true if we are egp peering with addr */
int is_egp_peer (addr)
     inaddr addr;
{
  egpng *ng;
  for (ng = &egpngh[0] ; ng < &egpngh[nneigh]; ng++)
    if (addr.i_long == ng->ng_addr.i_long) return TRUE;
  return FALSE;
}

/* This must agree with "states" defined in egp.h */

static char *egp_states[] = {
  "EMPTY", "UNACQUIRED", "ACQUIRE_SENT", "NEIGHBOR", "CEASE_SENT", "BAD"};

egp_status_command(devp, arg, jcl)
dct *devp;
int arg;
char *jcl;
{
  reg	egpng	*ngp;
  reg	word	nn;

  switch (arg) {
  case EGP_CMD_STATS:
    fprintf (devp, 
	     "Updates:\tignored\troutes\tmetrics\textra hop\ttimeouts\n");
    fprintf (devp, "\t\t%d\t%d\t%d\t%d\t\t%d\n", 
	     egp_routes_ignored, egp_route_updates, egp_metric_updates,
	     egp_extra_hop_updates, egp_route_timeouts);
    break;
  case EGP_CMD_STATUS:
    fprintf (devp, "Status of EGP peers:\n");
    for (nn = 0, ngp = &egpngh[0]; nn < nneigh; nn++, ngp++) {
      fprintf (devp, "%d.%d.%d.%d -- %s", mksina(&(ngp->ng_addr)),
	       egp_states[ngp->ng_state]);
      if (ngp->ng_flags & COREGW) fprintf (devp, ", COREGW");
      if (ngp->ng_flags & PRIMARY) fprintf (devp, ", PRIMARY");
      if (ngp->ng_flags & REACHABLE) fprintf (devp, ", REACHABLE");      
      fprintf (devp, "\n");
    }
    break;
  case EGP_CMD_CLEAR:
    egp_route_updates = 0;
    egp_metric_updates = 0;
    egp_extra_hop_updates = 0;
    egp_route_timeouts = 0;
    egp_routes_ignored = 0;
    break;
  }
}

egp_flags_command (devp, arg, jcl)
     dct *devp;
     int arg;
     char *jcl;
{
  char the_bit_name[32], *dummy, *get_cmd(); /* in opcon.c */

  dummy = get_cmd (jcl, the_bit_name);
  do_flag_command_given_flag (devp, egp_control_flag_addr_table,
			      NEGPFLAGNAMES,  egp_control_flag_bit_names,
			      0x00 /* normal value */, NULL,
			      0 /* flag index */, "egpctl", 
			      jcl ? the_bit_name : NULL);
}

