shithub: riscv

Download patch

ref: 8f91d94d19af3531d3f1afd4361f807c76f42cff
parent: 69298c79ab3da8164f9b5317be4ae1b2a75ab4dd
parent: 190c40c9fff3eafd23ab89cb445d4f85d29963b0
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Tue May 1 19:40:00 EDT 2018

merge

--- a/sys/include/ip.h
+++ b/sys/include/ip.h
@@ -88,8 +88,8 @@
 	V6nd_home	= 8,
 	V6nd_srcaddrs	= 9,		/* rfc3122 */
 	V6nd_ip		= 17,
-	/* /lib/rfc/drafts/draft-jeong-dnsop-ipv6-dns-discovery-12.txt */
-	V6nd_rdns	= 25,
+	V6nd_rdns	= 25,		/* rfc6106 */
+	V6nd_rdnssl	= 31,
 	/* plan 9 extensions */
 	V6nd_9fs	= 250,
 	V6nd_9auth	= 251,
--- /dev/null
+++ b/sys/src/cmd/ip/ipconfig/dhcp.c
@@ -1,0 +1,979 @@
+/*
+ * ipconfig - configure parameters of an ip stack
+ */
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <ip.h>
+#include <ndb.h>
+#include "ipconfig.h"
+#include "../dhcp.h"
+
+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
+ */
+static 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 },
+};
+
+static uchar defrequested[] = {
+	OBmask, OBrouter, OBdnserver, OBhostname, OBdomainname, OBntpserver,
+};
+
+static uchar	requested[256];
+static int	nrequested;
+
+static char 	optmagic[4] = { 0x63, 0x82, 0x53, 0x63 };
+
+static int	openlisten(void);
+
+static void	dhcprecv(void);
+static void	dhcpsend(int);
+static void	dhcptimer(void);
+
+static uchar*	optaddaddr(uchar*, int, uchar*);
+static uchar*	optaddbyte(uchar*, int, int);
+static uchar*	optaddstr(uchar*, int, char*);
+static uchar*	optadd(uchar*, int, void*, int);
+static uchar*	optaddulong(uchar*, int, ulong);
+static uchar*	optaddvec(uchar*, int, uchar*, int);
+static int	optgetaddrs(uchar*, int, uchar*, int);
+static int	optgetp9addrs(uchar*, int, uchar*, int);
+static int	optgetaddr(uchar*, int, uchar*);
+static int	optgetbyte(uchar*, int);
+static int	optgetstr(uchar*, int, char*, int);
+static uchar*	optget(uchar*, int, int*);
+static ulong	optgetulong(uchar*, int);
+static int	optgetvec(uchar*, int, uchar*, int);
+static char*	optgetx(uchar*, uchar);
+
+static void	getoptions(uchar*);
+static int	parseoptions(uchar *p, int n);
+static Bootp*	parsebootp(uchar*, int);
+
+void
+dhcpinit(void)
+{
+	/* init set of requested dhcp parameters with the default */
+	nrequested = sizeof defrequested;
+	memcpy(requested, defrequested, nrequested);
+}
+
+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(catch);
+
+	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)
+{
+	ulong 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 on %s", conf.dev);
+	/* 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();
+			refresh();
+		}
+	}
+}
+
+static void
+dhcptimer(void)
+{
+	ulong 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;
+	}
+}
+
+static void
+dhcpsend(int type)
+{
+	Bootp bp;
+	uchar *p;
+	int n;
+	uchar 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 = 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)
+		warning("dhcpsend: write failed: %r");
+}
+
+static void
+dhcprecv(void)
+{
+	int i, n, type;
+	ulong lease;
+	char err[ERRMAX];
+	uchar 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)
+			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.
+			 */
+			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 = 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.
+			 */
+			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;
+	}
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static uchar*
+optaddbyte(uchar *p, int op, int b)
+{
+	p[0] = op;
+	p[1] = 1;
+	p[2] = b;
+	return p+3;
+}
+
+static uchar*
+optaddulong(uchar *p, int op, ulong x)
+{
+	p[0] = op;
+	p[1] = 4;
+	hnputl(p+2, x);
+	return p+6;
+}
+
+static uchar *
+optaddaddr(uchar *p, int op, uchar *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 */
+static 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;
+}
+
+static uchar *
+optaddstr(uchar *p, int op, char *v)
+{
+	int n;
+
+	n = strlen(v);
+	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.
+ */
+static uchar*
+optget(uchar *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;
+}
+
+static int
+optgetbyte(uchar *p, int op)
+{
+	int len;
+
+	len = 1;
+	p = optget(p, op, &len);
+	if(p == nil)
+		return 0;
+	return *p;
+}
+
+static ulong
+optgetulong(uchar *p, int op)
+{
+	int len;
+
+	len = 4;
+	p = optget(p, op, &len);
+	if(p == nil)
+		return 0;
+	return nhgetl(p);
+}
+
+static int
+optgetaddr(uchar *p, int op, uchar *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 */
+static int
+optgetaddrs(uchar *p, int op, uchar *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 */
+static int
+optgetp9addrs(uchar *ap, int op, uchar *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;
+}
+
+static int
+optgetvec(uchar *p, int op, uchar *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;
+}
+
+static int
+optgetstr(uchar *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;
+}
+
+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;
+}
+
+static char*
+optgetx(uchar *p, uchar opt)
+{
+	int i, n;
+	ulong x;
+	char *s, *ns;
+	char str[256];
+	uchar 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 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)
+			/* what's %H?  it's not installed */
+			s = smprint("%s=%.*H", o->name, n, vec);
+		break;
+	}
+	return s;
+}
+
+static 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);
+	}
+}
+
+/*
+ * sanity check options area
+ * 	- options don't overflow packet
+ * 	- options end with an OBend
+ */
+static int
+parseoptions(uchar *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
+ */
+static Bootp*
+parsebootp(uchar *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;
+}
+
--- a/sys/src/cmd/ip/ipconfig/ipconfig.h
+++ b/sys/src/cmd/ip/ipconfig/ipconfig.h
@@ -1,3 +1,23 @@
+/* possible verbs */
+enum
+{
+	/* commands */
+	Vadd,
+	Vremove,
+	Vunbind,
+	Vaddpref6,
+	Vra6,
+
+	/* media */
+	Vether,
+	Vgbe,
+	Vppp,
+	Vloopback,
+	Vtorus,
+	Vtree,
+	Vpkt,
+};
+
 typedef struct Conf Conf;
 typedef struct Ctl Ctl;
 
@@ -22,7 +42,7 @@
 	uchar	laddr[IPaddrlen];
 	uchar	mask[IPaddrlen];
 	uchar	raddr[IPaddrlen];
-	uchar	dns[2*IPaddrlen];
+	uchar	dns[8*IPaddrlen];
 	uchar	fs[2*IPaddrlen];
 	uchar	auth[2*IPaddrlen];
 	uchar	ntp[2*IPaddrlen];
@@ -60,6 +80,7 @@
 	int	ttl;		/* default 0 (unspecified) */
 
 	/* prefix related */
+	uchar	lladdr[IPaddrlen];
 	uchar	v6pref[IPaddrlen];
 	int	prefixlen;
 	uchar	onlink;		/* flag: address is `on-link' */
@@ -66,6 +87,8 @@
 	uchar	autoflag;	/* flag: autonomous */
 	ulong	validlt;	/* valid lifetime (seconds) */
 	ulong	preflt;		/* preferred lifetime (seconds) */
+
+	char	dnsdomain[256];
 };
 
 struct Ctl
@@ -74,152 +97,60 @@
 	char	*ctl;
 };
 
-extern Ctl *firstctl, **ctll;
-
-extern Conf conf;
-
+extern Conf	conf;
+extern int	myifc;
+extern int	beprimary;
 extern int	noconfig;
-extern int	ipv6auto;
+
 extern int	debug;
-extern int	dodhcp;
 extern int	dolog;
+
 extern int	plan9;
+extern int	Oflag;
+
 extern int	dupl_disc;
 
-extern Conf	conf;
-extern int	myifc;
-extern char	*vs;
+extern int	nodhcpwatch;
+extern int	sendhostname;
+extern char	*ndboptions;
 
+void	usage(void);
+int	ip4cfg(void);
+void	ipunconfig(void);
+
 void	adddefroute(uchar*, uchar*, uchar*, uchar*);
 void	removedefroute(int, uchar*, uchar*);
 
-void	doadd(int);
-void	doremove(void);
-void	dounbind(void);
-int	isether(void);
 long	jitter(void);
-void	mklladdr(void);
 void	procsetname(char *fmt, ...);
+void	catch(void*, char*);
+int	countaddrs(uchar *a, int len);
+void	addaddrs(uchar *to, int nto, uchar *from, int nfrom);
+void	addnames(char *d, char *s, int len);
+Ndb*	opendatabase(void);
+void	ndb2conf(Ndb *db, uchar *ip);
+void	putndb(void);
 void	refresh(void);
 ulong	randint(ulong low, ulong hi);
 int	validip(uchar*);
 void	warning(char *fmt, ...);
+#define DEBUG if(debug)warning
 
 /*
- * IPv6
+ * DHCP
  */
+void	dhcpinit(void);
+void	dhcpquery(int, int);
+void	dhcpwatch(int);
+int	addoption(char*);
 
-void	doipv6(int);
+/*
+ * IPv6
+ */
 void	v6paraminit(Conf*);
-
-typedef struct Headers Headers;
-typedef struct Ip4hdr  Ip4hdr;
-typedef struct Lladdropt Lladdropt;
-typedef struct Mtuopt Mtuopt;
-typedef struct Prefixopt Prefixopt;
-typedef struct Routeradv Routeradv;
-typedef struct Routersol Routersol;
-
-enum {
-	IsRouter 	= 1,
-	IsHostRecv	= 2,
-	IsHostNoRecv	= 3,
-
-	MAClen		= 6,
-
-	IPv4		= 4,
-	IPv6		= 6,
-	Defmtu		= 1400,
-
-	IP_HOPBYHOP	= 0,
-	ICMPv4		= 1,
-	IP_IGMPPROTO	= 2,
-	IP_TCPPROTO	= 6,
-	IP_UDPPROTO	= 17,
-	IP_ILPROTO	= 40,
-	IP_v6ROUTE	= 43,
-	IP_v6FRAG	= 44,
-	IP_IPsecESP	= 50,
-	IP_IPsecAH	= 51,
-	IP_v6NOMORE	= 59,
-	ICMP6_RS	= 133,
-	ICMP6_RA	= 134,
-
-	IP_IN_IP	= 41,
-};
-
-enum {
-	MFMASK = 1 << 7,
-	OCMASK = 1 << 6,
-	OLMASK = 1 << 7,
-	AFMASK = 1 << 6,
-	RFMASK = 1 << 5,
-};
-
-enum {
-	MAXTTL		= 255,
-	D64HLEN		= IPV6HDR_LEN - IPV4HDR_LEN,
-	IP_MAX		= 32*1024,
-};
-
-struct Headers {
-	uchar	dst[IPaddrlen];
-	uchar	src[IPaddrlen];
-};
-
-struct Routersol {
-	uchar	vcf[4];		/* version:4, traffic class:8, flow label:20 */
-	uchar	ploadlen[2];	/* payload length: packet length - 40 */
-	uchar	proto;		/* next header	type */
-	uchar	ttl;		/* hop limit */
-	uchar	src[IPaddrlen];
-	uchar	dst[IPaddrlen];
-	uchar	type;
-	uchar	code;
-	uchar	cksum[2];
-	uchar	res[4];
-};
-
-struct Routeradv {
-	uchar	vcf[4];		/* version:4, traffic class:8, flow label:20 */
-	uchar	ploadlen[2];	/* payload length: packet length - 40 */
-	uchar	proto;		/* next header	type */
-	uchar	ttl;		/* hop limit */
-	uchar	src[IPaddrlen];
-	uchar	dst[IPaddrlen];
-	uchar	type;
-	uchar	code;
-	uchar	cksum[2];
-	uchar	cttl;
-	uchar	mor;
-	uchar	routerlt[2];
-	uchar	rchbltime[4];
-	uchar	rxmtimer[4];
-};
-
-struct Lladdropt {
-	uchar	type;
-	uchar	len;
-	uchar	lladdr[MAClen];
-};
-
-struct Prefixopt {
-	uchar	type;
-	uchar	len;
-	uchar	plen;
-	uchar	lar;
-	uchar	validlt[4];
-	uchar	preflt[4];
-	uchar	reserv[4];
-	uchar	pref[IPaddrlen];
-};
-
-struct Mtuopt {
-	uchar	type;
-	uchar	len;
-	uchar	reserv[2];
-	uchar	mtu[4];
-};
-
+void	parse6pref(int argc, char **argv);
+void	parse6ra(int argc, char **argv);
+void	doipv6(int);
 void	ea2lla(uchar *lla, uchar *ea);
-void	ipv62smcast(uchar *smcast, uchar *a);
+int	findllip(uchar *ip, Ipifc *ifc);
+int	ip6cfg(void);
--- a/sys/src/cmd/ip/ipconfig/ipv6.c
+++ b/sys/src/cmd/ip/ipconfig/ipv6.c
@@ -8,73 +8,99 @@
 #include <libc.h>
 #include <bio.h>
 #include <ip.h>
+#include <ndb.h>
 #include "ipconfig.h"
 #include "../icmp.h"
 
-#include <libsec.h>
+#include <libsec.h>	/* for sha1 */
 
-#pragma varargck argpos ralog 1
+enum {
+	IsRouter 	= 1,
+	IsHostRecv	= 2,
+	IsHostNoRecv	= 3,
 
-#define RALOG "v6routeradv"
+	ICMP6_RS	= 133,
+	ICMP6_RA	= 134,
 
-#define NetS(x) (((uchar*)x)[0]<< 8 | ((uchar*)x)[1])
-#define NetL(x) (((uchar*)x)[0]<<24 | ((uchar*)x)[1]<<16 | \
-		 ((uchar*)x)[2]<< 8 | ((uchar*)x)[3])
+	MFMASK = 1 << 7,
+	OCMASK = 1 << 6,
+	OLMASK = 1 << 7,
+	AFMASK = 1 << 6,
+	RFMASK = 1 << 5,
 
-enum {
-	ICMP6LEN=	4,
+	MAXTTL		= 255,
+	DEFMTU		= 1500,
 };
 
-typedef struct Hdr Hdr;
-struct Hdr			/* ICMP v4 & v6 header */
-{
+typedef struct Routeradv Routeradv;
+typedef struct Routersol Routersol;
+typedef struct Lladdropt Lladdropt;
+typedef struct Prefixopt Prefixopt;
+typedef struct Mtuopt Mtuopt;
+typedef struct Ipaddrsopt Ipaddrsopt;
+
+struct Routersol {
+	uchar	vcf[4];		/* version:4, traffic class:8, flow label:20 */
+	uchar	ploadlen[2];	/* payload length: packet length - 40 */
+	uchar	proto;		/* next header	type */
+	uchar	ttl;		/* hop limit */
+	uchar	src[16];
+	uchar	dst[16];
 	uchar	type;
 	uchar	code;
-	uchar	cksum[2];	/* Checksum */
-	uchar	data[];
+	uchar	cksum[2];
+	uchar	res[4];
 };
 
-char *icmpmsg6[Maxtype6+1] =
-{
-[EchoReply]		"EchoReply",
-[UnreachableV6]		"UnreachableV6",
-[PacketTooBigV6]	"PacketTooBigV6",
-[TimeExceedV6]		"TimeExceedV6",
-[Redirect]		"Redirect",
-[EchoRequest]		"EchoRequest",
-[TimeExceed]		"TimeExceed",
-[InParmProblem]		"InParmProblem",
-[Timestamp]		"Timestamp",
-[TimestampReply]	"TimestampReply",
-[InfoRequest]		"InfoRequest",
-[InfoReply]		"InfoReply",
-[AddrMaskRequest]	"AddrMaskRequest",
-[AddrMaskReply]		"AddrMaskReply",
-[EchoRequestV6]		"EchoRequestV6",
-[EchoReplyV6]		"EchoReplyV6",
-[RouterSolicit]		"RouterSolicit",
-[RouterAdvert]		"RouterAdvert",
-[NbrSolicit]		"NbrSolicit",
-[NbrAdvert]		"NbrAdvert",
-[RedirectV6]		"RedirectV6",
+struct Routeradv {
+	uchar	vcf[4];		/* version:4, traffic class:8, flow label:20 */
+	uchar	ploadlen[2];	/* payload length: packet length - 40 */
+	uchar	proto;		/* next header	type */
+	uchar	ttl;		/* hop limit */
+	uchar	src[16];
+	uchar	dst[16];
+	uchar	type;
+	uchar	code;
+	uchar	cksum[2];
+	uchar	cttl;
+	uchar	mor;
+	uchar	routerlt[2];
+	uchar	rchbltime[4];
+	uchar	rxmtimer[4];
 };
 
-static char *icmp6opts[] =
-{
-[0]			"unknown option",
-[V6nd_srclladdr]	"srcll_addr",
-[V6nd_targlladdr]	"targll_addr",
-[V6nd_pfxinfo]		"prefix",
-[V6nd_redirhdr]		"redirect",
-[V6nd_mtu]		"mtu",
-[V6nd_home]		"home",
-[V6nd_srcaddrs]		"src_addrs",
-[V6nd_ip]		"ip",
-[V6nd_rdns]		"rdns",
-[V6nd_9fs]		"9fs",
-[V6nd_9auth]		"9auth",
+struct Lladdropt {
+	uchar	type;
+	uchar	len;
+	uchar	lladdr[6];
 };
 
+struct Prefixopt {
+	uchar	type;
+	uchar	len;
+	uchar	plen;
+	uchar	lar;
+	uchar	validlt[4];
+	uchar	preflt[4];
+	uchar	reserv[4];
+	uchar	pref[16];
+};
+
+struct Mtuopt {
+	uchar	type;
+	uchar	len;
+	uchar	reserv[2];
+	uchar	mtu[4];
+};
+
+struct Ipaddrsopt {
+	uchar	type;
+	uchar	len;
+	uchar	reserv[2];
+	uchar	lifetime[4];
+	uchar	addrs[];
+};
+
 uchar v6allroutersL[IPaddrlen] = {
 	0xff, 0x02, 0, 0,
 	0, 0, 0, 0,
@@ -132,15 +158,10 @@
 	0, 0, 0, 0
 };
 
-enum
-{
-	Vadd,
-	Vremove,
-	Vunbind,
-	Vaddpref6,
-	Vra6,
-};
+#pragma varargck argpos ralog 1
 
+#define RALOG "v6routeradv"
+
 static void
 ralog(char *fmt, ...)
 {
@@ -154,45 +175,14 @@
 }
 
 void
-ea2lla(uchar *lla, uchar *ea)
-{
-	assert(IPaddrlen == 16);
-	memset(lla, 0, IPaddrlen);
-	lla[0]  = 0xFE;
-	lla[1]  = 0x80;
-	lla[8]  = ea[0] ^ 0x2;
-	lla[9]  = ea[1];
-	lla[10] = ea[2];
-	lla[11] = 0xFF;
-	lla[12] = 0xFE;
-	lla[13] = ea[3];
-	lla[14] = ea[4];
-	lla[15] = ea[5];
-}
-
-void
-ipv62smcast(uchar *smcast, uchar *a)
-{
-	assert(IPaddrlen == 16);
-	memset(smcast, 0, IPaddrlen);
-	smcast[0]  = 0xFF;
-	smcast[1]  = 0x02;
-	smcast[11] = 0x1;
-	smcast[12] = 0xFF;
-	smcast[13] = a[13];
-	smcast[14] = a[14];
-	smcast[15] = a[15];
-}
-
-void
 v6paraminit(Conf *cf)
 {
 	cf->sendra = cf->recvra = 0;
 	cf->mflag = 0;
 	cf->oflag = 0;
+	cf->linkmtu = DEFMTU;
 	cf->maxraint = Maxv6initraintvl;
 	cf->minraint = Maxv6initraintvl / 4;
-	cf->linkmtu = 1500;
 	cf->reachtime = V6reachabletime;
 	cf->rxmitra = V6retranstimer;
 	cf->ttl = MAXTTL;
@@ -205,71 +195,113 @@
 	cf->validlt = cf->preflt = ~0L;
 }
 
-static char *
-optname(unsigned opt)
+void
+parse6pref(int argc, char **argv)
 {
-	static char buf[32];
-
-	if(opt >= nelem(icmp6opts) || icmp6opts[opt] == nil) {
-		snprint(buf, sizeof buf, "unknown option %d", opt);
-		return buf;
-	} else
-		return icmp6opts[opt];
+	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);
 }
 
-static char*
-opt_seprint(uchar *ps, uchar *pe, char *sps, char *spe)
+/* parse router advertisement (keyword, value) pairs */
+void
+parse6ra(int argc, char **argv)
 {
-	int otype, osz, pktlen;
-	uchar *a;
-	char *p = sps, *e = spe;
+	int i, argsleft;
+	char *kw, *val;
 
-	a = ps;
-	for (pktlen = pe - ps; pktlen > 0; pktlen -= osz) {
-		otype = a[0];
-		osz = a[1] * 8;
+	if (argc % 2 != 0)
+		usage();
 
-		switch (otype) {
-		default:
-			return seprint(p, e, " option=%s ", optname(otype));
-		case V6nd_srclladdr:
-		case V6nd_targlladdr:
-			if(pktlen < osz || osz != 8)
-				return seprint(p, e, " option=%s bad size=%d",
-					optname(otype), osz);
-			p = seprint(p, e, " option=%s maddr=%E", optname(otype),
-				a+2);
-			break;
-		case V6nd_pfxinfo:
-			if(pktlen < osz || osz != 32)
-				return seprint(p, e, " option=%s: bad size=%d",
-					optname(otype), osz);
-
-			p = seprint(p, e, " option=%s pref=%I preflen=%3.3d"
-				" lflag=%1.1d aflag=%1.1d unused1=%1.1d"
-				" validlt=%ud preflt=%ud unused2=%1.1d",
-				optname(otype), a+16, (int)(*(a+2)),
-				(*(a+3) & (1 << 7)) != 0,
-				(*(a+3) & (1 << 6)) != 0,
-				(*(a+3) & 63) != 0,
-				NetL(a+4), NetL(a+8), NetL(a+12)!=0);
-			break;
+	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();
 		}
-		a += osz;
+		i += 2;
 	}
-	return p;
+
+	/* consistency check */
+	if (conf.maxraint < conf.minraint)
+		sysfatal("maxraint %d < minraint %d",
+			conf.maxraint, conf.minraint);
 }
 
-static void
-catch(void *a, char *msg)
+void
+ea2lla(uchar *lla, uchar *ea)
 {
-	USED(a);
-	if(strstr(msg, "alarm"))
-		noted(NCONT);
-	else
-		noted(NDFLT);
+	memset(lla, 0, IPaddrlen);
+	lla[0]  = 0xFE;
+	lla[1]  = 0x80;
+	lla[8]  = ea[0] ^ 0x2;
+	lla[9]  = ea[1];
+	lla[10] = ea[2];
+	lla[11] = 0xFF;
+	lla[12] = 0xFE;
+	lla[13] = ea[3];
+	lla[14] = ea[4];
+	lla[15] = ea[5];
 }
 
+int
+findllip(uchar *ip, Ipifc *ifc)
+{
+	Iplifc *lifc;
+
+	for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
+		if(ISIPV6LINKLOCAL(lifc->ip)){
+			ipmove(ip, lifc->ip);
+			return 1;
+		}
+	}
+	ipmove(ip, v6Unspecified);
+	return 0;
+}
+
 static int
 dialicmpv6(uchar *ip, int port)
 {
@@ -277,13 +309,13 @@
 	int fd, cfd;
 
 	snprint(addr, sizeof(addr), "%s/icmpv6!%I!%d!r", conf.mpoint, ip, port);
-	snprint(local, sizeof(local), "%I!%d", conf.laddr, port);
+	snprint(local, sizeof(local), "%I!%d", conf.lladdr, port);
 	if((fd = dial(addr, local, nil, &cfd)) < 0)
 		sysfatal("dialicmp6: %r");
 	fprint(cfd, "headers");
 	fprint(cfd, "ignoreadvice");
 	if(ISIPV6MCAST(ip))
-		fprint(cfd, "addmulti %I", conf.laddr);
+		fprint(cfd, "addmulti %I", conf.lladdr);
 	close(cfd);
 	return fd;
 }
@@ -301,7 +333,7 @@
 		warning("couldn't open %s: %r", buf);
 		return -1;
 	}
-	n = snprint(buf, sizeof buf, "add %s %I %E %I\n", conf.type, ip, mac, conf.laddr);
+	n = snprint(buf, sizeof buf, "add %s %I %E %I\n", conf.type, ip, mac, conf.lladdr);
 	if(write(fd, buf, n) != n) {
 		warning("arpenter: %s: %r", buf);
 		close(fd);
@@ -341,35 +373,6 @@
 	return rv;
 }
 
-static int
-masklen(uchar *mask)
-{
-	int len;
-
-	for(len=0; len < 128; len += 8){
-		if(*mask != 255)
-			break;
-		mask++;
-	}
-	while(len < 128 && (*mask & (0x80 >> (len & 7))) != 0)
-		len++;
-	return len;
-}
-
-static void
-genipmkask(uchar *mask, int len)
-{
-	memset(mask, 0, IPaddrlen);
-	if(len < 0)
-		len = 0;
-	else if(len > 128)
-		len = 128;
-	for(; len >= 8; len -= 8)
-		*mask++ = 255;
-	if(len > 0)
-		*mask = ~((1<<(8-len))-1);
-}
-
 /* add ipv6 addr to an interface */
 int
 ip6cfg(void)
@@ -417,8 +420,8 @@
 	}
 
 	warning("found dup entry in arp cache");
-	doremove();
-	return 0;
+	ipunconfig();
+	return -1;
 }
 
 static int
@@ -434,21 +437,6 @@
 		return IsHostNoRecv;
 }
 
-static int
-findllip(uchar *ip, Ipifc *ifc)
-{
-	Iplifc *lifc;
-
-	for(lifc = ifc->lifc; lifc != nil; lifc = lifc->next){
-		if(ISIPV6LINKLOCAL(lifc->ip)){
-			ipmove(ip, lifc->ip);
-			return 1;
-		}
-	}
-	ipmove(ip, v6Unspecified);
-	return 0;
-}
-
 static void
 sendrs(int fd, uchar *dst)
 {
@@ -459,14 +447,14 @@
 
 	memset(buf, 0, sizeof buf);
 
-	rs = (Routersol *)buf;
+	rs = (Routersol*)buf;
 	rs->type = ICMP6_RS;
 	ipmove(rs->dst, dst);
-	ipmove(rs->src, conf.laddr);
+	ipmove(rs->src, conf.lladdr);
 	pktlen = sizeof *rs;
 
 	if(conf.hwalen > 0){
-		llao = (Lladdropt *)&buf[pktlen];
+		llao = (Lladdropt*)&buf[pktlen];
 		llao->type = V6nd_srclladdr;
 		llao->len = (2+7+conf.hwalen)/8;
 		memmove(llao->lladdr, conf.hwa, conf.hwalen);
@@ -494,8 +482,6 @@
 	USED(buf, pktlen);
 }
 
-/* host receiving a router advertisement calls this */
-
 static void
 ewrite(int fd, char *str)
 {
@@ -515,9 +501,9 @@
 	char *cfg;
 
 	cfg = smprint("ra6 mflag %d oflag %d reachtime %d rxmitra %d "
-		"ttl %d routerlt %d",
+		"ttl %d routerlt %d linkmtu %d",
 		cf->mflag, cf->oflag, cf->reachtime, cf->rxmitra,
-		cf->ttl, cf->routerlt);
+		cf->ttl, cf->routerlt, cf->linkmtu);
 	ewrite(cf->cfd, cfg);
 	free(cfg);
 }
@@ -527,10 +513,8 @@
 {
 	char *cfg;
 
-	cfg = smprint("ra6 sendra %d recvra %d maxraint %d minraint %d "
-		"linkmtu %d",
-		cf->sendra, cf->recvra, cf->maxraint, cf->minraint,
-		cf->linkmtu);
+	cfg = smprint("ra6 sendra %d recvra %d maxraint %d minraint %d",
+		cf->sendra, cf->recvra, cf->maxraint, cf->minraint);
 	ewrite(cf->cfd, cfg);
 	free(cfg);
 }
@@ -547,6 +531,35 @@
 }
 
 static int
+masklen(uchar *mask)
+{
+	int len;
+
+	for(len=0; len < 128; len += 8){
+		if(*mask != 255)
+			break;
+		mask++;
+	}
+	while(len < 128 && (*mask & (0x80 >> (len & 7))) != 0)
+		len++;
+	return len;
+}
+
+static void
+genipmkask(uchar *mask, int len)
+{
+	memset(mask, 0, IPaddrlen);
+	if(len < 0)
+		len = 0;
+	else if(len > 128)
+		len = 128;
+	for(; len >= 8; len -= 8)
+		*mask++ = 255;
+	if(len > 0)
+		*mask = ~((1<<(8-len))-1);
+}
+
+static int
 seen(Conf *cf)
 {
 	static uchar tab[SHA1dlen*100], *w;
@@ -563,14 +576,78 @@
 	return 0;
 }
 
+static int
+pnames(uchar *d, int nd, char *s)
+{
+	uchar *de = d + nd;
+	int l;
+
+	if(nd < 1)
+		return -1;
+	for(; *s != 0; s++){
+		for(l = 0; *s != 0 && *s != '.' && *s != ' '; l++)
+			s++;
+
+		d += l+1;
+		if(d >= de || l > 077)
+			return -1;
+
+		d[-l-1] = l;
+		memmove(d-l, s-l, l);
+
+		if(*s != '.')
+			*d++ = 0;
+	}
+	return d - (de - nd);
+}
+
+static int
+gnames(char *d, int nd, uchar *s, int ns)
+{
+	uchar *se = s + ns;
+	char  *de = d + nd;
+	int l;
+
+	if(nd < 1 || ns < 1)
+		return -1;
+	l = *s++ & 077;
+	while(l > 0){
+		if(d + l >= de || s + l >= se)
+			return -1;
+
+		memmove(d, s, l);
+		d += l;
+		s += l;
+
+		l = *s++ & 077;
+		if(l > 0)
+			*d++ = '.';
+		else {
+			if(s >= se)
+				break;
+
+			l = *s++ & 077;
+			if(l == 0)
+				break;
+			*d++ = ' ';
+		}
+	}
+	*d = 0;
+	return d - (de - nd);
+}
+
+/*
+ * host receiving a router advertisement calls this
+ */
 static void
 recvrahost(uchar buf[], int pktlen)
 {
-	int m, n, optype, needrefresh;
-	uchar src[IPaddrlen];
+	char dnsdomain[sizeof(conf.dnsdomain)];
+	int m, n, optype;
 	Lladdropt *llao;
 	Mtuopt *mtuo;
 	Prefixopt *prfo;
+	Ipaddrsopt *addrso;
 	Routeradv *ra;
 
 	m = sizeof *ra;
@@ -584,22 +661,27 @@
 	conf.ttl = ra->cttl;
 	conf.mflag = (MFMASK & ra->mor);
 	conf.oflag = (OCMASK & ra->mor);
-	conf.routerlt =  nhgets(ra->routerlt);
+	conf.routerlt = nhgets(ra->routerlt);
 	conf.reachtime = nhgetl(ra->rchbltime);
-	conf.rxmitra =   nhgetl(ra->rxmtimer);
-	issuebasera6(&conf);
+	conf.rxmitra = nhgetl(ra->rxmtimer);
+	conf.linkmtu = DEFMTU;
 
-	needrefresh = 0;
+	memset(conf.dns, 0, sizeof(conf.dns));
+	memset(conf.fs, 0, sizeof(conf.fs));
+	memset(conf.auth, 0, sizeof(conf.auth));
+	memset(conf.dnsdomain, 0, sizeof(conf.dnsdomain));
+
+	/* process options */
 	while(pktlen - m >= 8) {
 		n = m;
 		optype = buf[n];
 		m += 8 * buf[n+1];
-		if(pktlen < m)
+		if(m <= n || pktlen < m)
 			return;
 
 		switch (optype) {
 		case V6nd_srclladdr:
-			llao = (Lladdropt *)&buf[n];
+			llao = (Lladdropt*)&buf[n];
 			if(llao->len == 1 && conf.hwalen == 6)
 				arpenter(ra->src, llao->lladdr);
 			break;
@@ -607,63 +689,110 @@
 			mtuo = (Mtuopt*)&buf[n];
 			conf.linkmtu = nhgetl(mtuo->mtu);
 			break;
-		case V6nd_pfxinfo:
-			prfo = (Prefixopt*)&buf[n];
-			if(prfo->len != 4)
+
+		case V6nd_rdnssl:
+			addrso = (Ipaddrsopt*)&buf[n];
+			if(gnames(dnsdomain, sizeof(dnsdomain),
+				addrso->addrs, (addrso->len - 1)*8) <= 0)
 				break;
+			addnames(conf.dnsdomain, dnsdomain, sizeof(conf.dnsdomain));
+			break;
 
-			conf.prefixlen = prfo->plen & 127;
-			genipmkask(conf.mask, conf.prefixlen);
-			maskip(prfo->pref, conf.mask, conf.v6pref);
-			conf.onlink =   ((prfo->lar & OLMASK) != 0);
-			conf.autoflag = ((prfo->lar & AFMASK) != 0);
-			conf.validlt = nhgetl(prfo->validlt);
-			conf.preflt =  nhgetl(prfo->preflt);
-			issueadd6(&conf);
-			
-			if(conf.routerlt == 0)
-				ipmove(conf.gaddr, IPnoaddr);
-			else if((prfo->lar & RFMASK) != 0)
-				ipmove(conf.gaddr, prfo->pref);
-			else
-				ipmove(conf.gaddr, ra->src);
+		case V6nd_rdns:
+			addrso = (Ipaddrsopt*)&buf[n];
+			n = (addrso->len - 1) * 8;
+			if(n == 0 || n % IPaddrlen)
+				break;
+			addaddrs(conf.dns, sizeof(conf.dns), addrso->addrs, n);
+			break;
 
-			/* report prefix only once */
-			if(seen(&conf))
+		case V6nd_9fs:
+			addrso = (Ipaddrsopt*)&buf[n];
+			n = (addrso->len - 1) * 8;
+			if(n == 0 || n % IPaddrlen || !plan9)
 				break;
+			addaddrs(conf.fs, sizeof(conf.fs), addrso->addrs, n);
+			break;
+		case V6nd_9auth:
+			addrso = (Ipaddrsopt*)&buf[n];
+			n = (addrso->len - 1) * 8;
+			if(n == 0 || n % IPaddrlen || !plan9)
+				break;
+			addaddrs(conf.auth, sizeof(conf.auth), addrso->addrs, n);
+			break;
+		}
+	}
+	issuebasera6(&conf);
 
-			if(conf.prefixlen  == 0
-			|| !validip(conf.v6pref)
-			|| isv4(conf.v6pref)
-			|| ipcmp(conf.v6pref, v6loopback) == 0
-			|| ISIPV6MCAST(conf.v6pref)
-			|| ISIPV6LINKLOCAL(conf.v6pref)){
+	/* process prefixes */
+	m = sizeof *ra;
+	while(pktlen - m >= 8) {
+		n = m;
+		optype = buf[n];
+		m += 8 * buf[n+1];
+		if(m <= n || pktlen < m)
+			return;
+
+		if(optype != V6nd_pfxinfo)
+			continue;
+
+		prfo = (Prefixopt*)&buf[n];
+		if(prfo->len != 4)
+			continue;
+
+		conf.prefixlen = prfo->plen & 127;
+		genipmkask(conf.mask, conf.prefixlen);
+		maskip(prfo->pref, conf.mask, conf.v6pref);
+		memmove(conf.laddr, conf.v6pref, 8);
+		memmove(conf.laddr+8, conf.lladdr+8, 8);
+		conf.onlink = ((prfo->lar & OLMASK) != 0);
+		conf.autoflag = ((prfo->lar & AFMASK) != 0);
+		conf.validlt = nhgetl(prfo->validlt);
+		conf.preflt = nhgetl(prfo->preflt);
+
+		if(conf.routerlt == 0)
+			ipmove(conf.gaddr, IPnoaddr);
+		else if((prfo->lar & RFMASK) != 0)
+			ipmove(conf.gaddr, prfo->pref);
+		else
+			ipmove(conf.gaddr, ra->src);
+	
+		if(conf.prefixlen < 1
+		|| conf.prefixlen > 64
+		|| !validip(conf.v6pref)
+		|| isv4(conf.v6pref)
+		|| ipcmp(conf.v6pref, v6loopback) == 0
+		|| ISIPV6MCAST(conf.v6pref)
+		|| ISIPV6LINKLOCAL(conf.v6pref)){
+			if(!seen(&conf))
 				ralog("igoring bogus prefix from %I on %s; pfx %I %M",
 					ra->src, conf.dev, conf.v6pref, conf.mask);
-				break;
-			}
+			continue;
+		}
 
-			ralog("got initial RA from %I on %s; pfx %I %M",
-				ra->src, conf.dev, conf.v6pref, conf.mask);
+		/* add prefix and update parameters */
+		issueadd6(&conf);
 
-			if(validip(conf.gaddr)){
-				memmove(src, conf.v6pref, 8);
-				memmove(src+8, conf.laddr+8, 8);
-				adddefroute(conf.gaddr, conf.laddr, src, conf.mask);
-			}
-			needrefresh = 1;
-			break;
-		}
-	}
+		/* report this prefix configuration only once */
+		if(seen(&conf))
+			continue;
 
-	if(needrefresh)
+		ralog("got RA from %I on %s; pfx %I %M",
+			ra->src, conf.dev, conf.v6pref, conf.mask);
+
+		if(validip(conf.gaddr))
+			adddefroute(conf.gaddr, conf.lladdr, conf.laddr, conf.mask);
+
+		if(beprimary)
+			putndb();
 		refresh();
+	}
 }
 
 /*
  * daemon to receive router advertisements from routers
  */
-void
+static int
 recvra6(void)
 {
 	int fd, n, sendrscnt, recvracnt, sleepfor;
@@ -674,7 +803,7 @@
 	if(ifc == nil)
 		sysfatal("can't read ipifc: %r");
 
-	if(!findllip(conf.laddr, ifc))
+	if(!findllip(conf.lladdr, ifc))
 		sysfatal("no link local address");
 
 	fd = dialicmpv6(v6allnodesL, ICMP6_RA);
@@ -681,23 +810,30 @@
 	if(fd < 0)
 		sysfatal("can't open icmp_ra connection: %r");
 
-	notify(catch);
-	sendrscnt = Maxv6rss;
-	recvracnt = 0;
-
 	switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT|RFNOTEG)){
 	case -1:
 		sysfatal("can't fork: %r");
 	default:
 		close(fd);
-		return;
+		ralog("recvra6 on %s", conf.dev);
+
+		/* wait for initial RA */
+		return (int)(uintptr)rendezvous(recvra6, (void*)0);
 	case 0:
 		break;
 	}
+	procsetname("recvra6 on %s %I", conf.dev, conf.lladdr);
+	notify(catch);
 
-	procsetname("recvra6 on %s %I", conf.dev, conf.laddr);
-	ralog("recvra6 on %s", conf.dev);
+	sendrscnt = 0;
+	if(recvra6on(ifc) == IsHostRecv){
+		sendrs(fd, v6allroutersL);
+		sendrscnt = Maxv6rss;
+	}
+
+	recvracnt = Maxv6initras;
 	sleepfor = Minv6interradelay;
+
 	for (;;) {
 		alarm(sleepfor);
 		n = read(fd, buf, sizeof buf);
@@ -711,34 +847,27 @@
 		if(ifc == nil) {
 			ralog("recvra6: can't read router params on %s, quitting on %s",
 				conf.mpoint, conf.dev);
+			if(sendrscnt >= 0)
+				rendezvous(recvra6, (void*)-1);
 			exits(nil);
 		}
-		
+
 		if(n <= 0) {
 			if(sendrscnt > 0) {
 				sendrscnt--;
-				if(recvra6on(ifc) == IsHostRecv)
-					sendrs(fd, v6allroutersL);
+				sendrs(fd, v6allroutersL);
 				sleepfor = V6rsintvl + nrand(100);
 			}
 			if(sendrscnt == 0) {
 				sendrscnt--;
-				sleepfor = 0;
 				ralog("recvra6: no router advs after %d sols on %s",
 					Maxv6rss, conf.dev);
+				rendezvous(recvra6, (void*)0);
+				sleepfor = 0;
 			}
 			continue;
 		}
 
-		/* got at least initial ra; no whining */
-		sendrscnt = -1;
-		sleepfor = 0;
-
-		if(++recvracnt >= Maxv6initras){
-			recvracnt = 0;
-			sleepfor = Maxv6radelay;
-		}
-
 		switch (recvra6on(ifc)) {
 		case IsRouter:
 			recvrarouter(buf, n);
@@ -748,8 +877,23 @@
 			break;
 		case IsHostNoRecv:
 			ralog("recvra6: recvra off, quitting on %s", conf.dev);
+			if(sendrscnt >= 0)
+				rendezvous(recvra6, (void*)-1);
 			exits(nil);
 		}
+
+		/* got at least initial ra; no whining */
+		if(sendrscnt >= 0)
+			rendezvous(recvra6, (void*)1);
+		sendrscnt = -1;
+		sleepfor = 0;
+
+		if(recvracnt > 0)
+			recvracnt--;
+		else {
+			recvracnt = Maxv6initras;
+			sleepfor = Maxv6radelay;
+		}
 	}
 }
 
@@ -758,7 +902,7 @@
  *         0 -- no arp table updates
  *         1 -- successful arp table update
  */
-int
+static int
 recvrs(uchar *buf, int pktlen, uchar *sol)
 {
 	int n;
@@ -766,11 +910,11 @@
 	Lladdropt *llao;
 
 	n = sizeof *rs + sizeof *llao;
-	rs = (Routersol *)buf;
+	rs = (Routersol*)buf;
 	if(pktlen < n)
 		return 0;
 
-	llao = (Lladdropt *)&buf[sizeof *rs];
+	llao = (Lladdropt*)&buf[sizeof *rs];
 	if(llao->type != V6nd_srclladdr || llao->len != 1 || conf.hwalen != 6)
 		return 0;
 
@@ -787,21 +931,28 @@
 	return 1;
 }
 
-void
-sendra(int fd, uchar *dst, int rlt, Ipifc *ifc)
+static void
+sendra(int fd, uchar *dst, int rlt, Ipifc *ifc, Ndb *db)
 {
-	uchar buf[1024];
-	Iplifc *lifc;
-	Lladdropt *llao;
+	uchar dns[sizeof(conf.dns)], fs[sizeof(conf.fs)], auth[sizeof(conf.auth)];
+	char dnsdomain[sizeof(conf.dnsdomain)];
+	Ipaddrsopt *addrso;
 	Prefixopt *prfo;
+	Iplifc *lifc;
 	Routeradv *ra;
-	int pktlen;
+	uchar buf[1024];
+	int pktlen, n;
 
+	memset(dns, 0, sizeof(dns));
+	memset(fs, 0, sizeof(fs));
+	memset(auth, 0, sizeof(auth));
+	memset(dnsdomain, 0, sizeof(dnsdomain));
+
 	memset(buf, 0, sizeof buf);
 
-	ra = (Routeradv *)buf;
+	ra = (Routeradv*)buf;
 	ipmove(ra->dst, dst);
-	ipmove(ra->src, conf.laddr);
+	ipmove(ra->src, conf.lladdr);
 	ra->type = ICMP6_RA;
 	ra->cttl = conf.ttl;
 	if(conf.mflag > 0)
@@ -821,7 +972,7 @@
 	 * link layer address option
 	 */
 	if(conf.hwalen > 0){
-		llao = (Lladdropt *)&buf[pktlen];
+		Lladdropt *llao = (Lladdropt *)&buf[pktlen];
 		llao->type = V6nd_srclladdr;
 		llao->len = (2+7+conf.hwalen)/8;
 		memmove(llao->lladdr, conf.hwa, conf.hwalen);
@@ -832,6 +983,7 @@
 	for (lifc = (ifc != nil? ifc->lifc: nil); lifc != nil; lifc = lifc->next) {
 		if(pktlen > sizeof buf - 4*8)
 			break;
+
 		if(!validip(lifc->ip)
 		|| isv4(lifc->ip)
 		|| ipcmp(lifc->ip, v6loopback) == 0
@@ -838,7 +990,8 @@
 		|| ISIPV6MCAST(lifc->ip)
 		|| ISIPV6LINKLOCAL(lifc->ip))
 			continue;
-		prfo = (Prefixopt *)&buf[pktlen];
+
+		prfo = (Prefixopt*)&buf[pktlen];
 		prfo->type = V6nd_pfxinfo;
 		prfo->len = 4;
 		prfo->plen = masklen(lifc->mask) & 127;
@@ -849,8 +1002,58 @@
 		hnputl(prfo->validlt, lifc->validlt);
 		hnputl(prfo->preflt, lifc->preflt);
 		pktlen += 8 * prfo->len;
+
+		/* get ndb configuration for this prefix */
+		ipmove(conf.laddr, lifc->ip);
+		ndb2conf(db, lifc->net);
+
+		addaddrs(dns, sizeof(dns), conf.dns, sizeof(conf.dns));
+		addaddrs(fs, sizeof(fs), conf.fs, sizeof(conf.fs));
+		addaddrs(auth, sizeof(auth), conf.auth, sizeof(conf.auth));
+
+		addnames(dnsdomain, conf.dnsdomain, sizeof(dnsdomain));
 	}
 
+	addrso = (Ipaddrsopt*)&buf[pktlen];
+	n = pnames(addrso->addrs, sizeof buf - 8 - pktlen, dnsdomain);
+	if(n > 0){
+		addrso->type = V6nd_rdnssl;
+		addrso->len = 1 + ((n + 7) / 8);
+		hnputl(addrso->lifetime, ~0L);
+		pktlen += 8 * addrso->len;
+	}
+
+	if((n = countaddrs(dns, sizeof(dns))) > 0 && pktlen+8+n*IPaddrlen <= sizeof buf) {
+		addrso = (Ipaddrsopt*)&buf[pktlen];
+		addrso->type = V6nd_rdns;
+		addrso->len = 1 + n*2;
+		memmove(addrso->addrs, dns, n*IPaddrlen);
+		hnputl(addrso->lifetime, ~0L);
+		pktlen += 8 * addrso->len;
+	}
+
+	if(!plan9)
+		goto send;
+
+	/* send plan9 specific options */
+	if((n = countaddrs(fs, sizeof(fs))) > 0 && pktlen+8+n*IPaddrlen <= sizeof buf) {
+		addrso = (Ipaddrsopt*)&buf[pktlen];
+		addrso->type = V6nd_9fs;
+		addrso->len = 1 + n*2;
+		memmove(addrso->addrs, fs, n*IPaddrlen);
+		hnputl(addrso->lifetime, ~0L);
+		pktlen += 8 * addrso->len;
+	}
+	if((n = countaddrs(auth, sizeof(auth))) > 0 && pktlen+8+n*IPaddrlen <= sizeof buf) {
+		addrso = (Ipaddrsopt*)&buf[pktlen];
+		addrso->type = V6nd_9auth;
+		addrso->len = 1 + n*2;
+		memmove(addrso->addrs, auth, n*IPaddrlen);
+		hnputl(addrso->lifetime, ~0L);
+		pktlen += 8 * addrso->len;
+	}
+
+send:
 	write(fd, buf, pktlen);
 }
 
@@ -857,18 +1060,23 @@
 /*
  * daemon to send router advertisements to hosts
  */
-void
+static void
 sendra6(void)
 {
 	int fd, n, sleepfor, nquitmsgs;
 	uchar buf[4096], dst[IPaddrlen];
 	Ipifc *ifc;
+	Ndb *db;
 
+	db = opendatabase();
+	if(db == nil)
+		warning("couldn't open ndb: %r");
+
 	ifc = readipifc(conf.mpoint, nil, myifc);
 	if(ifc == nil)
 		sysfatal("can't read ipifc: %r");
 
-	if(!findllip(conf.laddr, ifc))
+	if(!findllip(conf.lladdr, ifc))
 		sysfatal("no link local address");
 
 	fd = dialicmpv6(v6allroutersL, ICMP6_RS);
@@ -875,29 +1083,29 @@
 	if(fd < 0)
 		sysfatal("can't open icmp_rs connection: %r");
 
-	notify(catch);
-	nquitmsgs = Maxv6finalras;
-
 	switch(rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT|RFNOTEG)){
 	case -1:
 		sysfatal("can't fork: %r");
 	default:
 		close(fd);
+		ralog("sendra6 on %s", conf.dev);
 		return;
 	case 0:
 		break;
 	}
+	procsetname("sendra6 on %s %I", conf.dev, conf.lladdr);
+	notify(catch);
 
-	procsetname("sendra6 on %s %I", conf.dev, conf.laddr);
-	ralog("sendra6 on %s", conf.dev);
+	nquitmsgs = Maxv6finalras;
 	sleepfor = 100 + jitter();
+
 	for (;;) {
 		alarm(sleepfor);
 		n = read(fd, buf, sizeof buf);
 		sleepfor = alarm(0);
 
-		if(n > 0 && recvrs(buf, n, dst) > 0)
-			sendra(fd, dst, 1, ifc);
+		if(ifc->sendra6 > 0 && n > 0 && recvrs(buf, n, dst) > 0)
+			sendra(fd, dst, 1, ifc, db);
 
 		/* wait for alarm to expire */
 		if(sleepfor > 100)
@@ -913,31 +1121,30 @@
 		if(ifc->sendra6 <= 0){
 			if(nquitmsgs > 0) {
 				nquitmsgs--;
-				sendra(fd, v6allnodesL, 0, ifc);
+				sendra(fd, v6allnodesL, 0, ifc, nil);
 				continue;
-			} else {
-				ralog("sendra6: sendra off, quitting on %s", conf.dev);
-				exits(nil);
 			}
+			ralog("sendra6: sendra off on %s, quitting on %s",
+				conf.mpoint, conf.dev);
+			exits(nil);
 		}
-		sendra(fd, v6allnodesL, 1, ifc);
+		db = opendatabase();
+		sendra(fd, v6allnodesL, 1, ifc, db);
 		sleepfor = randint(ifc->rp.minraint, ifc->rp.maxraint);
 	}
 }
 
-void
+static void
 startra6(void)
 {
 	static char routeon[] = "iprouting 1";
 
-	mklladdr();
-
 	if(conf.recvra > 0)
 		recvra6();
 
 	if(conf.sendra > 0) {
 		if(write(conf.cfd, routeon, sizeof routeon - 1) < 0) {
-			warning("write (iprouting 1) failed: %r");
+			warning("write (%s) failed: %r", routeon);
 			return;
 		}
 		sendra6();
--- a/sys/src/cmd/ip/ipconfig/main.c
+++ b/sys/src/cmd/ip/ipconfig/main.c
@@ -3,216 +3,49 @@
  */
 #include <u.h>
 #include <libc.h>
-#include <ip.h>
 #include <bio.h>
+#include <ip.h>
 #include <ndb.h>
-#include "../dhcp.h"
 #include "ipconfig.h"
+#include "../dhcp.h"
 
 #include <libsec.h> /* genrandom() */
 
-#define DEBUG if(debug)warning
+Conf	conf;
+int	myifc = -1;
+int	beprimary = -1;
+int	noconfig;
+Ipifc	*ifc;
+Ctl	*firstctl, **ctll = &firstctl;
 
-/* possible verbs */
-enum
-{
-	/* commands */
-	Vadd,
-	Vremove,
-	Vunbind,
-	Vaddpref6,
-	Vra6,
-	/* media */
-	Vether,
-	Vgbe,
-	Vppp,
-	Vloopback,
-	Vtorus,
-	Vtree,
-	Vpkt,
-};
+int	debug;
+int	dolog;
 
-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;
-
+int	plan9 = 1;
 int	Oflag;
-int	beprimary = -1;
-Conf	conf;
-int	debug;
+int	rflag;
+
 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	noconfig;
 int	nodhcpwatch;
-char 	optmagic[4] = { 0x63, 0x82, 0x53, 0x63 };
-int	plan9 = 1;
 int	sendhostname;
+char	*ndboptions;
 
+int	ipv6auto;
+int	dupl_disc = 1;		/* flag: V6 duplicate neighbor discovery */
+
+int	dondbconfig;
+char	*dbfile;
+
 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",
-};
+static void	binddevice(void);
+static void	controldevice(void);
+extern void	pppbinddev(void);
 
-int	addoption(char*);
-void	binddevice(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(uchar*);
-int	ip4cfg(void);
-int	ip6cfg(void);
-void	mkclientid(void);
-void	ndbconfig(void);
-int	openlisten(void);
-uchar*	optaddaddr(uchar*, int, uchar*);
-uchar*	optaddbyte(uchar*, int, int);
-uchar*	optaddstr(uchar*, int, char*);
-uchar*	optadd(uchar*, int, void*, int);
-uchar*	optaddulong(uchar*, int, ulong);
-uchar*	optaddvec(uchar*, int, uchar*, int);
-int	optgetaddrs(uchar*, int, uchar*, int);
-int	optgetp9addrs(uchar*, int, uchar*, int);
-int	optgetaddr(uchar*, int, uchar*);
-int	optgetbyte(uchar*, int);
-int	optgetstr(uchar*, int, char*, int);
-uchar*	optget(uchar*, int, 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	pppbinddev(void);
-void	putndb(void);
+static void	doadd(void);
+static void	doremove(void);
+static void	dounbind(void);
+static void	ndbconfig(void);
 
 void
 usage(void)
@@ -224,6 +57,29 @@
 	exits("usage");
 }
 
+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 */
+
+	conf.cfd = -1;
+	conf.rfd = -1;
+
+	setnetmtpt(conf.mpoint, sizeof conf.mpoint, nil);
+	conf.cputype = getenv("cputype");
+	if(conf.cputype == nil)
+		conf.cputype = "386";
+
+	v6paraminit(&conf);
+
+	dhcpinit();
+}
+
 void
 warning(char *fmt, ...)
 {
@@ -274,82 +130,6 @@
 	}
 }
 
-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)
 {
@@ -374,45 +154,32 @@
 	return dev;
 }
 
+/* look for an action */
 static int
-findifc(char *net, char *dev)
+parseverb(char *name)
 {
-	Ipifc *nifc;
+	static 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",
+	};
+	int i;
 
-	ifc = readipifc(net, ifc, -1);
-	for(nifc = ifc; nifc != nil; nifc = nifc->next)
-		if(strcmp(nifc->dev, dev) == 0)
-			return nifc->index;
-
+	for(i = 0; i < nelem(verbs); i++)
+		if(verbs[i] != nil && strcmp(name, verbs[i]) == 0)
+			return i;
 	return -1;
 }
 
-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 */
-
-	conf.cfd = -1;
-	conf.rfd = -1;
-
-	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)
 {
@@ -498,14 +265,26 @@
 	return action;
 }
 
+static int
+findifc(char *net, char *dev)
+{
+	Ipifc *nifc;
+
+	ifc = readipifc(net, ifc, -1);
+	for(nifc = ifc; nifc != nil; nifc = nifc->next)
+		if(strcmp(nifc->dev, dev) == 0)
+			return nifc->index;
+
+	return -1;
+}
+
 void
 main(int argc, char **argv)
 {
-	int retry, action;
+	int action;
 	Ctl *cp;
 
 	init();
-	retry = 0;
 	ARGBEGIN {
 	case '6': 			/* IPv6 auto config */
 		ipv6auto = 1;
@@ -566,7 +345,7 @@
 		beprimary = 0;
 		break;
 	case 'r':
-		retry = 1;
+		rflag = 1;
 		break;
 	case 'u':		/* IPv6: duplicate neighbour disc. off */
 		dupl_disc = 0;
@@ -607,7 +386,12 @@
 
 	switch(action){
 	case Vadd:
-		doadd(retry);
+		if(dondbconfig){
+			dodhcp = 0;
+			ndbconfig();
+			return;
+		}
+		doadd();
 		break;
 	case Vaddpref6:
 	case Vra6:
@@ -623,14 +407,51 @@
 	exits(nil);
 }
 
-void
-doadd(int retry)
+static int
+isether(void)
 {
+	return strcmp(conf.type, "ether") == 0 || strcmp(conf.type, "gbe") == 0;
+}
+
+/* create link local address */
+static void
+mklladdr(void)
+{
+	if(isether() && myetheraddr(conf.hwa, conf.dev) == 0){
+		conf.hwalen = 6;
+		conf.hwatype = 1;
+	} else {
+		genrandom(conf.hwa, sizeof(conf.hwa));
+		conf.hwatype = -1;
+	}
+	ea2lla(conf.lladdr, conf.hwa);
+}
+
+/* create a client id */
+static void
+mkclientid(void)
+{
+	if(isether() && 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);
+	}
+}
+
+static void
+doadd(void)
+{
 	if(!validip(conf.laddr)){
-		if(dondbconfig)
-			ndbconfig();
-		else if(ipv6auto){
+		if(ipv6auto){
 			mklladdr();
+			ipmove(conf.laddr, conf.lladdr);
 			dodhcp = 0;
 		} else
 			dodhcp = 1;
@@ -645,7 +466,7 @@
 	}
 
 	if(!validip(conf.laddr))
-		if(retry && dodhcp && !noconfig){
+		if(rflag && dodhcp && !noconfig){
 			warning("couldn't determine ip address, retrying");
 			dhcpwatch(1);
 			return;
@@ -652,6 +473,7 @@
 		} else
 			sysfatal("no success with DHCP");
 
+	DEBUG("adding address %I %M on %s", conf.laddr, conf.mask, conf.dev);
 	if(noconfig)
 		return;
 
@@ -671,7 +493,7 @@
 	refresh();
 }
 
-void
+static void
 doremove(void)
 {
 	if(!validip(conf.laddr))
@@ -680,7 +502,7 @@
 		warning("can't remove %I %M: %r", conf.laddr, conf.mask);
 }
 
-void
+static void
 dounbind(void)
 {
 	if(fprint(conf.cfd, "unbind") < 0)
@@ -687,101 +509,8 @@
 		warning("can't unbind %s: %r", conf.dev);
 }
 
-int
-issrcspec(uchar *src, uchar *smask)
-{
-	return isv4(src)? memcmp(smask+IPv4off, IPnoaddr+IPv4off, 4): ipcmp(smask, IPnoaddr);
-}
-void
-addroute(uchar *dst, uchar *mask, uchar *gate, uchar *laddr, uchar *src, uchar *smask)
-{
-	char *cmd;
-
-	if(issrcspec(src, smask))
-		cmd = "add %I %M %I %I %I %M";
-	else
-		cmd = "add %I %M %I %I";
-	fprint(conf.rfd, cmd, dst, mask, gate, laddr, src, smask);
-}
-void
-removeroute(uchar *dst, uchar *mask, uchar *src, uchar *smask)
-{
-	char *cmd;
-
-	if(issrcspec(src, smask))
-		cmd = "remove %I %M %I %M";
-	else
-		cmd = "remove %I %M";
-	fprint(conf.rfd, cmd, dst, mask, src, smask);
-}
-void
-adddefroute(uchar *gaddr, uchar *laddr, uchar *src, uchar *smask)
-{
-	uchar dst[IPaddrlen], mask[IPaddrlen];
-
-	if(isv4(gaddr)){
-		parseip(dst, "0.0.0.0");
-		parseipmask(mask, "0.0.0.0");
-		if(src == nil)
-			src = dst;
-		if(smask == nil)
-			smask = mask;
-	} else {
-		parseip(dst, "2000::");
-		parseipmask(mask, "/3");
-		if(src == nil)
-			src = IPnoaddr;
-		if(smask == nil)
-			smask = IPnoaddr;
-	}
-	addroute(dst, mask, gaddr, laddr, src, smask);
-
-	/* also add a source specific route */
-	if(ipcmp(src, IPnoaddr) != 0 && ipcmp(src, v4prefix) != 0)
-		addroute(dst, mask, gaddr, laddr, src, IPallbits);
-}
-
-
-int
-isether(void)
-{
-	return strcmp(conf.type, "ether") == 0 || strcmp(conf.type, "gbe") == 0;
-}
-
-/* create link local address */
-void
-mklladdr(void)
-{
-	if(isether() && myetheraddr(conf.hwa, conf.dev) == 0){
-		conf.hwalen = 6;
-		conf.hwatype = 1;
-	} else {
-		genrandom(conf.hwa, sizeof(conf.hwa));
-		conf.hwatype = -1;
-	}
-	ea2lla(conf.laddr, conf.hwa);
-}
-
-/* create a client id */
-void
-mkclientid(void)
-{
-	if(isether() && 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);
-	}
-}
-
 /* send some ctls to a device */
-void
+static void
 controldevice(void)
 {
 	char ctlfile[256];
@@ -805,7 +534,7 @@
 }
 
 /* bind an ip stack to a device, leave the control channel open */
-void
+static void
 binddevice(void)
 {
 	char buf[256];
@@ -867,7 +596,7 @@
 	return 0;
 }
 
-/* remove a logical interface to the ip stack */
+/* remove a logical interface from the ip stack */
 void
 ipunconfig(void)
 {
@@ -886,762 +615,15 @@
 	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)
-{
-	ulong 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 on %s", conf.dev);
-	/* 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();
-			refresh();
-		}
-	}
-}
-
-void
-dhcptimer(void)
-{
-	ulong 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;
-	uchar *p;
-	int n;
-	uchar 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 = 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)
-		warning("dhcpsend: write failed: %r");
-}
-
-void
-dhcprecv(void)
-{
-	int i, n, type;
-	ulong lease;
-	char err[ERRMAX];
-	uchar 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)
-			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.
-			 */
-			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 = 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.
-			 */
-			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) */
-ulong
-randint(ulong low, ulong hi)
-{
-	if (hi < low)
-		return low;
-	return low + nrand(hi - low);
-}
-
-long
-jitter(void)		/* compute small pseudo-random delay in ms */
-{
-	return randint(0, 10*1000);
-}
-
+/* return true if this is not a null address */
 int
-openlisten(void)
+validip(uchar *addr)
 {
-	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;
+	return ipcmp(addr, IPnoaddr) != 0 && ipcmp(addr, v4prefix) != 0;
 }
 
-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;
-}
-
-/* add dhcp option op with value v of length n to dhcp option array p */
-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);
-	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.
- */
-uchar*
-optget(uchar *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(uchar *p, int op)
-{
-	int len;
-
-	len = 1;
-	p = optget(p, op, &len);
-	if(p == nil)
-		return 0;
-	return *p;
-}
-
-ulong
-optgetulong(uchar *p, int op)
-{
-	int len;
-
-	len = 4;
-	p = optget(p, op, &len);
-	if(p == nil)
-		return 0;
-	return nhgetl(p);
-}
-
-int
-optgetaddr(uchar *p, int op, uchar *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(uchar *p, int op, uchar *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(uchar *ap, int op, uchar *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(uchar *p, int op, uchar *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(uchar *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(uchar *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(uchar *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*
+/* put server ip addresses into the ndb entry */
+static char*
 putaddrs(char *p, char *e, char *attr, uchar *a, int len)
 {
 	int i;
@@ -1651,22 +633,20 @@
 	return p;
 }
 
-void
-refresh(void)
+/* put space separated names into ndb entry */
+static char*
+putnames(char *p, char *e, char *attr, char *s)
 {
-	char file[64];
-	int fd;
+	char *x;
 
-	snprint(file, sizeof file, "%s/cs", conf.mpoint);
-	if((fd = open(file, OWRITE)) >= 0){
-		write(fd, "refresh", 7);
-		close(fd);
+	for(; *s != 0; s = x+1){
+		if((x = strchr(s, ' ')) == nil)
+			x = strchr(s, 0);
+		p = seprint(p, e, "%s=%.*s\n", attr, (int)(x - s), s);
+		if(*x == 0)
+			break;
 	}
-	snprint(file, sizeof file, "%s/dns", conf.mpoint);
-	if((fd = open(file, OWRITE)) >= 0){
-		write(fd, "refresh", 7);
-		close(fd);
-	}
+	return p;
 }
 
 /* make an ndb entry and put it into /net/ndb for the servers to see */
@@ -1693,12 +673,14 @@
 	if(*conf.domainname)
 		p = seprint(p, e, "\tdom=%s.%s\n",
 			conf.hostname, conf.domainname);
+	if(*conf.dnsdomain)
+		p = putnames(p, e, "\tdnsdomain", conf.dnsdomain);
+	if(validip(conf.dns))
+		p = putaddrs(p, e, "\tdns", conf.dns, sizeof conf.dns);
 	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)
@@ -1730,25 +712,89 @@
 	close(fd);
 }
 
-/* return true if this is a valid v4 address */
-int
-validip(uchar *addr)
+static int
+issrcspec(uchar *src, uchar *smask)
 {
-	return ipcmp(addr, IPnoaddr) != 0 && ipcmp(addr, v4prefix) != 0;
+	return isv4(src)? memcmp(smask+IPv4off, IPnoaddr+IPv4off, 4): ipcmp(smask, IPnoaddr);
 }
 
-/* look for an action */
-int
-parseverb(char *name)
+void
+addroute(uchar *dst, uchar *mask, uchar *gate, uchar *ia, uchar *src, uchar *smask)
 {
-	int i;
+	char *cmd;
 
-	for(i = 0; i < nelem(verbs); i++)
-		if(verbs[i] != nil && strcmp(name, verbs[i]) == 0)
-			return i;
-	return -1;
+	if(issrcspec(src, smask))
+		cmd = "add %I %M %I %I %I %M";
+	else
+		cmd = "add %I %M %I %I";
+	fprint(conf.rfd, cmd, dst, mask, gate, ia, src, smask);
 }
 
+void
+removeroute(uchar *dst, uchar *mask, uchar *src, uchar *smask)
+{
+	char *cmd;
+
+	if(issrcspec(src, smask))
+		cmd = "remove %I %M %I %M";
+	else
+		cmd = "remove %I %M";
+	fprint(conf.rfd, cmd, dst, mask, src, smask);
+}
+
+void
+adddefroute(uchar *gaddr, uchar *ia, uchar *src, uchar *smask)
+{
+	uchar dst[IPaddrlen], mask[IPaddrlen];
+
+	if(isv4(gaddr)){
+		parseip(dst, "0.0.0.0");
+		parseipmask(mask, "0.0.0.0");
+		if(src == nil)
+			src = dst;
+		if(smask == nil)
+			smask = mask;
+	} else {
+		parseip(dst, "2000::");
+		parseipmask(mask, "/3");
+		if(src == nil)
+			src = IPnoaddr;
+		if(smask == nil)
+			smask = IPnoaddr;
+	}
+	addroute(dst, mask, gaddr, ia, src, smask);
+
+	/* also add a source specific route */
+	if(ipcmp(src, IPnoaddr) != 0 && ipcmp(src, v4prefix) != 0)
+		addroute(dst, mask, gaddr, ia, src, IPallbits);
+}
+
+void
+refresh(void)
+{
+	char file[64];
+	int 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);
+	}
+}
+
+void
+catch(void*, char *msg)
+{
+	if(strstr(msg, "alarm"))
+		noted(NCONT);
+	noted(NDFLT);
+}
+
 /*
  * based on libthread's threadsetname, but drags in less library code.
  * actually just sets the arguments displayed.
@@ -1774,6 +820,83 @@
 	free(cmdname);
 }
 
+/* return pseudo-random integer in range low...(hi-1) */
+ulong
+randint(ulong low, ulong hi)
+{
+	if (hi < low)
+		return low;
+	return low + nrand(hi - low);
+}
+
+long
+jitter(void)		/* compute small pseudo-random delay in ms */
+{
+	return randint(0, 10*1000);
+}
+
+int
+countaddrs(uchar *a, int len)
+{
+	int i;
+
+	for(i = 0; i < len && validip(a); i += IPaddrlen, a += IPaddrlen)
+		;
+	return i / IPaddrlen;
+}
+
+void
+addaddrs(uchar *to, int nto, uchar *from, int nfrom)
+{
+	int i, j;
+
+	for(i = 0; i < nfrom; i += IPaddrlen, from += IPaddrlen){
+		if(!validip(from))
+			continue;
+		for(j = 0; j < nto && validip(to+j); j += IPaddrlen){
+			if(ipcmp(to+j, from) == 0)
+				return;
+		}
+		if(j == nto)
+			return;
+		ipmove(to+j, from);
+	}
+}
+
+void
+addnames(char *d, char *s, int len)
+{
+	char *p, *e, *f;
+	int n;
+
+	for(;;s++){
+		if((e = strchr(s, ' ')) == nil)
+			e = strchr(s, 0);
+		n = e - s;
+		if(n == 0)
+			goto next;
+		for(p = d;;p++){
+			if((f = strchr(p, ' ')) == nil)
+				f = strchr(p, 0);
+			if(f - p == n && memcmp(s, p, n) == 0)
+				goto next;
+			p = f;
+			if(*p == 0)
+				break;
+		}
+		if(1 + n + p - d >= len)
+			break;
+		if(p > d)
+			*p++ = ' ';
+		p[n] = 0;
+		memmove(p, s, n);
+next:
+		s = e;
+		if(*s == 0)
+			break;
+	}
+}
+
 static Ndbtuple*
 uniquent(Ndbtuple *t)
 {
@@ -1792,148 +915,149 @@
 	return t;
 }
 
-/* get everything out of ndb */
+/* read configuration (except laddr) for myip from ndb */
 void
-ndbconfig(void)
+ndb2conf(Ndb *db, uchar *myip)
 {
-	int nattr, nauth = 0, ndns = 0, nfs = 0, nntp = 0, ok;
-	char etheraddr[32];
-	char *attrs[10];
-	Ndb *db;
+	int nattr;
+	char *attrs[10], val[64];
+	uchar ip[IPaddrlen];
 	Ndbtuple *t, *nt;
 
-	db = ndbopen(dbfile);
+	ipmove(conf.mask, defmask(conf.laddr));
+
+	memset(conf.gaddr, 0, sizeof(conf.gaddr));
+	memset(conf.dns, 0, sizeof(conf.dns));
+	memset(conf.ntp, 0, sizeof(conf.ntp));
+	memset(conf.fs, 0, sizeof(conf.fs));
+	memset(conf.auth, 0, sizeof(conf.auth));
+	memset(conf.dnsdomain, 0, sizeof(conf.dnsdomain));
+
 	if(db == nil)
-		sysfatal("can't open ndb: %r");
-	if (!isether() || myetheraddr(conf.hwa, conf.dev) != 0)
-		sysfatal("can't read hardware address");
-	sprint(etheraddr, "%E", conf.hwa);
+		return;
+
 	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);
+
+	attrs[nattr++] = "dnsdomain";
+
+	snprint(val, sizeof(val), "%I", myip);
+	t = ndbipinfo(db, "ip", val, attrs, nattr);
 	for(nt = t; nt != nil; nt = nt->entry) {
-		ok = 1;
-		if(strcmp(nt->attr, "ip") == 0)
-			ok = parseip(conf.laddr, uniquent(nt)->val);
-		else if(strcmp(nt->attr, "ipmask") == 0)
-			parseipmask(conf.mask, uniquent(nt)->val);  /* could be -1 */
-		else if(strcmp(nt->attr, "ipgw") == 0)
-			ok = parseip(conf.gaddr, uniquent(nt)->val);
-		else if(ndns < sizeof(conf.dns)/IPaddrlen && strcmp(nt->attr, "dns") == 0)
-			ok = parseip(conf.dns+IPaddrlen*ndns++, nt->val);
-		else if(nntp < sizeof(conf.ntp)/IPaddrlen && strcmp(nt->attr, "ntp") == 0)
-			ok = parseip(conf.ntp+IPaddrlen*nntp++, nt->val);
-		else if(nfs < sizeof(conf.fs)/IPaddrlen && strcmp(nt->attr, "fs") == 0)
-			ok = parseip(conf.fs+IPaddrlen*nfs++, nt->val);
-		else if(nauth < sizeof(conf.auth)/IPaddrlen && strcmp(nt->attr, "auth") == 0)
-			ok = parseip(conf.auth+IPaddrlen*nauth++, nt->val);
-		if(ok == -1)
+		if(strcmp(nt->attr, "dnsdomain") == 0) {
+			addnames(conf.dnsdomain, nt->val, sizeof(conf.dnsdomain));
+			continue;
+		}
+		if(strcmp(nt->attr, "ipmask") == 0) {
+			nt = uniquent(nt);
+			parseipmask(conf.mask, nt->val);  /* could be -1 */
+			continue;
+		}
+		if(parseip(ip, nt->val) == -1) {
 			fprint(2, "%s: bad %s address in ndb: %s\n", argv0,
 				nt->attr, nt->val);
+			continue;
+		}
+		if(strcmp(nt->attr, "ipgw") == 0) {
+			nt = uniquent(nt);
+			ipmove(conf.gaddr, ip);
+		} else if(strcmp(nt->attr, "dns") == 0) {
+			addaddrs(conf.dns, sizeof(conf.dns), ip, IPaddrlen);
+		} else if(strcmp(nt->attr, "ntp") == 0) {
+			addaddrs(conf.ntp, sizeof(conf.ntp), ip, IPaddrlen);
+		} else if(strcmp(nt->attr, "fs") == 0) {
+			addaddrs(conf.fs, sizeof(conf.fs), ip, IPaddrlen);
+		} else if(strcmp(nt->attr, "auth") == 0) {
+			addaddrs(conf.auth, sizeof(conf.auth), ip, IPaddrlen);
+		}
 	}
 	ndbfree(t);
-	if(!validip(conf.laddr))
-		sysfatal("address not found in ndb");
 }
 
-int
-addoption(char *opt)
+Ndb*
+opendatabase(void)
 {
-	int i;
-	Option *o;
+	static Ndb *db;
 
-	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;
+	if(db != nil)
+		ndbclose(db);
+	db = ndbopen(dbfile);
+	return db;
 }
 
-char*
-optgetx(uchar *p, uchar opt)
+/* add addresses for my ethernet address from ndb */
+static void
+ndbconfig(void)
 {
-	int i, n;
-	ulong x;
-	char *s, *ns;
-	char str[256];
-	uchar ip[IPaddrlen], ips[16*IPaddrlen], vec[256];
-	Option *o;
+	uchar ips[128*IPaddrlen];
+	char etheraddr[32], *attr;
+	Ndbtuple *t, *nt;
+	Ndb *db;
+	int n, i;
 
-	o = &option[opt];
-	if(o->name == nil)
-		return nil;
+	db = opendatabase();
+	if(db == nil)
+		sysfatal("can't open ndb: %r");
 
-	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;
+	if(validip(conf.laddr)){
+		ndb2conf(db, conf.laddr);
+		doadd();
+		return;
+	}
+
+	memset(ips, 0, sizeof(ips));
+
+	if(!isether() || myetheraddr(conf.hwa, conf.dev) != 0)
+		sysfatal("can't read hardware address");
+	snprint(etheraddr, sizeof(etheraddr), "%E", conf.hwa);
+
+	attr = "ip";
+	t = ndbipinfo(db, "ether", etheraddr, &attr, 1);
+	for(nt = t; nt != nil; nt = nt->entry) {
+		if(parseip(conf.laddr, nt->val) == -1){
+			fprint(2, "%s: bad %s address in ndb: %s\n", argv0,
+				nt->attr, nt->val);
+			continue;
 		}
-		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)
-			/* what's %H?  it's not installed */
-			s = smprint("%s=%.*H", o->name, n, vec);
-		break;
+		addaddrs(ips, sizeof(ips), conf.laddr, IPaddrlen);
 	}
-	return s;
-}
+	ndbfree(t);
 
-void
-getoptions(uchar *p)
-{
-	int i;
-	char *s, *t;
+	n = countaddrs(ips, sizeof(ips));
+	if(n == 0)
+		sysfatal("no ip addresses found in ndb");
 
-	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);
+	/* add link local address first, if not already done */
+	if(!validip(conf.lladdr) && !findllip(conf.lladdr, ifc)){
+		for(i = 0; i < n; i++){
+			ipmove(conf.laddr, ips+i*IPaddrlen);
+			if(ISIPV6LINKLOCAL(conf.laddr)){
+				ipv6auto = 0;
+				ipmove(conf.lladdr, conf.laddr);
+				ndb2conf(db, conf.laddr);
+				doadd();
+				break;
+			}
 		}
-		free(s);
+		if(ipv6auto){
+			ipmove(conf.laddr, IPnoaddr);
+			doadd();
+		}
+	}
+
+	/* add v4 addresses and v6 if link local address is available */
+	for(i = 0; i < n; i++){
+		ipmove(conf.laddr, ips+i*IPaddrlen);
+		if(isv4(conf.laddr)
+		|| validip(conf.lladdr) && ipcmp(conf.laddr, conf.lladdr) != 0){
+			ndb2conf(db, conf.laddr);
+			doadd();
+		}
 	}
 }
--- a/sys/src/cmd/ip/ipconfig/mkfile
+++ b/sys/src/cmd/ip/ipconfig/mkfile
@@ -4,6 +4,7 @@
 
 OFILES=\
 	main.$O\
+	dhcp.$O\
 	ipv6.$O\
 	ppp.$O\
 
--- a/sys/src/cmd/ip/ipconfig/ppp.c
+++ b/sys/src/cmd/ip/ipconfig/ppp.c
@@ -3,7 +3,6 @@
 #include <ip.h>
 #include <bio.h>
 #include <ndb.h>
-#include "../dhcp.h"
 #include "ipconfig.h"
 
 void
--- a/sys/src/cmd/ip/ppp/ppp.c
+++ b/sys/src/cmd/ip/ppp/ppp.c
@@ -1510,19 +1510,6 @@
 	close(fd);
 }
 
-enum
-{
-	Mofd=	32,
-};
-struct
-{
-	Lock;
-
-	int	fd[Mofd];
-	int	cfd[Mofd];
-	int	n;
-} old;
-
 static char*
 ipopen(PPP *ppp)
 {
--- a/sys/src/cmd/ndb/cs.c
+++ b/sys/src/cmd/ndb/cs.c
@@ -1386,6 +1386,20 @@
 	return parseip(ip, s) != -1 && isv4(ip);
 }
 
+static Ndbtuple*
+ndbline(Ndbtuple *t)
+{
+	Ndbtuple *nt;
+
+	for(nt = t; nt != nil; nt = nt->entry){
+		if(nt->entry == nil)
+			nt->line = t;
+		else
+			nt->line = nt->entry;
+	}
+	return t;
+}
+
 /*
  *  lookup an ip destination
  */
@@ -1410,13 +1424,13 @@
 
 	/* for dial strings with no host */
 	if(strcmp(host, "*") == 0)
-		return ndbnew("ip", "*");
+		return ndbline(ndbnew("ip", "*"));
 
 	/*
 	 *  hack till we go v6 :: = 0.0.0.0
 	 */
 	if(strcmp("::", host) == 0)
-		return ndbnew("ip", "*");
+		return ndbline(ndbnew("ip", "*"));
 
 	/*
 	 *  just accept addresses
@@ -1423,7 +1437,7 @@
 	 */
 	attr = ipattr(host);
 	if(strcmp(attr, "ip") == 0)
-		return ndbnew("ip", host);
+		return ndbline(ndbnew("ip", host));
 
 	/*
 	 *  give the domain name server the first opportunity to
@@ -1627,14 +1641,8 @@
 		if (strcmp(tt->attr, "ipv6") == 0)
 			strcpy(tt->attr, "ip");
 
-	if (t == nil)
-		return t6;
-
 	/* append t6 list to t list */
-	for (tt = t; tt->entry != nil; tt = tt->entry)
-		;
-	tt->entry = t6;
-	return t;
+	return ndbconcatenate(t, t6);
 }
 
 /*
@@ -1911,12 +1919,7 @@
 	}
 
 	/* make it all one line */
-	for(nt = t; nt != nil; nt = nt->entry){
-		if(nt->entry == nil)
-			nt->line = t;
-		else
-			nt->line = nt->entry;
-	}
+	t = ndbline(t);
 
 	qreply(mf, t);
 
--- a/sys/src/cmd/ndb/csquery.c
+++ b/sys/src/cmd/ndb/csquery.c
@@ -16,7 +16,7 @@
 void
 query(char *addr)
 {
-	char buf[8192];
+	char buf[8192+1];
 	int fd, n;
 
 	fd = open(server, ORDWR);
@@ -31,8 +31,10 @@
 	}
 	if(!statusonly){
 		seek(fd, 0, 0);
-		while((n = read(fd, buf, sizeof(buf))) > 0)
+		while((n = read(fd, buf, sizeof(buf)-1)) > 0){
+			buf[n++] = '\n';
 			write(1, buf, n);
+		}
 		write(1, "\n", 1);
 	}
 	close(fd);
--- a/sys/src/libndb/csgetval.c
+++ b/sys/src/libndb/csgetval.c
@@ -14,15 +14,12 @@
 csgetvalue(char *netroot, char *attr, char *val, char *rattr, Ndbtuple **pp)
 {
 	Ndbtuple *t, *first, *last;
-	int n, linefound;
 	char line[1024];
-	int fd;
-	int oops = 0;
+	int fd, n;
 	char *rv;
 
-	if(pp)
+	if(pp != nil)
 		*pp = nil;
-	rv = nil;
 
 	if(netroot)
 		snprint(line, sizeof(line), "%s/cs", netroot);
@@ -30,17 +27,17 @@
 		strcpy(line, "/net/cs");
 	fd = open(line, ORDWR);
 	if(fd < 0)
-		return 0;
+		return nil;
 	seek(fd, 0, 0);
 	snprint(line, sizeof(line), "!%s=%s %s=*", attr, val, rattr);
 	if(write(fd, line, strlen(line)) < 0){
 		close(fd);
-		return 0;
+		return nil;
 	}
 	seek(fd, 0, 0);
 
-	first = last = 0;
-	linefound = 0;
+	rv = nil;
+	first = last = nil;
 	for(;;){
 		n = read(fd, line, sizeof(line)-2);
 		if(n <= 0)
@@ -49,35 +46,22 @@
 		line[n+1] = 0;
 
 		t = _ndbparseline(line);
-		if(t == 0)
+		if(t == nil)
 			continue;
-		if(first)
+		if(first != nil)
 			last->entry = t;
 		else
 			first = t;
-		last = t;
-
-		while(last->entry)
-			last = last->entry;
-
-		for(; t; t = t->entry){
-			if(linefound == 0){
-				if(strcmp(rattr, t->attr) == 0){
-					linefound = 1;
-					rv = strdup(t->val);
-				}
-			}
-		}
+		do {
+			last = t;
+			if(rv == nil && strcmp(rattr, t->attr) == 0)
+				rv = strdup(t->val);
+			t = t->entry;
+		} while(t != nil);
 	}
 	close(fd);
 
-	if(oops){
-		werrstr("buffer too short");
-		ndbfree(first);
-		return nil;
-	}
-
-	if(pp){
+	if(pp != nil){
 		setmalloctag(first, getcallerpc(&netroot));
 		*pp = first;
 	} else
--- a/sys/src/libndb/dnsquery.c
+++ b/sys/src/libndb/dnsquery.c
@@ -129,17 +129,14 @@
 
 		t = _ndbparseline(buf);
 		if(t != nil){
-			if(first)
+			if(first != nil)
 				last->entry = t;
 			else
 				first = t;
 			last = t;
-
-			while(last->entry)
+			while(last->entry != nil)
 				last = last->entry;
 		}
 	}
-
-	ndbsetmalloctag(first, getcallerpc(&fd));
 	return first;
 }
--- a/sys/src/libndb/ndbdiscard.c
+++ b/sys/src/libndb/ndbdiscard.c
@@ -25,6 +25,5 @@
 	a->entry = nil;
 	ndbfree(a);
 
-	ndbsetmalloctag(t, getcallerpc(&t));
 	return t;
 }
--- a/sys/src/libndb/ndbfree.c
+++ b/sys/src/libndb/ndbfree.c
@@ -71,6 +71,6 @@
 void
 ndbsetmalloctag(Ndbtuple *t, uintptr tag)
 {
-	for(; t; t=t->entry)
+	for(; t != nil; t=t->entry)
 		setmalloctag(t, tag);
 }
--- a/sys/src/libndb/ndbgetval.c
+++ b/sys/src/libndb/ndbgetval.c
@@ -7,7 +7,7 @@
  *  search for a tuple that has the given 'attr=val' and also 'rattr=x'.
  *  copy 'x' into 'buf' and return the whole tuple.
  *
- *  return 0 if not found.
+ *  return nil if not found.
  */
 char*
 ndbgetvalue(Ndb *db, Ndbs *s, char *attr, char *val, char *rattr, Ndbtuple **pp)
@@ -21,32 +21,15 @@
 	if(pp)
 		*pp = nil;
 	t = ndbsearch(db, s, attr, val);
-	while(t){
-		/* first look on same line (closer binding) */
-		nt = s->t;
-		for(;;){
-			if(strcmp(rattr, nt->attr) == 0){
-				rv = strdup(nt->val);
-				if(pp != nil)
-					*pp = t;
-				else
-					ndbfree(t);
-				return rv;
-			}
-			nt = nt->line;
-			if(nt == s->t)
-				break;
-		}
-		/* search whole tuple */
-		for(nt = t; nt; nt = nt->entry){
-			if(strcmp(rattr, nt->attr) == 0){
-				rv = strdup(nt->val);
-				if(pp != nil)
-					*pp = t;
-				else
-					ndbfree(t);
-				return rv;
-			}
+	while(t != nil){
+		nt = ndbfindattr(t, s->t, rattr);
+		if(nt != nil){
+			rv = strdup(nt->val);
+			if(pp != nil)
+				*pp = t;
+			else
+				ndbfree(t);
+			return rv;
 		}
 		ndbfree(t);
 		t = ndbsnext(s, attr, val);
--- a/sys/src/libndb/ndbhash.c
+++ b/sys/src/libndb/ndbhash.c
@@ -123,26 +123,22 @@
 	memset(s, 0, sizeof(*s));
 	if(_ndbcachesearch(db, s, attr, val, &t) == 0){
 		/* found in cache */
-		if(t != nil){
-			ndbsetmalloctag(t, getcallerpc(&db));
-			return t;	/* answer from this file */
-		}
+		if(t != nil)
+			goto out;
 		if(db->next == nil)
 			return nil;
 		t = ndbsearch(db->next, s, attr, val);
-		ndbsetmalloctag(t, getcallerpc(&db));
-		return t;
+		goto out;
 	}
 
 	s->db = db;
 	s->hf = hf;
-	if(s->hf){
+	if(s->hf != nil){
 		s->ptr = ndbhash(val, s->hf->hlen)*NDBPLEN;
 		p = hfread(s->hf, s->ptr+NDBHLEN, NDBPLEN);
-		if(p == 0){
+		if(p == nil){
 			t = _ndbcacheadd(db, s, attr, val, nil);
-			ndbsetmalloctag(t, getcallerpc(&db));
-			return t;
+			goto out;
 		}
 		s->ptr = NDBGETP(p);
 		s->type = Cptr1;
@@ -153,11 +149,10 @@
 		/* advance search to next db file */
 		s->ptr = NDBNAP;
 		_ndbcacheadd(db, s, attr, val, nil);
-		if(db->next == 0)
+		if(db->next == nil)
 			return nil;
 		t = ndbsearch(db->next, s, attr, val);
-		ndbsetmalloctag(t, getcallerpc(&db));
-		return t;
+		goto out;
 	} else {
 		s->ptr = 0;
 		s->type = Dptr;
@@ -164,6 +159,7 @@
 	}
 	t = ndbsnext(s, attr, val);
 	_ndbcacheadd(db, s, attr, val, (t != nil && s->db == db)?t:nil);
+out:
 	ndbsetmalloctag(t, getcallerpc(&db));
 	return t;
 }
@@ -173,7 +169,7 @@
 {
 	Ndbtuple *nt;
 
-	for(nt = t; nt; nt = nt->entry)
+	for(nt = t; nt != nil; nt = nt->entry)
 		if(strcmp(attr, nt->attr) == 0
 		&& strcmp(val, nt->val) == 0)
 			return nt;
@@ -200,12 +196,10 @@
 				break;
 			t = ndbparse(db);
 			s->ptr = Boffset(&db->b);
-			if(t == 0)
+			if(t == nil)
 				break;
-			if(s->t = match(t, attr, val)){
-				ndbsetmalloctag(t, getcallerpc(&s));
-				return t;
-			}
+			if((s->t = match(t, attr, val)) != nil)
+				goto out;
 			ndbfree(t);
 		} else if(s->type == Cptr){
 			if(Bseek(&db->b, s->ptr, 0) < 0)
@@ -213,18 +207,16 @@
 			s->ptr = s->ptr1;
 			s->type = Cptr1;
 			t = ndbparse(db);
-			if(t == 0)
+			if(t == nil)
 				break;
-			if(s->t = match(t, attr, val)){
-				ndbsetmalloctag(t, getcallerpc(&s));
-				return t;
-			}
+			if((s->t = match(t, attr, val)) != nil)
+				goto out;
 			ndbfree(t);
 		} else if(s->type == Cptr1){
 			if(s->ptr & NDBCHAIN){	/* hash chain continuation */
 				s->ptr &= ~NDBCHAIN;
 				p = hfread(s->hf, s->ptr+NDBHLEN, 2*NDBPLEN);
-				if(p == 0)
+				if(p == nil)
 					break;
 				s->ptr = NDBGETP(p);
 				s->ptr1 = NDBGETP(p+NDBPLEN);
@@ -234,12 +226,10 @@
 					break; 
 				s->ptr = NDBNAP;
 				t = ndbparse(db);
-				if(t == 0)
+				if(t == nil)
 					break;
-				if(s->t = match(t, attr, val)){
-					ndbsetmalloctag(t, getcallerpc(&s));
-					return t;
-				}
+				if((s->t = match(t, attr, val)) != nil)
+					goto out;
 				ndbfree(t);
 				break;
 			}
@@ -247,14 +237,14 @@
 	}
 
 nextfile:
-
 	/* nothing left to search? */
 	s->ptr = NDBNAP;
-	if(db->next == 0)
-		return 0;
+	if(db->next == nil)
+		return nil;
 
 	/* advance search to next db file */
 	t = ndbsearch(db->next, s, attr, val);
+out:
 	ndbsetmalloctag(t, getcallerpc(&s));
 	return t;
 }
--- a/sys/src/libndb/ndbparse.c
+++ b/sys/src/libndb/ndbparse.c
@@ -30,9 +30,9 @@
 	Ndbtuple *first, *last;
 	int len;
 
-	first = last = 0;
+	first = last = nil;
 	for(;;){
-		if((line = Brdline(&db->b, '\n')) == 0)
+		if((line = Brdline(&db->b, '\n')) == nil)
 			break;
 		len = Blinelen(&db->b);
 		if(line[len-1] != '\n')
@@ -42,15 +42,15 @@
 			break;
 		}
 		t = _ndbparseline(line);
-		if(t == 0)
+		if(t == nil)
 			continue;
 		setmalloctag(t, getcallerpc(&db));
-		if(first)
+		if(first != nil)
 			last->entry = t;
 		else
 			first = t;
 		last = t;
-		while(last->entry)
+		while(last->entry != nil)
 			last = last->entry;
 	}
 	ndbsetmalloctag(first, getcallerpc(&db));
--- a/sys/src/libndb/ndbreorder.c
+++ b/sys/src/libndb/ndbreorder.c
@@ -4,7 +4,7 @@
 #include <ndb.h>
 
 /*
- *  reorder the tuple to put x's line first in the entry and x fitst in its line
+ *  reorder the tuple to put x's line first in the entry and x first in its line
  */
 Ndbtuple*
 ndbreorder(Ndbtuple *t, Ndbtuple *x)