From: Roy Marples Date: Tue, 1 Jul 2014 20:34:19 +0000 (+0000) Subject: Support http://datatracker.ietf.org/doc/draft-ietf-dhc-dhcpv6-stateful-issues. X-Git-Tag: v6.4.1~43 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b0ca9ae9b1dd740036e3577b0fd74f3c8ce3958a;p=thirdparty%2Fdhcpcd.git Support http://datatracker.ietf.org/doc/draft-ietf-dhc-dhcpv6-stateful-issues. It's not an RFC yet, but it seems it's needed for Prefix Delegation to work on ComCast where a IA_NA is required as well. --- diff --git a/dhcp6.c b/dhcp6.c index 7677c9d3..22369180 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -415,7 +415,7 @@ dhcp6_makemessage(struct interface *ifp) if (ap->prefix_vltime == 0 && !(ap->flags & IPV6_AF_REQUEST)) continue; - if (ifo->ia_type == D6_OPTION_IA_PD) + if (ap->ia_type == D6_OPTION_IA_PD) len += sizeof(*o) + sizeof(u8) + sizeof(u32) + sizeof(u32) + sizeof(ap->prefix.s6_addr); @@ -531,7 +531,7 @@ dhcp6_makemessage(struct interface *ifp) for (l = 0; IA && l < ifo->ia_len; l++) { o = D6_NEXT_OPTION(o); - o->code = htons(ifo->ia_type); + o->code = htons(ifo->ia[l].ia_type); o->len = htons(sizeof(u32) + sizeof(u32) + sizeof(u32)); p = D6_OPTION_DATA(o); memcpy(p, ifo->ia[l].iaid, sizeof(u32)); @@ -544,7 +544,7 @@ dhcp6_makemessage(struct interface *ifp) if (memcmp(ifo->ia[l].iaid, ap->iaid, sizeof(u32))) continue; so = D6_NEXT_OPTION(o); - if (ifo->ia_type == D6_OPTION_IA_PD) { + if (ap->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)); @@ -990,13 +990,65 @@ dhcp6_dadcallback(void *arg) } static void -dhcp6_startdiscover(void *arg) +dhcp6_addrequestedaddrs(struct interface *ifp) { - struct interface *ifp; struct dhcp6_state *state; size_t i; struct if_ia *ia; struct ipv6_addr *a; + char iabuf[INET6_ADDRSTRLEN]; + const char *iap; + + state = D6_STATE(ifp); + /* Add any requested prefixes / addresses */ + for (i = 0; i < ifp->options->ia_len; i++) { + ia = &ifp->options->ia[i]; + if (!((ia->ia_type == D6_OPTION_IA_PD && ia->prefix_len) || + !IN6_IS_ADDR_UNSPECIFIED(&ia->addr))) + continue; + a = calloc(1, sizeof(*a)); + if (a == NULL) { + syslog(LOG_ERR, "%s: %m", __func__); + return; + } + a->flags = IPV6_AF_REQUEST; + a->iface = ifp; + a->dadcallback = dhcp6_dadcallback; + memcpy(&a->iaid, &ia->iaid, sizeof(a->iaid)); + a->ia_type = ia->ia_type; + //a->prefix_pltime = 0; + //a->prefix_vltime = 0; + + if (ia->ia_type == D6_OPTION_IA_PD) { + memcpy(&a->prefix, &ia->addr, sizeof(a->addr)); + a->prefix_len = ia->prefix_len; + iap = inet_ntop(AF_INET6, &a->prefix.s6_addr, + iabuf, sizeof(iabuf)); + } else { + memcpy(&a->addr, &ia->addr, sizeof(a->addr)); + /* + * RFC 5942 Section 5 + * We cannot assume any prefix length, nor tie the + * address to an existing one as it could expire + * before the address. + * As such we just give it a 128 prefix. + */ + a->prefix_len = 128; + ipv6_makeprefix(&a->prefix, &a->addr, a->prefix_len); + iap = inet_ntop(AF_INET6, &a->addr.s6_addr, + iabuf, sizeof(iabuf)); + } + snprintf(a->saddr, sizeof(a->saddr), + "%s/%d", iap, a->prefix_len); + TAILQ_INSERT_TAIL(&state->addrs, a, next); + } +} + +static void +dhcp6_startdiscover(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; ifp = arg; dhcp6_delete_delegates(ifp); @@ -1018,32 +1070,7 @@ dhcp6_startdiscover(void *arg) dhcp6_freedrop_addrs(ifp, 0, NULL); unlink(state->leasefile); - /* Add any requested prefixes / addresses */ - for (i = 0; i < ifp->options->ia_len; i++) { - ia = &ifp->options->ia[i]; - if (!IN6_IS_ADDR_UNSPECIFIED(&ia->addr) || - (ia->prefix_len && - ifp->options->ia_type == D6_OPTION_IA_PD)) - { - a = calloc(1, sizeof(*a)); - if (a == NULL) { - syslog(LOG_ERR, "%s: %m", __func__); - return; - } - a->flags = IPV6_AF_REQUEST; - a->iface = ifp; - a->dadcallback = dhcp6_dadcallback; - memcpy(&a->iaid, &ia->iaid, sizeof(a->iaid)); - //a->prefix_pltime = 0; - //a->prefix_vltime = 0; - if (ifp->options->ia_type == D6_OPTION_IA_PD) { - memcpy(&a->prefix, &ia->addr, sizeof(a->addr)); - a->prefix_len = ia->prefix_len; - } else - memcpy(&a->addr, &ia->addr, sizeof(a->addr)); - TAILQ_INSERT_TAIL(&state->addrs, a, next); - } - } + dhcp6_addrequestedaddrs(ifp); if (dhcp6_makemessage(ifp) == -1) syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name); @@ -1092,11 +1119,25 @@ dhcp6_failrebind(void *arg) dhcp6_startdiscover(ifp); } + +static int +dhcp6_hasprefixdelegation(struct interface *ifp) +{ + size_t i; + + for (i = 0; i < ifp->options->ia_len; i++) { + if (ifp->options->ia[i].ia_type == D6_OPTION_IA_PD) + return 1; + } + return 0; +} + static void dhcp6_startrebind(void *arg) { struct interface *ifp; struct dhcp6_state *state; + int pd; ifp = arg; eloop_timeout_delete(ifp->ctx->eloop, dhcp6_sendrenew, ifp); @@ -1104,13 +1145,16 @@ dhcp6_startrebind(void *arg) if (state->state == DH6S_RENEW) syslog(LOG_WARNING, "%s: failed to renew DHCPv6, rebinding", ifp->name); + else + syslog(LOG_INFO, "%s: rebinding prior DHCPc6 lease", + ifp->name); state->state = DH6S_REBIND; state->RTC = 0; state->MRC = 0; /* RFC 3633 section 12.1 */ - if (ifp->options->ia_type == D6_OPTION_IA_PD) { - syslog(LOG_INFO, "%s: confirming Prefix Delegation", ifp->name); + pd = dhcp6_hasprefixdelegation(ifp); + if (pd) { state->IMD = CNF_MAX_DELAY; state->IRT = CNF_TIMEOUT; state->MRT = CNF_MAX_RT; @@ -1125,7 +1169,7 @@ dhcp6_startrebind(void *arg) dhcp6_sendrebind(ifp); /* RFC 3633 section 12.1 */ - if (ifp->options->ia_type == D6_OPTION_IA_PD) + if (pd) eloop_timeout_add_sec(ifp->ctx->eloop, CNF_MAX_RD, dhcp6_failrebind, ifp); } @@ -1336,7 +1380,7 @@ dhcp6_addrexists(struct dhcpcd_ctx *ctx, const struct ipv6_addr *addr) } static int -dhcp6_findna(struct interface *ifp, const uint8_t *iaid, +dhcp6_findna(struct interface *ifp, uint16_t ot, const uint8_t *iaid, const uint8_t *d, size_t l) { struct dhcp6_state *state; @@ -1378,6 +1422,7 @@ dhcp6_findna(struct interface *ifp, const uint8_t *iaid, a->iface = ifp; a->flags = IPV6_AF_NEW | IPV6_AF_ONLINK; a->dadcallback = dhcp6_dadcallback; + a->ia_type = ot; memcpy(a->iaid, iaid, sizeof(a->iaid)); memcpy(&a->addr.s6_addr, &in6.s6_addr, sizeof(in6.s6_addr)); @@ -1390,20 +1435,18 @@ dhcp6_findna(struct interface *ifp, const uint8_t *iaid, * As such we just give it a 128 prefix. */ a->prefix_len = 128; - if (ipv6_makeprefix(&a->prefix, &a->addr, - a->prefix_len) == -1) - { - syslog(LOG_ERR, "%s: %m", __func__); - free(a); - continue; - } + ipv6_makeprefix(&a->prefix, &a->addr, a->prefix_len); 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); - } else - a->flags &= ~(IPV6_AF_STALE | IPV6_AF_REQUEST); + } else { + if (!(a->flags & IPV6_AF_ONLINK)) + a->flags |= IPV6_AF_ONLINK | IPV6_AF_NEW; + a->flags &= ~IPV6_AF_STALE; + } memcpy(&u32, p, sizeof(u32)); a->prefix_pltime = ntohl(u32); p += sizeof(u32); @@ -1478,6 +1521,7 @@ dhcp6_findpd(struct interface *ifp, const uint8_t *iaid, a->iface = ifp; a->flags = IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX; a->dadcallback = dhcp6_dadcallback; + a->ia_type = D6_OPTION_IA_PD; memcpy(a->iaid, iaid, sizeof(a->iaid)); memcpy(&a->prefix.s6_addr, &prefix.s6_addr, sizeof(a->prefix.s6_addr)); @@ -1488,6 +1532,8 @@ dhcp6_findpd(struct interface *ifp, const uint8_t *iaid, "%s/%d", ia, a->prefix_len); TAILQ_INSERT_TAIL(&state->addrs, a, next); } else { + if (!(a->flags & IPV6_AF_DELEGATEDPFX)) + a->flags |= IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX; a->flags &= ~(IPV6_AF_STALE | IPV6_AF_REQUEST); if (a->prefix_vltime != vltime) a->flags |= IPV6_AF_NEW; @@ -1495,6 +1541,7 @@ dhcp6_findpd(struct interface *ifp, const uint8_t *iaid, a->prefix_pltime = pltime; a->prefix_vltime = vltime; + if (a->prefix_pltime && a->prefix_pltime < state->lowpl) state->lowpl = a->prefix_pltime; if (a->prefix_vltime && a->prefix_vltime > state->expire) @@ -1505,7 +1552,7 @@ dhcp6_findpd(struct interface *ifp, const uint8_t *iaid, } static int -dhcp6_findia(struct interface *ifp, const uint8_t *d, size_t l, +dhcp6_findia(struct interface *ifp, const struct dhcp6_message *m, size_t l, const char *sfrom) { struct dhcp6_state *state; @@ -1513,25 +1560,47 @@ dhcp6_findia(struct interface *ifp, const uint8_t *d, size_t l, const struct dhcp6_option *o; const uint8_t *p; int i, e; + size_t j; uint32_t u32, renew, rebind; + uint16_t code, ol; uint8_t iaid[4]; - size_t ol; + char buf[sizeof(iaid) * 3]; struct ipv6_addr *ap, *nap; + if (l < sizeof(*m)) { + syslog(LOG_ERR, "%s: message truncated", ifp->name); + errno = EINVAL; + return -1; + } + ifo = ifp->options; i = e = 0; state = D6_STATE(ifp); TAILQ_FOREACH(ap, &state->addrs, next) { ap->flags |= IPV6_AF_STALE; } - while ((o = dhcp6_findoption(ifo->ia_type, d, l))) { - ol = (size_t)((const uint8_t *)o - d); - l -= ol; - d += ol; + l -= sizeof(*m); + for (o = D6_FIRST_OPTION(m); l > sizeof(*o); o = D6_NEXT_OPTION(o)) { ol = ntohs(o->len); + if (sizeof(*o) + ol > l) { + errno = EINVAL; + syslog(LOG_ERR, "%s: option overflow", ifp->name); + break; + } l -= sizeof(*o) + ol; - d += sizeof(*o) + ol; - u32 = ifo->ia_type == D6_OPTION_IA_TA ? 4 : 12; + + code = ntohs(o->code); + switch(code) { + case D6_OPTION_IA_TA: + u32 = 4; + break; + case D6_OPTION_IA_NA: + case D6_OPTION_IA_PD: + u32 = 12; + break; + default: + continue; + } if (ol < u32) { errno = EINVAL; syslog(LOG_ERR, "%s: IA option truncated", ifp->name); @@ -1542,7 +1611,25 @@ dhcp6_findia(struct interface *ifp, const uint8_t *d, size_t l, memcpy(iaid, p, sizeof(iaid)); p += sizeof(iaid); ol -= sizeof(iaid); - if (ifo->ia_type != D6_OPTION_IA_TA) { + + for (j = 0; j < ifo->ia_len; j++) { + if (memcmp(&ifo->ia[j].iaid, iaid, sizeof(iaid)) == 0) + break; + } + if (j == ifo->ia_len) { + syslog(LOG_DEBUG, "%s: ignoring unrequested IAID %s", + ifp->name, + hwaddr_ntoa(iaid, sizeof(iaid), buf, sizeof(buf))); + continue; + } + if (ifo->ia[j].ia_type != code) { + syslog(LOG_ERR, "%s: IAID %s: option type mismatch", + ifp->name, + hwaddr_ntoa(iaid, sizeof(iaid), buf, sizeof(buf))); + continue; + } + + if (code != D6_OPTION_IA_TA) { memcpy(&u32, p, sizeof(u32)); renew = ntohl(u32); p += sizeof(u32); @@ -1557,7 +1644,7 @@ dhcp6_findia(struct interface *ifp, const uint8_t *d, size_t l, e = 1; continue; } - if (ifo->ia_type == D6_OPTION_IA_PD) { + if (code == D6_OPTION_IA_PD) { if (dhcp6_findpd(ifp, iaid, p, ol) == 0) { syslog(LOG_WARNING, "%s: %s: DHCPv6 REPLY missing Prefix", @@ -1565,14 +1652,14 @@ dhcp6_findia(struct interface *ifp, const uint8_t *d, size_t l, continue; } } else { - if (dhcp6_findna(ifp, iaid, p, ol) == 0) { + if (dhcp6_findna(ifp, code, iaid, p, ol) == 0) { syslog(LOG_WARNING, "%s: %s: DHCPv6 REPLY missing IA Address", ifp->name, sfrom); continue; } } - if (ifo->ia_type != D6_OPTION_IA_TA) { + if (code != D6_OPTION_IA_TA) { if (renew > rebind && rebind > 0) { if (sfrom) syslog(LOG_WARNING, @@ -1592,11 +1679,15 @@ dhcp6_findia(struct interface *ifp, const uint8_t *d, size_t l, } TAILQ_FOREACH_SAFE(ap, &state->addrs, next, nap) { if (ap->flags & IPV6_AF_STALE) { - TAILQ_REMOVE(&state->addrs, ap, next); if (ap->dadcallback) eloop_q_timeout_delete(ap->iface->ctx->eloop, 0, NULL, ap); - free(ap); + if (ap->flags & IPV6_AF_REQUEST) { + ap->prefix_vltime = ap->prefix_pltime = 0; + } else { + TAILQ_REMOVE(&state->addrs, ap, next); + free(ap); + } } } if (i == 0 && e) @@ -1610,25 +1701,14 @@ dhcp6_validatelease(struct interface *ifp, const char *sfrom) { struct dhcp6_state *state; - const struct dhcp6_option *o; state = D6_STATE(ifp); - o = dhcp6_getmoption(ifp->options->ia_type, m, len); - if (o == NULL) { - if (sfrom && - dhcp6_checkstatusok(ifp, m, NULL, len) != -1) - syslog(LOG_ERR, "%s: no IA in REPLY from %s", - ifp->name, sfrom); - return -1; - } - if (dhcp6_checkstatusok(ifp, m, NULL, len) == -1) return -1; state->renew = state->rebind = state->expire = 0; state->lowpl = ND6_INFINITE_LIFETIME; - len -= (size_t)((const char *)o - (const char *)m); - return dhcp6_findia(ifp, (const uint8_t *)o, len, sfrom); + return dhcp6_findia(ifp, m, len, sfrom); } static ssize_t @@ -1748,18 +1828,34 @@ ex: return 0; } + static void dhcp6_startinit(struct interface *ifp) { struct dhcp6_state *state; int r; + uint8_t has_ta, has_non_ta; + size_t i; state = D6_STATE(ifp); state->state = DH6S_INIT; state->expire = ND6_INFINITE_LIFETIME; state->lowpl = ND6_INFINITE_LIFETIME; + + dhcp6_addrequestedaddrs(ifp); + has_ta = has_non_ta = 0; + for (i = 0; i < ifp->options->ia_len; i++) { + switch (ifp->options->ia[i].ia_type) { + case D6_OPTION_IA_TA: + has_ta = 1; + break; + default: + has_non_ta = 1; + } + } + if (!(ifp->ctx->options & DHCPCD_TEST) && - ifp->options->ia_type != D6_OPTION_IA_TA && + !(has_ta && !has_non_ta) && ifp->options->reboot != 0) { r = dhcp6_readlease(ifp); @@ -1768,7 +1864,7 @@ dhcp6_startinit(struct interface *ifp) ifp->name, state->leasefile); else if (r != 0) { /* RFC 3633 section 12.1 */ - if (ifp->options->ia_type == D6_OPTION_IA_PD) + if (dhcp6_hasprefixdelegation(ifp)) dhcp6_startrebind(ifp); else dhcp6_startconfirm(ifp); @@ -2264,7 +2360,7 @@ dhcp6_handledata(void *arg) /* PD doesn't use CONFIRM, so REBIND could * throw up an invalid prefix if we * changed link */ - if (ifp->options->ia_type == D6_OPTION_IA_PD) + if (dhcp6_hasprefixdelegation(ifp)) dhcp6_startdiscover(ifp); return; } @@ -2476,10 +2572,10 @@ recv: if (state->expire && state->expire != ND6_INFINITE_LIFETIME) eloop_timeout_add_sec(ifp->ctx->eloop, (time_t)state->expire, dhcp6_startexpire, ifp); - if (ifp->options->ia_type == D6_OPTION_IA_PD) - dhcp6_delegate_prefix(ifp); ipv6_addaddrs(&state->addrs); + dhcp6_delegate_prefix(ifp); + if (state->state == DH6S_INFORMED) syslog(has_new ? LOG_INFO : LOG_DEBUG, "%s: refresh in %"PRIu32" seconds", @@ -2833,13 +2929,18 @@ dhcp6_env(char **env, const char *prefix, const struct interface *ifp, const struct if_options *ifo; struct dhcp_opt *opt, *vo; const struct dhcp6_option *o; - size_t i, l, n; + size_t i, n; uint16_t ol, oc; - char *v, *val, *pfx; - const struct ipv6_addr *ap; + char *pfx; uint32_t en; const struct dhcpcd_ctx *ctx; + if (len < sizeof(*m)) { + syslog(LOG_ERR, "%s: message truncated", ifp->name); + errno = EINVAL; + return -1; + } + state = D6_CSTATE(ifp); n = 0; ifo = ifp->options; @@ -2924,65 +3025,6 @@ dhcp6_env(char **env, const char *prefix, const struct interface *ifp, } 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) { - if (ifo->ia_type == D6_OPTION_IA_PD) { - l = strlen(prefix) + - strlen("_dhcp6_prefix="); - TAILQ_FOREACH(ap, &state->addrs, next) { - l += strlen(ap->saddr) + 1; - } - v = val = env[n] = malloc(l); - if (v == NULL) { - syslog(LOG_ERR, "%s: %m", __func__); - return -1; - } - i = (size_t)snprintf(v, l, "%s_dhcp6_prefix=", - prefix); - v += i; - l -= i; - TAILQ_FOREACH(ap, &state->addrs, next) { - i = strlen(ap->saddr); - strlcpy(v, ap->saddr, l); - v += i; - l -= i; - *v++ = ' '; - } - *--v = '\0'; - } else { - l = strlen(prefix) + - strlen("_dhcp6_ip_address="); - TAILQ_FOREACH(ap, &state->addrs, next) { - l += strlen(ap->saddr) + 1; - } - v = val = env[n] = malloc(l); - if (v == NULL) { - syslog(LOG_ERR, "%s: %m", __func__); - return -1; - } - i = (size_t)snprintf(v, l, - "%s_dhcp6_ip_address=", - prefix); - v += i; - l -= i; - TAILQ_FOREACH(ap, &state->addrs, next) { - i = strlen(ap->saddr); - strlcpy(v, ap->saddr, l); - v += i; - l -= i; - *v++ = ' '; - } - *--v = '\0'; - } - } - n++; - } - return (ssize_t)n; } diff --git a/dhcpcd.c b/dhcpcd.c index 45e8e92d..1acc2522 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -323,6 +323,7 @@ configure_interface1(struct interface *ifp) { struct if_options *ifo = ifp->options; int ra_global, ra_iface; + size_t i; /* Do any platform specific configuration */ if_conf(ifp); @@ -431,17 +432,25 @@ configure_interface1(struct interface *ifp) } #ifdef INET6 - if (ifo->ia == NULL && ifo->options & DHCPCD_IPV6) { - ifo->ia = malloc(sizeof(*ifo->ia)); - if (ifo->ia == NULL) - syslog(LOG_ERR, "%s: %m", __func__); - else { - if (ifo->ia_type == 0) - ifo->ia_type = D6_OPTION_IA_NA; - memcpy(ifo->ia->iaid, ifo->iaid, sizeof(ifo->iaid)); - ifo->ia_len = 1; - ifo->ia->sla = NULL; - ifo->ia->sla_len = 0; + if (ifo->options & DHCPCD_IPV6) { + if (ifo->ia == NULL) { + ifo->ia = malloc(sizeof(*ifo->ia)); + if (ifo->ia == NULL) + syslog(LOG_ERR, "%s: %m", __func__); + else { + ifo->ia_len = 1; + ifo->ia->ia_type = D6_OPTION_IA_NA; + memcpy(ifo->ia->iaid, ifo->iaid, + sizeof(ifo->iaid)); + ifo->ia->sla = NULL; + ifo->ia->sla_len = 0; + } + } else { + for (i = 0; i < ifo->ia_len; i++) { + if (!ifo->ia[i].iaid_set) + memcpy(ifo->ia->iaid, ifo->iaid, + sizeof(ifo->iaid)); + } } } #endif @@ -627,7 +636,9 @@ dhcpcd_startinterface(void *arg) !(ifo->options & DHCPCD_INFORM)) ipv6nd_startrs(ifp); - if (!(ifo->options & DHCPCD_IPV6RS)) { + if (!(ifo->options & DHCPCD_IPV6RS) || + ifo->options & DHCPCD_IA_FORCED) + { ssize_t nolease; if (ifo->options & DHCPCD_IA_FORCED) diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index fa74e64f..1b3f6b0d 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd June 20, 2014 +.Dd July 1, 2014 .Dt DHCPCD.CONF 5 .Os .Sh NAME @@ -739,11 +739,13 @@ Same as .Sh AUTHORS .An Roy Marples Aq Mt roy@marples.name .Sh BUGS -When configuring DHCPv6 you can only select one IA type. -I can't think of a use case where you would want different types, -so if you have one then please bring it up for discussion on the -.Aq Mt dhcpcd-discuss@marples.name -mailing list. +Combining different DHCPv6 IA options in the same interface block is not +currently RFC conformant. +See +.Lk http://datatracker.ietf.org/doc/draft-ietf-dhc-dhcpv6-stateful-issues +for details. +.Nm dhcpcd +strives to comply with this document and will be updated when finally published. .Pp Please report them to .Lk http://roy.marples.name/projects/dhcpcd diff --git a/if-options.c b/if-options.c index 77593a33..a2345c9d 100644 --- a/if-options.c +++ b/if-options.c @@ -1220,38 +1220,40 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, } i = D6_OPTION_IA_PD; } - if (arg != NULL && ifname == NULL) { + if (ifname == NULL && arg) { syslog(LOG_ERR, "IA with IAID must belong in an interface block"); return -1; } 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 = (uint16_t)i; - if (arg == NULL) - break; - fp = strwhite(arg); - if (fp) - *fp++ = '\0'; - p = strchr(arg, '/'); - if (p) - *p++ = '\0'; - if (parse_iaid(iaid, arg, sizeof(iaid)) == -1) - return -1; + if (arg) { + t = 1; + fp = strwhite(arg); + if (fp) + *fp++ = '\0'; + p = strchr(arg, '/'); + if (p) + *p++ = '\0'; + if (parse_iaid(iaid, arg, sizeof(iaid)) == -1) + return -1; + } else + t = 0; ia = NULL; for (sl = 0; sl < ifo->ia_len; sl++) { - if (ifo->ia[sl].iaid[0] == iaid[0] && + if ((t == 0 && !ifo->ia[sl].iaid_set) || + (ifo->ia[sl].iaid[0] == iaid[0] && ifo->ia[sl].iaid[1] == iaid[1] && ifo->ia[sl].iaid[2] == iaid[2] && - ifo->ia[sl].iaid[3] == iaid[3]) + ifo->ia[sl].iaid[3] == iaid[3])) { ia = &ifo->ia[sl]; break; } } + if (ia && ia->ia_type != (uint16_t)i) { + syslog(LOG_ERR, "Cannot mix IA for the same IAID"); + break; + } if (ia == NULL) { ia = realloc(ifo->ia, sizeof(*ifo->ia) * (ifo->ia_len + 1)); @@ -1261,11 +1263,19 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, } ifo->ia = ia; ia = &ifo->ia[ifo->ia_len++]; - ia->iaid[0] = iaid[0]; - ia->iaid[1] = iaid[1]; - ia->iaid[2] = iaid[2]; - ia->iaid[3] = iaid[3]; - if (p == NULL || ifo->ia_type == D6_OPTION_IA_TA) { + ia->ia_type = (uint16_t)i; + if (t) { + ia->iaid[0] = iaid[0]; + ia->iaid[1] = iaid[1]; + ia->iaid[2] = iaid[2]; + ia->iaid[3] = iaid[3]; + ia->iaid_set = 1; + } else + ia->iaid_set = 0; + if (!ia->iaid_set || + p == NULL || + ia->ia_type == D6_OPTION_IA_TA) + { memset(&ia->addr, 0, sizeof(ia->addr)); ia->prefix_len = 0; } else { @@ -1277,7 +1287,7 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, syslog(LOG_ERR, "%s: %m", arg); memset(&ia->addr, 0, sizeof(ia->addr)); } - if (p && ifo->ia_type == D6_OPTION_IA_PD) { + if (p && ia->ia_type == D6_OPTION_IA_PD) { i = atoint(p); if (i != -1 && (i < 8 || i > 120)) { errno = EINVAL; @@ -1293,7 +1303,7 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ia->sla_len = 0; ia->sla = NULL; } - if (ifo->ia_type != D6_OPTION_IA_PD) + if (ia->ia_type != D6_OPTION_IA_PD) break; for (p = fp; p; p = fp) { fp = strwhite(p); diff --git a/if-options.h b/if-options.h index bfcca817..63618fc4 100644 --- a/if-options.h +++ b/if-options.h @@ -115,6 +115,8 @@ struct if_sla { struct if_ia { uint8_t iaid[4]; #ifdef INET6 + uint16_t ia_type; + uint8_t iaid_set; struct in6_addr addr; uint8_t prefix_len; size_t sla_len; @@ -165,7 +167,6 @@ struct if_options { in_addr_t *arping; char *fallback; - uint16_t ia_type; struct if_ia *ia; size_t ia_len; diff --git a/ipv6.c b/ipv6.c index 2c1280f4..98a4d454 100644 --- a/ipv6.c +++ b/ipv6.c @@ -652,7 +652,6 @@ ipv6_addaddrs(struct ipv6_addrhead *addrs) i = 0; TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { if (ap->prefix_vltime == 0) { - TAILQ_REMOVE(addrs, ap, next); if (ap->flags & IPV6_AF_ADDED) { syslog(LOG_INFO, "%s: deleting address %s", ap->iface->name, ap->saddr); @@ -664,7 +663,12 @@ ipv6_addaddrs(struct ipv6_addrhead *addrs) } eloop_q_timeout_delete(ap->iface->ctx->eloop, 0, NULL, ap); - free(ap); + if (ap->flags & IPV6_AF_REQUEST) { + ap->flags &= ~IPV6_AF_ADDED; + } else { + TAILQ_REMOVE(addrs, ap, next); + free(ap); + } } else if (!IN6_IS_ADDR_UNSPECIFIED(&ap->addr)) { if (ap->flags & IPV6_AF_NEW) i++; diff --git a/ipv6.h b/ipv6.h index a1259413..5faddcf1 100644 --- a/ipv6.h +++ b/ipv6.h @@ -90,6 +90,7 @@ struct ipv6_addr { short flags; char saddr[INET6_ADDRSTRLEN]; uint8_t iaid[4]; + uint16_t ia_type; struct interface *delegating_iface; void (*dadcallback)(void *);