/* Address Resolution Protocol (ARP) functions. Sits between IP and
 * Level 2, mapping IP to Level 2 addresses for all outgoing datagrams.
 */
#include "machdep.h"
#include "mbuf.h"
#include "timer.h"
#include "iface.h"
#include "ether.h"
#include "ax25.h"
#include "arp.h"
#include "cmdparse.h"

extern int32 ip_addr;		/* Our IP address */

int ec_output();
int pether(),gether();

int setcall(),psax25();

/* Table of ARP hardware types */
struct arp_type arp_type[] = {
	0,
	0,
	0,
	NULLCHAR,
	NULLFP,
	NULLFP,

	/* 10 megabit Ethernet */
	6,			/* Ethernet address length */
	0x800,			/* Ethernet type field for IP */
	0x806,			/* Ethernet type field for ARP */
	ether_bdcst,		/* Ethernet broadcast address */
	pether,
	gether,
	
	/* 3 megabit Ethernet */
	0,
	0,
	0,
	NULLCHAR,
	NULLFP,
	NULLFP,

	/* AX.25 */
	7,			/* AX.25 address length */
	0xCC,			/* AX.25 pid field for IP */
	0xCD,			/* AX.25 pid field for ARP */
	(char *)&ax25_bdcst,	/* AX.25 broadcast address */
	psax25,
	setcall,
};
#define	NTYPES	4

/* Hash table headers */
struct arp_tab *arp_tab[ARPSIZE];

struct arp_stat arp_stat;

/* Resolve an IP address to a hardware address; if not found,
 * initiate query and return NULLCHAR.  If an address is returned, the
 * interface driver may send the packet; if NULLCHAR is returned,
 * res_arp() will have saved the packet on its pending queue,
 * so no further action (like freeing the packet) is necessary.
 */
char *
res_arp(interface,hardware,target,bp)
struct interface *interface;	/* Pointer to interface block */
int16 hardware;		/* Hardware type */
int32 target;		/* Target IP address */
struct mbuf *bp;	/* IP datagram to be queued if unresolved */
{
	struct arp_tab *arp_lookup(),*arp_add();
	void arp_output();
	register struct arp_tab *arp;

	if((arp = arp_lookup(hardware,target)) != NULLARP && arp->state == ARP_VALID)
		return arp->hw_addr;
	/* Create an entry and put the datagram on the
	 * queue pending an answer
	 */
	arp = arp_add(target,hardware,NULLCHAR,0);
	enqueue(&arp->pending,bp);
	arp_output(interface,hardware,target);
	return NULLCHAR;
}
/* Handle incoming ARP packets. This is almost a direct implementation of
 * the algorithm on page 5 of RFC 826, except for:
 * 1. Outgoing datagrams to unresolved addresses are kept on a queue
 *    pending a reply to our ARP request.
 * 2. The names of the fields in the ARP packet were made more mnemonic.
 */
void
arp_input(interface,bp)
struct interface *interface;
struct mbuf *bp;
{
	struct arp arp;
	struct arp_tab *arp_lookup(),*ap;
	struct arp_type *at;
	struct mbuf *htonarp();
	
	arp_stat.recv++;
	if(ntoharp(&arp,bp) == -1)	/* Convert into host format */
		return;
	if(arp.hardware >= NTYPES){
		/* Unknown hardware type, ignore */
		arp_stat.badtype++;
		return;
	}
	at = &arp_type[arp.hardware];
	if(arp.protocol != at->iptype){
		/* Unsupported protocol type, ignore */
		arp_stat.badtype++;
		return;
	}
	if(arp.hwalen > MAXHWALEN || arp.pralen != sizeof(int32)){
		/* Incorrect protocol addr length (different hw addr lengths
		 * are OK since AX.25 addresses can be of variable length)
		 */
	arp_stat.badlen++;
		return;
	}
	/* If this guy is already in the table, update its entry
	 * unless it's a manual entry (noted by the lack of a timer)
	 */
	ap = NULLARP;	/* ap plays the role of merge_flag in the spec */
	if((ap = arp_lookup(arp.hardware,arp.sprotaddr)) != NULLARP
	 && ap->timer.start != 0){
		ap = arp_add(arp.sprotaddr,arp.hardware,arp.shwaddr,arp.hwalen & 0xff);
	}
	/* See if we're the address they're looking for */
	if(arp.tprotaddr == ip_addr){
		if(ap == NULLARP)	/* Only if not already in the table */
			arp_add(arp.sprotaddr,arp.hardware,arp.shwaddr,arp.hwalen & 0xff);

		if(arp.opcode == ARP_REQUEST){
			/* Swap sender's and target's (us) hardware and protocol
			 * fields, and send the packet back as a reply
			 */
			bcopy(arp.shwaddr,arp.thwaddr,arp.hwalen);
			/* Mark the end of the sender's AX.25 address
			 * in case he didn't
			 */
			if(arp.hardware == ARP_AX25)
				arp.thwaddr[arp.hwalen-1] |= E;

			bcopy(interface->hwaddr,arp.shwaddr,at->hwalen);
			arp.tprotaddr = arp.sprotaddr;
			arp.sprotaddr = ip_addr;
			arp.opcode = ARP_REPLY;
			bp = htonarp(&arp);
			(*interface->output)(interface,arp.thwaddr,
				interface->hwaddr,at->arptype,bp);
			arp_stat.inreq++;
		} else {
			arp_stat.replies++;
		}
	}
}
/* Add an IP-addr / hardware-addr pair to the ARP table */
static
struct arp_tab *
arp_add(ip_addr,hardware,hw_addr,hw_alen)
int32 ip_addr;	/* IP address, host order */
int16 hardware;	/* Hardware type */
char *hw_addr;	/* Hardware address, if known; NULLCHAR otherwise */
int16 hw_alen;	/* Length of hardware address */
{
	char *calloc(),*malloc();
	struct arp_tab *arp_lookup();
	void arp_drop();
	struct mbuf *bp,*dequeue();
	register struct arp_tab *ap;
	register struct arp_type *at;
	unsigned hashval,arp_hash();

	at = &arp_type[hardware];
	if((ap = arp_lookup(hardware,ip_addr)) == NULLARP){
		/* New entry */
		if((ap = (struct arp_tab *)calloc(1,sizeof(struct arp_tab))) == NULLARP)
			return NULLARP;
		ap->timer.func = arp_drop;
		ap->timer.arg = (int *)ap;
		ap->hardware = hardware;
		ap->ip_addr = ip_addr;

		/* Put on head of hash chain */
		hashval = arp_hash(hardware,ip_addr);
		ap->prev = NULLARP;
		ap->next = arp_tab[hashval];
		arp_tab[hashval] = ap;
		if(ap->next != NULLARP){
			ap->next->prev = ap;
		}
	}
	if(hw_addr == NULLCHAR){
		/* Await response */
		ap->state = ARP_PENDING;
		ap->timer.start = PENDTIME;
	} else {
		/* Response has come in, update entry and run through queue */
		ap->state = ARP_VALID;
		ap->timer.start = ARPLIFE;
		if(ap->hw_addr != NULLCHAR)
			free(ap->hw_addr);
		if((ap->hw_addr = malloc(hw_alen)) == NULLCHAR){
			free((char *)ap);
			return NULLARP;
		}
		bcopy(hw_addr,ap->hw_addr,hw_alen);
		/* This kludge marks the end of an AX.25 address to allow
		 * for optional digipeaters (insert Joan Rivers salute here)
		 */
		if(hardware == ARP_AX25)
			ap->hw_addr[hw_alen-1] |= E;
		while((bp = dequeue(&ap->pending)) != NULLBUF)
			ip_route(bp,0);
	}
	start_timer(&ap->timer);
	return ap;
}

/* Remove an entry from the ARP table */
static
void
arp_drop(ap)
register struct arp_tab *ap;
{
	unsigned arp_hash();

	if(ap == NULLARP)
		return;
	stop_timer(&ap->timer);	/* Shouldn't be necessary */
	if(ap->next != NULLARP)
		ap->next->prev = ap->prev;
	if(ap->prev != NULLARP)
		ap->prev->next = ap->next;
	else
		arp_tab[arp_hash(ap->hardware,ap->ip_addr)] = ap->next;
	if(ap->hw_addr != NULLCHAR)
		free(ap->hw_addr);
	free_q(&ap->pending);
	free((char *)ap);
}

/* Look up the given IP address in the ARP table */
static
struct arp_tab *
arp_lookup(hardware,ip_addr)
int16 hardware;
int32 ip_addr;
{
	unsigned arp_hash();
	register struct arp_tab *ap;

	for(ap = arp_tab[arp_hash(hardware,ip_addr)]; ap != NULLARP; ap = ap->next){
		if(ap->ip_addr == ip_addr && ap->hardware == hardware)
			break;
	}
	return ap;
}
/* Send an ARP request to resolve IP address target_ip */
static
void
arp_output(interface,hardware,target)
struct interface *interface;
int16 hardware;
int32 target;
{
	struct arp arp;
	struct mbuf *bp,*htonarp();
	struct arp_type *at;

	at = &arp_type[hardware];
	if(interface->output == NULLFP)
		return;
	
	arp.hardware = hardware;
	arp.protocol = at->iptype;
	arp.hwalen = at->hwalen;
	arp.pralen = sizeof(int32);
	arp.opcode = ARP_REQUEST;
	bcopy(interface->hwaddr,arp.shwaddr,at->hwalen);
	arp.sprotaddr = ip_addr;
	bzero(arp.thwaddr,at->hwalen);
	arp.tprotaddr = target;
	bp = htonarp(&arp);
	(*interface->output)(interface,at->bdcst,
		interface->hwaddr,at->arptype,bp);
	arp_stat.outreq++;
}

/* Hash a {hardware type, IP address} pair */
static
unsigned
arp_hash(hardware,ip_addr)
int16 hardware;
int32 ip_addr;
{
	register unsigned hashval;

	hashval = hardware;
	hashval ^= hiword(ip_addr);
	hashval ^= loword(ip_addr);
	hashval %= ARPSIZE;
	return hashval;
}		
/* Copy a host format arp structure into mbuf for transmission */
#ifdef	AMIGA
/*
 *  We play some dirty tricks here.  Since the AMIGA is a 68000 based
 *  machine, it doesn't take kindly to doing word and long word stores 
 *  on odd address boundaries.  We'll use bcopy() instead.  We can do
 *  this simply because the 68000 is a big-endian machine, and we don't
 *  need to convert to network byte order.  This is ugly.
 */
#endif
static
struct mbuf *
htonarp(arp)
register struct arp *arp;
{
	struct mbuf *bp;
	register char *buf;

	if(arp == (struct arp *)NULL)
		return NULLBUF;
	if((bp = alloc_mbuf(sizeof(struct arp))) == NULLBUF)
		return NULLBUF;

	buf = bp->data;

	*(int16 *)buf = htons(arp->hardware);
	buf += sizeof(int16);

	*(int16 *)buf = htons(arp->protocol);
	buf += sizeof(int16);

	*buf++ = arp->hwalen;

	*buf++ = arp->pralen;

	*(int16 *)buf = htons(arp->opcode);
	buf += sizeof(int16);

	bcopy(arp->shwaddr,buf,arp->hwalen);
	buf += arp->hwalen;

#ifndef	AMIGA
	*(int32 *)buf = htonl(arp->sprotaddr);
#else
	/* we've been alright up to now, but arp->hwalen may have been
	   odd, so we don't know if buf is word aligned any more! */
	bcopy(&arp->sprotaddr, buf, sizeof(int32));
#endif
	buf += sizeof(int32);

	bcopy(arp->thwaddr,buf,arp->hwalen);
	buf += arp->hwalen;

#ifndef	AMIGA
	*(int32 *)buf = htonl(arp->tprotaddr);
#else
	bcopy(&arp->tprotaddr, buf, sizeof(int32));
#endif
	buf += sizeof(int32);

	bp->cnt = buf - bp->data;
	return bp;
}
/* Convert an incoming ARP packet into a host-format structure */
static
int
ntoharp(arp,bp)
register struct arp *arp;
struct mbuf *bp;
{
	if(arp == (struct arp *)NULL || bp == NULLBUF)
		return -1;

	pullup(&bp,(char *)&arp->hardware,sizeof(int16));
	arp->hardware = ntohs(arp->hardware);

	pullup(&bp,(char *)&arp->protocol,sizeof(int16));
	arp->protocol = ntohs(arp->protocol);

	pullup(&bp,(char *)&arp->hwalen,sizeof(char));

	pullup(&bp,(char *)&arp->pralen,sizeof(char));

	pullup(&bp,(char *)&arp->opcode,sizeof(int16));
	arp->opcode = ntohs(arp->opcode);

	pullup(&bp,arp->shwaddr,arp->hwalen);

	pullup(&bp,(char *)&arp->sprotaddr,sizeof(int32));
	arp->sprotaddr = ntohl(arp->sprotaddr);

	pullup(&bp,arp->thwaddr,arp->hwalen);

	pullup(&bp,(char *)&arp->tprotaddr,sizeof(int32));
	arp->tprotaddr = ntohl(arp->tprotaddr);

	free_p(bp);
	return 0;
}
#ifdef	TRACE
char *arptypes[] = {
	NULLCHAR,
	"Ethernet",
	"Exp Ethernet",
	"AX.25",
	"Pronet",
	"Chaos"
};
int doarpadd(),doarpdrop();
struct cmds arpcmds[] = {
	"add", doarpadd, 4,
	"usage: arp add <ip addr> ether|ax25 <callsign|ether addr>",
	"arp add failed",

	"drop", doarpdrop, 3,
	"usage: arp drop <ip addr> ether|ax25",
	"not in table",

	NULLCHAR, NULLFP, 0,
	"arp subcommands: add, drop",
	NULLCHAR, 
};
int
doarp(argc,argv)
int argc;
char *argv[];
{
	if(argc < 2){
		dumparp();
		return 0;
	}
	return subcmd(arpcmds,argc,argv);
}
static
doarpadd(argc,argv)
int argc;
char *argv[];
{
	int hardware,hwalen,i;
	int32 addr,aton();
	char *malloc(),*hwaddr;
	int naddr;
	struct arp_tab *ap;
	struct arp_type *at;
	struct ax25_addr *axp;

	addr = aton(argv[1]);
	/* This is a kludge. It really ought to be table driven */
	switch(tolower(argv[2][0])){
	case 'e':	/* "ether" */
		hardware = ARP_ETHER;
		naddr = 1;
		break;		
	case 'a':	/* "ax25" */
		hardware = ARP_AX25;
		naddr = argc - 3;
		break;
	default:
		printf("unknown hardware type \"%s\"\r\n",argv[2]);
		return -1;
	}
	/* If an entry already exists, clear it */
	if((ap = arp_lookup(hardware,addr)) != NULLARP)
		arp_drop(ap);

	at = &arp_type[hardware];

	/* Allocate buffer for hardware address and fill with remaining args */
	hwalen = at->hwalen * naddr;
	if((hwaddr = malloc(hwalen)) == NULLCHAR){
		printf("No space\r\n");
		return 0;
	}
	(*at->scan)(hwaddr,argv[3]);	/* Destination address */

	/* Special hackery to handle a series of AX.25 digipeaters */
	if(hardware == ARP_AX25){
		axp = (struct ax25_addr *)hwaddr;
		for(i=1;i<naddr;i++){
			/* Set E bit only on last AX.25 call */
			axp->ssid &= ~E;
			/* axp++; */
			axp = (struct ax25_addr *) ((char *)axp + AXALEN);
			(*at->scan)((char *)axp,argv[3+i]);
		}
	}
	ap = arp_add(addr,hardware,hwaddr,hwalen);	/* Put in table */
	free(hwaddr);					/* Clean up */
	stop_timer(&ap->timer);			/* Make entry permanent */
	ap->timer.count = ap->timer.start = 0;
}
/* Remove an ARP entry */
static
doarpdrop(argc,argv)
int argc;
char *argv[];
{
	int hardware;
	int32 addr,aton();
	struct arp_tab *ap;

	addr = aton(argv[1]);
	/* This is a kludge. It really ought to be table driven */
	switch(tolower(argv[2][0])){
	case 'e':	/* "ether" */
		hardware = ARP_ETHER;
		break;		
	case 'a':	/* "ax25" */
		hardware = ARP_AX25;
		break;
	default:
		hardware = 0;
		break;
	}
	if((ap = arp_lookup(hardware,addr)) == NULLARP)
		return -1;
	arp_drop(ap);
	return 0;	
}
/* Dump ARP table */
static
dumparp()
{
	register int i;
	extern struct arp_stat arp_stat;
	register struct arp_tab *ap;
	char e[128];
	char *inet_ntoa();

	printf("received %u badtype %u reqst in %u replies %u reqst out %u\r\n",
	 arp_stat.recv,arp_stat.badtype,arp_stat.inreq,
	 arp_stat.replies,arp_stat.outreq);

	printf("IP addr         Type     Time Q Addr\r\n");
	for(i=0;i<ARPSIZE;i++){
		for(ap = arp_tab[i];ap != (struct arp_tab *)NULL;ap = ap->next){
			printf("%-16s",inet_ntoa(ap->ip_addr));
			printf("%-9s",arptypes[ap->hardware]);
			printf("%-5ld",ap->timer.count*(long)MSPTICK/1000);
			if(ap->state == ARP_PENDING)
				printf("%-2u",len_q(ap->pending));
			else
				printf("  ");
			if(ap->state == ARP_VALID){
				if(arp_type[ap->hardware].format != NULLFP){
					(*arp_type[ap->hardware].format)(e,ap->hw_addr);
				} else {
					e[0] = '\0';
				}
				printf("%s",e);
			} else {
				printf("[unknown]");
			}
			printf("\r\n");
		}
	}
	return 0;
}
/* Dump ARP packets (incomplete) */
static char *hwtypes[] = {
	"",
	"10 Mb Ethernet",
	"3 Mb Ethernet",
	"AX.25",
	NULLCHAR,
};
#define	NHWTYPES 4
arp_dump(bp)
struct mbuf *bp;
{
	struct arp arp;
	struct mbuf *tbp;
	char *inet_ntoa();

	if(bp == NULLBUF)
		return;
	/* Make temporary copy  */
	dup_p(&tbp,bp,0,len_mbuf(bp));
	ntoharp(&arp,tbp);

	if(arp.hardware < NHWTYPES)
		printf("ARP: hwtype %s",hwtypes[arp.hardware]);
	else
		printf("ARP: hwtype %u",arp.hardware);
	printf(" prot 0x%x hwlen %u prlen %u",
		arp.protocol,arp.hwalen,arp.pralen);
	switch(arp.opcode){
	case ARP_REQUEST:
		printf(" op REQUEST");
		break;
	case ARP_REPLY:
		printf(" op REPLY");
		break;
	default:
		printf(" op %u",arp.opcode);
		break;
	}
	printf(" target %s\r\n",inet_ntoa(arp.tprotaddr));
}
#endif
