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

int	noconfig;
int	debug;
int	dodhcp;
int	nip;
int	myifc = -1;
int	plan9 = 1;
int	beprimary = -1;
int	nodhcpwatch;
int	sendhostname;
int	dondbconfig = 0;

char	*ndboptions;

Ipifc	*ifc;

// possible verbs
enum
{
	Vadd,
	Vremove,
	Vunbind,
	Vether,
	Vloopback,
};

struct {
	// locally generated
	char	*type;
	char	*dev;
	char	mpoint[32];
	int	cfd;			// ifc control channel
	int	dfd;			// ifc data channel (for ppp)
	char	*cputype;
	uchar	hwa[32];		// hardware address
	int	hwatype;
	int	hwalen;
	uchar	cid[32];
	int	cidlen;
	char	*baud;

	// learned info
	uchar	gaddr[IPaddrlen];
	uchar	laddr[IPaddrlen];
	uchar	mask[IPaddrlen];
	uchar	raddr[IPaddrlen];
	uchar	dns[2*IPaddrlen];
	uchar	fs[2*IPaddrlen];
	uchar	auth[2*IPaddrlen];
	uchar	ntp[IPaddrlen];
	int	mtu;

	// dhcp specific
	int	state;
	int	fd;
	ulong	xid;
	ulong	starttime;
	char	sname[64];
	char	hostname[32];
	char	domainname[64];
	uchar	server[IPaddrlen];	/* server IP address */
	ulong	offered;		/* offered lease time */
	ulong	lease;			/* lease time */
	ulong	resend;			/* number of resends for current state */
	ulong	timeout;		/* time to timeout - seconds */
} conf;

enum
{
	Taddr,
	Taddrs,
	Tstr,
	Tbyte,
	Tulong,
	Tvec,
};

typedef struct Option Option;
struct Option
{
	char	*name;
	int	type;
};

// I was too lazy to look up the types for each of these
// options.  If someone feels like it, please mail me a
// corrected array -- presotto
Option option[256] =
{
[OBmask]		{ "ipmask",		Taddr },
[OBtimeoff]		{ "timeoff",		Tulong },
[OBrouter]		{ "ipgw",		Taddrs },
[OBtimeserver]		{ "time",		Taddrs },
[OBnameserver]		{ "name",		Taddrs },
[OBdnserver]		{ "dns",		Taddrs },
[OBlogserver]		{ "log",		Taddrs },
[OBcookieserver]	{ "cookie",		Taddrs },
[OBlprserver]		{ "lpr",		Taddrs },
[OBimpressserver]	{ "impress",		Taddrs },
[OBrlserver]		{ "rl",			Taddrs },
[OBhostname]		{ "sys",		Tstr },
[OBbflen]		{ "bflen",		Tulong },
[OBdumpfile]		{ "dumpfile",		Tstr },
[OBdomainname]		{ "dom",		Tstr },
[OBswapserver]		{ "swap",		Taddrs },
[OBrootpath]		{ "rootpath",		Tstr },
[OBextpath]		{ "extpath",		Tstr },
[OBipforward]		{ "ipforward",		Taddrs },
[OBnonlocal]		{ "nonlocal",		Taddrs },
[OBpolicyfilter]	{ "policyfilter",	Taddrs },
[OBmaxdatagram]		{ "maxdatagram",	Tulong },
[OBttl]			{ "ttl",		Tulong },
[OBpathtimeout]		{ "pathtimeout",	Taddrs },
[OBpathplateau]		{ "pathplateau",	Taddrs },
[OBmtu]			{ "mtu",		Tulong },
[OBsubnetslocal]	{ "subnetslocal",	Taddrs },
[OBbaddr]		{ "baddr",		Taddrs },
[OBdiscovermask]	{ "discovermask",	Taddrs },
[OBsupplymask]		{ "supplymask",		Taddrs },
[OBdiscoverrouter]	{ "discoverrouter",	Taddrs },
[OBrsserver]		{ "rs",			Taddrs },
[OBstaticroutes]	{ "staticroutes",	Taddrs },
[OBtrailerencap]	{ "trailerencap",	Taddrs },
[OBarptimeout]		{ "arptimeout",		Tulong },
[OBetherencap]		{ "etherencap",		Taddrs },
[OBtcpttl]		{ "tcpttl",		Tulong },
[OBtcpka]		{ "tcpka",		Tulong },
[OBtcpkag]		{ "tcpkag",		Tulong },
[OBnisdomain]		{ "nisdomain",		Tstr },
[OBniserver]		{ "ni",			Taddrs },
[OBntpserver]		{ "ntp",		Taddrs },
[OBnetbiosns]		{ "netbiosns",		Taddrs },
[OBnetbiosdds]		{ "netbiosdds",		Taddrs },
[OBnetbiostype]		{ "netbiostype",	Taddrs },
[OBnetbiosscope]	{ "netbiosscope",	Taddrs },
[OBxfontserver]		{ "xfont",		Taddrs },
[OBxdispmanager]	{ "xdispmanager",	Taddrs },
[OBnisplusdomain]	{ "nisplusdomain",	Tstr },
[OBnisplusserver]	{ "nisplus",		Taddrs },
[OBhomeagent]		{ "homeagent",		Taddrs },
[OBsmtpserver]		{ "smtp",		Taddrs },
[OBpop3server]		{ "pop3",		Taddrs },
[OBnntpserver]		{ "nntp",		Taddrs },
[OBwwwserver]		{ "www",		Taddrs },
[OBfingerserver]	{ "finger",		Taddrs },
[OBircserver]		{ "irc",		Taddrs },
[OBstserver]		{ "st",			Taddrs },
[OBstdaserver]		{ "stdar",		Taddrs },

[ODipaddr]		{ "ipaddr",		Taddr },
[ODlease]		{ "lease",		Tulong },
[ODoverload]		{ "overload",		Taddr },
[ODtype]		{ "type",		Tbyte },
[ODserverid]		{ "serverid",		Taddr },
[ODparams]		{ "params",		Tvec },
[ODmessage]		{ "message",		Tstr },
[ODmaxmsg]		{ "maxmsg",		Tulong },
[ODrenewaltime]		{ "renewaltime",	Tulong },
[ODrebindingtime]	{ "rebindingtime",	Tulong },
[ODvendorclass]		{ "vendorclass",	Tvec },
[ODclientid]		{ "clientid",		Tvec },
[ODtftpserver]		{ "tftp",		Taddr },
[ODbootfile]		{ "bootfile",		Tstr },
};

uchar defrequested[] = {
	OBmask, OBrouter, OBdnserver, OBhostname, OBdomainname, OBntpserver,
};

uchar requested[256];
int nrequested;

void	adddefroute(char*, uchar*);
int	addoption(char*);
void	binddevice(void);
void	bootprequest(void);
void	controldevice(void);
void	dhcpquery(int, int);
void	dhcprecv(void);
void	dhcpsend(int);
int	dhcptimer(void);
void	dhcpwatch(int);
void	doadd(int);
void	doremove(void);
void	dounbind(void);
int	getndb(void);
void	getoptions(uchar*);
int	ipconfig(void);
void	lookforip(char*);
void	mkclientid(void);
void	ndbconfig(void);
int	nipifcs(char*);
int	openlisten(void);
uchar*	optadd(uchar*, int, void*, int);
uchar*	optaddaddr(uchar*, int, uchar*);
uchar*	optaddbyte(uchar*, int, int);
uchar*	optaddstr(uchar*, int, char*);
uchar*	optaddulong(uchar*, int, ulong);
uchar*	optaddvec(uchar*, int, uchar*, int);
uchar*	optget(uchar*, int, int*);
int	optgetaddr(uchar*, int, uchar*);
int	optgetaddrs(uchar*, int, uchar*, int);
int	optgetbyte(uchar*, int);
int	optgetstr(uchar*, int, char*, int);
ulong	optgetulong(uchar*, int);
int	optgetvec(uchar*, int, uchar*, int);
char*	optgetx(uchar*, uchar);
Bootp*	parsebootp(uchar*, int);
int	parseoptions(uchar *p, int n);
int	parseverb(char*);
void	putndb(void);
void	tweakservers(void);
void	usage(void);
int	validip(uchar*);
void	writendb(char*, int, int);

char optmagic[4] = { 0x63, 0x82, 0x53, 0x63 };

#define DEBUG if(debug)print

typedef struct Ctl Ctl;
struct Ctl
{
	Ctl	*next;
	char	*ctl;
};
Ctl *firstctl, **ctll;

void
usage(void)
{
	fprint(2, "usage: %s [-ndDrGX] [-x netmtpt] [-m mtu] [-b baud] [-g gateway] [-h hostname] [-c control-string]* type device [verb] [localaddr [mask [remoteaddr [fsaddr [authaddr]]]]]\n", argv0);
	exits("usage");
}

void
main(int argc, char **argv)
{
	char *p;
	int retry, verb, action;
	Ctl *cp;

	srand(truerand());
	fmtinstall('E', eipfmt);
	fmtinstall('I', eipfmt);
	fmtinstall('M', eipfmt);
	fmtinstall('V', eipfmt);

	setnetmtpt(conf.mpoint, sizeof(conf.mpoint), nil);
	conf.cputype = getenv("cputype");
	if(conf.cputype == nil)
		conf.cputype = "386";

	retry = 0;
	ctll = &firstctl;

	// init set of requested parameters with the default
	nrequested = sizeof(defrequested);
	memcpy(requested, defrequested, nrequested);

	ARGBEGIN {
	case 'D':
		debug = 1;
		break;
	case 'G':
		plan9 = 0;
		break;
	case 'N':
		dondbconfig = 1;
		break;
	case 'p':
		beprimary = 1;
		break;
	case 'P':
		beprimary = 0;
	case 'b':
		p = ARGF();
		if(p == nil)
			usage();
		conf.baud = p;
		break;
	case 'c':
		p = ARGF();
		if(p == nil)
			usage();
		cp = malloc(sizeof(*cp));
		if(cp == nil)
			sysfatal("%r");
		*ctll = cp;
		ctll = &cp->next;
		cp->next = nil;
		cp->ctl = p;
		break;
	case 'd':
		dodhcp = 1;
		break;
	case 'g':
		p = ARGF();
		if(p == nil)
			usage();
		parseip(conf.gaddr, p);
		break;
	case 'h':
		p = ARGF();
		if(p == nil)
			usage();
		snprint(conf.hostname, sizeof(conf.hostname), "%s", p);
		sendhostname = 1;
		break;
	case 'n':
		noconfig = 1;
		break;
	case 'm':
		p = ARGF();
		if(p == nil)
			usage();
		conf.mtu = atoi(p);
		break;
	case 'r':
		retry = 1;
		break;
	case 'x':
		p = ARGF();
		if(p == nil)
			usage();
		setnetmtpt(conf.mpoint, sizeof(conf.mpoint), p);
		break;
	case 'X':
		nodhcpwatch = 1;
		break;
	case 'o':
		if(addoption(ARGF()) < 0)
			usage();
		break;
	} ARGEND;

	// default to any host name we already have
	if(*conf.hostname == 0){
		p = getenv("sysname");
		if(p == nil || *p == 0)
			p = sysname();
		if(p != nil)
			strncpy(conf.hostname, p, sizeof(conf.hostname)-1);
	}		

	// default
	conf.type = "ether";
	conf.dev = "/net/ether0";
	action = Vadd;

	// get verb, default is "add"
	while(argc > 0){
		verb = parseverb(argv[0]);
		switch(verb){
		case Vadd:
		case Vremove:
		case Vunbind:
			action = verb;
			break;
		case Vether:
		case Vloopback:
			conf.type = argv[0];
			if(argc > 1){
				conf.dev = argv[1];
				argc--, argv++;
			}
			break;
		}
		if(verb < 0)
			break;
		argc--, argv++;
	}

	// get addresses
	switch(argc){
	case 5:
		parseip(conf.auth, argv[4]);
		/* fall through */
	case 4:
		parseip(conf.fs, argv[3]);
		/* fall through */
	case 3:
		parseip(conf.raddr, argv[2]);
		/* fall through */
	case 2:
		parseipmask(conf.mask, argv[1]);
		/* fall through */
	case 1:
		parseip(conf.laddr, argv[0]);
		break;
	}

	switch(action){
	case Vadd:
		doadd(retry);
		break;
	case Vremove:
		doremove();
		break;
	case Vunbind:
		dounbind();
		break;
	}

	exits(0);
}

int
havendb(char *net)
{
	Dir *d;
	char buf[128];

	snprint(buf, sizeof buf, "%s/ndb", net);
	if((d = dirstat("/net/ndb")) == nil)
		return 0;
	if(d->length == 0){
		free(d);
		return 0;
	}
	free(d);
	return 1;
}

void
doadd(int retry)
{
	int tries;

	// get number of preexisting interfaces
	nip = nipifcs(conf.mpoint);
	if(beprimary == -1 && (nip == 0 || !havendb(conf.mpoint)))
		beprimary = 1;

	// get ipifc into name space and condition device for ip
	if(!noconfig){
		lookforip(conf.mpoint);
		controldevice();
		binddevice();
	}

	if(!validip(conf.laddr)){
		if(dondbconfig)
			ndbconfig();
		else
			dodhcp = 1;
	}

	// run dhcp if we need something
	if(dodhcp){
		mkclientid();
		for(tries = 0; tries < 6; tries++){
			dhcpquery(!noconfig, Sselecting);
			if(conf.state == Sbound)
				break;
			sleep(1000);
		}
	}

	if(!validip(conf.laddr)){
		if(retry && dodhcp && !noconfig){
			fprint(2, "%s: couldn't determine ip address, retrying\n", argv0);
			dhcpwatch(1);
			return;
		} else {
			fprint(2, "%s: no success with DHCP\n", argv0);
			exits("failed");
		}
	}

	if(!noconfig){
		if(ipconfig() < 0)
			sysfatal("can't start ip");
		if(dodhcp && conf.lease != Lforever)
			dhcpwatch(0);
	}

	// leave everything we've learned somewhere other procs can find it
	if(beprimary == 1){
		putndb();
		tweakservers();
	}
}

void
doremove(void)
{
	char file[128];
	int cfd;
	Ipifc *nifc;
	Iplifc *lifc;

	if(!validip(conf.laddr)){
		fprint(2, "%s: remove requires an address\n", argv0);
		exits("usage");
	}

	ifc = readipifc(conf.mpoint, ifc, -1);
	for(nifc = ifc; nifc != nil; nifc = nifc->next){
		if(strcmp(nifc->dev, conf.dev) != 0)
			continue;
		for(lifc = nifc->lifc; lifc != nil; lifc = lifc->next){
			if(ipcmp(conf.laddr, lifc->ip) != 0)
				continue;
			if(validip(conf.mask) && ipcmp(conf.mask, lifc->mask) != 0)
				continue;
			if(validip(conf.raddr) && ipcmp(conf.raddr, lifc->net) != 0)
				continue;

			snprint(file, sizeof(file), "%s/ipifc/%d/ctl", conf.mpoint, nifc->index);
			cfd = open(file, ORDWR);
			if(cfd < 0){
				fprint(2, "%s: can't open %s: %r\n", argv0, conf.mpoint);
				continue;
			}
			if(fprint(cfd, "remove %I %M", lifc->ip, lifc->mask) < 0)
				fprint(2, "%s: can't remove %I %I from %s: %r\n", argv0,
					lifc->ip, lifc->mask, file);
		}
	}
}

void
dounbind(void)
{
	Ipifc *nifc;
	char file[128];
	int cfd;

	ifc = readipifc(conf.mpoint, ifc, -1);
	for(nifc = ifc; nifc != nil; nifc = nifc->next){
		if(strcmp(nifc->dev, conf.dev) == 0){
			snprint(file, sizeof(file), "%s/ipifc/%d/ctl", conf.mpoint, nifc->index);
			cfd = open(file, ORDWR);
			if(cfd < 0){
				fprint(2, "%s: can't open %s: %r\n", argv0, conf.mpoint);
				break;
			}
			if(fprint(cfd, "unbind") < 0)
				fprint(2, "%s: can't unbind from %s: %r\n", argv0, file);
			break;
		}
	}
}

// set the default route
void
adddefroute(char *mpoint, uchar *gaddr)
{
	char buf[256];
	int cfd;

	sprint(buf, "%s/iproute", mpoint);
	cfd = open(buf, ORDWR);
	if(cfd < 0)
		return;
	fprint(cfd, "add 0 0 %I", gaddr);
	close(cfd);
}

// create a client id
void
mkclientid(void)
{
	if(strcmp(conf.type, "ether") == 0)
	if(myetheraddr(conf.hwa, conf.dev) == 0){
		conf.hwalen = 6;
		conf.hwatype = 1;
		conf.cid[0] = conf.hwatype;
		memmove(&conf.cid[1], conf.hwa, conf.hwalen);
		conf.cidlen = conf.hwalen+1;
	} else {
		conf.hwatype = -1;
		snprint((char*)conf.cid, sizeof(conf.cid), "plan9_%ld.%d", lrand(), getpid());
		conf.cidlen = strlen((char*)conf.cid);
	}
}

// bind ip into the namespace
void
lookforip(char *net)
{
	char proto[64];

	snprint(proto, sizeof(proto), "%s/ipifc", net);
	if(access(proto, 0) == 0)
		return;
	sysfatal("no ip stack bound onto %s", net);
}

// send some ctls to a device
void
controldevice(void)
{
	char ctlfile[256];
	int fd;
	Ctl *cp;

	if(firstctl == nil)
		return;

	if(strcmp(conf.type, "ether") == 0)
		snprint(ctlfile, sizeof(ctlfile), "%s/clone", conf.dev);
	else
		return;

	fd = open(ctlfile, ORDWR);
	if(fd < 0)
		sysfatal("can't open %s", ctlfile);

	for(cp = firstctl; cp != nil; cp = cp->next){
		if(write(fd, cp->ctl, strlen(cp->ctl)) < 0)
			sysfatal("ctl message %s: %r", cp->ctl);
		seek(fd, 0, 0);
	}
}

// bind an ip stack to a device, leave the control channel open
void
binddevice(void)
{
	char buf[256];

	if(myifc < 0){
		// get a new ip interface
		snprint(buf, sizeof(buf), "%s/ipifc/clone", conf.mpoint);
		conf.cfd = open(buf, ORDWR);
		if(conf.cfd < 0)
			sysfatal("opening %s/ipifc/clone: %r", conf.mpoint);

		// specify the medium as an ethernet, and bind the interface to it
		if(fprint(conf.cfd, "bind %s %s", conf.type, conf.dev) < 0)
			sysfatal("binding device: %r");
	} else {
		// open the old interface
		snprint(buf, sizeof(buf), "%s/ipifc/%d/ctl", conf.mpoint, myifc);
		conf.cfd = open(buf, ORDWR);
		if(conf.cfd < 0)
			sysfatal("opening %s/ipifc/%d/ctl: %r", conf.mpoint, myifc);
	}

}

// add a logical interface to the ip stack
int
ipconfig(void)
{
	char buf[256];
	int n;

	if(!validip(conf.laddr))
		return -1;

	n = sprint(buf, "add");
	n += snprint(buf+n, sizeof(buf)-n, " %I", conf.laddr);

	if(!validip(conf.mask))
		ipmove(conf.mask, defmask(conf.laddr));
	n += snprint(buf+n, sizeof(buf)-n, " %I", conf.mask);

	if(validip(conf.raddr)){
		n += snprint(buf+n, sizeof(buf)-n, " %I", conf.raddr);
		if(conf.mtu != 0)
			n += snprint(buf+n, sizeof(buf)-n, " %d", conf.mtu);
	}

	if(write(conf.cfd, buf, n) < 0){
		fprint(2, "ipconfig: write(%s): %r\n", buf);
		return -1;
	}

	if(beprimary==1 && validip(conf.gaddr))
		adddefroute(conf.mpoint, conf.gaddr);

	return 0;
}

// remove a logical interface to the ip stack
void
ipunconfig(void)
{
	char buf[256];
	int n;

	if(!validip(conf.laddr))
		return;
	DEBUG("couldn't renew IP lease, releasing %I\n", conf.laddr);
	n = sprint(buf, "remove");
	n += snprint(buf+n, sizeof(buf)-n, " %I", conf.laddr);

	if(!validip(conf.mask))
		ipmove(conf.mask, defmask(conf.laddr));
	n += snprint(buf+n, sizeof(buf)-n, " %I", conf.mask);

	write(conf.cfd, buf, n);

	ipmove(conf.laddr, IPnoaddr);
	ipmove(conf.raddr, IPnoaddr);
	ipmove(conf.mask, IPnoaddr);

	// forget configuration info
	if(beprimary==1)
		writendb("", 0, 0);
}

void
ding(void*, char *msg)
{
	if(strstr(msg, "alarm"))
		noted(NCONT);
	noted(NDFLT);
}

void
dhcpquery(int needconfig, int startstate)
{
	if(needconfig)
		fprint(conf.cfd, "add %I %I", IPnoaddr, IPnoaddr);

	conf.fd = openlisten();
	if(conf.fd < 0){
		conf.state = Sinit;
		return;
	}
	notify(ding);

	// try dhcp for 10 seconds
	conf.xid = lrand();
	conf.starttime = time(0);
	conf.state = startstate;
	switch(startstate){
	case Sselecting:
		conf.offered = 0;
		dhcpsend(Discover);
		break;
	case Srenewing:
		dhcpsend(Request);
		break;
	default:
		sysfatal("internal error 0");
	}
	conf.resend = 0;
	conf.timeout = time(0) + 4;

	while(conf.state != Sbound){
		dhcprecv();
		if(dhcptimer() < 0)
			break;
		if(time(0) - conf.starttime > 10)
			break;
	}
	close(conf.fd);

	if(needconfig)
		fprint(conf.cfd, "remove %I %I", IPnoaddr, IPnoaddr);

}

#define HOUR (60*60)

void
dhcpwatch(int needconfig)
{
	int secs, s;
	ulong t;

	if(nodhcpwatch)
		return;

	switch(rfork(RFPROC|RFFDG|RFNOWAIT|RFNOTEG)){
	default:
		return;
	case 0:
		break;
	}

	// keep trying to renew the lease
	for(;;){
		if(conf.lease == 0)
			secs = 5;
		else
			secs = conf.lease>>1;

		// avoid overflows
		for(s = secs; s > 0; s -= t){
			if(s > HOUR)
				t = HOUR;
			else
				t = s;
			sleep(t*1000);
		}

		if(conf.lease > 0){
			// during boot, the starttime can be bogus so avoid
			// spurious ipinconfig's
			t = time(0) - conf.starttime;
			if(t > (3*secs)/2)
				t = secs;
			if(t >= conf.lease){
				conf.lease = 0;
				if(!noconfig){
					ipunconfig();
					needconfig = 1;
				}
			} else
				conf.lease -= t;
		}

		dhcpquery(needconfig, needconfig ? Sselecting : Srenewing);

		if(needconfig && conf.state == Sbound){
			if(ipconfig() < 0)
				sysfatal("can't start ip: %r");
			needconfig = 0;

			// leave everything we've learned somewhere other procs can find it
			if(beprimary==1){
				putndb();
				tweakservers();
			}
		}
	}
}

int
dhcptimer(void)
{
	ulong now;

	now = time(0);

	if(now < conf.timeout)
		return 0;

	switch(conf.state) {
	default:
		sysfatal("dhcptimer: unknown state %d", conf.state);
	case Sinit:
		break;
	case Sselecting:
		dhcpsend(Discover);
		conf.timeout = now + 4;
		conf.resend++;
		if(conf.resend>5)
			goto err;
		break;
	case Srequesting:
		dhcpsend(Request);
		conf.timeout = now + 4;
		conf.resend++;
		if(conf.resend>5)
			goto err;
		break;
	case Srenewing:
		dhcpsend(Request);
		conf.timeout = now + 1;
		conf.resend++;
		if(conf.resend>3) {
			conf.state = Srebinding;
			conf.resend = 0;
		}
		break;
	case Srebinding:
		dhcpsend(Request);
		conf.timeout = now + 4;
		conf.resend++;
		if(conf.resend>5)
			goto err;
		break;
	case Sbound:
		break;
	}
	return 0;
err:
	conf.state = Sinit;
	return -1;
}

void
dhcpsend(int type)
{
	Bootp bp;
	uchar *p;
	int n;
	uchar vendor[64];
	OUdphdr *up = (OUdphdr*)bp.udphdr;

	memset(&bp, 0, sizeof(bp));

	hnputs(up->rport, 67);
	bp.op = Bootrequest;
	hnputl(bp.xid, conf.xid);
	hnputs(bp.secs, time(0)-conf.starttime);
	hnputs(bp.flags, 0);
	memmove(bp.optmagic, optmagic, 4);
	if(conf.hwatype >= 0 && conf.hwalen < sizeof(bp.chaddr)){
		memmove(bp.chaddr, conf.hwa, conf.hwalen);
		bp.hlen = conf.hwalen;
		bp.htype = conf.hwatype;
	}
	p = bp.optdata;
	p = optaddbyte(p, ODtype, type);
	p = optadd(p, ODclientid, conf.cid, conf.cidlen);
	switch(type) {
	default:
		sysfatal("dhcpsend: unknown message type: %d", type);
	case Discover:
		ipmove(up->raddr, IPv4bcast);	// broadcast
		if(*conf.hostname && sendhostname)
			p = optaddstr(p, OBhostname, conf.hostname);
		if(plan9){
			n = snprint((char*)vendor, sizeof(vendor), "plan9_%s", conf.cputype);
			p = optaddvec(p, ODvendorclass, vendor, n);
		}
		p = optaddvec(p, ODparams, requested, nrequested);
		if(validip(conf.laddr))
			p = optaddaddr(p, ODipaddr, conf.laddr);
		break;
	case Request:
		switch(conf.state){
		case Srenewing:
			ipmove(up->raddr, conf.server);
			v6tov4(bp.ciaddr, conf.laddr);
			break;
		case Srebinding:
			ipmove(up->raddr, IPv4bcast);	// broadcast
			v6tov4(bp.ciaddr, conf.laddr);
			break;
		case Srequesting:
			ipmove(up->raddr, IPv4bcast);	// broadcast
			p = optaddaddr(p, ODipaddr, conf.laddr);
			p = optaddaddr(p, ODserverid, conf.server);
			break;
		}
		p = optaddulong(p, ODlease, conf.offered);
		if(plan9){
			n = snprint((char*)vendor, sizeof(vendor), "plan9_%s", conf.cputype);
			p = optaddvec(p, ODvendorclass, vendor, n);
		}
		p = optaddvec(p, ODparams, requested, nrequested);
		if(*conf.hostname && sendhostname)
			p = optaddstr(p, OBhostname, conf.hostname);
		break;	
	case Release:
		ipmove(up->raddr, conf.server);
		v6tov4(bp.ciaddr, conf.laddr);
		p = optaddaddr(p, ODipaddr, conf.laddr);
		p = optaddaddr(p, ODserverid, conf.server);
		break;
	}

	*p++ = OBend;

	n = p - (uchar*)&bp;
	USED(n);

	/*
	 *  We use a maximum size DHCP packet to survive the
	 *  All_Aboard NAT package from Internet Share.  It
	 *  always replies to DHCP requests with a packet of the
	 *  same size, so if the request is too short the reply
	 *  is truncated.
	 */
	if(write(conf.fd, &bp, sizeof(bp)) != sizeof(bp))
		fprint(2, "dhcpsend: write failed: %r\n");
}

void
dhcprecv(void)
{
	uchar buf[8000];
	Bootp *bp;
	int i, n, type;
	ulong lease;
	char err[ERRMAX];
	uchar vopts[256];

	alarm(1000);
	n = read(conf.fd, buf, sizeof(buf));
	alarm(0);

	if(n < 0){
		errstr(err, sizeof err);
		if(strstr(err, "interrupt") == nil)
			fprint(2, "ipconfig: bad read: %s\n", err);
		else
			DEBUG("read timed out\n");
		return;
	}

	bp = parsebootp(buf, n);
	if(bp == 0) {
		DEBUG("parsebootp failed: dropping packet\n");
		return;
	}

	type = optgetbyte(bp->optdata, ODtype);
	switch(type) {
	default:
		fprint(2, "%s: unknown type: %d\n", argv0, type);
		break;
	case Offer:
		DEBUG("got offer from %V ", bp->siaddr);
		if(conf.state != Sselecting){
			DEBUG("\n");
			break;
		}
		lease = optgetulong(bp->optdata, ODlease);
		if(lease == 0){
			/*
			 *  The All_Aboard NAT package from Internet Share doesn't
			 *  give a lease time, so we have to assume one.
			 */
			fprint(2, "%s: Offer with %lud lease, using %d\n", argv0, lease, MinLease);
			lease = MinLease;
		}
		DEBUG("lease=%lud ", lease);
		if(!optgetaddr(bp->optdata, ODserverid, conf.server)) {
			fprint(2, "%s: Offer from server with invalid serverid\n", argv0);
			break;
		}

		v4tov6(conf.laddr, bp->yiaddr);
		memmove(conf.sname, bp->sname, sizeof(conf.sname));
		conf.sname[sizeof(conf.sname)-1] = 0;
		DEBUG("server=%I sname=%s\n", conf.server, conf.sname);
		conf.offered = lease;
		conf.state = Srequesting;
		dhcpsend(Request);
		conf.resend = 0;
		conf.timeout = time(0) + 4;
		break;
	case Ack:
		DEBUG("got ack from %V ", bp->siaddr);
		if(conf.state != Srequesting)
		if(conf.state != Srenewing)
		if(conf.state != Srebinding)
			break;

		// ignore a bad lease
		lease = optgetulong(bp->optdata, ODlease);
		if(lease == 0){
			/*
			 *  The All_Aboard NAT package from Internet Share doesn't
			 *  give a lease time, so we have to assume one.
			 */
			fprint(2, "%s: Ack with %lud lease, using %d\n", argv0, lease, MinLease);
			lease = MinLease;
		}
		DEBUG("lease=%lud ", lease);

		// address and mask
		v4tov6(conf.laddr, bp->yiaddr);
		if(!optgetaddr(bp->optdata, OBmask, conf.mask))
			ipmove(conf.mask, IPnoaddr);
		DEBUG("ipaddr=%I ipmask=%M ", conf.laddr, conf.mask);

		// get a router address either from the router option
		// or from the router that forwarded the dhcp packet
		if(optgetaddr(bp->optdata, OBrouter, conf.gaddr)){
			DEBUG("ipgw=%I ", conf.gaddr);
		} else {
			if(memcmp(bp->giaddr, IPnoaddr+IPv4off, IPv4addrlen) != 0){
				v4tov6(conf.gaddr, bp->giaddr);
				DEBUG("giaddr=%I ", conf.gaddr);
			}
		}

		// get dns servers
		memset(conf.dns, 0, sizeof(conf.dns));
		n = optgetaddrs(bp->optdata, OBdnserver, conf.dns,
				sizeof(conf.dns)/IPaddrlen);
		for(i = 0; i < n; i++)
			DEBUG("dns=%I ", conf.dns+i*IPaddrlen);

		// get ntp servers
		memset(conf.ntp, 0, sizeof(conf.ntp));
		n = optgetaddrs(bp->optdata, OBntpserver, conf.ntp,
				sizeof(conf.ntp)/IPaddrlen);
		for(i = 0; i < n; i++)
			DEBUG("ntp=%I ", conf.ntp+i*IPaddrlen);

		// get names
		optgetstr(bp->optdata, OBhostname, conf.hostname, sizeof(conf.hostname));
		optgetstr(bp->optdata, OBdomainname, conf.domainname, sizeof(conf.domainname));

		// get anything else we asked for
		getoptions(bp->optdata);

		// get plan9 specific options
		n = optgetvec(bp->optdata, OBvendorinfo, vopts, sizeof(vopts)-1);
		if(n > 0){
			if(parseoptions(vopts, n) == 0){
				n = optgetaddrs(vopts, OP9fs, conf.fs, 2);
				for(i = 0; i < n; i++)
					DEBUG("fs=%I ", conf.fs+i*IPaddrlen);
				n = optgetaddrs(vopts, OP9auth, conf.auth, 2);
				for(i = 0; i < n; i++)
					DEBUG("auth=%I ", conf.auth+i*IPaddrlen);
			}
		}

		conf.lease = lease;
		conf.state = Sbound;
		DEBUG("server=%I sname=%s\n", conf.server, conf.sname);
		break;
	case Nak:
		conf.state = Sinit;
		fprint(2, "%s: recved dhcpnak on %s\n", argv0, conf.mpoint);
		break;
	}

}

int
openlisten()
{
	int fd, cfd;
	char data[128];
	char devdir[40];
	int n;

	if(validip(conf.laddr)
	&& (conf.state == Srenewing || conf.state == Srebinding))
		sprint(data, "%s/udp!%I!68", conf.mpoint, conf.laddr);
	else
		sprint(data, "%s/udp!*!68", conf.mpoint);
	for(n=0;;n++) {
		cfd = announce(data, devdir);
		if(cfd >= 0)
			break;
		if(!noconfig)
			sysfatal("can't announce for dhcp: %r");

		// might be another client - wait and try again
		fprint(2, "%s: can't announce: %r\n", argv0);
		sleep((nrand(10)+1)*1000);
		if(n > 10)
			return -1;
	}

	if(fprint(cfd, "headers") < 0)
		sysfatal("can't set header mode: %r");
	fprint(cfd, "oldheaders");

	sprint(data, "%s/data", devdir);

	fd = open(data, ORDWR);
	if(fd < 0)
		sysfatal("open udp data: %r");
	close(cfd);

	return fd;
}

uchar*
optadd(uchar *p, int op, void *d, int n)
{
	p[0] = op;
	p[1] = n;
	memmove(p+2, d, n);
	return p+n+2;
}

uchar*
optaddbyte(uchar *p, int op, int b)
{
	p[0] = op;
	p[1] = 1;
	p[2] = b;
	return p+3;
}

uchar*
optaddulong(uchar *p, int op, ulong x)
{
	p[0] = op;
	p[1] = 4;
	hnputl(p+2, x);
	return p+6;
}

uchar *
optaddaddr(uchar *p, int op, uchar *ip)
{
	p[0] = op;
	p[1] = 4;
	v6tov4(p+2, ip);
	return p+6;
}

uchar *
optaddvec(uchar *p, int op, uchar *v, int n)
{
	p[0] = op;
	p[1] = n;
	memmove(p+2, v, n);
	return p+2+n;
}

uchar *
optaddstr(uchar *p, int op, char *v)
{
	int n;

	n = strlen(v)+1;	// microsoft leaves on the null, so we do too
	p[0] = op;
	p[1] = n;
	memmove(p+2, v, n);
	return p+2+n;
}

uchar*
optget(uchar *p, int op, int *np)
{
	int len, code;

	for(;;) {
		code = *p++;
		if(code == OBpad)
			continue;
		if(code == OBend)
			return 0;
		len = *p++;
		if(code != op) {
			p += len;
			continue;
		}
		if(np != nil){
			if(*np > len)
				return 0;
			*np = len;
		}
		return p;
	}
}

int
optgetbyte(uchar *p, int op)
{
	int len;

	len = 1;
	p = optget(p, op, &len);
	if(p == 0)
		return 0;
	return *p;
}

ulong
optgetulong(uchar *p, int op)
{
	int len;

	len = 4;
	p = optget(p, op, &len);
	if(p == 0)
		return 0;
	return nhgetl(p);
}

int
optgetaddr(uchar *p, int op, uchar *ip)
{
	int len;

	len = 4;
	p = optget(p, op, &len);
	if(p == 0)
		return 0;
	v4tov6(ip, p);
	return 1;
}

int
optgetaddrs(uchar *p, int op, uchar *ip, int n)
{
	int len, i;

	len = 4;
	p = optget(p, op, &len);
	if(p == 0)
		return 0;
	len /= IPv4addrlen;
	if(len > n)
		len = n;
	for(i = 0; i < len; i++)
		v4tov6(&ip[i*IPaddrlen], &p[i*IPv4addrlen]);

	return i;
}

int
optgetvec(uchar *p, int op, uchar *v, int n)
{
	int len;

	len = 1;
	p = optget(p, op, &len);
	if(p == 0)
		return 0;
	if(len > n)
		len = n;
	memmove(v, p, len);

	return len;
}

int
optgetstr(uchar *p, int op, char *s, int n)
{
	int len;

	len = 1;
	p = optget(p, op, &len);
	if(p == 0)
		return 0;
	if(len >= n)
		len = n-1;
	memmove(s, p, len);
	s[len] = 0;

	return len;
}

// sanity check options area
//	- options don't overflow packet
//	- options end with an OBend
int
parseoptions(uchar *p, int n)
{
	int code, len;
	int nin = n;

	while(n>0) {
		code = *p++;
		n--;
		if(code == OBpad)
			continue;
		if(code == OBend)
			return 0;
		if(n == 0) {
			fprint(2, "%s: parse: bad option: 0x%ux: truncated: opt length = %d\n",
				argv0, code, nin);
			return -1;
		}
		len = *p++;
		n--;
		if(len > n) {
			fprint(2, "%s: parse: bad option: 0x%ux: %d > %d: opt length = %d\n",
				argv0, code, len, n, nin);
			return -1;
		}
		p += len;
		n -= len;		
	}

	// make sure packet ends with an OBend all the optget code
	*p = OBend;
	return 0;
}

//  sanity check received packet:
//	- magic is dhcp magic
//	- options don't overflow packet
Bootp *
parsebootp(uchar *p, int n)
{
	Bootp *bp;

	bp = (Bootp*)p;
	if(n < bp->optmagic - p) {
		fprint(2, "%s: parse: short bootp packet\n", argv0);
		return nil;
	}

	if(conf.xid != nhgetl(bp->xid))
		return nil;

	if(bp->op != Bootreply) {
		fprint(2, "%s: parse: bad op\n", argv0);
		return nil;
	}

	n -= bp->optmagic - p;
	p = bp->optmagic;

	if(n < 4) {
		fprint(2, "%s: parse: not option data\n", argv0);
		return nil;
	}
	if(memcmp(optmagic, p, 4) != 0) {
		fprint(2, "%s: parse: bad opt magic %ux %ux %ux %ux\n", argv0,
			p[0], p[1], p[2], p[3]);
		return nil;
	}
	p += 4;
	n -= 4;
	if(parseoptions(p, n) < 0)
		return nil;

	return bp;
}

// write out an ndb entry
void
writendb(char *s, int n, int append)
{
	char file[64];
	int fd;

	snprint(file, sizeof file, "%s/ndb", conf.mpoint);
	if(append){
		fd = open(file, OWRITE);
		seek(fd, 0, 2);
	} else
		fd = open(file, OWRITE|OTRUNC);
	write(fd, s, n);
	close(fd);
}

// put server addresses into the ndb entry
char*
putaddrs(char *p, char *e, char *attr, uchar *a, int len)
{
	int i;

	for(i = 0; i < len; i += IPaddrlen, a += IPaddrlen){
		if(!validip(a))
			break;
		p = seprint(p, e, "%s=%I\n", attr, a);
	}
	return p;
}

// make an ndb entry and put it into /net/ndb for the servers to see
void
putndb(void)
{
	char buf[1024];
	char *p, *e, *np;
	int append;

	e = buf + sizeof(buf);
	p = buf;
	if(getndb() == 0){
		append = 1;
	} else {
		append = 0;
		p = seprint(p, e, "ip=%I ipmask=%M ipgw=%I\n",
				conf.laddr, conf.mask, conf.gaddr);
	}
	if(np = strchr(conf.hostname, '.')){
		if(*conf.domainname == 0)
			strcpy(conf.domainname, np+1);
		*np = 0;
	}
	if(*conf.hostname)
		p = seprint(p, e, "\tsys=%s\n", conf.hostname);
	if(*conf.domainname)
		p = seprint(p, e, "\tdom=%s.%s\n", conf.hostname, conf.domainname);
	if(validip(conf.fs))
		p = putaddrs(p, e, "\tfs", conf.fs, sizeof(conf.fs));
	if(validip(conf.auth))
		p = putaddrs(p, e, "\tauth", conf.auth, sizeof(conf.auth));
	if(validip(conf.dns))
		p = putaddrs(p, e, "\tdns", conf.dns, sizeof(conf.dns));
	if(validip(conf.ntp))
		p = putaddrs(p, e, "\tntp", conf.ntp, sizeof(conf.ntp));
	if(ndboptions)
		p = seprint(p, e, "%s\n", ndboptions);
	if(p > buf)
		writendb(buf, p-buf, append);
}

// get an ndb entry someone else wrote
int
getndb(void)
{
	char buf[1024];
	int fd;
	int n;
	char *p;

	snprint(buf, sizeof buf, "%s/ndb", conf.mpoint);
	fd = open(buf, OREAD);
	n = read(fd, buf, sizeof(buf)-1);
	close(fd);
	if(n <= 0)
		return -1;
	buf[n] = 0;
	p = strstr(buf, "ip=");
	if(p == nil)
		return -1;
	parseip(conf.laddr, p+3);

	return 0;
}

// tell a server to refresh
void
tweakserver(char *server)
{
	char file[64];
	int fd;

	snprint(file, sizeof(file), "%s/%s", conf.mpoint, server);
	fd = open(file, ORDWR);
	if(fd < 0)
		return;
	fprint(fd, "refresh");
	close(fd);
}

// tell all servers to refresh their information
void
tweakservers(void)
{
	tweakserver("dns");
	tweakserver("cs");
}

// return number of networks
int
nipifcs(char *net)
{
	Ipifc *nifc;
	Iplifc *lifc;
	int n;

	n = 0;
	ifc = readipifc(net, ifc, -1);
	for(nifc = ifc; nifc != nil; nifc = nifc->next){
		/*
		 * ignore loopback devices when trying to
		 * figure out if we're the primary interface.
		 */
		if(strcmp(nifc->dev, "/dev/null") != 0)
		for(lifc = nifc->lifc; lifc != nil; lifc = lifc->next)
			if(validip(lifc->ip)){
				n++;
				break;
			}
		if(strcmp(nifc->dev, conf.dev) == 0)
			myifc = nifc->index;
	}
	return n;
}

// return true if this is a valid v4 address
int
validip(uchar *addr)
{
	return ipcmp(addr, IPnoaddr) != 0 && ipcmp(addr, v4prefix) != 0;
}

char *verbs[] = {
[Vadd]		"add",
[Vremove]	"remove",
[Vunbind]	"unbind",
[Vether]	"ether",
[Vloopback]	"loopback",
};

// look for an action
int
parseverb(char *name)
{
	int i;

	for(i = 0; i < nelem(verbs); i++){
		if(verbs[i] == 0)
			continue;
		if(strcmp(name, verbs[i]) == 0)
			return i;
	}
	return -1;
}

// get everything out of ndb
void
ndbconfig(void)
{
	Ndb *db;
	Ndbtuple *t, *nt;
	char etheraddr[32];
	char *attrs[10];
	int nattr;
	int ndns = 0;
	int nfs = 0;
	int nauth = 0;

	db = ndbopen(0);
	if(db == nil)
		sysfatal("can't open ndb: %r");
	if(strcmp(conf.type, "ether") != 0 || myetheraddr(conf.hwa, conf.dev) != 0)
		sysfatal("can't read hardware address");
	sprint(etheraddr, "%E", conf.hwa);
	nattr = 0;
	attrs[nattr++] = "ip";
	attrs[nattr++] = "ipmask";
	attrs[nattr++] = "ipgw";
	attrs[nattr++] = "@dns";
	attrs[nattr++] = "@ntp";
	attrs[nattr++] = "@fs";
	attrs[nattr++] = "@auth";
	attrs[nattr] = nil;
	t = ndbipinfo(db, "ether", etheraddr, attrs, nattr);
	for(nt = t; nt != nil; nt = nt->entry){
		if(strcmp(nt->attr, "ip") == 0){
			parseip(conf.laddr, nt->val);
		} else if(strcmp(nt->attr, "ipmask") == 0){
			parseipmask(conf.mask, nt->val);
		} else if(strcmp(nt->attr, "ipgw") == 0){
			parseip(conf.gaddr, nt->val);
		} else if(ndns < 2 && strcmp(nt->attr, "dns") == 0){
			parseip(conf.dns+IPaddrlen*ndns, nt->val);
		} else if(strcmp(nt->attr, "ntp") == 0){
			parseip(conf.ntp, nt->val);
		} else if(nfs < 2 && strcmp(nt->attr, "fs") == 0){
			parseip(conf.fs+IPaddrlen*nfs, nt->val);
		} else if(nauth < 2 && strcmp(nt->attr, "auth") == 0){
			parseip(conf.auth+IPaddrlen*nauth, nt->val);
		}
	}
	ndbfree(t);
	if(!validip(conf.laddr))
		sysfatal("address not found in ndb");
}

int
addoption(char *opt)
{
	Option *o;

	if(opt == nil)
		return -1;
	for(o = option; o < &option[nelem(option)]; o++){
		if(o->name == nil)
			continue;
		if(strcmp(opt, o->name) == 0){
			if(!memchr(requested, (int)opt, nrequested))
				if(nrequested < nelem(requested))
					requested[nrequested++] = o-option;
			return 0;
		}
	}
	return -1;
}

char*
optgetx(uchar *p, uchar opt)
{
	Option *o;
	int i, n;
	ulong x;
	char *s, *ns;
	uchar ip[IPaddrlen];
	uchar ips[16*IPaddrlen];
	char str[256];
	uchar vec[256];

	o = &option[opt];
	if(o->name == nil)
		return nil;

	s = nil;
	switch(o->type){
	case Taddr:
		if(optgetaddr(p, opt, ip))
			s = smprint("%s=%I", o->name, ip);
		break;
	case Taddrs:
		n = optgetaddrs(p, opt, ips, 16);
		if(n > 0)
			s = smprint("%s=%I", o->name, ips);
		for(i = 1; i < n; i++){
			ns = smprint("%s %s=%I", s, o->name, &ips[i*IPaddrlen]);
			free(s);
			s = ns;
		}
		break;
	case Tulong:
		x = optgetulong(p, opt);
		if(x != 0)
			s = smprint("%s=%lud", o->name, x);
		break;
	case Tbyte:
		x = optgetbyte(p, opt);
		if(x != 0)
			s = smprint("%s=%lud", o->name, x);
		break;
	case Tstr:
		if(optgetstr(p, opt, str, sizeof(str)))
			s = smprint("%s=%s", o->name, str);
		break;
	case Tvec:
		n = optgetvec(p, opt, vec, sizeof(vec));
		if(n > 0)
			s = smprint("%s=%.*H", o->name, n, vec);
		break;
	}
	return s;
}

void
getoptions(uchar *p)
{
	int i;
	char *s, *t;

	for(i = nelem(defrequested); i < nrequested; i++){
		s = optgetx(p, requested[i]);
		if(s != nil)
			DEBUG("%s ", s);
		if(ndboptions == nil)
			ndboptions = smprint("\t%s", s);
		else{
			t = ndboptions;
			ndboptions = smprint("\t%s%s", s, ndboptions);
			free(t);
		}
		free(s);
	}
}
