git: 9front

Download patch

ref: c12f73cb4c31d9f6fc3f6ab6bb2ddd86531d24a8
parent: 733db65e8deda366890074444119919ac75c8ca6
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Mon Oct 28 15:06:47 EDT 2024

ip/ipconfig: implement dhcpv6 prefix delegation, dynamic client

Implement prefix delegation by requesting a
prefix and populate ipnet=val entry (val
passed from -i option).

Before, DHCPv6 was just implemented for stateless
one-shot operation, just exiting once we got out
IA address.

Moodies mediacom-enterprise-enterprise-ISP...
... they actually do enterprise-grade dyanmic dhcpv6
so here we are, implementing renewals...

--- a/sys/man/8/ipconfig
+++ b/sys/man/8/ipconfig
@@ -16,6 +16,8 @@
 .IR gateway ]
 .RB [ -h
 .IR host ]
+.BR [ -i
+.IR ipnet ]
 .RB [ -m
 .IR mtu ]
 .RB [ -o
@@ -191,6 +193,15 @@
 the hostname to add to DHCP requests.  Some DHCP
 servers, such as the one used by Comcast, will not respond
 unless a correct hostname is in the request.
+.TP
+.B i
+when writing
+.B /net/ndb
+configuration, create an
+.B ipnet=
+tuple with the value of
+.I ipnet
+and the gathered network attributes.
 .TP
 .B m
 the maximum IP packet size to use on this interface.
--- a/sys/src/cmd/ip/ipconfig/dhcpv6.c
+++ b/sys/src/cmd/ip/ipconfig/dhcpv6.c
@@ -63,16 +63,60 @@
 }
 
 static int
-transaction(int fd, int type, int timeout)
+findopt(int id, uchar **sp, uchar *e)
 {
+	uchar *p;
+	int opt;
+	int len;
+
+	p = *sp;
+	while(p + 4 <= e) {
+		opt = (int)p[0] << 8 | p[1];
+		len = (int)p[2] << 8 | p[3];
+		p += 4;
+		if(p + len > e)
+			break;
+		if(opt == id){
+			*sp = p;
+			return len;
+		}
+		p += len;
+	}
+	return -1;
+}
+
+static int
+badstatus(int type, int opt, uchar *p, uchar *e)
+{
+	int len, status;
+
+	len = findopt(13, &p, e);
+	if(len < 0)
+		return 0;
+	if(len < 2)
+		return 1;
+	status = (int)p[0] << 8 | p[1];
+	if(status == 0)
+		return 0;
+	warning("dhcpv6: bad status in request/response type %x, option %d, status %d: %.*s",
+		type, opt, status, len - 2, (char*)p + 2);
+	return status;
+}
+
+static int
+transaction(int fd, int type, int irt, int retrans, int timeout)
+{
 	union {
 		Udphdr;
 		uchar	buf[4096];
 	} ipkt, opkt;
 
+	int tra, opt, len, status, sleepfor, jitter;
 	uchar *p, *e, *x;
-	int tra, opt, len, sleepfor;
+	ulong t1, apflt;
 
+	conf.lease = ~0UL;	/* infinity */
+
 	tra = lrand() & 0xFFFFFF;
 
 	ipmove(opkt.laddr, conf.lladdr);
@@ -98,8 +142,8 @@
 
 	/* IA for non-temporary address */
 	len = 12;
-	if(validip(conf.laddr))
-		len += 4 + IPaddrlen + 2*4;
+	if(validv6prefix(conf.laddr))
+		len += 4 + IPaddrlen+2*4;
 	*p++ = 0x00; *p++ = 0x03;
 	*p++ = len >> 8;
 	*p++ = len;
@@ -110,7 +154,7 @@
 	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
 	if(len > 12){
 		*p++ = 0x00; *p++ = 0x05;
-		*p++ = 0x00; *p++ = IPaddrlen + 2*4;
+		*p++ = 0x00; *p++ = IPaddrlen+2*4;
 		memmove(p, conf.laddr, IPaddrlen);
 		p += IPaddrlen;
 		memset(p, 0xFF, 2*4);
@@ -117,12 +161,46 @@
 		p += 2*4;
 	}
 
+	/* IA for prefix delegation */
+	len = 12;
+	if(validv6prefix(conf.v6pref))
+		len += 4 + 2*4+1+IPaddrlen;
+	*p++ = 0x00; *p++ = 0x19;
+	*p++ = len >> 8;
+	*p++ = len;
+	/* IAID */
+	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x02;	/* lies */
+	/* T1, T2 */
+	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
+	*p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
+	if(len > 12){
+		*p++ = 0x00; *p++ = 0x1a;
+		*p++ = 0x00; *p++ = 2*4+1+IPaddrlen;
+
+		*p++ = conf.preflt >> 24;
+		*p++ = conf.preflt >> 16;
+		*p++ = conf.preflt >> 8;
+		*p++ = conf.preflt;
+
+		*p++ = conf.validlt >> 24;
+		*p++ = conf.validlt >> 16;
+		*p++ = conf.validlt >> 8;
+		*p++ = conf.validlt;
+
+		*p++ = conf.prefixlen;
+
+		ipmove(p, conf.v6pref);
+		p += IPaddrlen;
+	}
+
 	/* Option Request */
 	*p++ = 0x00; *p++ = 0x06;
 	*p++ = 0x00; *p++ = 0x02;
 	*p++ = 0x00; *p++ = 0x17;	/* DNS servers */
 
-	if(sidlen > 0){
+	/* server identifier */
+	if(sidlen > 0
+	&& type != SOLICIT && type != CONFIRM && type != REBIND){
 		*p++ = 0x00; *p++ = 0x02;
 		/* len */
 		*p++ = sidlen >> 8;
@@ -132,9 +210,13 @@
 	}
 
 	len = -1;
-	for(sleepfor = 500; timeout > 0; sleepfor <<= 1){
+	for(sleepfor = irt; timeout > 0; sleepfor <<= 1){
 		DEBUG("sending dhcpv6 request %x", opkt.buf[Udphdrsize]);
 
+		jitter = sleepfor / 10;
+		if(jitter > 1)
+			sleepfor += nrand(jitter);
+
 		alarm(sleepfor);
 		if(len < 0)
 			write(fd, opkt.buf, p - opkt.buf);
@@ -144,9 +226,14 @@
 		timeout -= sleepfor;
 		if(len == 0)
 			break;
-
+		if(len < 0){
+			if(--retrans == 0)
+				break;
+			continue;
+		}
 		if(len < Udphdrsize+4)
 			continue;
+
 		if(ipkt.buf[Udphdrsize+1] != ((tra>>16)&0xFF)
 		|| ipkt.buf[Udphdrsize+2] != ((tra>>8)&0xFF)
 		|| ipkt.buf[Udphdrsize+3] != ((tra>>0)&0xFF))
@@ -158,6 +245,8 @@
 		switch(type){
 		case ADVERTISE << 8 | SOLICIT:
 		case REPLY << 8 | REQUEST:
+		case REPLY << 8 | RENEW:
+		case REPLY << 8 | REBIND:
 			goto Response;
 		default:
 			return -1;
@@ -177,74 +266,174 @@
 		if (x > e)
 			return -1;
 
-		DEBUG("got dhcpv6 option %x: [%d] %.*H", opt, len, len, p);
+		DEBUG("got dhcpv6 option %d: [%d] %.*H", opt, len, len, p);
 
 		switch(opt){
-		case 0x01:		/* client identifier */
+		case 1:		/* client identifier */
 			continue;
-		case 0x02:		/* server identifier */
+		case 2:		/* server identifier */
 			if(len < 1 || len > sizeof(sid))
 				break;
 			sidlen = len;
 			memmove(sid, p, sidlen);
 			continue;
-		case 0x03:		/* IA for non-temporary address */
+		case 3:		/* IA for non-temporary address */
 			if(p+12 > x)
 				break;
+
+			t1 =	(ulong)p[4] << 24 |
+				(ulong)p[5] << 16 |
+				(ulong)p[6] << 8 |
+				(ulong)p[7];
+
 			/* skip IAID, T1, T2 */
 			p += 12;
-			/* find IA Address option */
-			while(p + 4 <= x) {
-				opt = (int)p[0] << 8 | p[1];
-				len = (int)p[2] << 8 | p[3];
-				p += 4;
-				if(p + len > x)
-					break;
-				if(opt == 5)
-					break;
-				p += len;
-			}
+
+			status = badstatus(type, opt, p, x);
+			if(status != 0)
+				return -status;
+
 			/* IA Addresss */
-			if(opt != 5)
+			if(findopt(5, &p, x) < IPaddrlen + 2*4)
 				break;
-			if(len < IPaddrlen)
-				break;
-			memmove(conf.laddr, p, IPaddrlen);
+
+			ipmove(conf.laddr, p);
+			memset(conf.mask, 0xFF, IPaddrlen);
+			p += IPaddrlen;
+
+			/* preferred lifetime of IA Address */
+			apflt =	(ulong)p[0] << 24 |
+				(ulong)p[1] << 16 |
+				(ulong)p[2] << 8 |
+				(ulong)p[3];
+
+			/* adjust lease */
+			if(t1 != 0 && t1 < conf.lease)
+				conf.lease = t1;
+			if(apflt != 0 && apflt < conf.lease)
+				conf.lease = apflt;
+
 			continue;
-		case 0x17:	/* dns servers */
+		case 13:		/* status */
+			status = badstatus(type, opt, p - 4, x);
+			if(status != 0)
+				return -status;
+			continue;
+		case 23:		/* dns servers */
 			if(len % IPaddrlen)
 				break;
 			addaddrs(conf.dns, sizeof(conf.dns), p, len);
 			continue;
+		case 25:		/* IA for prefix delegation */
+			if(p+12 > x)
+				break;
+
+			t1 =	(ulong)p[4] << 24 |
+				(ulong)p[5] << 16 |
+				(ulong)p[6] << 8 |
+				(ulong)p[7];
+
+			/* skip IAID, T1, T2 */
+			p += 12;
+
+			status = badstatus(type, opt, p, x);
+			if(status != 0){
+				if(type == (ADVERTISE << 8 | SOLICIT))
+					continue;
+				return -status;
+			}
+
+			/* IA Prefix */
+			if(findopt(26, &p, x) < 2*4+1+IPaddrlen)
+				break;
+
+			conf.preflt = 	(ulong)p[0] << 24 |
+					(ulong)p[1] << 16 |
+					(ulong)p[2] << 8 |
+					(ulong)p[3];
+			conf.validlt = 	(ulong)p[4] << 24 |
+					(ulong)p[5] << 16 |
+					(ulong)p[6] << 8 |
+					(ulong)p[7];
+			p += 8;
+			if(conf.preflt > conf.validlt)
+				break;
+
+			conf.prefixlen = *p++ & 127;
+			genipmask(conf.v6mask, conf.prefixlen);
+			maskip(p, conf.v6mask, conf.v6pref);
+
+			/* adjust lease */
+			if(t1 != 0 && t1 < conf.lease)
+				conf.lease = t1;
+			if(conf.preflt != 0 && conf.preflt < conf.lease)
+				conf.lease = conf.preflt;
+
+			continue;
 		default:
-			DEBUG("unknown dhcpv6 option %x", opt);
+			DEBUG("unknown dhcpv6 option: %d", opt);
 			continue;
 		}
-		warning("dhcpv6: malformed option %x: [%d] %.*H", opt, len, len, x-len);
+		warning("dhcpv6: malformed option %d: [%d] %.*H", opt, len, len, x-len);
 	}
-
 	return 0;
 }
 
-void
-dhcpv6query(void)
+int
+dhcpv6query(int renew)
 {
 	int fd;
 
-	ipmove(conf.laddr, IPnoaddr);
-	memset(conf.mask, 0xFF, IPaddrlen);
+	if(!renew){
+		ipmove(conf.laddr, IPnoaddr);
+		ipmove(conf.v6pref, IPnoaddr);
+		ipmove(conf.v6mask, IPnoaddr);
+		conf.prefixlen = 0;
+		conf.preflt = 0;
+		conf.validlt = 0;
+		conf.autoflag = 0;
+		conf.onlink = 0;
+	}
 
 	if(conf.duidlen <= 0)
-		return;
+		return -1;
+
 	fd = openlisten();
 	if(fd < 0)
-		return;
-	if(transaction(fd, SOLICIT, 5000) < 0)
-		goto out;
-	if(!validip(conf.laddr))
-		goto out;
-	if(transaction(fd, REQUEST, 10000) < 0)
-		goto out;
-out:
+		return -1;
+
+	if(renew){
+		if(!validv6prefix(conf.laddr))
+			goto fail;
+		/*
+		 * the standard says 600 seconds for maxtimeout,
+		 * but this seems ridiculous. better start over.
+		 */
+		if(transaction(fd, RENEW, 10*1000, 0, 30*1000) < 0){
+			if(!validv6prefix(conf.laddr))
+				goto fail;
+			if(transaction(fd, REBIND, 10*1000, 0, 30*1000) < 0)
+				goto fail;
+		}
+	} else {
+		/*
+		 * the standard says SOL_MAX_RT is 3600 seconds,
+		 * but it is better to fail quickly here and wait
+		 * for the next router advertisement.
+		 */
+		if(transaction(fd, SOLICIT, 1000, 0, 10*1000) < 0)
+			goto fail;
+		if(!validv6prefix(conf.laddr))
+			goto fail;
+		if(transaction(fd, REQUEST, 1000, 10, 30*1000) < 0)
+			goto fail;
+	}
+	if(!validv6prefix(conf.laddr))
+		goto fail;
 	close(fd);
+	return 0;
+fail:
+	close(fd);
+	return -1;
+
 }
--- a/sys/src/cmd/ip/ipconfig/ipconfig.h
+++ b/sys/src/cmd/ip/ipconfig/ipconfig.h
@@ -45,6 +45,7 @@
 	uchar	laddr[IPaddrlen];
 	uchar	mask[IPaddrlen];
 	uchar	raddr[IPaddrlen];
+
 	uchar	dns[8*IPaddrlen];
 	uchar	fs[2*IPaddrlen];
 	uchar	auth[2*IPaddrlen];
@@ -69,6 +70,7 @@
 	/*
 	 * IPv6
 	 */
+	uchar	v6router[IPaddrlen];
 
 	/* router-advertisement related */
 	uchar	sendra;
@@ -86,6 +88,7 @@
 	/* prefix related */
 	uchar	lladdr[IPaddrlen];
 	uchar	v6pref[IPaddrlen];
+	uchar	v6mask[IPaddrlen];
 	int	prefixlen;
 	uchar	onlink;		/* flag: address is `on-link' */
 	uchar	autoflag;	/* flag: autonomous */
@@ -118,13 +121,14 @@
 extern int	nodhcpwatch;
 extern int	sendhostname;
 extern char	*ndboptions;
+extern char	*ipnet;		/* put ipnet= tuple in ndb for raddr */
 
 void	usage(void);
 int	ip4cfg(void);
 void	ipunconfig(void);
 
-void	adddefroute(uchar*, uchar*, uchar*, uchar*);
-void	deldefroute(uchar*, uchar*, uchar*, uchar*);
+void	adddefroute(uchar*, uchar*, uchar*, uchar*, uchar*);
+void	deldefroute(uchar*, uchar*, uchar*, uchar*, uchar*);
 
 int	myip(Ipifc*, uchar*);
 int	isether(void);
@@ -163,9 +167,10 @@
 void	ea2lla(uchar *lla, uchar *ea);
 int	findllip(uchar *ip, Ipifc *ifc);
 int	ip6cfg(void);
+int	validv6prefix(uchar *ip);
+void	genipmask(uchar *mask, int len);
 
 /*
  * DHCPv6
  */
-void	dhcpv6init(void);
-void	dhcpv6query(void);
+int	dhcpv6query(int);
--- a/sys/src/cmd/ip/ipconfig/ipv6.c
+++ b/sys/src/cmd/ip/ipconfig/ipv6.c
@@ -198,6 +198,7 @@
 			sysfatal("bad address %s", argv[0]);
 		break;
 	}
+	genipmask(conf.v6mask, conf.prefixlen);
 	DEBUG("parse6pref: pref %I len %d", conf.v6pref, conf.prefixlen);
 }
 
@@ -391,7 +392,7 @@
 		if(validip(conf.gaddr) && !isv4(conf.gaddr)
 		&& ipcmp(conf.gaddr, conf.laddr) != 0
 		&& ipcmp(conf.gaddr, conf.lladdr) != 0)
-			adddefroute(conf.gaddr, conf.laddr, conf.laddr, conf.mask);
+			adddefroute(conf.gaddr, conf.laddr, conf.laddr, conf.raddr, conf.mask);
 		return 0;
 	}
 
@@ -530,8 +531,8 @@
 	return len;
 }
 
-static void
-genipmkask(uchar *mask, int len)
+void
+genipmask(uchar *mask, int len)
 {
 	memset(mask, 0, IPaddrlen);
 	if(len < 0)
@@ -563,11 +564,27 @@
 
 static Route	*routelist;
 
+int
+validv6prefix(uchar *ip)
+{
+	if(!validip(ip))
+		return 0;
+	if(isv4(ip))
+		return 0;
+	if(ipcmp(ip, v6loopback) == 0)
+		return 0;
+	if(ISIPV6MCAST(ip))
+		return 0;
+	if(ISIPV6LINKLOCAL(ip))
+		return 0;
+	return 1;
+}
+
 /*
  * host receiving a router advertisement calls this
  */
 static int
-recvrahost(uchar buf[], int pktlen)
+recvrahost(uchar buf[], int pktlen, ulong now)
 {
 	char dnsdomain[sizeof(conf.dnsdomain)];
 	int m, n, optype, seen;
@@ -576,9 +593,9 @@
 	Prefixopt *prfo;
 	Ipaddrsopt *addrso;
 	Routeradv *ra;
+	uchar raddr[IPaddrlen];
 	uchar hash[SHA1dlen];
 	Route *r, **rr;
-	ulong now;
 
 	m = sizeof *ra;
 	ra = (Routeradv*)buf;
@@ -590,6 +607,13 @@
 
 	DEBUG("got RA from %I on %s; flags %x", ra->src, conf.dev, ra->mor);
 
+	/*
+	 * ignore all non-managed-flag router-advertisements
+	 * when dhcpv6 is active so we do not trash conf data.
+	 */
+	if(dodhcp && conf.state && (MFMASK & ra->mor) == 0)
+		return -1;
+
 	conf.ttl = ra->cttl;
 	conf.mflag = (MFMASK & ra->mor) != 0;
 	conf.oflag = (OCMASK & ra->mor) != 0;
@@ -598,6 +622,8 @@
 	conf.rxmitra = nhgetl(ra->rxmtimer);
 	conf.linkmtu = DEFMTU;
 
+	ipmove(conf.v6router, conf.routerlt? ra->src: IPnoaddr);
+
 	memset(conf.dns, 0, sizeof(conf.dns));
 	memset(conf.fs, 0, sizeof(conf.fs));
 	memset(conf.auth, 0, sizeof(conf.auth));
@@ -657,8 +683,6 @@
 
 	issuebasera6(&conf);
 
-	now = time(nil);
-
 	/* remove expired default routes */
 	m = 0;
 	for(rr = &routelist; (r = *rr) != nil;){
@@ -667,8 +691,10 @@
 		|| r->routerlt != ~0UL && r->routerlt < now-r->time){
 			DEBUG("purging RA from %I on %s; pfx %I %M",
 				r->src, conf.dev, r->laddr, r->mask);
-			if(!noconfig && validip(r->gaddr))
-				deldefroute(r->gaddr, conf.lladdr, r->laddr, r->mask);
+			if(validip(r->gaddr)){
+				maskip(r->laddr, r->mask, raddr);
+				deldefroute(r->gaddr, conf.lladdr, r->laddr, raddr, r->mask);
+			}
 			*rr = r->next;
 			free(r);
 			continue;
@@ -680,6 +706,10 @@
 	/* remove expired prefixes */
 	issuedel6(&conf);
 
+	/* managed netork: prefixes are acquired with dhcpv6 */
+	if(dodhcp && conf.mflag)
+		return 0;
+
 	/* process new prefixes */
 	m = sizeof *ra;
 	while(pktlen - m >= 8) {
@@ -704,18 +734,15 @@
 		|| conf.prefixlen > 64)
 			continue;
 
-		genipmkask(conf.mask, conf.prefixlen);
-		maskip(prfo->pref, conf.mask, conf.v6pref);
-		if(!validip(conf.v6pref)
-		|| isv4(conf.v6pref)
-		|| ipcmp(conf.v6pref, v6loopback) == 0
-		|| ISIPV6MCAST(conf.v6pref)
-		|| ISIPV6LINKLOCAL(conf.v6pref))
+		genipmask(conf.v6mask, conf.prefixlen);
+		maskip(prfo->pref, conf.v6mask, conf.v6pref);
+		if(!validv6prefix(conf.v6pref))
 			continue;
-
+		ipmove(conf.raddr, conf.v6pref);
+		ipmove(conf.mask, conf.v6mask);
 		memmove(conf.laddr, conf.v6pref, 8);
 		memmove(conf.laddr+8, conf.lladdr+8, 8);
-		ipmove(conf.gaddr, (prfo->lar & RFMASK) != 0? prfo->pref: ra->src);
+		ipmove(conf.gaddr, (prfo->lar & RFMASK) != 0? prfo->pref: conf.v6router);
 		conf.onlink = (prfo->lar & OLMASK) != 0;
 		conf.autoflag = (prfo->lar & AFMASK) != 0;
 		conf.validlt = nhgetl(prfo->validlt);
@@ -723,8 +750,7 @@
 		if(conf.preflt > conf.validlt)
 			continue;
 
-		if(conf.routerlt == 0
-		|| conf.preflt == 0
+		if(conf.preflt == 0
 		|| isula(conf.laddr)
 		|| ipcmp(conf.gaddr, conf.laddr) == 0
 		|| ipcmp(conf.gaddr, conf.lladdr) == 0)
@@ -754,8 +780,8 @@
 			seen = memcmp(r->hash, hash, SHA1dlen) == 0;
 			if(!seen && validip(r->gaddr) && ipcmp(r->gaddr, conf.gaddr) != 0){
 				DEBUG("changing router %I->%I", r->gaddr, conf.gaddr);
-				if(!noconfig)
-					deldefroute(r->gaddr, conf.lladdr, r->laddr, r->mask);
+				maskip(r->laddr, r->mask, raddr);
+				deldefroute(r->gaddr, conf.lladdr, r->laddr, raddr, r->mask);
 			}
 		} else {
 			seen = 0;
@@ -790,19 +816,13 @@
 		DEBUG("got prefix %I %M via %I on %s",
 			conf.v6pref, conf.mask, conf.gaddr, conf.dev);
 
-		if(noconfig)
-			continue;
-
 		if(validip(conf.gaddr))
-			adddefroute(conf.gaddr, conf.lladdr, conf.laddr, conf.mask);
+			adddefroute(conf.gaddr, conf.lladdr, conf.laddr, conf.raddr, conf.mask);
 
 		putndb(1);
 		refresh();
 	}
 
-	/* pass gateway to dhcpv6 if it is managed network */
-	ipmove(conf.gaddr, conf.mflag? ra->src: IPnoaddr);
-
 	return 0;
 }
 
@@ -814,6 +834,7 @@
 {
 	int fd, n, sendrscnt, gotra, pktcnt, sleepfor;
 	uchar buf[4096];
+	ulong now;
 
 	fd = dialicmpv6(v6allnodesL, ICMP6_RA);
 	if(fd < 0)
@@ -834,15 +855,16 @@
 	procsetname("recvra6 on %s %I", conf.dev, conf.lladdr);
 	notify(catch);
 
+	gotra = 0;
+restart:
 	sendrscnt = 0;
 	if(recvra6on(myifc) == IsHostRecv){
 		sendrs(fd, v6allroutersL);
 		sendrscnt = Maxv6rss;
 	}
-
-	gotra = 0;
 	pktcnt = 0;
 	sleepfor = Minv6interradelay;
+	conf.state = 0;	/* dhcpv6 off */
 
 	for (;;) {
 		alarm(sleepfor);
@@ -858,6 +880,7 @@
 			pktcnt = 1;
 		}
 		sleepfor = Maxv6radelay;
+		now = time(nil);
 
 		myifc = readipifc(conf.mpoint, myifc, myifc->index);
 		if(myifc == nil) {
@@ -884,34 +907,104 @@
 				sendrs(fd, v6allroutersL);
 				sleepfor = V6rsintvl + nrand(100);
 			} else if(!gotra) {
-				gotra = 1;
 				warning("recvra6: no router advs after %d sols on %s",
 					Maxv6rss, conf.dev);
 				rendezvous(recvra6, (void*)0);
-			}
+			} else if(dodhcp && conf.state)
+				goto renewdhcp;
 			continue;
 		}
 
-		if(recvrahost(buf, n) < 0)
+		if(recvrahost(buf, n, now) < 0)
 			continue;
 
-		/* got at least initial ra; no whining */
-		if(!gotra){
-			gotra = 1;
-			if(dodhcp && conf.mflag){
-				dhcpv6query();
-				if(noconfig || !validip(conf.laddr))
+		if(dodhcp && conf.mflag){
+			if(conf.state){
+				if(validip(conf.gaddr) && ipcmp(conf.gaddr, conf.v6router) != 0){
+					warning("dhcpv6: default router changed %I -> %I on %s",
+						conf.gaddr, conf.v6router, conf.dev);
+				} else {
+renewdhcp:
+					/* when renewing, wait for lease to time-out */
+					if(now < conf.timeout)
+						continue;
+				}
+			}
+			if(dhcpv6query(conf.state) < 0){
+				if(conf.state == 0)
 					continue;
+
+				DEBUG("dhcpv6 failed renew for %I with prefix %I %M via %I on %s",
+					conf.laddr, conf.v6pref, conf.v6mask, conf.gaddr, conf.dev);
+
+				if(!noconfig){
+					fprint(conf.rfd, "tag dhcp");
+					if(validip(conf.gaddr))
+						if(conf.preflt && validv6prefix(conf.v6pref) && !isula(conf.v6pref))
+							deldefroute(conf.gaddr, conf.lladdr, IPnoaddr, conf.v6pref, conf.v6mask);
+					ipunconfig();
+					fprint(conf.rfd, "tag ra6");
+				}
+				/* restart sending router solitications */
+				conf.state = 0;
+				goto restart;
+			}
+			now = time(nil);
+			conf.state = 1;	/* dhcpv6 active */
+			sendrscnt = 0;	/* stop sending router solicitations */
+
+			DEBUG("dhcpv6 got %I with prefix %I %M via %I on %s for lease %lud",
+				conf.laddr, conf.v6pref, conf.v6mask, conf.v6router, conf.dev, conf.lease);
+
+			if(!noconfig){
 				fprint(conf.rfd, "tag dhcp");
+
+				if(validip(conf.gaddr) && ipcmp(conf.gaddr, conf.v6router) != 0){
+					deldefroute(conf.gaddr, conf.lladdr, conf.laddr, conf.raddr, conf.mask);
+					if(conf.preflt && validv6prefix(conf.v6pref) && !isula(conf.v6pref))
+						deldefroute(conf.gaddr, conf.lladdr, IPnoaddr, conf.v6pref, conf.v6mask);
+				}
+				ipmove(conf.gaddr, conf.v6router);
+
 				if(ip6cfg() < 0){
 					fprint(conf.rfd, "tag ra6");
 					continue;
 				}
 				putndb(1);
+				if(conf.preflt && validv6prefix(conf.v6pref)){
+					uchar save[IPaddrlen];
+
+					/* if we got a delegated prefix, add the default route */
+					if(validip(conf.gaddr) && !isula(conf.v6pref))
+						adddefroute(conf.gaddr, conf.lladdr, IPnoaddr, conf.v6pref, conf.v6mask);
+
+					/* store prefix info in /net/ndb */
+					ipmove(save, conf.laddr);
+					ipmove(conf.laddr, IPnoaddr);		/* we don't have an address there */
+					ipmove(conf.raddr, conf.v6pref);
+					ipmove(conf.mask, conf.v6mask);
+					putndb(1);
+					ipmove(conf.laddr, save);
+					ipmove(conf.raddr, save);		/* was same as laddr as /128 */
+					memset(conf.mask, 0xFF, IPaddrlen);
+				}
 				refresh();
-				rendezvous(recvra6, (void*)1);
+				fprint(conf.rfd, "tag ra6");
+			}
+			/* infinity */
+			if(conf.lease == ~0UL){
+				if(!gotra)
+					rendezvous(recvra6, (void*)1);
 				exits(nil);
 			}
+			if(conf.lease < 60)
+				conf.lease = 60;
+			conf.timeout = now + conf.lease;
+		}
+
+		/* got at least initial ra; no whining */
+		if(!gotra){
+			gotra = 1;
 			rendezvous(recvra6, (void*)1);
 		}
 	}
@@ -1004,11 +1097,7 @@
 		if(pktlen > sizeof buf - 4*8)
 			break;
 
-		if(!validip(lifc->ip)
-		|| isv4(lifc->ip)
-		|| ipcmp(lifc->ip, v6loopback) == 0
-		|| ISIPV6MCAST(lifc->ip)
-		|| ISIPV6LINKLOCAL(lifc->ip))
+		if(!validv6prefix(lifc->ip))
 			continue;
 
 		prfo = (Prefixopt*)&buf[pktlen];
--- a/sys/src/cmd/ip/ipconfig/main.c
+++ b/sys/src/cmd/ip/ipconfig/main.c
@@ -30,6 +30,7 @@
 int	nodhcpwatch;
 int	sendhostname;
 char	*ndboptions;
+char	*ipnet;
 
 int	ipv6auto;
 int	dupl_disc = 1;		/* flag: V6 duplicate neighbor discovery */
@@ -58,7 +59,7 @@
 usage(void)
 {
 	fprint(2, "usage: %s [-6dDGnNOpPrtuXy][-b baud][-c ctl]* [-U duid] [-g gw]"
-		"[-h host][-m mtu][-s dns]...\n"
+		"[-h host][-i ipnet][-m mtu][-s dns]...\n"
 		"\t[-f dbfile][-x mtpt][-o dhcpopt] type dev [verb] [laddr [mask "
 		"[raddr [fs [auth]]]]]\n", argv0);
 	exits("usage");
@@ -400,6 +401,9 @@
 			sysfatal("bad hostname");
 		sendhostname = 1;
 		break;
+	case 'i':
+		ipnet = EARGF(usage());
+		break;
 	case 'm':
 		conf.mtu = atoi(EARGF(usage()));
 		break;
@@ -569,7 +573,7 @@
 		return;
 
 	if(validip(conf.gaddr))
-		deldefroute(conf.gaddr, conf.laddr, conf.laddr, conf.mask);
+		deldefroute(conf.gaddr, conf.laddr, conf.laddr, conf.raddr, conf.mask);
 
 	/* use "remove" verb instead of "del" for older kernels */
 	if(conf.cfd >= 0 && fprint(conf.cfd, "remove %I %M", conf.laddr, conf.mask) < 0)
@@ -595,6 +599,8 @@
 {
 	char buf[127];
 
+	if(noconfig)
+		return;
 	snprint(buf, sizeof buf, "%s/iproute", conf.mpoint);
 	conf.rfd = open(buf, OWRITE);
 }
@@ -684,7 +690,7 @@
 
 	if(validip(conf.gaddr) && isv4(conf.gaddr)
 	&& ipcmp(conf.gaddr, conf.laddr) != 0)
-		adddefroute(conf.gaddr, conf.laddr, conf.laddr, conf.mask);
+		adddefroute(conf.gaddr, conf.laddr, conf.laddr, conf.laddr, conf.mask);
 
 	if(tflag)
 		fprint(conf.cfd, "iprouting 1");
@@ -754,7 +760,7 @@
 	Ndb *db;
 	int fd;
 
-	if(beprimary == 0)
+	if(beprimary == 0 || noconfig)
 		return;
 
 	p = buf;
@@ -761,30 +767,36 @@
 	e = buf + sizeof buf;
 
 	if(doadd){
-		p = seprint(p, e, "ip=%I ipmask=%M ipgw=%I\n",
-			conf.laddr, conf.mask, conf.gaddr);
-		if(np = strchr(conf.hostname, '.')){
-			if(*conf.domainname == 0)
-				strcpy(conf.domainname, np+1);
-			*np = 0;
+		if(ipnet != nil && validip(conf.raddr)){
+			p = seprint(p, e, "ipnet=%s ip=%I ipmask=%M ipgw=%I\n",
+				ipnet, conf.raddr, conf.mask, conf.gaddr);
 		}
-		if(*conf.hostname)
-			p = seprint(p, e, "\tsys=%U\n", conf.hostname);
-		if(*conf.domainname)
-			p = seprint(p, e, "\tdom=%U.%U\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.ntp))
-			p = putaddrs(p, e, "\tntp", conf.ntp, sizeof conf.ntp);
-		if(ndboptions)
-			p = seprint(p, e, "%s\n", ndboptions);
+		if(validip(conf.laddr)){
+			p = seprint(p, e, "ip=%I ipmask=%M ipgw=%I\n",
+				conf.laddr, conf.mask, conf.gaddr);
+			if(np = strchr(conf.hostname, '.')){
+				if(*conf.domainname == 0)
+					strcpy(conf.domainname, np+1);
+				*np = 0;
+			}
+			if(*conf.hostname)
+				p = seprint(p, e, "\tsys=%U\n", conf.hostname);
+			if(*conf.domainname)
+				p = seprint(p, e, "\tdom=%U.%U\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.ntp))
+				p = putaddrs(p, e, "\tntp", conf.ntp, sizeof conf.ntp);
+			if(ndboptions)
+				p = seprint(p, e, "%s\n", ndboptions);
+		}
 	}
 
 	/* for myip() */
@@ -797,10 +809,11 @@
 		while((t = ndbparse(db)) != nil){
 			uchar ip[IPaddrlen];
 
-			if(ndbfindattr(t, t, "ipnet") != nil
-			|| (nt = ndbfindattr(t, t, "ip")) == nil
-			|| parseip(ip, nt->val) == -1
-			|| ipcmp(ip, conf.laddr) != 0 && myip(allifcs, ip)){
+			nt = ndbfindattr(t, t, "ipnet");
+			if(nt != nil && (ipnet == nil || strcmp(nt->val, ipnet) != 0)
+			|| nt == nil && ((nt = ndbfindattr(t, t, "ip")) == nil
+				|| parseip(ip, nt->val) == -1
+				|| ipcmp(ip, conf.laddr) != 0 && myip(allifcs, ip))){
 				if(p > buf)
 					p = seprint(p, e, "\n");
 				for(nt = t; nt != nil; nt = nt->entry)
@@ -843,7 +856,7 @@
 }
 
 static void
-defroutectl(char *cmd, uchar *gaddr, uchar *ia, uchar *src, uchar *smask)
+defroutectl(char *cmd, uchar *gaddr, uchar *ia, uchar *laddr, uchar *src, uchar *smask)
 {
 	uchar dst[IPaddrlen], mask[IPaddrlen];
 
@@ -870,21 +883,21 @@
 	}
 
 	/* add source specific route for us */
-	if(validip(src))
-		routectl(cmd, dst, mask, gaddr, "", ia, src, IPallbits);
+	if(validip(laddr))
+		routectl(cmd, dst, mask, gaddr, "", ia, laddr, IPallbits);
 }
 
 void
-adddefroute(uchar *gaddr, uchar *ia, uchar *src, uchar *smask)
+adddefroute(uchar *gaddr, uchar *ia, uchar *laddr, uchar *src, uchar *smask)
 {
-	defroutectl("add", gaddr, ia, src, smask);
+	defroutectl("add", gaddr, ia, laddr, src, smask);
 }
 
 void
-deldefroute(uchar *gaddr, uchar *ia, uchar *src, uchar *smask)
+deldefroute(uchar *gaddr, uchar *ia, uchar *laddr, uchar *src, uchar *smask)
 {
 	/* use "remove" verb instead of "del" for older kernels */
-	defroutectl("remove", gaddr, ia, src, smask);
+	defroutectl("remove", gaddr, ia, laddr, src, smask);
 }
 
 void
@@ -892,6 +905,9 @@
 {
 	char file[64];
 	int fd;
+
+	if(noconfig)
+		return;
 
 	snprint(file, sizeof file, "%s/cs", conf.mpoint);
 	if((fd = open(file, OWRITE)) >= 0){
--