changeset 2186:b4d8f5bb2fe4 draft

Fix encapsulated options. IA_NA, IA_TA and IA_PD can now be expressed fully in our config :)
author Roy Marples <roy@marples.name>
date Tue, 03 Dec 2013 17:55:40 +0000
parents 7f9e761f857c
children 9c8b8f4c11f4
files dhcp-common.c dhcp-common.h dhcp.c dhcp6.c dhcpcd-definitions.conf dhcpcd.conf.5.in if-options.c
diffstat 7 files changed, 351 insertions(+), 204 deletions(-) [+]
line wrap: on
line diff
--- a/dhcp-common.c	Mon Dec 02 20:45:19 2013 +0000
+++ b/dhcp-common.c	Tue Dec 03 17:55:40 2013 +0000
@@ -286,6 +286,8 @@
 	return bytes;
 }
 
+#define ADDRSZ		4
+#define ADDR6SZ		16
 static size_t
 dhcp_optlen(const struct dhcp_opt *opt, size_t dl)
 {
@@ -306,9 +308,15 @@
 	}
 
 	if ((opt->type & (ADDRIPV4 | ARRAY)) == (ADDRIPV4 | ARRAY)) {
-		if (dl < sizeof(uint32_t))
+		if (dl < ADDRSZ)
 			return 0;
-		return dl - (dl % sizeof(uint32_t));
+		return dl - (dl % ADDRSZ);
+	}
+
+	if ((opt->type & (ADDRIPV6 | ARRAY)) == (ADDRIPV6 | ARRAY)) {
+		if (dl < ADDR6SZ)
+			return 0;
+		return dl - (dl % ADDR6SZ);
 	}
 
 	sz = 0;
@@ -318,6 +326,8 @@
 		sz = sizeof(uint16_t);
 	else if (opt->type & UINT8)
 		sz = sizeof(uint8_t);
+	else if (opt->type & ADDRIPV6)
+		sz = ADDR6SZ;
 	else
 		/* If we don't know the size, assume it's valid */
 		return dl;
@@ -451,12 +461,12 @@
 			len--;
 		}
 		if (type & UINT8) {
-			l = snprintf(s, len, "%d", *data);
+			l = snprintf(s, len, "%u", *data);
 			data++;
 		} else if (type & UINT16) {
 			memcpy(&u16, data, sizeof(u16));
 			u16 = ntohs(u16);
-			l = snprintf(s, len, "%d", u16);
+			l = snprintf(s, len, "%u", u16);
 			data += sizeof(u16);
 		} else if (type & SINT16) {
 			memcpy(&s16, data, sizeof(s16));
@@ -466,7 +476,7 @@
 		} else if (type & UINT32) {
 			memcpy(&u32, data, sizeof(u32));
 			u32 = ntohl(u32);
-			l = snprintf(s, len, "%d", u32);
+			l = snprintf(s, len, "%u", u32);
 			data += sizeof(u32);
 		} else if (type & SINT32) {
 			memcpy(&s32, data, sizeof(s32));
@@ -502,9 +512,9 @@
 }
 
 static ssize_t
-dhcp_envoption1(char **env, const char *prefix, const char *famprefix,
-    const char *ifname, const struct dhcp_opt *opt,
-    const uint8_t *od, int ol)
+dhcp_envoption1(char **env, const char *prefix,
+    const struct dhcp_opt *opt, int vname, const uint8_t *od, int ol,
+    const char *ifname)
 {
 	ssize_t len;
 	size_t e;
@@ -515,59 +525,79 @@
 	len = print_option(NULL, 0, opt->type, ol, od, ifname);
 	if (len < 0)
 		return 0;
-	e = strlen(prefix) + strlen(famprefix) + strlen(opt->v.var) + len + 4;
+	if (vname)
+		e = strlen(opt->v.var);
+	else
+		e = 0;
+	e += strlen(prefix) + len + 4;
 	v = val = *env = malloc(e);
 	if (v == NULL) {
 		syslog(LOG_ERR, "%s: %m", __func__);
 		return 0;
 	}
-	v += snprintf(val, e, "%s%s_%s=", prefix, famprefix, opt->v.var);
+	if (vname)
+		v += snprintf(val, e, "%s_%s=", prefix, opt->v.var);
+	else
+		v += snprintf(val, e, "%s=", prefix);
 	if (len != 0)
 		print_option(v, len, opt->type, ol, od, ifname);
 	return len;
 }
 
 ssize_t
-dhcp_envoption(char **env, const char *prefix, const char *famprefix,
-    const char *ifname, const struct dhcp_opt *opt,
-    const uint8_t *(*dgetopt)(int *, int, const uint8_t *, int),
+dhcp_envoption(char **env, const char *prefix,
+    const char *ifname, struct dhcp_opt *opt,
+    const uint8_t *(*dgetopt)(int *, int *, int *, const uint8_t *, int,
+        struct dhcp_opt **),
     const uint8_t *od, int ol)
 {
 	ssize_t e, n;
 	size_t i;
-	const uint8_t *ed;
-	int el;
-	const struct dhcp_opt *eopt;
-	char *eprefix;
+	int eoc;
+	const uint8_t *eod;
+	int eos, eol, ov;
+	struct dhcp_opt *eopt, *oopt;
+	char *pfx;
 	const char *p;
 
 	/* If no embedded or encapsulated options, it's easy */
 	if (opt->embopts_len == 0 && opt->encopts_len == 0) {
 		if (env)
-			dhcp_envoption1(&env[0], prefix, famprefix, ifname,
-			    opt, od, ol);
+			dhcp_envoption1(&env[0], prefix, opt, 1, od, ol,
+			    ifname);
 		return 1;
 	}
 
 	/* Create a new prefix based on the option */
 	if (env) {
-		e = strlen(famprefix) + strlen(opt->v.var) + 2;
-		eprefix = malloc(e);
-		if (eprefix == NULL) {
+		if (opt->type & INDEX) {
+			if (opt->index > 999) {
+				errno = ENOBUFS;
+				syslog(LOG_ERR, "%s: %m", __func__);
+				return 0;
+			}
+		}
+		e = strlen(prefix) + strlen(opt->v.var) + 2 +
+		    (opt->type & INDEX ? 3 : 0);
+		pfx = malloc(e);
+		if (pfx == NULL) {
 			syslog(LOG_ERR, "%s: %m", __func__);
 			return 0;
 		}
-		snprintf(eprefix, e, "%s_%s", famprefix, opt->v.var);
-	}
-	/* Silence bogus gcc warning */
-	else
-		eprefix = NULL;
+		if (opt->type & INDEX)
+			snprintf(pfx, e, "%s_%s%d", prefix,
+			    opt->v.var, ++opt->index);
+		else
+			snprintf(pfx, e, "%s_%s", prefix, opt->v.var);
+		p = pfx;
+	} else
+		p = pfx = NULL;
 
 	/* Embedded options are always processed first as that
 	 * is a fixed layout */
 	n = 0;
-	for (i = 0; i < opt->embopts_len; i++) {
-		eopt = &opt->embopts[i];
+
+	for (i = 0, eopt = opt->embopts; i < opt->embopts_len; i++, eopt++) {
 		e = dhcp_optlen(eopt, ol);
 		if (e == 0)
 			/* Report error? */
@@ -576,37 +606,71 @@
 			/* Use the option prefix if the embedded option
 			 * name is different.
 			 * This avoids new_fqdn_fqdn which would be silly. */
-			if (strcmp(opt->v.var, eopt->v.var) == 0)
-				p = famprefix;
-			else
-				p = eprefix;
-			dhcp_envoption1(&env[n], prefix, p, ifname,
-			    eopt, od, e);
+			ov = strcmp(opt->v.var, eopt->v.var);
+			dhcp_envoption1(&env[n], p, eopt, ov, od, e, ifname);
 		}
 		n++;
 		od += e;
 		ol -= e;
 	}
 
-	/* Now find our encapsulated option in what's left */
-	for (i = 0; i < opt->encopts_len; i++) {
-		eopt = &opt->encopts[i];
-		if ((ed = dgetopt(&el, eopt->option, od, ol))) {
-			if (env) {
-				if (strcmp(opt->v.var, eopt->v.var) == 0)
-					p = famprefix;
-				else
-					p = eprefix;
-				dhcp_envoption1(&env[n], prefix, p,
-				    ifname, eopt, ed, el);
+	/* Enumerate our encapsulated options */
+	if (opt->encopts_len && ol > 0) {
+		/* Zero any option indexes
+		 * We assume that referenced encapsulated options are NEVER
+		 * recursive as the index order could break. */
+		for (i = 0, eopt = opt->encopts;
+		    i < opt->encopts_len;
+		    i++, eopt++)
+		{
+			eoc = opt->option;
+			if (eopt->type & OPTION) {
+				dgetopt(NULL, &eoc, NULL, NULL, 0, &oopt);
+				if (oopt)
+					oopt->index = 0;
 			}
-			n++;
+		}
+
+		while ((eod = dgetopt(&eos, &eoc, &eol, od, ol, &oopt))) {
+			for (i = 0, eopt = opt->encopts;
+			    i < opt->encopts_len;
+			    i++, eopt++)
+			{
+				if (eopt->option == eoc) {
+					if (eopt->type & OPTION) {
+						if (oopt == NULL)
+							/* Report error? */
+							continue;
+						eopt = oopt;
+					}
+					n += dhcp_envoption(
+					    env == NULL ? NULL : &env[n], p,
+					    ifname, eopt,
+					    dgetopt, eod, eol);
+					break;
+				}
+			}
+			od += eos + eol;
+			ol -= eos + eol;
 		}
 	}
 
 	if (env)
-		free(eprefix);
+		free(pfx);
 
 	/* Return number of options found */
 	return n;
 }
+
+void
+dhcp_zero_index(struct dhcp_opt *opt)
+{
+	size_t i;
+	struct dhcp_opt *o;
+
+	opt->index = 0;
+	for (i = 0, o = opt->embopts; i < opt->embopts_len; i++, opt++)
+		dhcp_zero_index(o);
+	for (i = 0, o = opt->encopts; i < opt->encopts_len; i++, opt++)
+		dhcp_zero_index(o);
+}
--- a/dhcp-common.h	Mon Dec 02 20:45:19 2013 +0000
+++ b/dhcp-common.h	Tue Dec 03 17:55:40 2013 +0000
@@ -60,6 +60,8 @@
 #define NOREQ		(1 << 18)
 #define EMBED		(1 << 19)
 #define ENCAP		(1 << 20)
+#define INDEX		(1 << 21)
+#define OPTION		(1 << 22)
 
 struct dhcp_opt {
 	uint16_t option;
@@ -74,6 +76,8 @@
 		const char *var;
 	} v;
 
+	int index; /* Index counter for many instances of the same option */
+
 	/* Embedded options.
 	 * The option code is irrelevant here. */
 	struct dhcp_opt *embopts;
@@ -95,9 +99,10 @@
 ssize_t print_string(char *, ssize_t, int, const uint8_t *);
 ssize_t print_option(char *, ssize_t, int, int, const uint8_t *, const char *);
 
-ssize_t dhcp_envoption(char **, const char *, const char *, const char *,
-    const struct dhcp_opt *,
-    const uint8_t *(*)(int *, int, const uint8_t *, int),
-    const uint8_t *, int);
+ssize_t dhcp_envoption(char **, const char *, const char *, struct dhcp_opt *,
+    const uint8_t *(*dgetopt)(int *, int *, int *, const uint8_t *, int,
+        struct dhcp_opt **),
+    const uint8_t *od, int ol);
+void dhcp_zero_index(struct dhcp_opt *);
 
 #endif
--- a/dhcp.c	Mon Dec 02 20:45:19 2013 +0000
+++ b/dhcp.c	Tue Dec 03 17:55:40 2013 +0000
@@ -985,23 +985,34 @@
 }
 
 static const uint8_t *
-dhcp_getoption(int *len, int option, const uint8_t *od, int ol)
+dhcp_getoption(int *os, int *code, int *len, const uint8_t *od, int ol,
+   struct dhcp_opt **oopt)
 {
-	const uint8_t *o;
-	uint8_t l;
+	size_t i;
+	struct dhcp_opt *opt;
 
-	while (ol > 0) {
-		o = od++;
-		l = *od++;
-		if (l > ol)
-			/* Report malformed data? */
+	if (od) {
+		if (ol < 0) {
+			errno = EINVAL;
 			return NULL;
-		if (*o == option) {
-			*len = l;
-			return od;
+		}
+		*os = 2; /* code + len */
+		*code = (int)*od++;
+		*len = (int)*od++;
+		if (*len < 0 || *len > ol) {
+			errno = EINVAL;
+			return NULL;
 		}
 	}
-	return NULL;
+
+	for (i = 0, opt = dhcp_opts; i < dhcp_opts_len; i++, opt++) {
+		if (opt->option == *code) {
+			*oopt = opt;
+			break;
+		}
+	}
+
+	return od;
 }
 
 ssize_t
@@ -1014,12 +1025,12 @@
 	struct in_addr addr;
 	struct in_addr net;
 	struct in_addr brd;
-	const struct dhcp_opt *opt;
+	struct dhcp_opt *opt;
 	ssize_t e = 0;
 	char **ep;
 	char cidr[4];
 	uint8_t overl = 0;
-	size_t oi;
+	size_t i;
 
 	ifo = ifp->options;
 	get_option_uint8(&overl, dhcp, DHO_OPTIONSOVERLOADED);
@@ -1031,9 +1042,9 @@
 			e++;
 		if (*dhcp->servername && !(overl & 2))
 			e++;
-		for (oi = 0, opt = dhcp_opts;
-		    oi < dhcp_opts_len;
-		    oi++, opt++)
+		for (i = 0, opt = dhcp_opts;
+		    i < dhcp_opts_len;
+		    i++, opt++)
 		{
 			if (has_option_mask(ifo->nomask, opt->option))
 				continue;
@@ -1042,19 +1053,19 @@
 			p = get_option(dhcp, opt->option, &pl);
 			if (!p)
 				continue;
-			e += dhcp_envoption(NULL, prefix, "", ifp->name,
+			e += dhcp_envoption(NULL, NULL, ifp->name,
 			    opt, dhcp_getoption, p, pl);
 		}
-		for (oi = 0, opt = ifo->dhcp_override;
-		    oi < ifo->dhcp_override_len;
-		    oi++, opt++)
+		for (i = 0, opt = ifo->dhcp_override;
+		    i < ifo->dhcp_override_len;
+		    i++, opt++)
 		{
 			if (has_option_mask(ifo->nomask, opt->option))
 				continue;
 			p = get_option(dhcp, opt->option, &pl);
 			if (!p)
 				continue;
-			e += dhcp_envoption(NULL, prefix, "", ifp->name,
+			e += dhcp_envoption(NULL, NULL, ifp->name,
 			    opt, dhcp_getoption, p, pl);
 		}
 		return e;
@@ -1087,27 +1098,37 @@
 		setvar(&ep, prefix, "server_name",
 		    (const char *)dhcp->servername);
 
-	for (oi = 0, opt = dhcp_opts;
-	    oi < dhcp_opts_len;
-	    oi++, opt++)
+	/* Zero our indexes */
+	if (env) {
+		for (i = 0, opt = dhcp_opts; i < dhcp_opts_len; i++, opt++)
+			dhcp_zero_index(opt);
+		for (i = 0, opt = ifp->options->dhcp_override;
+		    i < ifp->options->dhcp_override_len;
+		    i++, opt++)
+			dhcp_zero_index(opt);
+	}
+
+	for (i = 0, opt = dhcp_opts;
+	    i < dhcp_opts_len;
+	    i++, opt++)
 	{
 		if (has_option_mask(ifo->nomask, opt->option))
 			continue;
 		if (dhcp_getoverride(ifo, opt->option))
 			continue;
 		if ((p = get_option(dhcp, opt->option, &pl)))
-			ep += dhcp_envoption(ep, prefix, "", ifp->name,
+			ep += dhcp_envoption(ep, prefix, ifp->name,
 			    opt, dhcp_getoption, p, pl);
 	}
 
-	for (oi = 0, opt = ifo->dhcp_override;
-	    oi < ifo->dhcp_override_len;
-	    oi++, opt++)
+	for (i = 0, opt = ifo->dhcp_override;
+	    i < ifo->dhcp_override_len;
+	    i++, opt++)
 	{
 		if (has_option_mask(ifo->nomask, opt->option))
 			continue;
 		if ((p = get_option(dhcp, opt->option, &pl)))
-			ep += dhcp_envoption(ep, prefix, "", ifp->name,
+			ep += dhcp_envoption(ep, prefix, ifp->name,
 			    opt, dhcp_getoption, p, pl);
 	}
 
--- a/dhcp6.c	Mon Dec 02 20:45:19 2013 +0000
+++ b/dhcp6.c	Tue Dec 03 17:55:40 2013 +0000
@@ -286,15 +286,39 @@
 }
 
 static const uint8_t *
-dhcp6_getoption(int *len, int option, const uint8_t *od, int ol)
+dhcp6_getoption(int *os, int *code, int *len, const uint8_t *od, int ol,
+    struct dhcp_opt **oopt)
 {
 	const struct dhcp6_option *o;
+	size_t i;
+	struct dhcp_opt *opt;
 
-	o = dhcp6_findoption(option, od, ol);
-	if (o == NULL)
-		return NULL;
-	*len = ntohs(o->len);
-	return D6_COPTION_DATA(o);
+	if (od) {
+		*os = sizeof(*o);
+		if (ol < *os) {
+			errno = EINVAL;
+			return NULL;
+		}
+		o = (const struct dhcp6_option *)od;
+		*len = ntohs(o->len);
+		if (*len < 0 || *len > ol) {
+			errno = EINVAL;
+			return NULL;
+		}
+		*code = ntohs(o->code);
+	} else
+		o = NULL;
+
+	for (i = 0, opt = dhcp6_opts; i < dhcp6_opts_len; i++, opt++) {
+		if (opt->option == *code) {
+			*oopt = opt;
+			break;
+		}
+	}
+
+	if (o)
+		return D6_COPTION_DATA(o);
+	return NULL;
 }
 
 static const struct dhcp6_option *
@@ -2542,36 +2566,20 @@
 			continue;
 		ipv6_handleifa_addrs(cmd, &state->addrs, addr, flags);
 	}
-}
 
-static const struct dhcp_opt *
-dhcp6_getoverride(const struct if_options *ifo, uint16_t o)
-{
-	size_t i;
-	const struct dhcp_opt *opt;
-
-	for (i = 0, opt = ifo->dhcp6_override;
-	    i < ifo->dhcp6_override_len;
-	    i++, opt++)
-	{
-		if (opt->option == o)
-			return opt;
-	}
-	return NULL;
 }
 
 ssize_t
 dhcp6_env(char **env, const char *prefix, const struct interface *ifp,
-    const struct dhcp6_message *m, ssize_t mlen)
+    const struct dhcp6_message *m, ssize_t len)
 {
 	const struct dhcp6_state *state;
 	const struct if_options *ifo;
-	const struct dhcp_opt *opt;
+	struct dhcp_opt *opt;
 	const struct dhcp6_option *o;
-	size_t e, n, oi;
-	uint16_t ol;
-	const uint8_t *od;
-	char **ep, *v, *val;
+	size_t i, n;
+	uint16_t ol, oc;
+	char **ep, *v, *val, *pfx;
 	const struct ipv6_addr *ap;
 
 	state = D6_CSTATE(ifp);
@@ -2579,40 +2587,80 @@
 	ep = env;
 	ifo = ifp->options;
 
-	for (oi = 0, opt = dhcp6_opts;
-	    oi < dhcp6_opts_len;
-	    oi++, opt++)
-	{
-		if (has_option_mask(ifo->nomask, opt->option))
-			continue;
-		if (dhcp6_getoverride(ifo, opt->option))
-			continue;
+	/* Zero our indexes */
+	if (env) {
+		for (i = 0, opt = dhcp6_opts; i < dhcp6_opts_len; i++, opt++)
+			dhcp_zero_index(opt);
+		for (i = 0, opt = ifp->options->dhcp6_override;
+		    i < ifp->options->dhcp6_override_len;
+		    i++, opt++)
+			dhcp_zero_index(opt);
+		i = strlen(prefix) + strlen("_dhcp6") + 1;
+		pfx = malloc(i);
+		if (pfx == NULL) {
+			syslog(LOG_ERR, "%s: %m", __func__);
+			return 0;
+		}
+		snprintf(pfx, i, "%s_dhcp6", prefix);
+	} else
+		pfx = NULL;
 
-		o = dhcp6_getmoption(opt->option, m, mlen);
-		if (o == NULL)
-			continue;
+	/* Unlike DHCP, DHCPv6 options *may* occur more than once.
+	 * There is also no provision for option concatenation unlike DHCP. */
+	for (o = D6_CFIRST_OPTION(m);
+	    len > (ssize_t)sizeof(*o);
+	    o = D6_CNEXT_OPTION(o))
+	{
 		ol = ntohs(o->len);
-		od = D6_COPTION_DATA(o);
-		n += dhcp_envoption(env == NULL ? NULL : &env[n],
-		    prefix, "_dhcp6", ifp->name, opt, dhcp6_getoption, od, ol);
+		len -= sizeof(*o) + ol;
+		if (len < 0) {
+			errno = EINVAL;
+			break;
+		}
+		oc = ntohs(o->code);
+		if (has_option_mask(ifo->nomask6, oc))
+			continue;
+		for (i = 0, opt = ifo->dhcp6_override;
+		    i < ifo->dhcp6_override_len;
+		    i++, opt++)
+			if (opt->option == oc)
+				break;
+		if (opt == NULL) {
+			for (i = 0, opt = dhcp6_opts;
+			    i < dhcp6_opts_len;
+			    i++, opt++)
+				if (opt->option == oc)
+					break;
+		}
+		if (opt) {
+			n += dhcp_envoption(env == NULL ? NULL : &env[n],
+			    pfx, ifp->name,
+			    opt, dhcp6_getoption, D6_COPTION_DATA(o), ol);
+		}
 	}
+	free(pfx);
 
+	/* It is tempting to remove this section.
+	 * However, we need it at least for Delegated Prefixes
+	 * (they don't have a DHCPv6 message to parse to get the addressses)
+	 * and it's easier for shell scripts to see which addresses have
+	 * been added */
 	if (TAILQ_FIRST(&state->addrs)) {
 		if (env == NULL)
 			n++;
 		else {
 			if (ifo->ia_type == D6_OPTION_IA_PD) {
-				e = strlen(prefix) +
+				i = strlen(prefix) +
 				    strlen("_dhcp6_prefix=");
 				TAILQ_FOREACH(ap, &state->addrs, next) {
-					e += strlen(ap->saddr) + 1;
+					i += strlen(ap->saddr) + 1;
 				}
-				v = val = *ep++ = malloc(e);
+				v = val = *ep++ = malloc(i);
 				if (v == NULL) {
 					syslog(LOG_ERR, "%s: %m", __func__);
 					return -1;
 				}
-				v += snprintf(val, e, "%s_dhcp6_prefix=",
+				v += snprintf(val, i, "%s_dhcp6_prefix=",
 					prefix);
 				TAILQ_FOREACH(ap, &state->addrs, next) {
 					strcpy(v, ap->saddr);
@@ -2621,17 +2669,17 @@
 				}
 				*--v = '\0';
 			} else {
-				e = strlen(prefix) +
+				i = strlen(prefix) +
 				    strlen("_dhcp6_ip_address=");
 				TAILQ_FOREACH(ap, &state->addrs, next) {
-					e += strlen(ap->saddr) + 1;
+					i += strlen(ap->saddr) + 1;
 				}
-				v = val = *ep++ = malloc(e);
+				v = val = *ep++ = malloc(i);
 				if (v == NULL) {
 					syslog(LOG_ERR, "%s: %m", __func__);
 					return -1;
 				}
-				v += snprintf(val, e, "%s_dhcp6_ip_address=",
+				v += snprintf(val, i, "%s_dhcp6_ip_address=",
 					prefix);
 				TAILQ_FOREACH(ap, &state->addrs, next) {
 					strcpy(v, ap->saddr);
@@ -2643,20 +2691,5 @@
 		}
 	}
 
-	for (oi = 0, opt = ifo->dhcp6_override;
-	    oi < ifo->dhcp6_override_len;
-	    oi++, opt++)
-	{
-		if (has_option_mask(ifo->nomask, opt->option))
-			continue;
-		o = dhcp6_getmoption(opt->option, m, mlen);
-		if (o == NULL)
-			continue;
-		ol = ntohs(o->len);
-		od = D6_COPTION_DATA(o);
-		n += dhcp_envoption(env == NULL ? NULL : &env[n],
-		    prefix, "_dhcp6", ifp->name, opt, dhcp6_getoption, od, ol);
-	}
-
 	return n;
 }
--- a/dhcpcd-definitions.conf	Mon Dec 02 20:45:19 2013 +0000
+++ b/dhcpcd-definitions.conf	Tue Dec 03 17:55:40 2013 +0000
@@ -134,41 +134,23 @@
 define6 1	binhex			client_id
 define6 2	binhex			server_id
 
-# DHCPv6 addresses.
-# These are currently handled internally by dhcpcd(8) and only
-# the addresses are exposed to dhcpcd-run-hooks(8).
-# When they can be expressed properly they will be uncommented.
-# many signifies that it could occur more than once and be index.
-# option signifies that the specified option can be encapsulated.
-# For example:
-#	ia_na_count=1
-#	ia_na_1_iaid="00112233"
-#	ia_na_1_t1=3600
-#	ia_na_1_t2=7200
-#	ia_na_1_ia_addr_count=1
-#	ia_na_1_ia_addr_1_addr="dead:beef"
-#	ia_na_1_ia_addr_1_pltime="3600"
-#	ia_na_1_ia_addr_1_pltime="7200"
-#	ia_na_1_ia_addr_1_status_code=0
-#	ia_na_1_ia_addr_1_status_code_message="OK"
-#	ia_na_1_code=0
-#	ia_na_1_status_code_message="OK"
-#
-#define6 3	embed many		ia_na
-#embed		uint32			iaid
-#embed		uint32			t1
-#embed		uint32			t2
-#encap		option			5
-#encap		option			13
-#define6 4	embed many		ia_ta
-#embed		uint32			iaid
-#encap		option			5
-#encap		option			13
-#define6 5	embed many		ia_addr
-#embed		ip6address		addr
-#embed		uint32			pltime
-#embed		uint32			vltime
-#encap		option			13
+define6 3	norequest index embed	ia_na
+embed		binhex:4		iaid
+embed		uint32			t1
+embed		uint32			t2
+encap 5		option
+encap 13	option
+
+define6 4	norequest index embed	ia_ta
+embed		uint32			iaid
+encap 5		option
+encap 13	option
+
+define6 5	norequest index embed	ia_addr
+embed		ip6address		ia_addr
+embed		uint32			pltime
+embed		uint32			vltime
+encap 13	option
 
 define6 6	array uint16		option_request
 define6 7	byte			preference
@@ -203,6 +185,19 @@
 define6 23	array ip6address	name_servers
 define6 24	domain			domain_search
 
+# DHCPv6 Prefix Options, RFC6603
+define6 25	norequest index embed	ia_pd
+embed		binhex:4		iaid
+embed		uint32			t1
+embed		uint32			t2
+encap 26	option
+
+define 26	index embed		prefix
+embed		uint32			pltime
+embed		uint32			vltime
+embed		ip6address		prefix
+encap 13	option
+
 # DHCPv6 Network Information Service Options, RFC3898
 define6 27	array ip6address	nis_servers
 define6 28	array ip6address	nisp_servers
--- a/dhcpcd.conf.5.in	Mon Dec 02 20:45:19 2013 +0000
+++ b/dhcpcd.conf.5.in	Tue Dec 03 17:55:40 2013 +0000
@@ -22,7 +22,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd December 2, 2013
+.Dd December 3, 2013
 .Dt DHCPCD.CONF 5
 .Os
 .Sh NAME
@@ -535,6 +535,8 @@
 configuration
 .It Ic norequest
 This option cannot be requested, regardless of user configuration
+.It Ic index
+The option can appear more than once and will be indexed.
 .It Ic array
 The option data is split into a space seperated array, each element being
 the same type.
@@ -571,6 +573,8 @@
 Contains embedded options (implies encap as well)
 .It Ic encap
 Contains encapsulated options (implies embed as well)
+.It Ic option
+References an option from the global definition
 .El
 .Ss Example definition
 .D1 # DHCP option 81, Fully Qualified Domain Name, RFC4702
--- a/if-options.c	Mon Dec 02 20:45:19 2013 +0000
+++ b/if-options.c	Tue Dec 03 17:55:40 2013 +0000
@@ -482,6 +482,7 @@
 
 /* Pointer to last defined option */
 static struct dhcp_opt *ldop;
+static struct dhcp_opt *edop;
 
 void
 free_dhcp_opt_embenc(struct dhcp_opt *opt)
@@ -1212,17 +1213,23 @@
 			dop = &ifo->dhcp6_override;
 			dop_len = &ifo->dhcp6_override_len;
 		}
-		ldop = NULL;
+		edop = ldop = NULL;
 		/* FALLTHROUGH */
 	case O_EMBED:
 		if (dop == NULL) {
-			if (ldop == NULL) {
-				syslog(LOG_ERR, "embed must be after a define");
+			if (edop) {
+				dop = &edop->embopts;
+				dop_len = &edop->embopts_len;
+			} else if (ldop) {
+				dop = &ldop->embopts;
+				dop_len = &ldop->embopts_len;
+			} else {
+				syslog(LOG_ERR,
+				    "embed must be after a define or encap");
 				return -1;
 			}
-			dop = &ldop->embopts;
-			dop_len = &ldop->embopts_len;
 		}
+		/* FALLTHROUGH */
 	case O_ENCAP:
 		if (dop == NULL) {
 			if (ldop == NULL) {
@@ -1281,6 +1288,16 @@
 			}
 			*fp++ = '\0';
 		}
+		if (strcasecmp(arg, "index") == 0) {
+			t |= INDEX;
+			arg = strskipwhite(fp);
+			fp = strwhite(arg);
+			if (fp == NULL) {
+				syslog(LOG_ERR, "incomplete index type");
+				return -1;
+			}
+			*fp++ = '\0';
+		}
 		if (strcasecmp(arg, "array") == 0) {
 			t |= ARRAY;
 			arg = strskipwhite(fp);
@@ -1314,15 +1331,17 @@
 		else if (strcasecmp(arg, "binhex") == 0)
 			t |= BINHEX;
 		else if (strcasecmp(arg, "embed") == 0)
-			t = EMBED;
+			t |= EMBED;
 		else if (strcasecmp(arg, "encap") == 0)
-			t = ENCAP;
+			t |= ENCAP;
 		else if (strcasecmp(arg, "rfc3361") ==0)
-			t = STRING | RFC3361;
+			t |= STRING | RFC3361;
 		else if (strcasecmp(arg, "rfc3442") ==0)
-			t = RFC3442;
+			t |= STRING | RFC3442;
 		else if (strcasecmp(arg, "rfc5969") == 0)
-			t = RFC5969;
+			t |= STRING | RFC5969;
+		else if (strcasecmp(arg, "option") == 0)
+			t |= OPTION;
 		else {
 			syslog(LOG_ERR, "unknown type: %s", arg);
 			return -1;
@@ -1338,18 +1357,22 @@
 		}
 		/* variable */
 		if (!fp) {
-		        syslog(LOG_ERR,
-			    "type %s requires a variable name", arg);
-			return -1;
-		}
-		arg = strskipwhite(fp);
-		fp = strwhite(arg);
-		if (fp)
-			*fp++ = '\0';
-		np = strdup(arg);
-		if (np == NULL) {
-			syslog(LOG_ERR, "%s: %m", __func__);
-			return -1;
+			if (!(t & OPTION)) {
+			        syslog(LOG_ERR,
+				    "type %s requires a variable name", arg);
+				return -1;
+			}
+			np = NULL;
+		} else {
+			arg = strskipwhite(fp);
+			fp = strwhite(arg);
+			if (fp)
+				*fp++ = '\0';
+			np = strdup(arg);
+			if (np == NULL) {
+				syslog(LOG_ERR, "%s: %m", __func__);
+				return -1;
+			}
 		}
 		if (opt == O_EMBED)
 			dl = *dop_len;
@@ -1383,6 +1406,8 @@
 		/* Save the define for embed and encap options */
 		if (opt == O_DEFINE || opt == O_DEFINE6)
 			ldop = ndop;
+		else if (opt == O_ENCAP)
+			edop = ndop;
 		break;
 	default:
 		return 0;