/*
 * ipconfig - configure parameters of an ip stack
 */
#include <u.h>
#include <lib9.h>
#include <ip.h>
#include <bio.h>
#include <ndb.h>
#include "../dhcp.h"
#include "ipconfig.h"

#define DEBUG if(debug)warning

/* possible verbs */
enum
{
	/* commands */
	Vadd,
	Vremove,
	Vunbind,
	Vaddpref6,
	Vra6,
	/* media */
	Vether,
	Vgbe,
	Vppp,
	Vloopback,
	Vtorus,
	Vtree,
	Vpkt,
};

enum
{
	Taddr,
	Taddrs,
	Tstr,
	Tbyte,
	Tuint32_t,
	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",		Tuint32_t },
[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",		Tuint32_t },
[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",	Tuint32_t },
[OBttl]			{ "ttl",		Tuint32_t },
[OBpathtimeout]		{ "pathtimeout",	Taddrs },
[OBpathplateau]		{ "pathplateau",	Taddrs },
[OBmtu]			{ "mtu",		Tuint32_t },
[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",		Tuint32_t },
[OBetherencap]		{ "etherencap",		Taddrs },
[OBtcpttl]		{ "tcpttl",		Tuint32_t },
[OBtcpka]		{ "tcpka",		Tuint32_t },
[OBtcpkag]		{ "tcpkag",		Tuint32_t },
[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",		Tuint32_t },
[ODoverload]		{ "overload",		Taddr },
[ODtype]		{ "type",		Tbyte },
[ODserverid]		{ "serverid",		Taddr },
[ODparams]		{ "params",		Tvec },
[ODmessage]		{ "message",		Tstr },
[ODmaxmsg]		{ "maxmsg",		Tuint32_t },
[ODrenewaltime]		{ "renewaltime",	Tuint32_t },
[ODrebindingtime]	{ "rebindingtime",	Tuint32_t },
[ODvendorclass]		{ "vendorclass",	Tvec },
[ODclientid]		{ "clientid",		Tvec },
[ODtftpserver]		{ "tftp",		Taddr },
[ODbootfile]		{ "bootfile",		Tstr },
};

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

uint8_t	requested[256];
int	nrequested;

int	Oflag;
int	beprimary = -1;
Conf	conf;
int	debug;
int	dodhcp;
int	dolog;
int	dondbconfig = 0;
int	dupl_disc = 1;		/* flag: V6 duplicate neighbor discovery */
Ctl	*firstctl, **ctll;
Ipifc	*ifc;
int	ipv6auto = 0;
int	myifc = -1;
char	*dbfile;
char	*ndboptions;
int	nip;
int	noconfig;
int	nodhcpwatch;
char 	optmagic[4] = { 0x63, 0x82, 0x53, 0x63 };
int	plan9 = 1;
int	sendhostname;

static char logfile[] = "ipconfig";

char *verbs[] = {
[Vadd]		"add",
[Vremove]	"remove",
[Vunbind]	"unbind",
[Vether]	"ether",
[Vgbe]		"gbe",
[Vppp]		"ppp",
[Vloopback]	"loopback",
[Vaddpref6]	"add6",
[Vra6]		"ra6",
[Vtorus]	"torus",
[Vtree]		"tree",
[Vpkt]		"pkt",
};

void	adddefroute(char*, uint8_t*);
int	addoption(char*);
void	binddevice(void);
void	bootprequest(void);
void	controldevice(void);
void	dhcpquery(int, int);
void	dhcprecv(void);
void	dhcpsend(int);
void	dhcptimer(void);
void	dhcpwatch(int);
void	doadd(int);
void	doremove(void);
void	dounbind(void);
void	getoptions(uint8_t*);
int	ip4cfg(void);
int	ip6cfg(int a);
void	lookforip(char*);
void	mkclientid(void);
void	ndbconfig(void);
int	nipifcs(char*);
int	openlisten(void);
uint8_t*	optaddaddr(uint8_t*, int, uint8_t*);
uint8_t*	optaddbyte(uint8_t*, int, int);
uint8_t*	optaddstr(uint8_t*, int, char*);
uint8_t*	optadd(uint8_t*, int, void*, int);
uint8_t*	optadduint32_t(uint8_t*, int, uint32_t);
uint8_t*	optaddvec(uint8_t*, int, uint8_t*, int);
int	optgetaddrs(uint8_t*, int, uint8_t*, int);
int	optgetp9addrs(uint8_t*, int, uint8_t*, int);
int	optgetaddr(uint8_t*, int, uint8_t*);
int	optgetbyte(uint8_t*, int);
int	optgetstr(uint8_t*, int, char*, int);
uint8_t*	optget(uint8_t*, int, int*);
uint32_t	optgetuint32_t(uint8_t*, int);
int	optgetvec(uint8_t*, int, uint8_t*, int);
char*	optgetx(uint8_t*, uint8_t);
Bootp*	parsebootp(uint8_t*, int);
int	parseoptions(uint8_t *p, int n);
int	parseverb(char*);
void	pppbinddev(void);
void	putndb(void);
void	usage(void);
int	validip(uint8_t*);

void
usage(void)
{
	fprint(2, "usage: %s [-6dDGnNOpPruX][-b baud][-c ctl]* [-g gw]"
		"[-h host][-m mtu]\n"
		"\t[-f dbfile][-x mtpt][-o dhcpopt] type dev [verb] [laddr [mask "
		"[raddr [fs [auth]]]]]\n", argv0);
	exits("usage");
}

void
warning(char *fmt, ...)
{
	char buf[1024];
	va_list arg;

	va_start(arg, fmt);
	vseprint(buf, buf + sizeof buf, fmt, arg);
	va_end(arg);
	if (dolog)
		syslog(0, logfile, "%s", buf);
	else
		fprint(2, "%s: %s\n", argv0, buf);
}

static void
parsenorm(int argc, char **argv)
{
	switch(argc){
	case 5:
		 if (parseip(conf.auth, argv[4]) == -1)
			usage();
		/* fall through */
	case 4:
		 if (parseip(conf.fs, argv[3]) == -1)
			usage();
		/* fall through */
	case 3:
		 if (parseip(conf.raddr, argv[2]) == -1)
			usage();
		/* fall through */
	case 2:
		/*
		 * can't test for parseipmask()==-1 cuz 255.255.255.255
		 * looks like that.
		 */
		if (strcmp(argv[1], "0") != 0)
			parseipmask(conf.mask, argv[1]);
		/* fall through */
	case 1:
		 if (parseip(conf.laddr, argv[0]) == -1)
			usage();
		/* fall through */
	case 0:
		break;
	default:
		usage();
	}
}

static void
parse6pref(int argc, char **argv)
{
	switch(argc){
	case 6:
		conf.preflt = strtoul(argv[5], 0, 10);
		/* fall through */
	case 5:
		conf.validlt = strtoul(argv[4], 0, 10);
		/* fall through */
	case 4:
		conf.autoflag = (atoi(argv[3]) != 0);
		/* fall through */
	case 3:
		conf.onlink = (atoi(argv[2]) != 0);
		/* fall through */
	case 2:
		conf.prefixlen = atoi(argv[1]);
		/* fall through */
	case 1:
		if (parseip(conf.v6pref, argv[0]) == -1)
			sysfatal("bad address %s", argv[0]);
		break;
	}
	DEBUG("parse6pref: pref %I len %d", conf.v6pref, conf.prefixlen);
}

/* parse router advertisement (keyword, value) pairs */
static void
parse6ra(int argc, char **argv)
{
	int i, argsleft;
	char *kw, *val;

	if (argc % 2 != 0)
		usage();

	i = 0;
	for (argsleft = argc; argsleft > 1; argsleft -= 2) {
		kw =  argv[i];
		val = argv[i+1];
		if (strcmp(kw, "recvra") == 0)
			conf.recvra = (atoi(val) != 0);
		else if (strcmp(kw, "sendra") == 0)
			conf.sendra = (atoi(val) != 0);
		else if (strcmp(kw, "mflag") == 0)
			conf.mflag = (atoi(val) != 0);
		else if (strcmp(kw, "oflag") == 0)
			conf.oflag = (atoi(val) != 0);
		else if (strcmp(kw, "maxraint") == 0)
			conf.maxraint = atoi(val);
		else if (strcmp(kw, "minraint") == 0)
			conf.minraint = atoi(val);
		else if (strcmp(kw, "linkmtu") == 0)
			conf.linkmtu = atoi(val);
		else if (strcmp(kw, "reachtime") == 0)
			conf.reachtime = atoi(val);
		else if (strcmp(kw, "rxmitra") == 0)
			conf.rxmitra = atoi(val);
		else if (strcmp(kw, "ttl") == 0)
			conf.ttl = atoi(val);
		else if (strcmp(kw, "routerlt") == 0)
			conf.routerlt = atoi(val);
		else {
			warning("bad ra6 keyword %s", kw);
			usage();
		}
		i += 2;
	}

	/* consistency check */
	if (conf.maxraint < conf.minraint)
		sysfatal("maxraint %d < minraint %d",
			conf.maxraint, conf.minraint);
}

static char*
finddev(char *dir, char *name, char *dev)
{
	int fd, i, nd;
	Dir *d;

	fd = open(dir, OREAD);
	if(fd >= 0){
		d = nil;
		nd = dirreadall(fd, &d);
		close(fd);
		for(i=0; i<nd; i++){
			if(strncmp(d[i].name, name, strlen(name)))
				continue;
			if(strstr(d[i].name, "ctl") != nil)
				continue;	/* ignore ctl files */
			dev = smprint("%s/%s", dir, d[i].name);
			break;
		}
		free(d);
	}
	return dev;
}

static void
init(void)
{
	srand(truerand());
	fmtinstall('E', eipfmt);
	fmtinstall('I', eipfmt);
	fmtinstall('M', eipfmt);
	fmtinstall('V', eipfmt);
 	nsec();			/* make sure time file is open before forking */

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

	ctll = &firstctl;
	v6paraminit(&conf);

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

static int
parseargs(int argc, char **argv)
{
	char *p;
	int action, verb;

	/* 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);
	}

	/* defaults */
	conf.type = "ether";
	conf.dev = nil;
	action = Vadd;

	/* get optional medium and device */
	if (argc > 0){
		verb = parseverb(*argv);
		switch(verb){
		case Vether:
		case Vgbe:
		case Vppp:
		case Vloopback:
		case Vtorus:
		case Vtree:
		case Vpkt:
			conf.type = *argv++;
			argc--;
			if(argc > 0){
				conf.dev = *argv++;
				argc--;
			} else if(verb == Vppp)
				conf.dev = finddev("/dev", "eia", "/dev/eia0");
			break;
		}
	}
	if(conf.dev == nil)
		conf.dev = finddev(conf.mpoint, "ether", "/net/ether0");

	/* get optional verb */
	if (argc > 0){
		verb = parseverb(*argv);
		switch(verb){
		case Vether:
		case Vgbe:
		case Vppp:
		case Vloopback:
		case Vtorus:
		case Vtree:
		case Vpkt:
			sysfatal("medium %s already specified", conf.type);
		case Vadd:
		case Vremove:
		case Vunbind:
		case Vaddpref6:
		case Vra6:
			argv++;
			argc--;
			action = verb;
			break;
		}
	}

	/* get verb-dependent arguments */
	switch (action) {
	case Vadd:
	case Vremove:
	case Vunbind:
		parsenorm(argc, argv);
		break;
	case Vaddpref6:
		parse6pref(argc, argv);
		break;
	case Vra6:
		parse6ra(argc, argv);
		break;
	}
	return action;
}

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

	init();
	retry = 0;
	ARGBEGIN {
	case '6': 			/* IPv6 auto config */
		ipv6auto = 1;
		break;
	case 'b':
		conf.baud = EARGF(usage());
		break;
	case 'c':
		cp = malloc(sizeof *cp);
		if(cp == nil)
			sysfatal("%r");
		*ctll = cp;
		ctll = &cp->next;
		cp->next = nil;
		cp->ctl = EARGF(usage());
		break;
	case 'd':
		dodhcp = 1;
		break;
	case 'D':
		debug = 1;
		break;
	case 'f':
		dbfile = EARGF(usage());
		break;
	case 'g':
		if (parseip(conf.gaddr, EARGF(usage())) == -1)
			usage();
		break;
	case 'G':
		plan9 = 0;
		break;
	case 'h':
		snprint(conf.hostname, sizeof conf.hostname, "%s",
			EARGF(usage()));
		sendhostname = 1;
		break;
	case 'm':
		conf.mtu = atoi(EARGF(usage()));
		break;
	case 'n':
		noconfig = 1;
		break;
	case 'N':
		dondbconfig = 1;
		break;
	case 'o':
		if(addoption(EARGF(usage())) < 0)
			usage();
		break;
	case 'O':
		Oflag = 1;
		break;
	case 'p':
		beprimary = 1;
		break;
	case 'P':
		beprimary = 0;
		break;
	case 'r':
		retry = 1;
		break;
	case 'u':		/* IPv6: duplicate neighbour disc. off */
		dupl_disc = 0;
		break;
	case 'x':
		setnetmtpt(conf.mpoint, sizeof conf.mpoint, EARGF(usage()));
		break;
	case 'X':
		nodhcpwatch = 1;
		break;
	default:
		usage();
	} ARGEND;
	argv0 = "ipconfig";		/* boot invokes us as tcp? */

	action = parseargs(argc, argv);
	switch(action){
	case Vadd:
		doadd(retry);
		break;
	case Vremove:
		doremove();
		break;
	case Vunbind:
		dounbind();
		break;
	case Vaddpref6:
	case Vra6:
		doipv6(action);
		break;
	}
	exits(0);
}

void
doadd(int retry)
{
	int ppp;

	ppp = strcmp(conf.type, "ppp") == 0;

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

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

	if (ipv6auto && !ppp) {
		if (ip6cfg(ipv6auto) < 0)
			sysfatal("can't automatically start IPv6 on %s",
				conf.dev);
	} else if (validip(conf.laddr) && !isv4(conf.laddr)) {
		if (ip6cfg(0) < 0)
			sysfatal("can't start IPv6 on %s, address %I",
				conf.dev, conf.laddr);
	}

	if(!validip(conf.laddr) && !ppp)
		if(dondbconfig)
			ndbconfig();
		else
			dodhcp = 1;

	/* run dhcp if we need something */
	if(dodhcp){
		mkclientid();
		dhcpquery(!noconfig, Sselecting);
	}

	if(!validip(conf.laddr))
		if(retry && dodhcp && !noconfig){
			warning("couldn't determine ip address, retrying");
			dhcpwatch(1);
			return;
		} else
			sysfatal("no success with DHCP");

	if(noconfig)
		return;

	if(ip4cfg() < 0)
		sysfatal("can't start ip");
	else if(dodhcp && conf.lease != Lforever)
		dhcpwatch(0);

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

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

	if(!validip(conf.laddr))
		sysfatal("remove requires an address");
	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){
				warning("can't open %s: %r", conf.mpoint);
				continue;
			}
			if(fprint(cfd, "remove %I %M", lifc->ip, lifc->mask) < 0)
				warning("can't remove %I %M from %s: %r",
					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){
				warning("can't open %s: %r", conf.mpoint);
				break;
			}
			if(fprint(cfd, "unbind") < 0)
				warning("can't unbind from %s: %r", file);
			break;
		}
	}
}

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

	sprint(buf, "%s/iproute", mpoint);
	cfd = open(buf, ORDWR);
	if(cfd < 0)
		return;

	if(isv4(gaddr))
		fprint(cfd, "add 0 0 %I", gaddr);
	else
		fprint(cfd, "add :: /0 %I", gaddr);
	close(cfd);
}

/* create a client id */
void
mkclientid(void)
{
	if(strcmp(conf.type, "ether") == 0 || strcmp(conf.type, "gbe") == 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, AEXIST) == 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 ||
	    strcmp(conf.type, "ether") != 0 && strcmp(conf.type, "gbe") != 0)
		return;

	snprint(ctlfile, sizeof ctlfile, "%s/clone", conf.dev);
	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);
	}
//	close(fd);		/* or does it need to be left hanging? */
}

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

	if(strcmp(conf.type, "ppp") == 0)
		pppbinddev();
	else 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 medium as ethernet, bind the interface to it */
		if(fprint(conf.cfd, "bind %s %s", conf.type, conf.dev) < 0)
			sysfatal("%s: bind %s %s: %r", buf, conf.type, conf.dev);
	} 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("open %s: %r", buf);
	}

}

/* add a logical interface to the ip stack */
int
ip4cfg(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){
		warning("write(%s): %r", 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", 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);
}

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);

	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 && conf.state != Sinit){
		dhcprecv();
		dhcptimer();
	}
	close(conf.fd);

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

}

enum {
	/*
	 * was an hour, needs to be less for the ARM/GS1 until the timer
	 * code has been cleaned up (pb).
	 */
	Maxsleep = 450,
};

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

	if(nodhcpwatch)
		return;

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

	dolog = 1;			/* log, don't print */
	procsetname("dhcpwatch");
	/* keep trying to renew the lease */
	for(;;){
		secs = conf.lease/2;
		if(secs < 5)
			secs = 5;

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

		if(conf.lease > 0){
			/*
			 * during boot, the starttime can be bogus so avoid
			 * spurious ipunconfig'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(ip4cfg() < 0)
				sysfatal("can't start ip: %r");
			needconfig = 0;
			/*
			 * leave everything we've learned somewhere that
			 * other procs can find it.
			 */
			if(beprimary)
				putndb();
		}
	}
}

void
dhcptimer(void)
{
	uint32_t now;

	now = time(0);
	if(now < conf.timeout)
		return;

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

void
dhcpsend(int type)
{
	Bootp bp;
	uint8_t *p;
	int n;
	uint8_t vendor[64];
	Udphdr *up = (Udphdr*)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 = optadduint32_t(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 - (uint8_t*)&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)
		warning("dhcpsend: write failed: %r");
}

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

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

	if(n < 0){
		rerrstr(err, sizeof err);
		if(strstr(err, "interrupt") == nil)
			warning("dhcprecv: bad read: %s", err);
		else
			DEBUG("dhcprecv: read timed out");
		return;
	}

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

	type = optgetbyte(bp->optdata, ODtype);
	switch(type) {
	default:
		warning("dhcprecv: unknown type: %d", type);
		break;
	case Offer:
		DEBUG("got offer from %V ", bp->siaddr);
		if(conf.state != Sselecting){
//			DEBUG("");
			break;
		}
		lease = optgetuint32_t(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.
			 */
			warning("Offer with %lud lease, using %d", lease, MinLease);
			lease = MinLease;
		}
		DEBUG("lease=%lud ", lease);
		if(!optgetaddr(bp->optdata, ODserverid, conf.server)) {
			warning("Offer from server with invalid serverid");
			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", 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 && conf.state != Srenewing &&
		    conf.state != Srebinding)
			break;

		/* ignore a bad lease */
		lease = optgetuint32_t(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.
			 */
			warning("Ack with %lud lease, using %d", lease, MinLease);
			lease = MinLease;
		}
		DEBUG("lease=%lud ", lease);

		/* address and mask */
		if(!validip(conf.laddr) || !Oflag)
			v4tov6(conf.laddr, bp->yiaddr);
		if(!validip(conf.mask) || !Oflag){
			if(!optgetaddr(bp->optdata, OBmask, conf.mask))
				ipmove(conf.mask, IPnoaddr);
			if(ipcmp(conf.mask, IPv4bcast) == 0)
				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(validip(conf.gaddr) && Oflag) {
			DEBUG("ipgw=%I ", conf.gaddr);
		} else 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 && parseoptions(vopts, n) == 0){
			if(validip(conf.fs) && Oflag)
				n = 1;
			else {
				n = optgetp9addrs(vopts, OP9fs, conf.fs, 2);
				if (n == 0)
					n = optgetaddrs(vopts, OP9fsv4,
						conf.fs, 2);
			}
			for(i = 0; i < n; i++)
				DEBUG("fs=%I ", conf.fs + i*IPaddrlen);

			if(validip(conf.auth) && Oflag)
				n = 1;
			else {
				n = optgetp9addrs(vopts, OP9auth, conf.auth, 2);
				if (n == 0)
					n = optgetaddrs(vopts, OP9authv4,
						conf.auth, 2);
			}
			for(i = 0; i < n; i++)
				DEBUG("auth=%I ", conf.auth + i*IPaddrlen);

			n = optgetp9addrs(vopts, OP9ipaddr, taddr, 1);
			if (n > 0)
				memmove(conf.laddr, taddr, IPaddrlen);
			n = optgetp9addrs(vopts, OP9ipmask, taddr, 1);
			if (n > 0)
				memmove(conf.mask, taddr, IPaddrlen);
			n = optgetp9addrs(vopts, OP9ipgw, taddr, 1);
			if (n > 0)
				memmove(conf.gaddr, taddr, IPaddrlen);
			DEBUG("new ipaddr=%I new ipmask=%M new ipgw=%I",
				conf.laddr, conf.mask, conf.gaddr);
		}
		conf.lease = lease;
		conf.state = Sbound;
		DEBUG("server=%I sname=%s", conf.server, conf.sname);
		break;
	case Nak:
		conf.state = Sinit;
		warning("recved dhcpnak on %s", conf.mpoint);
		break;
	}
}

/* return pseudo-random integer in range low...(hi-1) */
uint32_t
randint(uint32_t low, uint32_t hi)
{
	if (hi < low)
		return low;
	return low + nrand(hi - low);
}

int32_t
jitter(void)		/* compute small pseudo-random delay in ms */
{
	return randint(0, 10*1000);
}

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

	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; (cfd = announce(data, devdir)) < 0; n++) {
		if(!noconfig)
			sysfatal("can't announce for dhcp: %r");

		/* might be another client - wait and try again */
		warning("can't announce %s: %r", data);
		sleep(jitter());
		if(n > 10)
			return -1;
	}

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

	sprint(data, "%s/data", devdir);
	fd = open(data, ORDWR);
	if(fd < 0)
		sysfatal("open %s: %r", data);
	close(cfd);
	return fd;
}

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

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

uint8_t*
optadduint32_t(uint8_t *p, int op, uint32_t x)
{
	p[0] = op;
	p[1] = 4;
	hnputl(p+2, x);
	return p+6;
}

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

/* add dhcp option op with value v of length n to dhcp option array p */
uint8_t *
optaddvec(uint8_t *p, int op, uint8_t *v, int n)
{
	p[0] = op;
	p[1] = n;
	memmove(p+2, v, n);
	return p+2+n;
}

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

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

/*
 * parse p, looking for option `op'.  if non-nil, np points to minimum length.
 * return nil if option is too small, else ptr to opt, and
 * store actual length via np if non-nil.
 */
uint8_t*
optget(uint8_t *p, int op, int *np)
{
	int len, code;

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

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

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

uint32_t
optgetuint32_t(uint8_t *p, int op)
{
	int len;

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

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

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

/* expect at most n addresses; ip[] only has room for that many */
int
optgetaddrs(uint8_t *p, int op, uint8_t *ip, int n)
{
	int len, i;

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

/* expect at most n addresses; ip[] only has room for that many */
int
optgetp9addrs(uint8_t *ap, int op, uint8_t *ip, int n)
{
	int len, i, slen, addrs;
	char *p;

	len = 1;			/* minimum bytes needed */
	p = (char *)optget(ap, op, &len);
	if(p == nil)
		return 0;
	addrs = *p++;			/* first byte is address count */
	for (i = 0; i < n  && i < addrs && len > 0; i++) {
		slen = strlen(p) + 1;
		if (parseip(&ip[i*IPaddrlen], p) == -1)
			fprint(2, "%s: bad address %s\n", argv0, p);
		DEBUG("got plan 9 option %d addr %I (%s)",
			op, &ip[i*IPaddrlen], p);
		p += slen;
		len -= slen;
	}
	return addrs;
}

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

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

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

	len = 1;
	p = optget(p, op, &len);
	if(p == nil)
		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(uint8_t *p, int n)
{
	int code, len, nin = n;

	while (n > 0) {
		code = *p++;
		n--;
		if(code == OBend)
			return 0;
		if(code == OBpad)
			continue;
		if(n == 0) {
			warning("parseoptions: bad option: 0x%ux: truncated: "
				"opt length = %d", code, nin);
			return -1;
		}

		len = *p++;
		n--;
		DEBUG("parseoptions: %s(%d) len %d, bytes left %d",
			option[code].name, code, len, n);
		if(len > n) {
			warning("parseoptions: bad option: 0x%ux: %d > %d: "
				"opt length = %d", code, len, n, nin);
			return -1;
		}
		p += len;
		n -= len;
	}

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

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

	bp = (Bootp*)p;
	if(n < bp->optmagic - p) {
		warning("parsebootp: short bootp packet");
		return nil;
	}

	if(conf.xid != nhgetl(bp->xid))		/* not meant for us */
		return nil;

	if(bp->op != Bootreply) {
		warning("parsebootp: bad op %d", bp->op);
		return nil;
	}

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

	if(n < 4) {
		warning("parsebootp: no option data");
		return nil;
	}
	if(memcmp(optmagic, p, 4) != 0) {
		warning("parsebootp: bad opt magic %ux %ux %ux %ux",
			p[0], p[1], p[2], p[3]);
		return nil;
	}
	p += 4;
	n -= 4;
	DEBUG("parsebootp: new packet");
	if(parseoptions(p, n) < 0)
		return nil;
	return bp;
}

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

	for(i = 0; i < len && validip(a); i += IPaddrlen, a += IPaddrlen)
		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)
{
	static char buf[16*1024];
	char file[64], *p, *e, *np;
	Ndbtuple *t, *nt;
	Ndb *db;
	int fd;

	p = buf;
	e = buf + sizeof buf;
	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);

	/* append preexisting entries not matching our ip */
	snprint(file, sizeof file, "%s/ndb", conf.mpoint);
	db = ndbopen(file);
	if(db != nil ){
		while((t = ndbparse(db)) != nil){
			uint8_t ip[IPaddrlen];

			if((nt = ndbfindattr(t, t, "ip")) == nil
			|| parseip(ip, nt->val) < 0 || ipcmp(ip, conf.laddr) != 0){
				p = seprint(p, e, "\n");
				for(nt = t; nt != nil; nt = nt->entry)
					p = seprint(p, e, "%s=%s%s", nt->attr, nt->val,
						nt->entry==nil? "\n": nt->line!=nt->entry? "\n\t": " ");
			}
			ndbfree(t);
		}
		ndbclose(db);
	}

	if((fd = open(file, OWRITE|OTRUNC)) < 0)
		return;
	write(fd, buf, p-buf);
	close(fd);

	snprint(file, sizeof file, "%s/cs", conf.mpoint);
	if((fd = open(file, OWRITE)) >= 0){
		write(fd, "refresh", 7);
		close(fd);
	}
	snprint(file, sizeof file, "%s/dns", conf.mpoint);
	if((fd = open(file, OWRITE)) >= 0){
		write(fd, "refresh", 7);
		close(fd);
	}
}

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

	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(uint8_t *addr)
{
	return ipcmp(addr, IPnoaddr) != 0 && ipcmp(addr, v4prefix) != 0;
}

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

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

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

	db = ndbopen(dbfile);
	if(db == nil)
		sysfatal("can't open ndb: %r");
	if (strcmp(conf.type, "ether") != 0 && strcmp(conf.type, "gbe") != 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";
	/* the @ triggers resolution to an IP address; see ndb(2) */
	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) {
		ok = 1;
		if(strcmp(nt->attr, "ip") == 0)
			ok = parseip(conf.laddr, nt->val);
		else if(strcmp(nt->attr, "ipmask") == 0)
			parseipmask(conf.mask, nt->val);  /* could be -1 */
		else if(strcmp(nt->attr, "ipgw") == 0)
			ok = parseip(conf.gaddr, nt->val);
		else if(ndns < 2 && strcmp(nt->attr, "dns") == 0)
			ok = parseip(conf.dns+IPaddrlen*ndns, nt->val);
		else if(strcmp(nt->attr, "ntp") == 0)
			ok = parseip(conf.ntp, nt->val);
		else if(nfs < 2 && strcmp(nt->attr, "fs") == 0)
			ok = parseip(conf.fs+IPaddrlen*nfs, nt->val);
		else if(nauth < 2 && strcmp(nt->attr, "auth") == 0)
			ok = parseip(conf.auth+IPaddrlen*nauth, nt->val);
		if (!ok)
			fprint(2, "%s: bad %s address in ndb: %s\n", argv0,
				nt->attr, nt->val);
	}
	ndbfree(t);
	if(!validip(conf.laddr))
		sysfatal("address not found in ndb");
}

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

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

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

	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 Tuint32_t:
		x = optgetuint32_t(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)
			/* what's %H?  it's not installed */
			s = smprint("%s=%.*H", o->name, n, vec);
		break;
	}
	return s;
}

void
getoptions(uint8_t *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);
	}
}
