#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>
#include <ndb.h>
#include "arp.h"

typedef struct Rarp	Rarp;

struct Rarp
{
	uchar	edst[6];
	uchar	esrc[6];
	uchar	type[2];
	uchar	hrd[2];
	uchar	pro[2];
	uchar	hln;
	uchar	pln;
	uchar	op[2];
	uchar	sha[6];
	uchar	spa[4];
	uchar	tha[6];
	uchar	tpa[4];
};

uchar	myip[IPaddrlen];
uchar	myether[6];
char	rlog[] = "ipboot";
char	*device = "ether0";
int	debug;
Ndb	*db;

char*	lookup(char*, char*, char*, char*, int);

void
error(char *s)
{
	syslog(1, rlog, "error %s: %r", s);
	exits(s);
}

char net[32];

void
usage(void)
{
	fprint(2, "usage: %s [-e device] [-x netmtpt] [-f ndb-file] [-d]\n", argv0);
	exits("usage");
}

void
main(int argc, char *argv[])
{
	int edata, ectl;
	uchar buf[2048];
	long n;
	Rarp *rp;
	char ipbuf[64];
	char ebuf[128];
	char file[128];
	int arp;
	char *p, *ndbfile;

	ndbfile = nil;
	setnetmtpt(net, sizeof(net), nil);
	ARGBEGIN{
	case 'e':
		p = ARGF();
		if(p == nil)
			usage();
		device = p;
		break;
	case 'd':
		debug = 1;
		break;
	case 'f':
		p = ARGF();
		if(p == nil)
			usage();
		ndbfile = p;
		break;
	case 'x':
		p = ARGF();
		if(p == nil)
			usage();
		setnetmtpt(net, sizeof(net), p);
		break;
	}ARGEND
	USED(argc, argv);

	fmtinstall('E', eipfmt);
	fmtinstall('I', eipfmt);
	fmtinstall('V', eipfmt);

	db = ndbopen(ndbfile);
	if(db == 0)
		error("can't open the database");

	edata = dial(netmkaddr("0x8035", device, 0), 0, 0, &ectl);
	if(edata < 0)
		error("can't open ethernet");

	if(myipaddr(myip, net) < 0)
		error("can't get my ip address");
	snprint(ebuf, sizeof(ebuf), "%s/%s", net, device);
	if(myetheraddr(myether, ebuf) < 0)
		error("can't get my ether address");

	snprint(file, sizeof(file), "%s/arp", net);
	if((arp = open(file, ORDWR)) < 0)
		fprint(2, "rarpd: can't open %s\n", file);

	switch(rfork(RFNOTEG|RFPROC|RFFDG)) {
	case -1:
		error("fork");
	case 0:
		break;
	default:
		exits(0);
	}

	for(;;){
		n = read(edata, buf, sizeof(buf));
		if(n <= 0)
			error("reading");
		if(n < sizeof(Rarp)){
			syslog(debug, rlog, "bad packet size %ld", n);
			continue;
		}
		rp = (Rarp*)buf;
		if(rp->op[0]!=0 && rp->op[1]!=3){
			syslog(debug, rlog, "bad op %d %d %E",
				rp->op[1], rp->op[0], rp->esrc);
			continue;
		}

		if(debug)
			syslog(debug, rlog, "rcv se %E si %V te %E ti %V",
				 rp->sha, rp->spa, rp->tha, rp->tpa);

		snprint(ebuf, sizeof(ebuf), "%E", rp->tha);
		if(lookup("ether", ebuf, "ip", ipbuf, sizeof ipbuf) == nil){
			syslog(debug, rlog, "client lookup failed: %s", ebuf);
			continue;
		}
		v4parseip(rp->tpa, ipbuf);

		memmove(rp->sha, myether, sizeof(rp->sha));
		v6tov4(rp->spa, myip);

		rp->op[0] = 0;
		rp->op[1] = 4;
		memmove(rp->edst, rp->esrc, sizeof(rp->edst));

		if(debug)
			syslog(debug, rlog, "send se %E si %V te %E ti %V",
				 rp->sha, rp->spa, rp->tha, rp->tpa);

		if(write(edata, buf, 60) != 60)
			error("write failed");

		if(arp < 0)
			continue;
		if(fprint(arp, "add %E %V", rp->esrc, rp->tpa) < 0)
			fprint(2, "can't write arp entry\n");
	}
}

char*
lookup(char *sattr, char *sval, char *tattr, char *tval, int len)
{
	static Ndb *db;
	char *attrs[1];
	Ndbtuple *t;

	if(db == nil)
		db = ndbopen(0);
	if(db == nil)
		return nil;

	if(sattr == nil)
		sattr = ipattr(sval);

	attrs[0] = tattr;
	t = ndbipinfo(db, sattr, sval, attrs, 1);
	if(t == nil)
		return nil;
	strncpy(tval, t->val, len);
	tval[len-1] = 0;
	ndbfree(t);
	return tval;
}
