changeset 1886:23ca5addb202 draft

Add DHCPv6 Prefix Delegation support, RFC3633. Add DHCPv6 Temporary Address support, RFC3315.
author Roy Marples <roy@marples.name>
date Mon, 01 Apr 2013 12:15:47 +0000
parents 158c55bbce72
children 5aca9cc6a54e
files defs.h dhcp6.c dhcp6.h dhcpcd.8.in dhcpcd.c dhcpcd.conf.5.in if-options.c if-options.h ipv6.c ipv6.h
diffstat 10 files changed, 739 insertions(+), 190 deletions(-) [+]
line wrap: on
line diff
--- a/defs.h	Thu Mar 28 06:38:27 2013 +0000
+++ b/defs.h	Mon Apr 01 12:15:47 2013 +0000
@@ -28,7 +28,7 @@
 #define CONFIG_H
 
 #define PACKAGE			"dhcpcd"
-#define VERSION			"5.99.3"
+#define VERSION			"5.99.4"
 
 #ifndef CONFIG
 # define CONFIG			SYSCONFDIR "/" PACKAGE ".conf"
--- a/dhcp6.c	Thu Mar 28 06:38:27 2013 +0000
+++ b/dhcp6.c	Mon Apr 01 12:15:47 2013 +0000
@@ -89,6 +89,7 @@
 	{ DHCP6_REQUEST, "REQUEST6" },
 	{ DHCP6_REPLY, "REPLY6" },
 	{ DHCP6_RENEW, "RENEW6" },
+	{ DHCP6_REBIND, "REBIND6" },
 	{ DHCP6_CONFIRM, "CONFIRM6" },
 	{ DHCP6_INFORMATION_REQ, "INFORM6" },
 	{ 0, NULL }
@@ -333,7 +334,7 @@
 	uint32_t xid;
 
 	if (ifp->options->options & DHCPCD_XID_HWADDR &&
-	    ifp->hwlen >= sizeof(xid)) 
+	    ifp->hwlen >= sizeof(xid))
 		/* The lower bits are probably more unique on the network */
 		memcpy(&xid, (ifp->hwaddr + ifp->hwlen) - sizeof(xid),
 		    sizeof(xid));
@@ -353,10 +354,12 @@
 	struct dhcp6_option *o, *so;
 	const struct dhcp6_option *si;
 	ssize_t len, ml;
+	size_t l;
+	uint8_t u8;
 	uint16_t *u16;
 	const struct if_options *ifo;
 	const struct dhcp_opt *opt;
-	uint8_t IA_NA, *p;
+	uint8_t IA, *p;
 	uint32_t u32;
 	const struct ipv6_addr *ap;
 
@@ -381,12 +384,13 @@
 	len += sizeof(*o);
 
 	len += sizeof(*state->send);
-	len += sizeof(*o) + 14; /* clientid */ 
+	len += sizeof(*o) + 14; /* clientid */
 	len += sizeof(*o) + sizeof(uint16_t); /* elapsed */
 #ifdef DHCPCD_IANA_PEN
 	len += sizeof(*o) + dhcp6_makevendor(NULL);
 #endif
-	/* IA_NA */
+
+	/* IA */
 	m = NULL;
 	ml = 0;
 	switch(state->state) {
@@ -402,23 +406,30 @@
 		si = dhcp6_getoption(D6_OPTION_SERVERID, m, ml);
 		len += sizeof(*si) + ntohs(si->len);
 		/* FALLTHROUGH */
-	case DH6S_REBOOT:
+	case DH6S_REBIND:
+		/* FALLTHROUGH */
+	case DH6S_CONFIRM:
 		if (m == NULL) {
 			m = state->new;
 			ml = state->new_len;
 		}
 		TAILQ_FOREACH(ap, &state->addrs, next) {
-			len += sizeof(*o) + sizeof(ap->addr.s6_addr) +
-				sizeof(u32) + sizeof(u32);
+			if (ifo->ia_type == D6_OPTION_IA_PD)
+				len += sizeof(*o) + sizeof(u8) +
+				    sizeof(u32) + sizeof(u32) + 
+				    sizeof(ap->prefix.s6_addr);
+			else
+				len += sizeof(*o) + sizeof(ap->addr.s6_addr) +
+				    sizeof(u32) + sizeof(u32);
 		}
 		/* FALLTHROUGH */
 	case DH6S_INIT: /* FALLTHROUGH */
 	case DH6S_DISCOVER:
-		len += sizeof(*o) + sizeof(u32) + sizeof(u32) + sizeof(u32);
-		IA_NA = 1;
+		len += ifo->iaid_len * (sizeof(*o) + (sizeof(u32) * 3));
+		IA = 1;
 		break;
 	default:
-		IA_NA = 0;
+		IA = 0;
 	}
 
 	if (m == NULL) {
@@ -437,21 +448,22 @@
 	case DH6S_DISCOVER:
 		state->send->type = DHCP6_SOLICIT;
 		break;
-	case DH6S_REQUEST: /* FALLTHROUGH */
+	case DH6S_REQUEST:
+		state->send->type = DHCP6_REQUEST;
+		break;
+	case DH6S_CONFIRM:
+		state->send->type = DHCP6_CONFIRM;
+		break;
 	case DH6S_REBIND:
-		state->send->type = DHCP6_REQUEST;
+		state->send->type = DHCP6_REBIND;
 		break;
 	case DH6S_RENEW:
 		state->send->type = DHCP6_RENEW;
 		break;
-	case DH6S_REBOOT:
-		state->send->type = DHCP6_CONFIRM;
-		break;
 	case DH6S_INFORM:
 		state->send->type = DHCP6_INFORMATION_REQ;
 		break;
 	default:
-		printf ("state %d\n", state->state);
 		errno = EINVAL;
 		free(state->send);
 		state->send = NULL;
@@ -481,31 +493,60 @@
 	dhcp6_makevendor(o);
 #endif
 
-	if (IA_NA) {
+	for (l = 0; IA && l < ifo->iaid_len; l++) {
 		o = D6_NEXT_OPTION(o);
-		o->code = htons(D6_OPTION_IA_NA);
+		o->code = htons(ifo->ia_type);
 		o->len = htons(sizeof(u32) + sizeof(u32) + sizeof(u32));
 		p = D6_OPTION_DATA(o);
-		memcpy(p, state->iaid, sizeof(u32));
+		memcpy(p, ifo->iaid[l].iaid, sizeof(u32));
 		p += sizeof(u32);
 		memset(p, 0, sizeof(u32) + sizeof(u32));
 		TAILQ_FOREACH(ap, &state->addrs, next) {
+			if (memcmp(ifo->iaid[l].iaid, ap->iaid, sizeof(u32)))
+				continue;
 			so = D6_NEXT_OPTION(o);
-			so->code = htons(D6_OPTION_IA_ADDR);
-			so->len = htons(sizeof(ap->addr.s6_addr) +
-			    sizeof(u32) + sizeof(u32));
-			p = D6_OPTION_DATA(so);
-			memcpy(p, &ap->addr.s6_addr, sizeof(ap->addr.s6_addr));
-			p += sizeof(ap->addr.s6_addr);
-			u32 = htonl(ap->prefix_pltime);
-			memcpy(p, &u32, sizeof(u32));
-			p += sizeof(u32);
-			u32 = htonl(ap->prefix_vltime);
-			memcpy(p, &u32, sizeof(u32));
-			/* Avoid a shadowed declaration warning by
-			 * moving our addition outside of the htons macro */
-			u32 = ntohs(o->len) + sizeof(*so) + ntohs(so->len);
-			o->len = htons(u32);
+			if (ifo->ia_type == D6_OPTION_IA_PD) {
+				so->code = htons(D6_OPTION_IAPREFIX);
+				so->len = htons(sizeof(ap->prefix.s6_addr) +
+				    sizeof(u32) + sizeof(u32) + sizeof(u8));
+				p = D6_OPTION_DATA(so);
+				u32 = htonl(ap->prefix_pltime);
+				memcpy(p, &u32, sizeof(u32));
+				p += sizeof(u32);
+				u32 = htonl(ap->prefix_vltime);
+				memcpy(p, &u32, sizeof(u32));
+				p += sizeof(u32);
+				u8 = ap->prefix_len;
+				memcpy(p, &u8, sizeof(u8));
+				p += sizeof(u8);
+				memcpy(p, &ap->prefix.s6_addr,
+				    sizeof(ap->prefix.s6_addr));
+				/* Avoid a shadowed declaration warning by
+				 * moving our addition outside of the htons
+				 * macro */
+				u32 = ntohs(o->len) + sizeof(*so)
+				    + ntohs(so->len);
+				o->len = htons(u32);
+			} else {
+				so->code = htons(D6_OPTION_IA_ADDR);
+				so->len = htons(sizeof(ap->addr.s6_addr) +
+				    sizeof(u32) + sizeof(u32));
+				p = D6_OPTION_DATA(so);
+				memcpy(p, &ap->addr.s6_addr,
+				    sizeof(ap->addr.s6_addr));
+				p += sizeof(ap->addr.s6_addr);
+				u32 = htonl(ap->prefix_pltime);
+				memcpy(p, &u32, sizeof(u32));
+				p += sizeof(u32);
+				u32 = htonl(ap->prefix_vltime);
+				memcpy(p, &u32, sizeof(u32));
+				/* Avoid a shadowed declaration warning by
+				 * moving our addition outside of the htons
+				 * macro */
+				u32 = ntohs(o->len) + sizeof(*so)
+				    + ntohs(so->len);
+				o->len = htons(u32);
+			}
 		}
 	}
 
@@ -554,7 +595,7 @@
 		/* Only drop the address if no other RAs have assigned it.
 		 * This is safe because the RA is removed from the list
 		 * before we are called. */
-		if (drop &&
+		if (drop && ap->onlink &&
 		    !dhcp6_addrexists(ap) &&
 		    !ipv6rs_addrexists(ap))
 		{
@@ -653,7 +694,7 @@
 	memset(&pi, 0, sizeof(pi));
 	pi.ipi6_ifindex = ifp->index;
 	memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
-	
+
 	if (sendmsg(sock, &sndhdr, 0) == -1) {
 		syslog(LOG_ERR, "%s: sendmsg: %m", ifp->name);
 		ifp->options->options &= ~DHCPCD_IPV6;
@@ -749,10 +790,17 @@
 	state = D6_STATE(ifp);
 	state->state = DH6S_REBIND;
 	state->RTC = 0;
-	state->IRT = REB_TIMEOUT;
-	state->MRT = REB_MAX_RT;
 	state->MRC = 0;
 
+	/* RFC 3633 section 12.1 */
+	if (ifp->options->ia_type == D6_OPTION_IA_PD) {
+	    state->IRT = CNF_TIMEOUT;
+	    state->MRT = CNF_MAX_RT;
+	} else {
+	    state->IRT = REB_TIMEOUT;
+	    state->MRT = REB_MAX_RT;
+	}
+
 	if (dhcp6_makemessage(ifp) == -1)
 		syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name);
 	else
@@ -843,7 +891,7 @@
 	struct dhcp6_state *state;
 
 	state = D6_STATE(ifp);
-	state->state = DH6S_REBOOT;
+	state->state = DH6S_CONFIRM;
 	state->start_uptime = uptime();
 	state->RTC = 0;
 	state->IRT = CNF_TIMEOUT;
@@ -940,7 +988,8 @@
 }
 
 static int
-dhcp6_findia(struct interface *ifp, const uint8_t *d, size_t l)
+dhcp6_findna(struct interface *ifp, const uint8_t *iaid,
+    const uint8_t *d, size_t l)
 {
 	struct dhcp6_state *state;
 	const struct dhcp6_option *o;
@@ -953,48 +1002,184 @@
 	uint32_t u32;
 
 	i = 0;
-	dhcp6_freedrop_addrs(ifp, 0);
 	state = D6_STATE(ifp);
 	while ((o = dhcp6_findoption(D6_OPTION_IA_ADDR, d, l))) {
 		d += ntohs(o->len);
 		l -= ntohs(o->len);
 		a = malloc(sizeof(*a));
-		if (a) {
-			a->new = 1;
-			a->onlink = 1; /* XXX: suprised no DHCP opt for this */
-			p = D6_COPTION_DATA(o);
-			memcpy(&a->addr.s6_addr, p,
-			    sizeof(a->addr.s6_addr));
-			p += sizeof(a->addr.s6_addr);
-			pa = ipv6rs_findprefix(a);
-			if (pa) {
-				memcpy(&a->prefix, &pa->prefix,
-				    sizeof(a->prefix));
-				a->prefix_len = pa->prefix_len;
-			} else {
-				a->prefix_len = 64;
-				ipv6_makeprefix(&a->prefix, &a->addr, 64);
+		if (a == NULL) {
+			syslog(LOG_ERR, "%s: %m", __func__);
+			break;
+		}
+		a->new = 1;
+		a->onlink = 1; /* XXX: suprised no DHCP opt for this */
+		memcpy(a->iaid, iaid, sizeof(a->iaid));
+		p = D6_COPTION_DATA(o);
+		memcpy(&a->addr.s6_addr, p,
+		    sizeof(a->addr.s6_addr));
+		p += sizeof(a->addr.s6_addr);
+		pa = ipv6rs_findprefix(a);
+		if (pa) {
+			memcpy(&a->prefix, &pa->prefix,
+			    sizeof(a->prefix));
+			a->prefix_len = pa->prefix_len;
+		} else {
+			a->prefix_len = 64;
+			if (ipv6_makeprefix(&a->prefix, &a->addr, 64) == -1) {
+				syslog(LOG_ERR, "%s: %m", __func__);
+				free(a);
+				continue;
 			}
-			memcpy(&u32, p, sizeof(u32));
-			a->prefix_pltime = ntohl(u32);
-			p += sizeof(u32);
-			memcpy(&u32, p, sizeof(u32));
-			a->prefix_vltime = ntohl(u32);
-			if (a->prefix_pltime < state->lowpl)
-				state->lowpl = a->prefix_pltime;
-			if (a->prefix_vltime > state->expire)
-				state->expire = a->prefix_vltime;
-			ia = inet_ntop(AF_INET6, &a->addr.s6_addr,
-			    iabuf, sizeof(iabuf));
-			snprintf(a->saddr, sizeof(a->saddr),
-			    "%s/%d", ia, a->prefix_len);
-			TAILQ_INSERT_TAIL(&state->addrs, a, next);
-			i++;
 		}
+		memcpy(&u32, p, sizeof(u32));
+		a->prefix_pltime = ntohl(u32);
+		p += sizeof(u32);
+		memcpy(&u32, p, sizeof(u32));
+		a->prefix_vltime = ntohl(u32);
+		if (a->prefix_pltime < state->lowpl)
+		    state->lowpl = a->prefix_pltime;
+		if (a->prefix_vltime > state->expire)
+		    state->expire = a->prefix_vltime;
+		ia = inet_ntop(AF_INET6, &a->addr.s6_addr,
+		    iabuf, sizeof(iabuf));
+		snprintf(a->saddr, sizeof(a->saddr),
+		    "%s/%d", ia, a->prefix_len);
+		TAILQ_INSERT_TAIL(&state->addrs, a, next);
+		i++;
 	}
 	return i;
 }
 
+static int
+dhcp6_findpd(struct interface *ifp, const uint8_t *iaid,
+    const uint8_t *d, size_t l)
+{
+	struct dhcp6_state *state;
+	const struct dhcp6_option *o;
+	const uint8_t *p;
+	struct ipv6_addr *a;
+	char iabuf[INET6_ADDRSTRLEN];
+	const char *ia;
+	int i;
+	uint8_t u8;
+	uint32_t u32;
+
+	i = 0;
+	state = D6_STATE(ifp);
+	while ((o = dhcp6_findoption(D6_OPTION_IAPREFIX, d, l))) {
+		u32 = ntohs(o->len);
+		d += u32;
+		l -= u32;
+		a = malloc(sizeof(*a));
+		if (a == NULL) {
+			syslog(LOG_ERR, "%s: %m", __func__);
+			break;
+		}
+		a->new = 1;
+		a->onlink = 0;
+		memcpy(a->iaid, iaid, sizeof(a->iaid));
+		p = D6_COPTION_DATA(o);
+		memcpy(&u32, p, sizeof(u32));
+		a->prefix_pltime = ntohl(u32);
+		p += sizeof(u32);
+		memcpy(&u32, p, sizeof(u32));
+		p += sizeof(u32);
+		a->prefix_vltime = ntohl(u32);
+		if (a->prefix_pltime < state->lowpl)
+			state->lowpl = a->prefix_pltime;
+		if (a->prefix_vltime > state->expire)
+			state->expire = a->prefix_vltime;
+		memcpy(&u8, p, sizeof(u8));
+		p += sizeof(u8);
+		a->prefix_len = u8;
+		memcpy(&a->prefix.s6_addr, p, sizeof(a->prefix.s6_addr));
+		p += sizeof(a->prefix.s6_addr);
+		ia = inet_ntop(AF_INET6, &a->prefix.s6_addr,
+		    iabuf, sizeof(iabuf));
+		snprintf(a->saddr, sizeof(a->saddr),
+		    "%s/%d", ia, a->prefix_len);
+		memset(a->addr.s6_addr, 0, sizeof(a->addr.s6_addr));
+		TAILQ_INSERT_TAIL(&state->addrs, a, next);
+		i++;
+	}
+	return i;
+}
+
+static int
+dhcp6_findia(struct interface *ifp, const uint8_t *d, size_t l,
+    const char *sfrom)
+{
+	struct dhcp6_state *state;
+	const struct if_options *ifo;
+	const struct dhcp6_option *o;
+	const uint8_t *p;
+	int i;
+	uint32_t u32, renew, rebind;
+	uint8_t iaid[4];
+	size_t ol;
+
+	ifo = ifp->options;
+	i = 0;
+	dhcp6_freedrop_addrs(ifp, 0);
+	state = D6_STATE(ifp);
+	while ((o = dhcp6_findoption(ifo->ia_type, d, l))) {
+		ol = sizeof(*o) + ntohs(o->len);
+		d += ol;
+		l -= ol;
+
+		p = D6_COPTION_DATA(o);
+		memcpy(iaid, p, sizeof(iaid));
+		p += sizeof(iaid);
+		ol -= sizeof(iaid);
+		if (ifo->ia_type == D6_OPTION_IA_NA) {
+			memcpy(&u32, p, sizeof(u32));
+			renew = ntohl(u32);
+			p += sizeof(u32);
+			ol -= sizeof(u32);
+			memcpy(&u32, p, sizeof(u32));
+			rebind = ntohl(u32);
+			p += sizeof(u32);
+			ol -= sizeof(u32);
+			if (renew > rebind && rebind > 0) {
+				if (sfrom)
+				    syslog(LOG_WARNING,
+					"%s: T1 (%d) > T2 (%d) from %s",
+					ifp->name, renew, rebind, sfrom);
+				renew = 0;
+				rebind = 0;
+			}
+			if (renew != 0 &&
+			    (renew < state->renew || state->renew == 0))
+				state->renew = renew;
+			if (rebind != 0 &&
+			    (rebind < state->rebind || state->rebind == 0))
+				state->rebind = rebind;
+		}
+		o = dhcp6_findoption(D6_OPTION_STATUS_CODE, p, ol);
+		if (o && dhcp6_getstatus(o) != D6_STATUS_OK) {
+			syslog(LOG_ERR, "%s: DHCPv6 REPLY: %s",
+			    ifp->name, status);
+			return -1;
+		}
+		if (ifo->ia_type == D6_OPTION_IA_PD) {
+			if (dhcp6_findpd(ifp, iaid, p, ol) == 0) {
+				syslog(LOG_ERR,
+				    "%s: %s: DHCPv6 REPLY missing Prefix",
+				    ifp->name, sfrom);
+				return -1;
+			}
+		} else {
+			if (dhcp6_findna(ifp, iaid, p, ol) == 0) {
+				syslog(LOG_ERR,
+				    "%s: %s: DHCPv6 REPLY missing IA Address",
+				    ifp->name, sfrom);
+				return -1;
+			}
+		}
+		i++;
+	}
+	return i;
+}
 
 static int
 dhcp6_validatelease(struct interface *ifp,
@@ -1003,60 +1188,20 @@
 {
 	struct dhcp6_state *state;
 	const struct dhcp6_option *o;
-	size_t l, ol;
-	const uint8_t *p;
-	uint32_t u32;
 
 	state = D6_STATE(ifp);
-	o = dhcp6_getoption(D6_OPTION_IA_NA, m, len);
+	o = dhcp6_getoption(ifp->options->ia_type, m, len);
 	if (o == NULL) {
 		if (sfrom)
-			syslog(LOG_ERR, "%s: no IA_NA in REPLY from %s",
-			    ifp->name, sfrom);
-		return -1;
-	}
-	ol = ntohs(o->len);
-	l = sizeof(state->iaid) + sizeof(uint32_t) + sizeof(uint32_t);
-	if (ol < l + sizeof(struct dhcp6_status)) {
-		if (sfrom)
-			syslog(LOG_ERR, "%s: truncated IA NA from %s",
+			syslog(LOG_ERR, "%s: no IA in REPLY from %s",
 			    ifp->name, sfrom);
 		return -1;
 	}
-	p = D6_COPTION_DATA(o);
-	if (memcmp(p, state->iaid, sizeof(state->iaid)) != 0) {
-		syslog(LOG_ERR, "%s: IAID mismatch from %s",
-		    ifp->name, sfrom ? sfrom : "lease");
-		return -1;
-	}
-	p += sizeof(state->iaid);
-	memcpy(&u32, p, sizeof(u32));
-	state->renew = ntohl(u32);
-	p += sizeof(u32);
-	memcpy(&u32, p, sizeof(u32));
-	state->rebind = ntohl(u32);
-	if (state->renew > state->rebind && state->rebind > 0) {
-		if (sfrom)
-			syslog(LOG_WARNING, "%s: T1 (%d) > T2 (%d) from %s",
-			    ifp->name, state->renew, state->rebind, sfrom);
-		state->renew = 0;
-		state->rebind = 0;
-	}
-	p += sizeof(u32);
-	state->expire = 0;
+
+	state->renew = state->rebind = state->expire = 0;
 	state->lowpl = ~0U;
-	ol -= l;
-	o = dhcp6_findoption(D6_OPTION_STATUS_CODE, p, ol);
-	if (o && dhcp6_getstatus(o) != D6_STATUS_OK) {
-		syslog(LOG_ERR, "%s: DHCPv6 REPLY: %s", ifp->name, status);
-		return -1;
-	}
-	if (dhcp6_findia(ifp, p, ol) == 0) {
-		syslog(LOG_ERR, "%s: %s: DHCPv6 REPLY missing IA ADDR",
-		    ifp->name, sfrom);
-		return -1;
-	}
-	return 0;
+	len -= (const char *)o - (const char *)m;
+	return dhcp6_findia(ifp, (const uint8_t *)o, len, sfrom);
 }
 
 static ssize_t
@@ -1138,19 +1283,187 @@
 	state->state = DH6S_INIT;
 	state->expire = ~0U;
 	state->lowpl = ~0U;
-	if (!(options & DHCPCD_TEST)) {
+	if (!(options & DHCPCD_TEST) &&
+	    ifp->options->ia_type != D6_OPTION_IA_TA)
+	{
 		r = dhcp6_readlease(ifp);
 		if (r == -1)
 			syslog(LOG_ERR, "%s: dhcp6_readlease: %s: %m",
 					ifp->name, state->leasefile);
 		else if (r != 0) {
-			dhcp6_startconfirm(ifp);
+			/* RFC 3633 section 12.1 */
+			if (ifp->options->ia_type == D6_OPTION_IA_PD)
+				dhcp6_startrebind(ifp);
+			else
+				dhcp6_startconfirm(ifp);
 			return;
 		}
 	}
 	dhcp6_startdiscover(ifp);
 }
 
+static struct ipv6_addr *
+dhcp6_delegate_addr(struct interface *ifp, const struct ipv6_addr *prefix,
+    const struct if_sla *sla, struct interface *ifs)
+{
+	struct dhcp6_state *state;
+	struct ipv6_addr *a, *ap;
+	int b, i, l;
+	const uint8_t *p;
+	char iabuf[INET6_ADDRSTRLEN];
+	const char *ia;
+
+	state = D6_STATE(ifp);
+
+	l = prefix->prefix_len + sla->sla_len;
+	if (l < 0 || l > 128) {
+		syslog(LOG_ERR, "%s: invalid prefix length (%d + %d = %d)",
+		    ifs->name, prefix->prefix_len, sla->sla_len, l);
+		return NULL;
+	}
+
+	if (state == NULL) {
+		ifp->if_data[IF_DATA_DHCP6] = calloc(1, sizeof(*state));
+		state = D6_STATE(ifp);
+		if (state == NULL) {
+			syslog(LOG_ERR, "%s: %m", __func__);
+			return NULL;
+		}
+
+		TAILQ_INIT(&state->addrs);
+		state->state = DH6S_DELEGATED;
+	}
+
+	a = malloc(sizeof(*a));
+	if (a == NULL) {
+		syslog(LOG_ERR, "%s: %m", __func__);
+		return NULL;
+	}
+
+	a->new = 1;
+	a->onlink = 1;
+	a->delegating_iface = ifs;
+	memcpy(&a->iaid, &prefix->iaid, sizeof(a->iaid));
+	a->prefix_pltime = prefix->prefix_pltime;
+	a->prefix_vltime = prefix->prefix_vltime;
+	a->prefix_len = l;
+
+	memset(&a->prefix.s6_addr, 0, sizeof(a->prefix.s6_addr));
+	for (i = 0, b = prefix->prefix_len; b > 0; b -= 8, i++)
+		a->prefix.s6_addr[i] = prefix->prefix.s6_addr[i];
+	p = &sla->sla[3];
+	i = (128 - 64) / 8;
+	for (b = sla->sla_len; b > 7; b -= 8, p--)
+		a->prefix.s6_addr[--i] = *p;
+	if (b)
+		a->prefix.s6_addr[--i] |= *p;
+
+	if (ipv6_makeaddr(&a->addr, ifp->name, &a->prefix, a->prefix_len) == -1)
+	{
+		syslog(LOG_ERR, "%s: %m", __func__);
+		free(a);
+		return NULL;
+	}
+
+	/* Remove any exiting address */
+	TAILQ_FOREACH(ap, &state->addrs, next) {
+		if (memcmp(&ap->addr, &a->addr, sizeof(ap->addr)) == 0) {
+			TAILQ_REMOVE(&state->addrs, ap, next);
+			free(ap);
+			break;
+		}
+	}
+
+	ia = inet_ntop(AF_INET6, &a->addr.s6_addr, iabuf, sizeof(iabuf));
+	snprintf(a->saddr, sizeof(a->saddr), "%s/%d", ia, a->prefix_len);
+	TAILQ_INSERT_TAIL(&state->addrs, a, next);
+	return a;
+}
+
+static void
+dhcp6_delegate_prefix(struct interface *ifp)
+{
+	struct if_options *ifo;
+	struct dhcp6_state *state, *ifd_state;
+	struct ipv6_addr *ap;
+	size_t i, j, k;
+	struct if_iaid *iaid;
+	struct if_sla *sla;
+	struct interface *ifd;
+
+	ifo = ifp->options;
+	state = D6_STATE(ifp);
+	TAILQ_FOREACH(ifd, ifaces, next) {
+		k = 0;
+		TAILQ_FOREACH(ap, &state->addrs, next) {
+			for (i = 0; i < ifo->iaid_len; i++) {
+				iaid = &ifo->iaid[i];
+				if (memcmp(iaid->iaid, ap->iaid,
+				    sizeof(iaid->iaid)))
+					continue;
+				for (j = 0; j < iaid->sla_len; j++) {
+					sla = &iaid->sla[j];
+					if (strcmp(ifd->name, sla->ifname))
+						continue;
+					if (dhcp6_delegate_addr(ifd, ap,
+					    sla, ifp))
+						k++;
+				}
+			}
+		}
+		if (k) {
+			ifd_state = D6_STATE(ifd);
+			ipv6_addaddrs(ifd, &ifd_state->addrs);
+		}
+	}
+}
+
+int
+dhcp6_find_delegates(struct interface *ifp)
+{
+	struct if_options *ifo;
+	struct dhcp6_state *state;
+	struct ipv6_addr *ap;
+	size_t i, j, k;
+	struct if_iaid *iaid;
+	struct if_sla *sla;
+	struct interface *ifd;
+
+	k = 0;
+	TAILQ_FOREACH(ifd, ifaces, next) {
+		ifo = ifd->options;
+		if (ifo->ia_type != D6_OPTION_IA_PD)
+			continue;
+		state = D6_STATE(ifd);
+		if (state == NULL || state->state != DH6S_BOUND)
+			continue;
+		TAILQ_FOREACH(ap, &state->addrs, next) {
+			for (i = 0; i < ifo->iaid_len; i++) {
+				iaid = &ifo->iaid[i];
+				if (memcmp(iaid->iaid, ap->iaid,
+				    sizeof(iaid->iaid)))
+					continue;
+				for (j = 0; j < iaid->sla_len; j++) {
+					sla = &iaid->sla[j];
+					if (strcmp(ifp->name, sla->ifname))
+						continue;
+					if (dhcp6_delegate_addr(ifp, ap,
+					    sla, ifd))
+					    k++;
+				}
+			}
+		}
+	}
+
+	if (k) {
+		syslog(LOG_INFO, "%s: adding delegated prefixes", ifp->name);
+		state = D6_STATE(ifp);
+		ipv6_addaddrs(ifp, &state->addrs);
+		ipv6_buildroutes();
+	}
+	return k;
+}
+
 /* ARGSUSED */
 static void
 dhcp6_handledata(_unused void *arg)
@@ -1240,7 +1553,7 @@
 	}
 
 	o = dhcp6_getoption(D6_OPTION_CLIENTID, r, len);
-	if (o == NULL || ntohs(o->len) != duid_len || 
+	if (o == NULL || ntohs(o->len) != duid_len ||
 	    memcmp(D6_COPTION_DATA(o), duid, duid_len) != 0)
 	{
 		syslog(LOG_ERR, "%s: incorrect client ID from %s",
@@ -1266,7 +1579,7 @@
 		if (state->state == DH6S_INFORM)
 			break;
 		switch(state->state) {
-		case DH6S_REBOOT:
+		case DH6S_CONFIRM:
 			o = dhcp6_getoption(D6_OPTION_STATUS_CODE, r, len);
 			if (o == NULL) {
 				syslog(LOG_ERR,
@@ -1332,7 +1645,7 @@
 recv:
 	syslog(LOG_INFO, "%s: %s received from %s", ifp->name, op, sfrom);
 
-	reason = NULL; 
+	reason = NULL;
 	eloop_timeout_delete(NULL, ifp);
 	switch(state->state) {
 	case DH6S_INFORM:
@@ -1353,7 +1666,7 @@
 	case DH6S_REBIND:
 		if (reason == NULL)
 			reason = "REBIND6";
-	case DH6S_REBOOT:
+	case DH6S_CONFIRM:
 		if (reason == NULL)
 			reason = "REBOOT6";
 		if (state->renew == 0) {
@@ -1374,7 +1687,7 @@
 		break;
 	}
 
-	if (state->state != DH6S_REBOOT) {
+	if (state->state != DH6S_CONFIRM) {
 		free(state->old);
 		state->old = state->new;
 		state->old_len = state->new_len;
@@ -1395,6 +1708,8 @@
 		if (state->expire != ~0U)
 			eloop_timeout_add_sec(state->expire,
 			    dhcp6_startexpire, ifp);
+		if (ifp->options->ia_type == D6_OPTION_IA_PD)
+			dhcp6_delegate_prefix(ifp);
 		ipv6_addaddrs(ifp, &state->addrs);
 		if (state->renew || state->rebind)
 			syslog(LOG_INFO,
@@ -1478,19 +1793,16 @@
 dhcp6_start(struct interface *ifp, int manage)
 {
 	struct dhcp6_state *state;
-	uint32_t u32;
 
 	state = D6_STATE(ifp);
 	if (state) {
+		if (state->state == DH6S_DELEGATED)
+			return dhcp6_find_delegates(ifp);
 		/* We're already running DHCP6 */
 		/* XXX: What if the managed flag changes? */
 		return 0;
 	}
 
-	syslog(LOG_INFO, "%s: %s", ifp->name,
-	    manage ? "soliciting DHCPv6 address" :
-	    "requesting DHCPv6 information");
-
 	if (sock == -1 && dhcp6_open() == -1)
 		return -1;
 
@@ -1506,21 +1818,20 @@
 	if (state == NULL)
 		return -1;
 
+	TAILQ_INIT(&state->addrs);
+	if (dhcp6_find_delegates(ifp)) {
+		state->state = DH6S_DELEGATED;
+		return 0;
+	}
+
+	syslog(LOG_INFO, "%s: %s", ifp->name,
+	    manage ? "soliciting DHCPv6 address" :
+	    "requesting DHCPv6 information");
+
 	state->state = manage ? DH6S_INIT : DH6S_INFORM;
-	TAILQ_INIT(&state->addrs);
 	snprintf(state->leasefile, sizeof(state->leasefile),
 	    LEASEFILE6, ifp->name);
 
-	u32 = strlen(ifp->name);
-	if (u32 < 5) {
-		memcpy(state->iaid, ifp->name, u32);
-		if (u32 < 4)
-			memset(state->iaid + u32, 0, 4 - u32);
-	} else {
-		u32 = htonl(ifp->index);
-		memcpy(state->iaid, &u32, 4);
-	}
-
 	if (state->state == DH6S_INFORM)
 		dhcp6_startinform(ifp);
 	else
@@ -1547,6 +1858,8 @@
 		free(state->recv);
 		free(state->new);
 		free(state->old);
+//		if (state->state == DH6S_DELEGATED)
+//			return;
 		free(state);
 		ifp->if_data[IF_DATA_DHCP6] = NULL;
 	}
@@ -1603,7 +1916,7 @@
 			continue;
 		if (has_option_mask(ifo->nomask6, opt->option))
 			continue;
- 		o = dhcp6_getoption(opt->option, m, mlen);
+		o = dhcp6_getoption(opt->option, m, mlen);
 		if (o == NULL)
 			continue;
 		if (env == NULL) {
@@ -1631,22 +1944,45 @@
 		if (env == NULL)
 			e++;
 		else {
-			e = strlen(prefix) + strlen("_dhcp6_ip_address=");
-			TAILQ_FOREACH(ap, &state->addrs, next) {
-				e += strlen(ap->saddr) + 1;
+			if (ifo->ia_type == D6_OPTION_IA_PD) {
+				e = strlen(prefix) +
+				    strlen("_dhcp6_prefix=");
+				TAILQ_FOREACH(ap, &state->addrs, next) {
+					e += strlen(ap->saddr) + 1;
+				}
+				v = val = *ep++ = malloc(e);
+				if (v == NULL) {
+					syslog(LOG_ERR, "%s: %m", __func__);
+					return -1;
+				}
+				v += snprintf(val, e, "%s_dhcp6_prefix=",
+					prefix);
+				TAILQ_FOREACH(ap, &state->addrs, next) {
+					strcpy(v, ap->saddr);
+					v += strlen(ap->saddr);
+					*v++ = ' ';
+				}
+				*--v = '\0';
+			} else {
+				e = strlen(prefix) +
+				    strlen("_dhcp6_ip_address=");
+				TAILQ_FOREACH(ap, &state->addrs, next) {
+					e += strlen(ap->saddr) + 1;
+				}
+				v = val = *ep++ = malloc(e);
+				if (v == NULL) {
+					syslog(LOG_ERR, "%s: %m", __func__);
+					return -1;
+				}
+				v += snprintf(val, e, "%s_dhcp6_ip_address=",
+					prefix);
+				TAILQ_FOREACH(ap, &state->addrs, next) {
+					strcpy(v, ap->saddr);
+					v += strlen(ap->saddr);
+					*v++ = ' ';
+				}
+				*--v = '\0';
 			}
-			v = val = *ep++ = malloc(e);
-			if (v == NULL) {
-				syslog(LOG_ERR, "%s: %m", __func__);
-				return -1;
-			}
-			v += snprintf(val, e, "%s_dhcp6_ip_address=", prefix);
-			TAILQ_FOREACH(ap, &state->addrs, next) {
-				strcpy(v, ap->saddr);
-				v += strlen(ap->saddr);
-				*v++ = ' ';
-			}
-			*--v = '\0';
 		}
 	}
 
--- a/dhcp6.h	Thu Mar 28 06:38:27 2013 +0000
+++ b/dhcp6.h	Mon Apr 01 12:15:47 2013 +0000
@@ -54,6 +54,7 @@
 #define D6_OPTION_CLIENTID		1
 #define D6_OPTION_SERVERID		2
 #define D6_OPTION_IA_NA			3
+#define D6_OPTION_IA_TA			4
 #define D6_OPTION_ORO			6
 #define D6_OPTION_IA_ADDR		5
 #define D6_OPTION_PREFERENCE		7
@@ -66,6 +67,8 @@
 #define D6_OPTION_SIP_SERVERS_ADDRESS	22
 #define D6_OPTION_DNS_SERVERS		23
 #define D6_OPTION_DOMAIN_LIST		24
+#define D6_OPTION_IA_PD			25
+#define D6_OPTION_IAPREFIX		26
 #define D6_OPTION_NIS_SERVERS		27
 #define D6_OPTION_NISP_SERVERS		28
 #define D6_OPTION_NIS_DOMAIN_NAME	29
@@ -140,15 +143,15 @@
 	DH6S_BOUND,
 	DH6S_RENEW,
 	DH6S_REBIND,
-	DH6S_REBOOT,
+	DH6S_CONFIRM,
 	DH6S_INFORM,
 	DH6S_RENEW_REQUESTED,
-	DH6S_PROBE
+	DH6S_PROBE,
+	DH6S_DELEGATED
 };
 
 struct dhcp6_state {
 	enum DH6S state;
-	uint8_t iaid[4];
 	time_t start_uptime;
 
 	/* Message retransmission timings */
@@ -201,6 +204,7 @@
 #ifdef INET6
 void dhcp6_printoptions(void);
 int dhcp6_addrexists(const struct ipv6_addr *);
+int dhcp6_find_delegates(struct interface *);
 int dhcp6_start(struct interface *, int);
 ssize_t dhcp6_env(char **, const char *, const struct interface *,
     const struct dhcp6_message *, ssize_t);
@@ -209,6 +213,7 @@
 #else
 #define dhcp6_printoptions()
 #define dhcp6_addrexists(a)
+#define dhcp6_find_delegates(a);
 #define dhcp6_start(a, b) 0
 #define dhcp6_env(a, b, c, d, e)
 #define dhcp6_free(a)
--- a/dhcpcd.8.in	Thu Mar 28 06:38:27 2013 +0000
+++ b/dhcpcd.8.in	Mon Apr 01 12:15:47 2013 +0000
@@ -1,4 +1,4 @@
-.\" Copyright (c) 2006-2012 Roy Marples
+.\" Copyright (c) 2006-2013 Roy Marples
 .\" All rights reserved
 .\"
 .\" Redistribution and use in source and binary forms, with or without
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd November 6, 2012
+.Dd April 1, 2013
 .Dt DHCPCD 8
 .Os
 .Sh NAME
@@ -604,8 +604,8 @@
 .Xr resolvconf 8
 .Sh STANDARDS
 RFC 951, RFC 1534, RFC 2131, RFC 2132, RFC 2855, RFC 3004, RFC 3315,RFC 3361,
-RFC 3396, RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702, RFC 4861,
-RFC 5969, RFC 6106.
+RFC 3633, RFC 3396, RFC 3397, RFC 3442, RFC 3927, RFC 4361, RFC 4390, RFC 4702, 
+RFC 4861, RFC 5969, RFC 6106.
 .Sh AUTHORS
 .An Roy Marples Aq roy@marples.name
 .Sh BUGS
--- a/dhcpcd.c	Thu Mar 28 06:38:27 2013 +0000
+++ b/dhcpcd.c	Mon Apr 01 12:15:47 2013 +0000
@@ -302,7 +302,7 @@
 		ifo->options &= ~DHCPCD_IPV6RS;
 	if (ifo->options & DHCPCD_LINK && carrier_status(ifp) == -1)
 		ifo->options &= ~DHCPCD_LINK;
-	
+
 	if (ifo->metric != -1)
 		ifp->metric = ifo->metric;
 
@@ -430,12 +430,13 @@
 		ipv6rs_start(ifp);
 
 	if (ifo->options & DHCPCD_IPV6) {
-		if (ifo->options & DHCPCD_INFORM)
+		if (ifo->options & DHCPCD_IPV6RS)
 			nolease = dhcp6_start(ifp, 0);
-		else if (!(ifo->options & DHCPCD_IPV6RS))
+		else if (ifo->options & DHCPCD_IA_FORCED)
 			nolease = dhcp6_start(ifp, 1);
-		else
-			nolease = 0;
+		else {
+			nolease = dhcp6_find_delegates(ifp);;
+		}
 		if (nolease == -1)
 			syslog(LOG_ERR, "%s: dhcp6_start: %m", ifp->name);
 	}
@@ -475,7 +476,7 @@
 		syslog(LOG_ERR, "ipv4_init: %m");
 		ifo->options &= ~DHCPCD_IPV4;
 	}
-	if (ifo->options & DHCPCD_IPV6RS && ipv6_init() == -1) {
+	if (ifo->options & DHCPCD_IPV6 && ipv6_init() == -1) {
 		syslog(LOG_ERR, "ipv6_init: %m");
 		ifo->options &= ~DHCPCD_IPV6RS;
 	}
@@ -508,7 +509,7 @@
 {
 	struct if_head *ifs;
 	struct interface *ifp, *ifn, *ifl = NULL;
-	const char * const argv[] = { ifname }; 
+	const char * const argv[] = { ifname };
 	int i;
 
 	if (action == -1) {
@@ -571,7 +572,7 @@
 			ifo = ifp->options;
 			if (!(ifo->options &
 			    (DHCPCD_INFORM | DHCPCD_STATIC | DHCPCD_CLIENTID))
-	    		    && state->new != NULL &&
+			    && state->new != NULL &&
 			    state->new->cookie == htonl(MAGIC_COOKIE))
 			{
 				syslog(LOG_INFO,
@@ -586,7 +587,7 @@
 			{
 				syslog(LOG_DEBUG, "%s: using hwaddr %s",
 				    ifp->name,
-		    		    hwaddr_ntoa(ifp->hwaddr, ifp->hwlen));
+				    hwaddr_ntoa(ifp->hwaddr, ifp->hwlen));
 				state->interval = 0;
 				state->nakoff = 0;
 				start_interface(ifp);
@@ -613,7 +614,7 @@
 {
 	struct if_head *ifs;
 	struct interface *ifn, *ifp;
-	
+
 	ifs = discover_interfaces(argc - oi, argv + oi);
 	if (ifs == NULL)
 		return;
@@ -982,7 +983,7 @@
 		options |= DHCPCD_PERSISTENT;
 		options &= ~DHCPCD_DAEMONISE;
 	}
-	
+
 #ifdef THERE_IS_NO_FORK
 	options &= ~DHCPCD_DAEMONISE;
 #endif
--- a/dhcpcd.conf.5.in	Thu Mar 28 06:38:27 2013 +0000
+++ b/dhcpcd.conf.5.in	Mon Apr 01 12:15:47 2013 +0000
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd March 27, 2013
+.Dd April 1, 2013
 .Dt DHCPCD.CONF 5 SMM
 .Os
 .Sh NAME
@@ -133,6 +133,36 @@
 If
 .Ar hostname
 is a FQDN (ie, contains a .) then it will be encoded as such.
+.It Ic ia_na Ar iaid
+Request a DHCPv6 Normal Address for
+.Ar iaid .
+If none is specified, a default
+.Ar iaid
+is used.
+If the interface name is 4 characters or less then that is used,
+otherwise the interface index is used.
+.It Ic ia_ta Ar iaid
+Request a DHCPv6 Temporary Address for
+.Ar iaid .
+.It Ic ia_pd Ar iaid Op Ar interface / Ar sla_id Op / Ar sla_len
+Request a DHCPv6 Delegated Prefix for
+.Ar iaid .
+If an
+.Ar interface
+and
+.Ar sla_id
+is given,
+then an address is also assigned to that
+.Ar interface .
+A default
+.Ar sla_len
+of 16 is assumed if not given.
+IPv6RS should be disabled globally when requesting a Prefix Delegation like so:
+.Pp
+.D1 noipv6rs
+.Pp
+.D1 interface eth0
+.D1 ia_pd 00:11:22:33 eth1/33:44:55:66
 .It Ic ipv4only
 Only configure IPv4.
 .It Ic ipv6only
--- a/if-options.c	Thu Mar 28 06:38:27 2013 +0000
+++ b/if-options.c	Mon Apr 01 12:15:47 2013 +0000
@@ -57,11 +57,14 @@
 #define O_FALLBACK		O_BASE + 2
 #define O_DESTINATION		O_BASE + 3
 #define O_IPV6RS		O_BASE + 4
-#define O_NOIPV6RS		O_BASE + 5 
-#define O_IPV6RA_FORK		O_BASE + 6 
+#define O_NOIPV6RS		O_BASE + 5
+#define O_IPV6RA_FORK		O_BASE + 6
 #define O_IPV6RA_OWN		O_BASE + 7
 #define O_IPV6RA_OWN_D		O_BASE + 8
 #define O_NOALIAS		O_BASE + 9
+#define O_IA_NA			O_BASE + 10
+#define O_IA_TA			O_BASE + 11
+#define O_IA_PD			O_BASE + 12
 
 const struct option cf_options[] = {
 	{"background",      no_argument,       NULL, 'b'},
@@ -95,7 +98,7 @@
 	{"lastlease",       no_argument,       NULL, 'E'},
 	{"fqdn",            optional_argument, NULL, 'F'},
 	{"nogateway",       no_argument,       NULL, 'G'},
-	{"xidhwaddr",       no_argument,       NULL, 'H'}, 
+	{"xidhwaddr",       no_argument,       NULL, 'H'},
 	{"clientid",        optional_argument, NULL, 'I'},
 	{"broadcast",       no_argument,       NULL, 'J'},
 	{"nolink",          no_argument,       NULL, 'K'},
@@ -120,6 +123,9 @@
 	{"ipv4only",        no_argument,       NULL, '4'},
 	{"ipv6only",        no_argument,       NULL, '6'},
 	{"noalias",         no_argument,       NULL, O_NOALIAS},
+	{"ia_na",           no_argument,       NULL, O_IA_NA},
+	{"ia_ta",           no_argument,       NULL, O_IA_TA},
+	{"ia_pd",           no_argument,       NULL, O_IA_PD},
 	{NULL,              0,                 NULL, '\0'}
 };
 
@@ -141,7 +147,7 @@
 	return (int)n;
 }
 
-static char * 
+static char *
 add_environ(struct if_options *ifo, const char *value, int uniq)
 {
 	char **newlist;
@@ -339,7 +345,7 @@
 		v[(*argc) - 1] = nt;
 	}
 	free(o);
-	return v;	
+	return v;
 }
 
 static int
@@ -365,7 +371,7 @@
 			syslog(LOG_ERR, "`%s' is not a valid CIDR", p);
 			return -1;
 		}
-	} 
+	}
 
 	if (addr != NULL && inet_aton(arg, addr) == 0) {
 		syslog(LOG_ERR, "`%s' is not a valid IP address", arg);
@@ -382,7 +388,7 @@
 #endif
 }
 
-static const char * 
+static const char *
 set_option_space(const char *arg, const struct dhcp_opt **d,
     struct if_options *ifo,
     uint8_t *request[], uint8_t *require[], uint8_t *no[])
@@ -415,12 +421,17 @@
 	int i;
 	char *p = NULL, *fp, *np, **nconf;
 	ssize_t s;
+	size_t sl;
 	struct in_addr addr, addr2;
 	in_addr_t *naddr;
 	struct rt *rt;
 	const struct dhcp_opt const *d;
 	uint8_t *request, *require, *no;
+	struct if_iaid *iaid;
+	uint8_t _iaid[4];
+	struct if_sla *sla;
 
+	i = 0;
 	switch(opt) {
 	case 'f': /* FALLTHROUGH */
 	case 'g': /* FALLTHROUGH */
@@ -780,7 +791,7 @@
 					syslog(LOG_ERR, "%s: %m", __func__);
 					return -1;
 				}
- 				TAILQ_INIT(ifo->routes);
+				TAILQ_INIT(ifo->routes);
 			}
 			rt = malloc(sizeof(*rt));
 			if (rt == NULL) {
@@ -898,7 +909,7 @@
 		ifo->fallback = strdup(arg);
 		if (ifo->fallback == NULL) {
 			syslog(LOG_ERR, "%s: %m", __func__);
-		    	return -1;
+			return -1;
 		}
 		break;
 #endif
@@ -920,6 +931,116 @@
 	case O_NOALIAS:
 		ifo->options |= DHCPCD_NOALIAS;
 		break;
+#ifdef INET6
+	case O_IA_NA:
+		i = D6_OPTION_IA_NA;
+		/* FALLTHROUGH */
+	case O_IA_TA:
+		if (i == 0)
+			i = D6_OPTION_IA_TA;
+		/* FALLTHROUGH */
+	case O_IA_PD:
+		if (i == 0)
+			i = D6_OPTION_IA_PD;
+		ifo->options |= DHCPCD_IA_FORCED;
+		if (ifo->ia_type != 0 && ifo->ia_type != i) {
+			syslog(LOG_ERR, "cannot specify a different IA type");
+			return -1;
+		}
+		ifo->ia_type = i;
+		if (arg == NULL)
+			break;
+		fp = strchr(arg, ' ');
+		if (fp == NULL) {
+			syslog(LOG_ERR, "%s: invalid syntax", arg);
+			return -1;
+		}
+		*fp++ = '\0';
+		if ((s = parse_string((char *)_iaid, sizeof(_iaid), arg)) < 1) {
+			syslog(LOG_ERR, "%s: invalid IAID", arg);
+			return -1;
+		}
+		if (s < 4)
+			_iaid[3] = '\0';
+		if (s < 3)
+			_iaid[2] = '\0';
+		if (s < 2)
+			_iaid[1] = '\0';
+		iaid = NULL;
+		for (sl = 0; sl < ifo->iaid_len; sl++) {
+			if (ifo->iaid[sl].iaid[0] == _iaid[0] &&
+			    ifo->iaid[sl].iaid[1] == _iaid[1] &&
+			    ifo->iaid[sl].iaid[2] == _iaid[2] &&
+			    ifo->iaid[sl].iaid[3] == _iaid[3])
+			{
+			        iaid = &ifo->iaid[sl];
+				break;
+			}
+		}
+		if (iaid == NULL) {
+			iaid = realloc(ifo->iaid,
+			    sizeof(*ifo->iaid) * (ifo->iaid_len + 1));
+			if (iaid == NULL) {
+				syslog(LOG_ERR, "%s: %m", __func__);
+				return -1;
+			}
+			ifo->iaid = iaid;
+			iaid = &ifo->iaid[ifo->iaid_len++];
+			iaid->iaid[0] = _iaid[0];
+			iaid->iaid[1] = _iaid[1];
+			iaid->iaid[2] = _iaid[2];
+			iaid->iaid[3] = _iaid[3];
+			iaid->sla = NULL;
+			iaid->sla_len = 0;
+		}
+		for (p = fp; p; p = fp) {
+			fp = strchr(p, ' ');
+			if (fp)
+				*fp++ = '\0';
+			sla = realloc(iaid->sla,
+			    sizeof(*iaid->sla) * (iaid->sla_len + 1));
+			if (sla == NULL) {
+				syslog(LOG_ERR, "%s: %m", __func__);
+				return -1;
+			}
+			iaid->sla = sla;
+			sla = &iaid->sla[iaid->sla_len++];
+			np = strchr(p, '/');
+			if (np)
+				*np++ = '\0';
+			else {
+				syslog(LOG_ERR, "%s: missing sla", arg);
+				return -1;
+			}
+			if (strlcpy(sla->ifname, p,
+			    sizeof(sla->ifname)) >= sizeof(sla->ifname))
+			{
+				syslog(LOG_ERR, "%s: interface name too long",
+				    arg);
+				return -1;
+			}
+			p = np;
+			np = strchr(p, '/');
+			if (np)
+				*np++ = '\0';
+			if (parse_string((char *)sla->sla,
+			    sizeof(sla->sla), p) == -1)
+			{
+				syslog(LOG_ERR, "%s: sla: %m", arg);
+				return -1;
+			}
+			if (np) {
+				sla->sla_len = atoint(np);
+				if (sla->sla_len < 0 || sla->sla_len > 128) {
+					syslog(LOG_ERR, "%s: sla len: range",
+					    arg);
+					return -1;
+				}
+			} else
+				sla->sla_len = 8;
+		}
+		break;
+#endif
 	default:
 		return 0;
 	}
@@ -969,7 +1090,7 @@
 	ifo->options |= DHCPCD_DAEMONISE | DHCPCD_LINK;
 #ifdef INET
 	ifo->options |= DHCPCD_IPV4 | DHCPCD_IPV4LL;
-	ifo->options |= DHCPCD_GATEWAY | DHCPCD_ARP; 
+	ifo->options |= DHCPCD_GATEWAY | DHCPCD_ARP;
 #endif
 #ifdef INET6
 	ifo->options |= DHCPCD_IPV6 | DHCPCD_IPV6RS | DHCPCD_IPV6RA_REQRDNSS;
@@ -1048,6 +1169,33 @@
 		ifo->vendor[0]++;
 		ifo->vendor[ifo->vendor[0]] = DHO_END;
 	}
+
+#ifdef INET6
+	if (ifname && ifo->iaid_len == 0 && ifo->options & DHCPCD_IPV6) {
+		ifo->iaid = malloc(sizeof(*ifo->iaid));
+		if (ifo->iaid == NULL)
+			syslog(LOG_ERR, "%s: %m", __func__);
+		else {
+			if (ifo->ia_type == 0)
+				ifo->ia_type = D6_OPTION_IA_NA;
+			ifo->iaid_len = strlen(ifname);
+			if (ifo->iaid_len <= sizeof(ifo->iaid->iaid)) {
+				strncpy((char *)ifo->iaid->iaid, ifname,
+					sizeof(ifo->iaid->iaid));
+				memset(ifo->iaid->iaid + ifo->iaid_len, 0,
+					sizeof(ifo->iaid->iaid) -ifo->iaid_len);
+			} else {
+				uint32_t idx = if_nametoindex(ifname);
+				memcpy(ifo->iaid->iaid, &idx, sizeof(idx));
+			}
+			ifo->iaid_len = 1;
+			ifo->iaid->sla = NULL;
+			ifo->iaid->sla_len = 0;
+		}
+	} else
+		ifo->options |= DHCPCD_IA_FORCED;
+#endif
+
 	return ifo;
 }
 
@@ -1093,6 +1241,11 @@
 		free(ifo->arping);
 		free(ifo->blacklist);
 		free(ifo->fallback);
+#ifdef INET6
+		for (i = 0; i < ifo->iaid_len; i++)
+			free(ifo->iaid[i].sla);
+		free(ifo->iaid);
+#endif
 		free(ifo);
 	}
 }
--- a/if-options.h	Thu Mar 28 06:38:27 2013 +0000
+++ b/if-options.h	Mon Apr 01 12:15:47 2013 +0000
@@ -1,6 +1,6 @@
 /* 
  * dhcpcd - DHCP client daemon
- * Copyright (c) 2006-2012 Roy Marples <roy@marples.name>
+ * Copyright (c) 2006-2013 Roy Marples <roy@marples.name>
  * All rights reserved
 
  * Redistribution and use in source and binary forms, with or without
@@ -70,7 +70,7 @@
 #define DHCPCD_HOSTNAME			(1ULL << 18)
 #define DHCPCD_CLIENTID			(1ULL << 19)
 #define DHCPCD_LINK			(1ULL << 20)
-#define DHCPCD_QUIET			(1ULL << 21) 
+#define DHCPCD_QUIET			(1ULL << 21)
 #define DHCPCD_BACKGROUND		(1ULL << 22)
 #define DHCPCD_VENDORRAW		(1ULL << 23)
 #define DHCPCD_TIMEOUT_IPV4LL		(1ULL << 24)
@@ -89,9 +89,22 @@
 #define DHCPCD_IPV6			(1ULL << 37)
 #define DHCPCD_STARTED			(1ULL << 38)
 #define DHCPCD_NOALIAS			(1ULL << 39)
+#define DHCPCD_IA_FORCED		(1ULL << 40)
 
 extern const struct option cf_options[];
 
+struct if_sla {
+	char ifname[IF_NAMESIZE];
+	uint8_t sla[16];
+	short sla_len;
+};
+
+struct if_iaid {
+	uint8_t iaid[4];
+	size_t sla_len;
+	struct if_sla *sla;
+};
+
 struct if_options {
 	int metric;
 	uint8_t requestmask[256 / 8];
@@ -113,7 +126,7 @@
 
 	char **environ;
 	char script[PATH_MAX];
-	
+
 	char hostname[HOSTNAME_MAX_LEN + 1]; /* We don't store the length */
 	int fqdn;
 	uint8_t vendorclassid[VENDORCLASSID_MAX_LEN + 2];
@@ -128,6 +141,12 @@
 	size_t arping_len;
 	in_addr_t *arping;
 	char *fallback;
+
+#ifdef INET6
+	uint16_t ia_type;
+	size_t iaid_len;
+	struct if_iaid *iaid;
+#endif
 };
 
 extern unsigned long long options;
--- a/ipv6.c	Thu Mar 28 06:38:27 2013 +0000
+++ b/ipv6.c	Mon Apr 01 12:15:47 2013 +0000
@@ -482,7 +482,10 @@
 	TAILQ_INIT(&dnr);
 	TAILQ_FOREACH(ifp, ifaces, next) {
 		d6_state = D6_CSTATE(ifp);
-		if (d6_state && d6_state->state == DH6S_BOUND) {
+		if (d6_state &&
+		    (d6_state->state == DH6S_BOUND ||
+		     d6_state->state == DH6S_DELEGATED))
+		{
 			TAILQ_FOREACH(addr, &d6_state->addrs, next) {
 				if (!addr->onlink)
 					continue;
--- a/ipv6.h	Thu Mar 28 06:38:27 2013 +0000
+++ b/ipv6.h	Mon Apr 01 12:15:47 2013 +0000
@@ -47,6 +47,8 @@
 	uint8_t onlink;
 	uint8_t new;
 	char saddr[INET6_ADDRSTRLEN];
+	uint8_t iaid[4];
+	struct interface *delegating_iface;
 };
 TAILQ_HEAD(ipv6_addrhead, ipv6_addr);