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

/*	Code to handle Internet routing.  These routines provide for
 * routing internet packets to the appropriate next hop as determined
 * by the routing tables and provide an interface for the various
 * routing protocols to update those routing tables as they need.
 *
 *
 * Apr-86: (dab) Written.
 */

/*
 *------------------------------------------------------------------
 *
 * $Source: /mit/cgw/src/gw/in/RCS/inrte.c,v $
 * $Revision: 1.16 $
 * $Date: 90/04/08 00:05:47 $
 * $State: Exp $
 * $Author: jon $
 * $Locker:  $
 *
 * $Log:	inrte.c,v $
 * Revision 1.16  90/04/08  00:05:47  jon
 * add net 18 routing hack (and disable mtu frag stuff)
 * 
 * Revision 1.15  89/03/15  08:38:24  jon
 * add support for host routes
 * 
 * Revision 1.14  88/12/04  23:36:52  jon
 * add support for a default metric for DONT_LISTEN
 * 
 * Revision 1.13  88/10/31  01:32:36  jon
 * add some debuging code inside of ifdef TESTGW
 * 
 * Revision 1.12  88/10/30  20:24:25  jon
 * Add support for outgoing fragmenation.
 * 
 * Revision 1.11  88/07/26  19:53:55  jon
 * Undo the simpler non-play restrictions game stuff and make res_ignore_route
 * return the metric as will.  Minor fix to res_advertise_route.
 * 
 * Revision 1.10  88/07/24  20:53:17  jon
 * Initialize ipr_usage correctly, just in case.
 * 
 * Revision 1.9  88/07/24  18:55:55  jon
 * Make 1.8 (no restrictions done easily) really work.
 * 
 * Revision 1.8  88/07/24  17:49:26  jon
 * Make it easier for a gateway to ignore the restrictions game.
 * 
 * Revision 1.7  88/07/24  17:36:30  jon
 * Adds usage counting and display for routes.
 * 
 * Revision 1.6  88/07/24  17:35:31  jon
 * Adds routing restrictions support.
 *
 * Revision 1.5  88/06/08  23:25:01  jon
 * Add ldrt commands to also print source gw and autonomous system.
 * 
 * Revision 1.4  88/06/08  15:01:11  jon
 * Handle things when there is no default route.
 *
 * Revision 1.3  88/05/30  20:36:12  jon
 * Add select drt commands, support new ipr fields (srg_gw and autonomous sys).
 * 
 * Revision 1.2  87/07/19  15:25:47  jon
 * More info in need to frag message.
 * 
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_inrte_c = "$Header: /mit/cgw/src/gw/in/RCS/inrte.c,v 1.16 90/04/08 00:05:47 jon Exp $";
#endif	lint

#include	<types.h>
#include	<sys.h>
#include	"../src/const.h"
#include	"../src/param.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	"inpro.h"
#include	"inrte.h"

iprte	*ip_routes[HASH_SIZE];	/* Routing table */
iprte	*iprte_free;		/* Free list of IP route blocks */
unsl	iprte_cnt;		/* Number of Routes in table */
unsl	iprte_max;		/* Maximum number of routes */

ipr_res_table	*ipr_restrictions;	/* Routing restrictions */
ipr_res_entry	*iprre_free;	/* Free list of IP route restriction blocks */
unsl	iprre_cnt;		/* Number of Routes in restriction tables */
unsl	iprre_max;		/* Maximum number of routes restrictions */

inaddr	indefga;		/* Default gateway */
unsl    default_route_usage;    /* How many packets sent that way */
net	*indefgi;		/*   and route */

long getdmch();

#ifdef TESTGW
ext int test_mtu;
#endif

/* Error messages */
#define sc static char
sc incli[] = "IN illegal addr frm %d.%d.%d.%d to %d.%d.%d.%d\n";
sc dmpcnt[] = "%d IP routes; max = %d\n";
sc dmphdr[] = "   Addr\t\ttype own  net\tgateway \tflg metric subnet-mask\n";
sc dmphdru[] = "   Addr\tusage\n";
sc defdmp[] = "Default \tFIX  --- %3d\t%d.%d.%d.%d, usage %9u\n";
sc rtedmp[] = "%d.%d.%d.%-2d\t%s  %s %3d\t%d.%d.%d.%-2d\t%c%c%c%c %6d %d.%d.%d.%d\n";

/* Hash algorithm. */
#define hash(addr) ((addr.i_byte.i_byteh ^ addr.i_byte.i_byteu ^ \
		     addr.i_byte.i_bytem ^ addr.i_byte.i_bytel) & 0xff)

/* Interface to command interpreter. */
void iprte_dump();
void ipr_res_dump();
void ipr_res_control();

#define USAGE_FORMAT -1
#define LONG_FORMAT -2
#define NORMAL_FORMAT -3

/* arg is being terribly overloaded, but it works */

static subcommand ip_scmd[] = {
    { "dres", "Dump IP routing restrictions table (number means only net n)",
      ipr_res_dump, 0 },
    { "res_enable", "Enable IP routing restrictions table (number means net n)",
      ipr_res_control, 0 },
    { "res_disable", "Disable IP routing restrictions table (number means net n)",
      ipr_res_control, 1 },
    { "drt_usage", "Dump IP routing table with usage (address dumps route to that address)",
      iprte_dump, USAGE_FORMAT },
    { "drt", "Dump IP routing table (address dumps route to that address)",
      iprte_dump, 0 },
    { "drt_dir", "Dump IP routing table (address dumps route to that address)",
      iprte_dump, T_DIR },
    { "drt_fix", "Dump IP routing table (address dumps route to that address)",
      iprte_dump, T_FIX },
    { "drt_rip", "Dump IP routing table (address dumps route to that address)",
      iprte_dump, (O_RIP | 0x100) /* gross but effective */ },
    { "drt_egp", "Dump IP routing table (address dumps route to that address)",
      iprte_dump, (O_EGP | 0x100) },
    { "ldrt", "Long dump IP routing table (addr dumps route to that address)",
      iprte_dump, 0x200 },
    { "ldrt_dir", "Long dump IP routing table.",
      iprte_dump, (T_DIR | 0x200) },
    { "ldrt_fix", "Long dump IP routing table.",
      iprte_dump, (T_FIX | 0x200) },
    { "ldrt_rip", "Long dump IP routing table",
      iprte_dump, (O_RIP | 0x300) },
    { "ldrt_egp", "Long dump IP routing table",
      iprte_dump, (O_EGP | 0x300) },
    { NULL, NULL, NULL, 0 },
};
static command ip_cmd = { NULL, "ip",
			  "Internet Protocol", ip_scmd };

int dont_fragment = 1;  /* should we fragment if we should */

/* Enter entries from hardware interface address tables.
 */
initintabs()
{
    reg	net	*netp;
	inaddr	*innm;
	inia	*inina;
    reg iprte	*ipr;
    reg	iprte	**iprp;
        void	iprte_dump();
    int i;

    /* Clear hash table */
    for (iprp = &ip_routes[0]; iprp < &ip_routes[HASH_SIZE]; *iprp++ = NULL)
      ;
    iprte_free = NULL;
    iprte_cnt = iprte_max = 0;

    /* Per interface routing restrictions */
    ipr_restrictions = (ipr_res_table *) getmem(sizeof(ipr_res_table)*nnets);

    for (i = 0; i < nnets;i++) {
      ipr_restrictions[i].table = (ipr_res_entry **)NULL;
      ipr_restrictions[i].saved_table = (ipr_res_entry **)NULL;
      ipr_restrictions[i].announce = FALSE;
      ipr_restrictions[i].dont_announce = FALSE;
      ipr_restrictions[i].listen = FALSE;
      ipr_restrictions[i].dont_listen = FALSE;
    }

    /* Fill routing table from hardware interface address tables */
    for (netp = &nets[0]; netp < lstnet; netp++) {
	if ((inina = iniatlst[netp->n_net]) == NULL)
	  continue;
	if (netp->n_prinit != NULL)
	  (*netp->n_prinit)(netp, P_IN, inina);
	
	while (inina != NULL) {
	    innm = mkina(inina->inia_addr);
	    if (*((long *)inina->inia_smask)) {
		/* Put in mark that says this net is subnetted. */
		ipr = ipr_make1(innm->i_long & net_mask(innm));
		if (ipr->ipr_subnet_mask.i_long != 0 &&
		    ipr->ipr_subnet_mask.i_long != *((long *)inina->inia_smask))
		  bughalt("Inconsistent subnet masks in IP config");
		ipr->ipr_subnet_mask.i_long = *((long *)inina->inia_smask);
		if (ipr->ipr_netp == NULL) {
		    ipr->ipr_gw.i_long = 0;
		    ipr->ipr_metric = 0;
		}
	    }
	    /* If this is a point to point interface with only a host (not
	       a gateway) on the other end, then install a host route,
	       otherwise install a subnet route */
	    if (inina->inia_flags & IF_PPP_PROXY) 
	      ipr = ipr_make(innm->i_long);	      
	    else
	      ipr = ipr_make_net(innm->i_long);	      
	    if (ipr->ipr_type != T_NONE)
	      bughalt("Two interfaces with the same address in IP config");
	    ipr->ipr_type = T_DIR;
	    ipr->ipr_owner = O_NONE;
	    ipr->ipr_gw.i_long = innm->i_long;
	    ipr->ipr_netp = netp;
	    ipr->ipr_metric = 0;
	    ipr->ipr_time = 0;

	    inina = inina->inia_link;
	  }
	init_restrictions (netp->n_net);
      }

    /* Add any static routes that have been specified */
    init_static_routes();

    /* Fill default gateway statically */
    initindefga();

    /* So I can get routing information */
/*    stat_reg("IP Routes", iprte_dump); */
    opcon_register(&ip_cmd);
  }

/* Read initial restrictions and the set up the hash tables */

init_restrictions (net_index)
  unsb net_index;
{
  ipr_res_entry *iprre, **iprrep;
  ipr_res_entry_init *init_iprre, **init_iprrep;
  ipr_res_entry *new_iprre, i;
  inaddr *address, *subnet;

  /* for each interface ... */
  init_iprrep = &ipr_init_restrictions[net_index];
  init_iprre = *init_iprrep;

  if (init_iprre == NULL) return; /* no restrictions on this net */

  /* see if we need to allocate a new hash table (if there already is
     one for this net we can just use it, the flags field will let us
     figure out what kind of a restriction it is, and the int flags in the 
     ipr_res_table will let us do a quick check to see whether or not 
     there are any of a given restriction type) */
  if (ipr_restrictions[net_index].table == NULL) {
    ipr_restrictions[net_index].table =
      (ipr_res_entry **) getmem (sizeof(ipr_res_entry) * HASH_SIZE);
    for (iprrep = ipr_restrictions[net_index].table;
	 iprrep < (ipr_restrictions[net_index].table)+HASH_SIZE;
	 *iprrep++ = (ipr_res_entry *)NULL);
    ipr_restrictions[net_index].default_metric = 0;
  }

  /* for each restriction on the interface ... */
  while (init_iprre && init_iprre->flags != 0) {
    if (init_iprre->flags & F_SET_DEFAULT_METRIC) {
      ipr_restrictions[net_index].default_metric = init_iprre->metric;
      init_iprre++;
      continue;
    }
    address = mkina(init_iprre->addr);
    subnet = mkina(init_iprre->subnet_mask);

    if (subnet->i_long != 0) {
      new_iprre = ipr_res_hash1 (ipr_restrictions[net_index].table,
				 address->i_long & net_mask (address),
				 1 /* CREATE */);
      if (new_iprre->subnet_mask.i_long != 0 && 
	  new_iprre->subnet_mask.i_long != subnet->i_long)
	bughalt ("Inconsistent subnet masks in routing restrictions.");
      new_iprre->subnet_mask.i_long = subnet->i_long;
    }

    new_iprre = ipr_res_hash (ipr_restrictions[net_index].table,
			      address->i_long, 1 /* CREATE */);

    new_iprre->metric = init_iprre->metric;
    new_iprre->flags = init_iprre->flags;

    if (new_iprre->flags & F_ANNOUNCE) {
      ipr_restrictions[net_index].announce = TRUE;
      if (ipr_restrictions[net_index].dont_announce == TRUE)
	bughalt ("Announce and don't announce restrictions on same interface.");
    }
    if (new_iprre->flags & F_DONT_ANNOUNCE) {
      ipr_restrictions[net_index].dont_announce = TRUE;
      if (ipr_restrictions[net_index].announce == TRUE)
	bughalt ("Announce and don't announce restrictions on same interface.");
    }
    if (new_iprre->flags & F_LISTEN) {
      ipr_restrictions[net_index].listen = TRUE;
      if (ipr_restrictions[net_index].dont_listen == TRUE)
	bughalt ("Listen and don't listen restrictions on same interface.");
    }
    if (new_iprre->flags & F_DONT_LISTEN) {
      ipr_restrictions[net_index].dont_listen = TRUE;
      if (ipr_restrictions[net_index].listen == TRUE)
	bughalt ("Listen and don't listen restrictions on same interface.");
    }
    init_iprre++;
  }
}

/* Read static routes and put them in the routing table.  These are
 * put in as type T_FIX so nobody should screw around with them.
 * Eventually, it might be nice to come up with a way to have initial
 * routes which can be shadowed by routes learned through some routing
 * protocol, but which reappear if the routing protocol decides it no
 * longer knows how to get there. */
init_static_routes()
{
    iprte *ipr, *iprgw;
    struct ipsrte *ipsr;
    inaddr *addr, *gw;

    for (ipsr = iprte_sinit; ipsr->sr_addr != 0; ipsr++) {
	gw = mkina(ipsr->sr_gw);
	iprgw = ipr_lookup(gw->i_long);
	if (iprgw == NULL || iprgw->ipr_type != T_DIR)
	  bughalt("Bad gateway for static route");

	addr = mkina(ipsr->sr_addr);
	ipr = ipr_make(addr->i_long);
	if (ipr->ipr_type != T_NONE)
	  bughalt("Static route tried to overwrite some other route");

	ipr->ipr_addr = *addr;
	ipr->ipr_gw.i_long = gw->i_long;
	ipr->ipr_netp = iprgw->ipr_netp;
	ipr->ipr_metric = ipsr->sr_metric;
	ipr->ipr_time = 0;
	ipr->ipr_type = T_FIX;
	ipr->ipr_owner = O_NONE;
	ipr->ipr_flags |= ipsr->sr_flags;
    }
}

/* Set up the default gateway from static info.  Check to make sure that
 * the gateway is on a directly connected net. */
initindefga()
{
    reg	inaddr	*gwp;
    reg	iprte	*ipr;
    ext byte	*defgw;

    default_route_usage = 0;
    
    if (defgw == NULL) {
      /* no default gateway */
      indefgi = NULL;
      indefga.i_long = 0;
      return;
    }

    gwp = mkina(defgw);
    ipr = ipr_lookup(gwp->i_long);

    if ((ipr == NULL) || (ipr->ipr_type != T_DIR))
      bughalt("Bad default gateway");
    
    indefgi = ipr->ipr_netp;
    indefga.i_long = gwp->i_long;
}

/* The main IP routing interface.  Returns the net to which the packet
 * should be sent and fills in dst as the next hop address. */
net *inhop(iob, dst)
iorb *iob;
reg inaddr *dst;
{
    unsw	tmp;
    reg	inpkt	*pkt;
    reg	iprte	*ipr;
    net		*netp;

    pkt = mkin(iob->i_addr);
    if (in_isi(pkt->i_dst.i_aaddr.i_anet)) {
	niflog(L_ERRU)
	  dolog(incli, mksina(&pkt->i_src), mksina(&pkt->i_dst));
	freebuf(iob);
	return(NULL);
    }
    if ((ipr = ipr_lookup(pkt->i_dst.i_long)) == NULL ||
	ipr->ipr_netp == NULL) {
	    netp = indefgi;
	    dst->i_long = indefga.i_long;
	    if (netp == NULL) {
	      /* no route and no default gateway ... bye, bye packet */
	      niflog (L_INFC)
		dolog ("IN no route to %d.%d.%d.%d (src %d.%d.%d.%d), discarded.\n",
		       mksina(&pkt->i_dst), mksina(&pkt->i_src));
	      freebuf(iob);
	      return (NULL);
	    }
	    /* Prevent lossage with IHTFP and the external network.
	       If we lose a net 18 route and wind up defaulting to a regional
	       gateway, we really lose, because that regional gateway will
	       still have a network route to net 18 (our common network is 
	       *not* a net 18 subnet and will there bounce the packet right
	       back at us.

	       We'll discard the packet if its destination is net 18 and 
	       we're forwarding it (via the default route) to a 
	       non net 18 gateway.   
	     */
#ifdef NET_18_EXT_NET_HACK
	    if ((pkt->i_dst.i_aaddr.i_anet == 18)
		&& (dst->i_aaddr.i_anet != 18)) {
	      niflog(L_INFC)
		dolog ("IN needed to fwd a net 18 packet (to %d.%d.%d.%d) to a non net 18 gw (%d.%d.%d.%d) via default route, discarding\n.",
		       mksina(&pkt->i_dst), mksina(&dst->i_long));
	      freebuf(iob);
	      return(NULL);
	    }
#endif /* NET_18_EXT_NET_HACK */
	    default_route_usage++;
	}
    else {
	netp = ipr->ipr_netp;
	if (ipr->ipr_type == T_DIR)
	  dst->i_long = pkt->i_dst.i_long;
	else
	  dst->i_long = ipr->ipr_gw.i_long;
	(ipr->ipr_usage)++;
    }

    tmp = swab(pkt->i_len);
    iob->i_breq = tmp;

#ifdef TESTGW
    if (tmp > test_mtu) {
#else
    if (tmp > netp->n_max) {
#endif
      if (dont_fragment) {
	niflog (L_ERRU)
	  dolog ("Won't fragment packet from %d.%d.%d.%d to %d.%d.%d.%d, len (%d).\n",
		 mksina(&pkt->i_src), mksina(&pkt->i_dst), tmp);
	return (NULL);
      }
      if (in_fragment_pkt(iob, netp, dst)) {
	niflog (L_ERRU)
	  dolog ("Couldn't fragment packet from %d.%d.%d.%d to %d.%d.%d.%d, len (%d).\n",
		 mksina(&pkt->i_src), mksina(&pkt->i_dst), tmp);
	return (NULL);
      }
    }

    return(netp);
}

char *n_type[] = { "---", "DIR", "FIX", "FLT" };
char *n_owner[] = { "---", "EGP", "RIP" };

/* Dump the IP routing table. */
iprte_dump(devp, arg, jcl)
dct *devp;
int arg;
char *jcl;
{
    reg iprte *ipr, **iprp;
    inaddr iaddr;
    int typ, own, format;
    
    if (arg == USAGE_FORMAT) {
      format = USAGE_FORMAT;
      arg = 0;
    }
    else if (arg & 0x200) {
      format = LONG_FORMAT;
      arg &= 0x1ff;
    }
    else format = NORMAL_FORMAT;

    if (arg & 0x100) {
      typ = T_FLT;
      own = arg &= 0xff;
    }
    else if (arg != 0) {
      typ = arg;
      own = 0;
    }

    fprintf(devp, dmpcnt, iprte_cnt, iprte_max);
    fprintf(devp, (format == USAGE_FORMAT) ? dmphdru : dmphdr);
    if (jcl == NULL) {
        if (indefgi == NULL)
	  fprintf (devp, "No default gateway!\n");
	else
	  fprintf(devp, defdmp, indefgi->n_net, mksina(&indefga), 
		  default_route_usage);
	for (iprp = &ip_routes[0]; iprp < &ip_routes[HASH_SIZE]; iprp++)
	  for (ipr = *iprp; ipr != NULL; ipr = ipr->ipr_link) {
	    /* only print route if it's the correct type */
	    if ((arg != 0) && (typ != ipr->ipr_type)) continue;
	    if ((arg != 0) && 
		(typ == T_FLT) && (own != ipr->ipr_owner)) continue;
	    ipr_prrte(devp, ipr, format);
	  }
	return;
    }
    iaddr.i_long = getdmch(jcl);
    if (iaddr.i_long != 0) {
	if ((ipr = ipr_lookup(iaddr.i_long)) != NULL) {
	    ipr_prrte(devp, ipr);
	    return;
	}
	fprintf(devp, "No route to %d.%d.%d.%d, use default\n",
		mksina(&iaddr));
    }

    if (indefgi == NULL)
      fprintf (devp, "No default gateway!\n");
    else
      fprintf(devp, defdmp, indefgi->n_net, mksina(&indefga),
	      default_route_usage);
}

/* Print a routing table entry. */
ipr_prrte(devp, ipr, format)
dct *devp;
reg iprte *ipr;
int format;
{
  if (format == USAGE_FORMAT) {
    fprintf (devp, "%d.%d.%d.%d\t%9u\n", mksina(&ipr->ipr_addr),
	     ipr->ipr_usage);
    return;
  }

  fprintf(devp, rtedmp, mksina(&ipr->ipr_addr),
	    n_type[ipr->ipr_type], n_owner[ipr->ipr_owner], 
	    ipr->ipr_netp->n_net, mksina(&ipr->ipr_gw),
	    ipr->ipr_flags & F_SUBNET ? 'S' : ' ',
	    ipr->ipr_flags & F_MODIFIED ? 'M' : ' ',
	    ipr->ipr_flags & F_SILENT ? 'Q' : ' ',
	    ipr->ipr_flags & F_EGP_PRIMARY ? 'P' : ' ',
	    ipr->ipr_metric, mksina(&ipr->ipr_subnet_mask));

  if (format == LONG_FORMAT)
    fprintf (devp, "\tsrc gw: %d.%d.%d.%d, AS: %d\n", 
	     mksina (&ipr->ipr_src_gw),
	     ipr->ipr_autonomous_system);
}

/* Parse foreign host number input as decimal string */
long getdmch(name)
char	*name;
{
    reg char	*p;
    reg int	i;
    reg int	n;
    union {
	char	bytes[sizeof(inaddr)];
	long	name;
    } addr;
    
    addr.name = 0;
    
    p = name;
    for (i = 0; i < sizeof(inaddr); i++) {
	n = 0;
	while((*p >= '0') && (*p <= '9')) {
	    n = ((n << 3) +(n << 1) + (*p++ - '0'));
	    if (n > 0377) return (0L);
	}
	addr.bytes[i] = n;
	if (*p == 0) break;
	if (*p == '.') p++;
	else return (0L);
    }
    return (addr.name);
}

/* Control IP routing restrictions. */
ipr_res_control(devp, arg, jcl)
dct *devp;
int arg;
char *jcl;
{
    unsb i;
    ext int good_password;

    if (!good_password) {
      fprintf (devp, "Must log in first.\n");
      return;
    }
      
    if (jcl == NULL)
      fprintf(devp, "Must give interface number.\n");
    else {
      if ((i = atoi(jcl)) >= nnets) {
	fprintf(devp, "Net %d, too large\n", i);
	return;
      }
      else {
	switch (arg) {
	case 0: /* enable */
	  if (ipr_restrictions[i].saved_table != NULL &&
	      ipr_restrictions[i].table == NULL) {
	    ipr_restrictions[i].table = ipr_restrictions[i].saved_table;
	    ipr_restrictions[i].saved_table = NULL;
	    fprintf (devp, "IP routing restrictions enabled for net %d.\n", i);
	  }
	  break;
	case 1: /* disable */
	  if (ipr_restrictions[i].table != NULL &&
	      ipr_restrictions[i].saved_table == NULL) {
	    ipr_restrictions[i].saved_table = ipr_restrictions[i].table;
	    ipr_restrictions[i].table = NULL;
	    fprintf (devp, "IP routing restrictions disabled for net %d.\n", i);
	  }
	  break;
	}
      }
    }
}

/* Dump IP routing restrictions. */
ipr_res_dump(devp, arg, jcl)
dct *devp;
int arg;
char *jcl;
{
    unsb i;

    fprintf(devp, "%d IP restrictions; max = %d\n", iprre_cnt, iprre_max);

    if (jcl == NULL) {
      for (i = 0; i < nnets; i++)
	if (ipr_restrictions[i].table == (ipr_res_entry **)NULL)
	  fprintf (devp, "Net %d no restrictions.\n", i);
	else 
	  ipr_res_dump_net(devp, i);
    }
    else {
      if ((i = atoi(jcl)) >= nnets) {
	fprintf(devp, "Net %d, too large\n", i);

	return;
      }
      ipr_res_dump_net(devp, i);
    }
  }

/* Print routing restrictions entries for a given interface */
ipr_res_dump_net(devp, net_index)
dct *devp;
unsb net_index;
{
  ipr_res_entry  *iprre, **iprrep;

  fprintf (devp, "Net %d restrictions:\n", net_index);
  if (ipr_restrictions[net_index].default_metric != 0)
    fprintf (devp, "Default metric %d.\n", 
	     ipr_restrictions[net_index].default_metric);
  fprintf (devp,"   Addr\t\tmetric\tproto\tflags\tsubnet-mask\n");
  for (iprrep = ipr_restrictions[net_index].table;
       iprrep < ipr_restrictions[net_index].table+HASH_SIZE;
       iprrep++) {
    for (iprre = *iprrep; iprre != NULL; iprre = iprre->link) {
      fprintf (devp,
	       "%d.%d.%d.%d\t%5d\t%c%c\t%c %2s %c %2s %c\t%d.%d.%d.%d\n",
	       mksina(&iprre->addr), 
	       iprre->metric,
	       iprre->flags & F_RES_RIP ? 'R' : ' ',
	       iprre->flags & F_RES_EGP ? 'E' : ' ',
	       iprre->flags & F_ANNOUNCE ? 'A' : ' ',
	       iprre->flags & F_DONT_ANNOUNCE ? "DA" : "  ",
	       iprre->flags & F_LISTEN ? 'L' : ' ',
	       iprre->flags & F_DONT_LISTEN ? "DL" : "  ",
	       iprre->flags & F_SUBNET ? 'S' : ' ',
	       mksina(&iprre->subnet_mask));
    }
  }
}

/* Delete route ipr from the routing table.  If the route is also the
 * marker for a subnetted net, then don't actually delete it.  Just
 * clear everything except what's needed for it to continue serving as
 * the marker. */
ipr_delete(ipr)
reg iprte *ipr;
{
    reg iprte **iprp;

    if (ipr->ipr_subnet_mask.i_long != 0) {
	ipr->ipr_type = T_NONE;
	ipr->ipr_owner = O_NONE;
	ipr->ipr_netp = NULL;
	return;
    }
    iprte_cnt--;
    iprp = &ip_routes[hash(ipr->ipr_addr)];
    while (*iprp != ipr)
      iprp = &((*iprp)->ipr_link);
    *iprp = ipr->ipr_link;
    ipr->ipr_link = iprte_free;
    iprte_free = ipr;
}

/* Routines below here are internal routines which do the management
 * of the hash table.  These should only be called through macros
 * defined in inrte.h.  As soon as the code is debugged, these
 * routines and the macros should be rearranged so as to reduce the
 * number of function calls. */

/*	Look up route for addr.  Subnetting is handled as follows.
 * Addr is first masked to be just the network number and the route to
 * that network is looked up.  If the network is not know to be
 * subnetted, then that route is returned.  If the route found for
 * this net has a subnet mask then addr is masked again, this time the
 * lookup is done with both the net and subnet.  If create is set,
 * then a new route for the net or subnet is created in the hash table
 * if one doesn't already exist.
 *	Note: If host routing (as well as net and subnet routing)
 * wanted to be added, then just changing this routine to try looking
 * up the host address first and failing that to look for the net or
 * subnet route should do the right thing. */

/* Okay let's try host routes (mostly for SLIP hacking) */

iprte *ipr_hash(addr, create, host)
inaddr addr;
int create;
int host;
{
    reg iprte *ipr;
    unsl mask;

    /* try host route first */
    if (host == IPR_HOST_ROUTES) 
      if (ipr = ipr_hash1(addr.i_long, create)) return (ipr);

    /* try network route */
    mask = net_mask(&addr);
    ipr = ipr_hash1(addr.i_long & mask, create);
    if ((ipr != NULL) && (ipr->ipr_subnet_mask.i_long != 0)) {
        /* try subnet route */
	ipr = ipr_hash1(addr.i_long & (mask | ipr->ipr_subnet_mask.i_long),
			create);
	if (create == IPR_CREATE)
	  ipr->ipr_flags |= F_SUBNET;
    }
    return (ipr);
}

/* Look up the route for addr.  Addr is assumed to have already been
 * masked appropriately.  If there is no route for addr and create is
 * set, then a new hash entry is created and returned, otherwise NULL
 * is returned. */
iprte *ipr_hash1(addr, create)
inaddr addr;
int create;
{
    unsw hashval;
    reg iprte *ipr;

    hashval = hash(addr);
    for (ipr = ip_routes[hashval]; ipr != NULL; ipr = ipr->ipr_link)
      if (addr.i_long == ipr->ipr_addr.i_long)
	return (ipr);
    if (create == IPR_CREATE) {
	if (iprte_free != NULL) {
	    ipr = iprte_free;
	    iprte_free = ipr->ipr_link;
	}
	else {
	    ipr = (iprte *)getmem(sizeof(iprte));
	    if (ipr == NULL)
	      bughalt("Out of memory to expand IP routing table");
	}
	ipr->ipr_addr = addr;
	ipr->ipr_subnet_mask.i_long = 0;
	ipr->ipr_netp = NULL;
	ipr->ipr_type = T_NONE;
	ipr->ipr_owner = O_NONE;
	ipr->ipr_flags = 0;
	ipr->ipr_link = ip_routes[hashval];
	ipr->ipr_autonomous_system = 0;
	ipr->ipr_src_gw.i_long = 0;
	ipr->ipr_usage = 0;
	ip_routes[hashval] = ipr;
	iprte_cnt++;
	if (iprte_cnt > iprte_max)
	  iprte_max = iprte_cnt;
	return (ipr);
    }
    return (NULL);
}

/* These are stolen from above, but deal with ipr_res structures ...
   perhaps the code could be taught to manage different types of structures,
   (i.e. these would deal with hash entries which would point to user data),
   but seems easier for now ... */

ipr_res_entry *ipr_res_hash(table, addr, create)
ipr_res_entry *table[];
inaddr addr;
int create;
{
    reg ipr_res_entry *iprre;
    unsl mask;

    mask = net_mask(&addr);
    iprre = ipr_res_hash1(table, addr.i_long & mask, create);
    if ((iprre != NULL) && (iprre->subnet_mask.i_long != 0)) {
	iprre = ipr_res_hash1(table, 
			      addr.i_long & (mask | iprre->subnet_mask.i_long),
			      create);
	if (create)
	  iprre->flags |= F_SUBNET;
    }
    return (iprre);
}

/* Look up the route for addr.  Addr is assumed to have already been
 * masked appropriately.  If there is no route for addr and create is
 * set, then a new hash entry is created and returned, otherwise NULL
 * is returned. */
ipr_res_entry *ipr_res_hash1(table, addr, create)
ipr_res_entry *table[];
inaddr addr;
int create;
{
    unsw hashval;
    reg ipr_res_entry *iprre;

    hashval = hash(addr);
    for (iprre = table[hashval]; iprre != (ipr_res_entry *)NULL; 
	 iprre = iprre->link)
      if (addr.i_long == iprre->addr.i_long)
	return (iprre);
    if (create) {
	if (iprre_free != NULL) {
	    iprre = iprre_free;
	    iprre_free = iprre->link;
	}
	else {
	    iprre = (ipr_res_entry *)getmem(sizeof(ipr_res_entry));
	    if (iprre == NULL)
	      bughalt("Out of memory to expand IP restrictions table");
	}
	/* this is okay, since addr is in static storage, but it feels
	   a bit wrong */
	iprre->link = table[hashval];
	iprre->addr.i_long = addr.i_long; 
	iprre->subnet_mask.i_long = 0;
	iprre->metric = 0;
	iprre->flags = 0;
	table[hashval] = iprre;
	iprre_cnt++;
	/* max is silly for this non-dynamic stuff, but what the heck */
	if (iprre_cnt > iprre_max)
	  iprre_max = iprre_cnt;
	return (iprre);
    }
    return (NULL);
}

/* The next two could really be one route with an additional argument,
   but this will read better and be simpler. */

int res_ignore_route (proto, network, net_idx, metric)
     int proto;
     inaddr network;
     unsb net_idx;
     int *metric;
{
  ipr_res_entry *iprre;
  ipr_res_table iprtbl;
  static int PUNT_ROUTE = TRUE;
  static int TAKE_ROUTE = FALSE;

  *metric = 0;

  iprtbl = ipr_restrictions[net_idx];
  if (iprtbl.table == NULL) return (TAKE_ROUTE);

  if ((iprtbl.listen == FALSE) && (iprtbl.dont_listen == FALSE))
    return (PUNT_ROUTE);

  iprre = ipr_res_hash (iprtbl.table, network.i_long, FALSE);

  if (iprtbl.listen == TRUE) {
    if (iprre == NULL) return (PUNT_ROUTE); /* we have a list to listen to
					       and this net isn't on it */
    /* he's on the list, do the protocol and restriction type match? */
    if ((iprre->flags & proto) && (iprre->flags & F_LISTEN)) {
      *metric = iprre->metric;
      return (TAKE_ROUTE); /* yes */
    }
    else
      return (PUNT_ROUTE); /* no */
  }

  if (iprtbl.dont_listen == TRUE) {
    if (iprre == NULL) {
      *metric = iprtbl.default_metric;
      return (TAKE_ROUTE); /* we have a list to ignore and 
			      this isn't on it, so we'll take
			      it */
    }
    /* he's on the list, do the protocol and restriction type match? */
    if ((iprre->flags & proto) && (iprre->flags & F_DONT_LISTEN))
      return (PUNT_ROUTE); /* yes */
    else 
      return (TAKE_ROUTE); /* no */
  }
}

/* needs to deal with the metric much better than this */

int res_advertise_route (proto, network, net_idx, metric)
     int proto;
     inaddr network;
     unsb net_idx;
     int *metric;
{
  ipr_res_entry *iprre;
  ipr_res_table iprtbl;
  static int SQUAWK = TRUE;
  static int SHUT_UP = FALSE;

  *metric = 0;

  iprtbl = ipr_restrictions[net_idx];
  if (iprtbl.table == NULL) return (SQUAWK);

  if ((iprtbl.announce == FALSE) && (iprtbl.dont_announce == FALSE)) 
    return (SQUAWK);

  iprre = ipr_res_hash (iprtbl.table, network.i_long, FALSE);

  if (iprtbl.announce == TRUE) {
    if (iprre == NULL) return (SHUT_UP); /* we have a list to listen to
					       and this net isn't on it */
    /* he's on the list, do the protocol and restriction type match? */
    if ((iprre->flags & proto) && (iprre->flags & F_ANNOUNCE)) {
      *metric = iprre->metric;
      return (SQUAWK);  /* yes */
    }
    else
      return (SHUT_UP); /* no */
  }

  if (iprtbl.dont_announce == TRUE) {
    if (iprre == NULL) return (SQUAWK); /* we have a list to ignore and 
					       this isn't on it, so we'll take
					       it */
    /* he's on the list, do the protocol and restriction type match? */
    if ((iprre->flags & proto) && (iprre->flags & F_DONT_ANNOUNCE))
      return (SHUT_UP);  /* yes */
    else
      return (SQUAWK);   /* no */
  }
}

byte amask[4] = { 0xff,    0,    0,  0 };
byte bmask[4] = { 0xff, 0xff,    0,  0 };
byte cmask[4] = { 0xff, 0xff, 0xff,  0 };
byte mask_table[8][4] = {
    { 0xff,    0,    0,  0 },
    { 0xff,    0,    0,  0 },
    { 0xff,    0,    0,  0 },
    { 0xff,    0,    0,  0 },
    { 0xff, 0xff,    0,  0 },
    { 0xff, 0xff,    0,  0 },
    { 0xff, 0xff, 0xff,  0 },
    {    0,    0,    0,  0 },
};
