/* Copyright 1985 by the Massachusetts Institute of Technology */
#include <notice.h>

/* These routines implement Dave Plummer's Address Resolution Protocol
 * (ARP) described in RFC 826.  This protocol translates protocol
 * addresses (e.g. internet or chaosnet) into hardware addresses (e.g.
 * ethernet) by broadcasting a request for the protocol address and by
 * responding to requests for its own protocol address with its own
 * hardware address.
 */

/* Written by David Bridgham (Nov 1985)
 */

/*
 *------------------------------------------------------------------
 *
 * $Source: /mit/cgw/src/gw/arp/RCS/arp.c,v $
 * $Revision: 1.3 $
 * $Date: 90/04/10 19:26:10 $
 * $State: Exp $
 * $Author: jon $
 * $Locker:  $
 *
 * $Log:	arp.c,v $
 * Revision 1.3  90/04/10  19:26:10  jon
 * fix evil bug discovered by mike patton.  if we get an arp with an unkown
 * hardware type, we increment the hardware type we're looking for instead
 * of the error counter.  since we use this spreads and persists until all
 * cgw's on the given network are shut down at the same time.
 * 
 * Revision 1.2  87/07/15  00:48:57  jon
 * ARP (init) now registers arpflg with log_register_flag
 * 
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_arp_c = "$Header: /mit/cgw/src/gw/arp/RCS/arp.c,v 1.3 90/04/10 19:26:10 jon Exp $";
#endif	lint

#include <types.h>
#include <sys.h>
#include <sysext.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 "arp.h"
#include "arp_ext.h"

/* Interface to command interpreter. */
void arp_prstat(), arp_stat(), arp_prcache();

static subcommand arp_scmd[] = {
    { "dc", "Dump the ARP cache for a net/protocol", arp_prcache, 0 },
    { "sa", "ARP cache statistics", arp_prstat, 0 },
    { "stat", "ARP stats on a specific net/protocol", arp_stat, 0 },
    { NULL, NULL, NULL, 0 },
};
static command arp_cmd =
    { NULL, "arp", "Address Resolution Protocol", arp_scmd };

/* Definitions for logging */
#define MSGFLG arpflg

/* Macros to make the code clearer */
#define arp_in_err(errmsg, val, errcnt) { \
	niflog(L_ERRU) \
	  dolog((errmsg), (val), PADDR(0, ARP_SPA(pkt)), \
		HADDR(1, ARP_SHA(pkt)), mksnet(netp)); \
	freebuf(iob); \
	(errcnt)++; \
	return; }

#define PADDR(arg,addr) (*ad->arpd_ppaddr)(ADDRBUFLEN, &addrbuf[arg][0],(addr))
#define HADDR(arg,addr) (*ad->arpd_phaddr)(ADDRBUFLEN, &addrbuf[arg][0],(addr))

/* Log messages */
#define schar static char
schar arpnopro[] = "ARP no prot %x frm nt %d intfc %s/%d\n";
schar arpbdhrd[] = "ARP bd hdrw %x frm %s hst %s nt %d intfc %s/%d\n";
schar arpbdhln[] = "ARP bd hdrw len %d frm %s hst %s nt %d intfc %s/%d\n";
schar arpbdpln[] = "ARP bd prot len %d frm %s hst %s nt %d intfc %s/%d\n";
schar arpreq[] = "ARP req fr %s frm %s hst %s nt %d intfc %s/%d\n";
schar arpreqi[] = "ARP req fr %s frm %s hst %s nt %d intfc %s/%d ign\n";
schar arprep[] = "ARP rep frm %s hst %s nt %d intfc %s/%d\n";
schar arpbdop[] = "ARP bd op %x frm %s hst %s nt %d intfc %s/%d\n";
schar arpnosndrp[] = "ARP cdnt snd rep to %s hst %s nt %d intfc %s/%d cd %d\n";
schar arpsndreq[] = "ARP snding req for %s to nt %d intfc %s/%d\n";
schar arpnosndrq[] = "ARP cdnt snd req to nt %d intfc %s/%d code %d\n";
schar arpnorsnd[] = "ARP cdnt rsnd pkt to %s hst %s nt %d intfc %s/%d cd %d\n";
schar arpnobuf[] = "ARP no pkt buf for req for %s on to nt %d intfc %s/%d\n";
schar arpnorep[] = "ARP no rep frm %s nt %d intfc %s/%d\n";

#define TIME_TO_FREE 60*20	/* If last accessed time is longer
				 * than this many seconds, then free
				 * the cache entry when gc'ing. */
#define ADDRBUFLEN 40
static char addrbuf[3][ADDRBUFLEN];
arp_bd_pro;			/* An ARP packet for an unknown
				 * protocol was recieved */
arp_ce *arp_lookup();

ext	dct	*DCTVT[];

/* Initialize the Address Resolution Protocol.  This involves
 * allocating and initializing the arp caches and hash tables and
 * calling the per net protocol init routine for each protocol which
 * is to use arp.
 */
ARP()
{
    intf i, j;
    reg arp_desc *ad;
    reg arp_ce *cent;
    void arp_prstat();

/*    stat_reg("ARP stats", arp_prstat); */
    opcon_register(&arp_cmd);
    log_register_flag ("arp", &MSGFLG);

    for (i = 0; i < nnets; i++) {
	ad = arp_ntbl[i];
	while (ad) {
	    /* Let network handler set the type and length fields and the
	     * per protocol myaddr routine. */
	    (*nets[i].n_prinit)(&nets[i], P_ARP, ad);

	    ad->arpd_hsize = ARP_HASH_SIZE(ad);
	    if ((ad->arpd_hash = (arp_ce **)getmem(ad->arpd_hsize * sizeof(arp_ce *)))
		== NULL)
	      bughalt("ARP no mem for hash table");

	    /* clear hash table */
	    for (j = 0; j < ad->arpd_hsize; j++)
	      ad->arpd_hash[j] = 0;

	    if ((ad->arpd_cache = (arp_ce *)getmem(ad->arpd_csize * ARP_CACHEENT_SIZE(ad))) == NULL)
	      bughalt("ARP no mem for cache");

	    /* init cache entries - link them together and set pointer
	     * to ARP descriptor */
	    for (cent = ad->arpd_cache, j = 1; j < ad->arpd_csize; j++) {
		cent->arpc_ad = ad;
		cent->arpc_link =
		  (arp_ce *)((byte *)cent + ARP_CACHEENT_SIZE(ad));
		cent = cent->arpc_link;
	    }
	    cent->arpc_ad = ad;
	    cent->arpc_link = 0;

	    /* Clear ARP statistics */
	    arp_bd_pro = 0;
	    ad->arpd_in_req = 0;
	    ad->arpd_in_reqi = 0;
	    ad->arpd_in_rep = 0;
	    ad->arpd_no_rep = 0;
	    ad->arpd_bd_hrd = 0;
	    ad->arpd_bd_hln = 0;
	    ad->arpd_bd_pln = 0;
	    ad->arpd_bd_op = 0;
	    ad->arpd_req = 0;
	    ad->arpd_lookups = 0;
	    ad->arpd_hmiss = 0;
	    ad->arpd_trans = 0;
	    ad->arpd_mwait = 0;
	    ad->arpd_he_used = 0;
	    ad->arpd_ce_free = ad->arpd_csize;

	    ad = ad->arpd_link;
	}
    }
}

/* Process received ARP packets.
 */
arp_in(iob)
reg iorb *iob;
{
    net *netp;
    reg arp_desc *ad;
    reg arppkt *pkt;

    pkt = mkarppkt(iob->i_addr);
    netp = mknet(iob->i_usr2);

    /* Find the ARP descriptor for the received packet */
    ad = arp_ntbl[netp->n_net];
    while (ad) {
	if (pkt->arp_pro == ad->arpd_pro)
	  break;
	ad = ad->arpd_link;
    }
    if (ad == NULL) {
	niflog(L_INFC)
	  dolog(arpnopro, pkt->arp_pro, mksnet(netp));
	freebuf(iob);
	arp_bd_pro++;
	return;
    }

    if (pkt->arp_hrd != ad->arpd_hrd)
      arp_in_err(arpbdhrd, pkt->arp_hrd, ad->arpd_bd_hrd);
    if (pkt->arp_hln != ad->arpd_hln)
      arp_in_err(arpbdhln, pkt->arp_hln, ad->arpd_bd_hln);
    if (pkt->arp_pln != ad->arpd_pln)
      arp_in_err(arpbdpln, pkt->arp_pln, ad->arpd_bd_pln);

    if (pkt->arp_op == ARP_REQUEST) {
	if ((*ad->arpd_myaddp)(netp, ARP_TPA(pkt))) {
	    niflog(L_TRCO)
	      dolog(arpreq, PADDR(0, ARP_TPA(pkt)), PADDR(1, ARP_SPA(pkt)),
		    HADDR(2, ARP_SHA(pkt)), mksnet(netp));
	    ad->arpd_in_req++;
	    arp_in_req(iob, ad);
	    return;
	}
	else {
	    niflog(L_TRCO)
	      dolog(arpreqi, PADDR(0, ARP_TPA(pkt)), PADDR(1, ARP_SPA(pkt)),
		    HADDR(2, ARP_SHA(pkt)), mksnet(netp));
	    ad->arpd_in_reqi++;
	    freebuf(iob);
	    return;
	}
    }
    if (pkt->arp_op == ARP_REPLY) {
	niflog(L_TRCO)
	  dolog(arprep, PADDR(0, ARP_SPA(pkt)),
		HADDR(1, ARP_SHA(pkt)), mksnet(netp));
	ad->arpd_in_rep++;
	arp_in_rep(iob, ad);
	return;
    }
    arp_in_err(arpbdop, pkt->arp_op, ad->arpd_bd_op);
}

/* Process an ARP request.  Update cache and send a reply.  The check
 * of whether or not a reply should be sent is made before this
 * routine is called. */
arp_in_req(iob, ad)
iorb *iob;
arp_desc *ad;
{
    reg arp_ce *cent;
    reg arppkt *pkt;
    net *netp;
    word code;

    netp = mknet(iob->i_usr2);
    pkt = mkarppkt(iob->i_addr);

    /* Cache address of requestor */
    cent = arp_lookup(ad, ARP_SPA(pkt));
    cent->arpc_ltime = systod;
    copy(ARP_SHA(pkt), ARP_CACHEENT_HRD(ad, cent), pkt->arp_hln);

    /* Convert request into a reply */
    pkt->arp_op = ARP_REPLY;
    copy(netp->n_idevp->d_dev1, ARP_SHA(pkt), pkt->arp_hln);
    copy(ARP_TPA(pkt), ARP_SPA(pkt), pkt->arp_pln);
    copy(ARP_CACHEENT_HRD(ad, cent), ARP_THA(pkt), pkt->arp_hln);
    copy(ARP_CACHEENT_PRO(ad, cent), ARP_TPA(pkt), pkt->arp_pln);
    iob->i_breq = ARP_PKT_SIZE(ad);
    if ((code = (*netp->n_send)(iob, netp, P_ARP, ARP_THA(pkt))) != D_OK) {
	niflog(L_ERRU)
	  dolog(arpnosndrp, PADDR(0, ARP_TPA(pkt)),
		HADDR(1, ARP_THA(pkt)), mksnet(netp), code);
	freebuf(iob);
    }
}

/* Process an ARP reply.  This involves looking up the target address
 * in the cache, updating it, and sending the packet that is waiting
 * for that reply if there is one. */
arp_in_rep(iob, ad)
iorb *iob;
reg arp_desc *ad;
{
    reg arp_ce *cent;
    reg arppkt *pkt;
    net *netp;
    word code;

    netp = mknet(iob->i_usr2);
    pkt = mkarppkt(iob->i_addr);

    /* Cache address that was in packet */
    cent = arp_lookup(ad, ARP_SPA(pkt));
    cent->arpc_ltime = systod;
    copy(ARP_SHA(pkt), ARP_CACHEENT_HRD(ad, cent), pkt->arp_hln);

    freebuf(iob);

    /* send waiting packet (if any) */
    if ((iob = cent->arpc_iob) != NULL) {
	cent->arpc_iob = NULL;
	if ((code = (*netp->n_send)(iob, netp, ad->arpd_protocol, ARP_CACHEENT_PRO(ad, cent))) != D_OK) {
	    niflog(L_ERRU)
	      dolog(arpnorsnd, PADDR(0, ARP_CACHEENT_PRO(ad, cent)),
		    HADDR(1, ARP_CACHEENT_HRD(ad, cent)), mksnet(netp), code);
	    freebuf(iob);
	}
    }	
}

/* Set the hardware address of an outgoing packet. If the address is
 * found in the cache then the address is set and 0 is returned.
 * Otherwise, an ARP request is sent for that address, the original
 * packet is saved to be sent when the response comes back, and ~0 is
 * returned. */
arp_set_addr(iob, netp, prot, nm, addr_dst)
xreg iorb *iob;
net *netp;
xreg unsw prot;
word nm;
unsb *addr_dst;
{
    reg arp_desc *ad;
    reg arp_ce *cent;
    reg arppkt *pkt;
    word code;
    void arp_res_timeout();

    /* Find the ARP descriptor for the protocol */
    ad = arp_ntbl[netp->n_net];
    for (;;) {
	if (ad == NULL)
	  bughalt("ARP translate - bad protocol");
	if (prot == ad->arpd_protocol)
	  break;
	ad = ad->arpd_link;
    }

    ad->arpd_trans++;
    cent = arp_lookup(ad, nm);
    if (cent->arpc_ltime != 0) { /* if entry is valid */
	cent->arpc_ltime = systod;
	copy(ARP_CACHEENT_HRD(ad, cent), addr_dst, ad->arpd_hln);
	return 0;
    }
    /* Only keep one outstanding packet around for any address */
#ifdef SAVE_PKT
    /* This was commented out because the gateway kept running out of
     * timers, especially just after it had rebooted.  The gateway
     * would reboot and immediately be flooded with packets that
     * needed arp'ing.  When I figure out a good solution for this,
     * I'll put the code back in.  One possibility is to have a
     * counter, and only save the first five or so packets and then
     * start flushing them. */
    if (cent->arpc_iob) {
	ad->arpd_mwait++;
	freebuf(cent->arpc_iob);
    }
    else
      stime(arp_res_timeout, cent, 2, ARPPRI);
    cent->arpc_iob = iob;
#else
    freebuf(iob);
#endif

    niflog(L_TRCO)
      dolog(arpsndreq, PADDR(0, nm), mksnet(netp));

    if ((iob = getbuf()) == NULL) {
	niflog(L_ERRU)
	  dolog(arpnobuf, PADDR(0, nm), mksnet(netp));
	return ~0;
    }
    iob->i_breq = ARP_PKT_SIZE(ad);
    iob->i_addr = ((byte *) iob) + pktoff;
    pkt = mkarppkt(iob->i_addr);
    pkt->arp_hrd = ad->arpd_hrd; /* These two fields are statically */
    pkt->arp_pro = ad->arpd_pro; /*  byte swapped */
    pkt->arp_hln = ad->arpd_hln;
    pkt->arp_pln = ad->arpd_pln;
    pkt->arp_op = ARP_REQUEST;
    copy(netp->n_idevp->d_dev1, ARP_SHA(pkt), ad->arpd_hln);
    copy((*ad->arpd_myaddr)(netp), ARP_SPA(pkt), ad->arpd_pln);
    copy(nm, ARP_TPA(pkt), ad->arpd_pln);
    ad->arpd_req++;
    if ((code = (*netp->n_send)(iob, netp, P_ARP, ANYHOST)) != D_OK) {
	niflog(L_ERRU)
	  dolog(arpnosndrq, mksnet(netp), code);
	freebuf(iob);
    }
    return ~0;
}

/* Returns the cache entry for paddr creating one if it doesn't
 * already exist.  If it needs to create a new entry, the protocol
 * address is set and the last accessed time and iob are cleared. */
arp_ce *
arp_lookup(ad, paddr)
reg arp_desc *ad;
unsb *paddr;
{
    reg arp_ce *cent;
    unsl hindex;

    ad->arpd_lookups++;
    hindex = arp_hash(ad->arpd_pln, paddr) % ad->arpd_hsize;
    cent = ad->arpd_hash[hindex];
    while (cent) {
	if (bcmp(paddr, ARP_CACHEENT_PRO(ad, cent), ad->arpd_pln) == 0)
	  break;
	cent = cent->arpc_link;
    }
    if (cent == NULL) {
	ad->arpd_hmiss++;
	if ((cent = ad->arpd_cache) == NULL) {
	    arp_gc(ad);
	    cent = ad->arpd_cache;
	}
	/* Unlink cache entry from free list */
	ad->arpd_cache = cent->arpc_link;
	ad->arpd_ce_free--;

	cent->arpc_ltime = 0;
	cent->arpc_iob = NULL;
	copy(paddr, ARP_CACHEENT_PRO(ad, cent), ad->arpd_pln);
	if ((cent->arpc_link = ad->arpd_hash[hindex]) == NULL)
	  ad->arpd_he_used++;
	ad->arpd_hash[hindex] = cent;
    }
    else {
	/* Should probably move entry to the head of the list */
    }
    return cent;
}

/* Garbage collect the ARP cache.  This is guarenteed to free at least
 * one cache entry. */
arp_gc(ad)
arp_desc *ad;
{
    reg arp_ce *cent;
    arp_ce *oce;
    unsl otime, ftime;
    reg int i;

    ad->arpd_gc++;
    oce = NULL;
    otime = systod;
    ftime = systod - TIME_TO_FREE;
    for (i = 0; i < ad->arpd_hsize; i++) {
	for (cent = ad->arpd_hash[i]; cent != NULL; ) {
	    if (cent->arpc_ltime <= ftime) {
		arp_ce *scent = cent->arpc_link;

		arp_free_ce(ad, cent);
		cent = scent;
		continue;
	    }
	    else if (cent->arpc_ltime < otime) {
		oce = cent;
		otime = cent->arpc_ltime;
	    }
	    cent = cent->arpc_link;
	}
    }
    if (oce)
      arp_free_ce(ad, oce);
}

/* Frees a cache entry.  That is, unlinks it from its place in the
 * hash table and links it onto the free list.
 */
arp_free_ce(ad, cent)
arp_desc *ad;
arp_ce *cent;
{
    arp_ce **pce;
    unsl hindex;

    ad->arpd_ce_free++;
    ad->arpd_gc_free++;
    hindex = arp_hash(ad->arpd_pln, ARP_CACHEENT_PRO(ad,cent)) %ad->arpd_hsize;
    pce = &ad->arpd_hash[hindex];
    while (*pce) {
	if (*pce == cent) {
	    *pce = cent->arpc_link;
	    cent->arpc_link = ad->arpd_cache;
	    ad->arpd_cache = cent;
	    if ((pce == &ad->arpd_hash[hindex]) && (*pce == NULL))
	      ad->arpd_he_used--;
	    return;
	}
	pce = &((*pce)->arpc_link);
    }
    bughalt("didn't find cache entry");
}

/* Computes and returns the hash value for an address.
 */
arp_hash(len, nm)
unsf len;
reg unsb *nm;
{
    reg intf i;
    reg int hash = 0;

    for (i = 0; i < len; i++)
      hash = (hash << 2) + *nm++;
    return hash;
}

/* If there is still a packet waiting for this response, free it.
 * Should probably free up the cache entry also, now that there is a
 * way to do that.
 */
arp_res_timeout(cent)
reg arp_ce *cent;
{
    net *netp;
    reg iorb *iob;
    arp_desc *ad;

    if ((iob = cent->arpc_iob) == NULL)
      return;
    cent->arpc_iob = NULL;
    ad = cent->arpc_ad;
    ad->arpd_no_rep++;
    netp = mknet(iob->i_usr2);
    netp->n_disc++;
    niflog(L_ERRC)
      dolog(arpnorep, PADDR(0, ARP_CACHEENT_PRO(ad, cent)), mksnet(netp));
    freebuf(iob);
}

/* Prints out statistics on ARP.  Eventually it should be changed so
 * that it takes arguments and just tells about the net/protocol
 * specified.
 */
arp_prstat(devp, arg, jcl)
dct *devp;
word arg;
char *jcl;
{
    int i;
    reg arp_desc *ad;

    /* What to print out and how
ARP	bpro = %d
net prot req-rec req-ign rep-rec norep bhrd bhln bpln  bop 
 .0  ..0  .....0  .....0  .....0 ....0 ...0 ...0 ...0 ...0
net prot rep-snt req-snt lookups %hit  trans he-used ce-free  gc gcfavg
 .0  ..0  .....0  .....0  .....0  ..0 .....0  .....0  .....0 ..0  ....0 */

    fprintf(devp, "ARP\tbpro = %d\nnet prot req-rec req-ign rep-rec norep bhrd bhln bpln  bop\n", arp_bd_pro);
    for (i = 0; i < nnets; i++)
      for (ad = arp_ntbl[i]; ad; ad = ad->arpd_link)
	fprintf(devp, " %2d  %3d  %6d  %6d  %6d %5d %4d %4d %4d %4d\n",
		i, ad->arpd_protocol, ad->arpd_in_req, ad->arpd_in_reqi,
		ad->arpd_in_rep, ad->arpd_no_rep, ad->arpd_bd_hrd,
		ad->arpd_bd_hln, ad->arpd_bd_pln, ad->arpd_bd_op);

    fprintf(devp, "\nnet prot rep-snt req-snt lookups %%hit  trans he-used ce-free  gc gcfavg\n");
    for (i = 0; i < nnets; i++)
      for (ad = arp_ntbl[i]; ad; ad = ad->arpd_link)
	fprintf(devp," %2d  %3d  %6d  %6d  %6d %3d%% %6d  %6d  %6d %3d  %5d\n",
		i, ad->arpd_protocol, ad->arpd_in_req, ad->arpd_req,
		ad->arpd_lookups, ad->arpd_lookups == 0 ? 0 :
		((ad->arpd_lookups - ad->arpd_hmiss) * 100) / ad->arpd_lookups,
		ad->arpd_trans, ad->arpd_he_used, ad->arpd_ce_free,
		ad->arpd_gc,
		ad->arpd_gc == 0 ? 0 : ad->arpd_gc_free / ad->arpd_gc);
}

/* Print ARP statictics on a particular net/protocol. */
arp_stat(devp, arg, jcl)
dct *devp;
word arg;
char *jcl;
{
    int netn, prot;
    reg arp_desc *ad;
    static char msg[] = "Usage: arp stat net protocol\n";
    ext unsw nnets;

    if (jcl == NULL) {
	fprintf(devp, msg);
	return;
    }
    netn = atoi(jcl);
    if (netn >= nnets) {
	fprintf(devp, "Net %d out of range\n", netn);
	return;
    }
    while (*jcl && *jcl != ' ')
      jcl++;
    while (*jcl && *jcl == ' ')
      jcl++;
    if (!*jcl) {
	fprintf(devp, msg);
	return;
    }
    prot = atoi(jcl);		/* should take a string here */

    for (ad = arp_ntbl[netn]; ad; ad = ad->arpd_link)
      if (ad->arpd_protocol == prot)
	break;
    if (ad == NULL) {
	fprintf(devp, "No protocol %d on net %d intfc %s/%d\n",
		prot, mksnet(&nets[netn]));
	return;
    }
    fprintf(devp, "ARP\tbpro = %d\nnet prot req-rec req-ign rep-rec norep bhrd bhln bpln  bop\n", arp_bd_pro);
    fprintf(devp, " %2d  %3d  %6d  %6d  %6d %5d %4d %4d %4d %4d\n",
	    netn, ad->arpd_protocol, ad->arpd_in_req, ad->arpd_in_reqi,
	    ad->arpd_in_rep, ad->arpd_no_rep, ad->arpd_bd_hrd,
	    ad->arpd_bd_hln, ad->arpd_bd_pln, ad->arpd_bd_op);
    
    fprintf(devp, "\nnet prot rep-snt req-snt lookups %%hit  trans he-used ce-free  gc gcfavg\n");
    fprintf(devp," %2d  %3d  %6d  %6d  %6d %3d%% %6d  %6d  %6d %3d  %5d\n",
	    netn, ad->arpd_protocol, ad->arpd_in_req, ad->arpd_req,
	    ad->arpd_lookups, ad->arpd_lookups == 0 ? 0 :
	    ((ad->arpd_lookups - ad->arpd_hmiss) * 100) / ad->arpd_lookups,
	    ad->arpd_trans, ad->arpd_he_used, ad->arpd_ce_free,
	    ad->arpd_gc,
	    ad->arpd_gc == 0 ? 0 : ad->arpd_gc_free / ad->arpd_gc);
}

/* Print the ARP cache for a particular net/protocol. */
arp_prcache(devp, arg, jcl)
dct *devp;
word arg;
char *jcl;
{
    int netn, prot;
    reg arp_desc *ad;
    arp_ce *cent;
    int i;
    static char msg[] = "Usage: arp dc net protocol\n";
    ext unsw nnets;

    if (jcl == NULL) {
	fprintf(devp, msg);
	return;
    }
    netn = atoi(jcl);
    if (netn >= nnets) {
	fprintf(devp, "Net %d out of range\n", netn);
	return;
    }
    while (*jcl && *jcl != ' ')
      jcl++;
    while (*jcl && *jcl == ' ')
      jcl++;
    if (!*jcl) {
	fprintf(devp, msg);
	return;
    }
    prot = atoi(jcl);		/* should take a string here */

    for (ad = arp_ntbl[netn]; ad; ad = ad->arpd_link)
      if (ad->arpd_protocol == prot)
	break;
    if (ad == NULL) {
	fprintf(devp, "No protocol %d on net %d intfc %s/%d\n",
		prot, mksnet(&nets[netn]));
	return;
    }

    fprintf(devp, "ARP cache for protocol %d net %d intfc %s/%d\n",
	    prot, mksnet(&nets[netn]));
    for (i = 0; i < ad->arpd_hsize; i++)
      for (cent = ad->arpd_hash[i]; cent != NULL; cent = cent->arpc_link) {
	  fprintf(devp, "%20s -->  %s\n",
		  PADDR(0, ARP_CACHEENT_PRO(ad, cent)),
		  cent->arpc_ltime == 0 ?
		    "------------" :
		    HADDR(1, ARP_CACHEENT_HRD(ad, cent)));
      }
}
