]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Support http://datatracker.ietf.org/doc/draft-ietf-dhc-dhcpv6-stateful-issues.
authorRoy Marples <roy@marples.name>
Tue, 1 Jul 2014 20:34:19 +0000 (20:34 +0000)
committerRoy Marples <roy@marples.name>
Tue, 1 Jul 2014 20:34:19 +0000 (20:34 +0000)
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.

dhcp6.c
dhcpcd.c
dhcpcd.conf.5.in
if-options.c
if-options.h
ipv6.c
ipv6.h

diff --git a/dhcp6.c b/dhcp6.c
index 7677c9d3d4a036a49a01259e453aac9c325f3327..223691809ace84fcf7c9d3acdd73a1388494fecf 100644 (file)
--- 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;
 }
 
index 45e8e92db9eedce8d092336fb2f6d4dcbec2611e..1acc25229f331786ef1dc6dd1c6486da8ca70653 100644 (file)
--- 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)
index fa74e64fa4f2b884a6dc49a63643ee464e093136..1b3f6b0d3586827b961ec140e1693d8692e50bb1 100644 (file)
@@ -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
index 77593a3360c7cf6fcdef37c9b2e3f0d838a9836d..a2345c9d3ba32bbfcac673d0fb6052be884b0abe 100644 (file)
@@ -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);
index bfcca81780e9adbf98717ee3ce60cbe6d967cc0a..63618fc4f2a86d9d8ad8483833bdb727ee1d90a8 100644 (file)
@@ -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 2c1280f4ac5a35981014f26104b362ced338298c..98a4d454cafdabe255c48cee89232cdc70cf8c9f 100644 (file)
--- 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 a12594137c0ef75a1a2c7f7f0b2c1c2fccd484f1..5faddcf145b5eca538e9ce2d20ab1798a65514d8 100644 (file)
--- 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 *);