]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
DHCP6: Implement DECLINE support for duplicated addresses
authorRoy Marples <roy@marples.name>
Sun, 26 Apr 2020 16:22:42 +0000 (17:22 +0100)
committerRoy Marples <roy@marples.name>
Sun, 26 Apr 2020 16:22:42 +0000 (17:22 +0100)
This is the final piece of DHCP6 to implement!
Part of this change drops the use of the IPV6_AF_DUPLICATED flag
and we just use IN6_IFF_DUPLICATED now.

src/dhcp6.c
src/dhcp6.h
src/ipv6.c
src/ipv6.h
src/ipv6nd.c

index db3c7e2e491440bcb278271123587d86c4e512c6..2956f6831433a27dacfccd7933d4bbe93c3efc73 100644 (file)
@@ -26,8 +26,6 @@
  * SUCH DAMAGE.
  */
 
-/* TODO: We should decline dupliate addresses detected */
-
 #include <sys/stat.h>
 #include <sys/utsname.h>
 
@@ -139,6 +137,7 @@ static const struct dhcp6_op dhcp6_ops[] = {
        { DHCP6_INFORMATION_REQ, "INFORM6" },
        { DHCP6_RELEASE, "RELEASE6" },
        { DHCP6_RECONFIGURE, "RECONFIGURE6" },
+       { DHCP6_DECLINE, "DECLINE6" },
        { 0, NULL }
 };
 
@@ -174,6 +173,7 @@ static const char * const dhcp6_statuses[] = {
 static void dhcp6_bind(struct interface *, const char *, const char *);
 static void dhcp6_failinform(void *);
 static void dhcp6_recvaddr(void *);
+static void dhcp6_startdecline(struct interface *);
 
 #ifdef SMALL
 #define dhcp6_hasprefixdelegation(a)   (0)
@@ -181,6 +181,12 @@ static void dhcp6_recvaddr(void *);
 static int dhcp6_hasprefixdelegation(struct interface *);
 #endif
 
+#define DECLINE_IA(ia) \
+       ((ia)->addr_flags & IN6_IFF_DUPLICATED && \
+       (ia)->ia_type != 0 && (ia)->ia_type != D6_OPTION_IA_PD && \
+       !((ia)->flags & IPV6_AF_STALE) && \
+       (ia)->prefix_vltime != 0)
+
 void
 dhcp6_printoptions(const struct dhcpcd_ctx *ctx,
     const struct dhcp_opt *opts, size_t opts_len)
@@ -672,7 +678,7 @@ dhcp6_makemessage(struct interface *ifp)
        len = 0;
        si = NULL;
        hl = 0; /* Appease gcc */
-       if (state->state != DH6S_RELEASE) {
+       if (state->state != DH6S_RELEASE && state->state != DH6S_DECLINE) {
                for (l = 0, opt = ifp->ctx->dhcp6_opts;
                    l < ifp->ctx->dhcp6_opts_len;
                    l++, opt++)
@@ -750,6 +756,8 @@ dhcp6_makemessage(struct interface *ifp)
                m = state->recv;
                ml = state->recv_len;
                /* FALLTHROUGH */
+       case DH6S_DECLINE:
+               /* FALLTHROUGH */
        case DH6S_RELEASE:
                /* FALLTHROUGH */
        case DH6S_RENEW:
@@ -778,6 +786,8 @@ dhcp6_makemessage(struct interface *ifp)
                            (ap->prefix_vltime == 0 ||
                            state->state == DH6S_DISCOVER))
                                continue;
+                       if (DECLINE_IA(ap) && state->state != DH6S_DECLINE)
+                               continue;
                        if (ap->ia_type == D6_OPTION_IA_PD) {
 #ifndef SMALL
                                len += sizeof(o) + sizeof(struct dhcp6_pd_addr);
@@ -836,6 +846,9 @@ dhcp6_makemessage(struct interface *ifp)
        case DH6S_RELEASE:
                type = DHCP6_RELEASE;
                break;
+       case DH6S_DECLINE:
+               type = DHCP6_DECLINE;
+               break;
        default:
                errno = EINVAL;
                return -1;
@@ -949,6 +962,8 @@ dhcp6_makemessage(struct interface *ifp)
                            (ap->prefix_vltime == 0 ||
                            state->state == DH6S_DISCOVER))
                                continue;
+                       if (DECLINE_IA(ap) && state->state != DH6S_DECLINE)
+                               continue;
                        if (ap->ia_type != ifia->ia_type)
                                continue;
                        if (memcmp(ap->iaid, ifia->iaid, sizeof(ap->iaid)))
@@ -1010,7 +1025,10 @@ dhcp6_makemessage(struct interface *ifp)
                memcpy(o_lenp, &ia_na_len, sizeof(ia_na_len));
        }
 
-       if (state->send->type != DHCP6_RELEASE && n_options) {
+       if (state->send->type != DHCP6_RELEASE &&
+           state->send->type != DHCP6_DECLINE &&
+           n_options)
+       {
                o_lenp = NEXTLEN;
                o.len = 0;
                COPYIN1(D6_OPTION_ORO, 0);
@@ -1072,7 +1090,9 @@ dhcp6_makemessage(struct interface *ifp)
        if (!has_option_mask(ifo->nomask6, D6_OPTION_VENDOR_CLASS))
                p += dhcp6_makevendor(p, ifp);
 
-       if (state->send->type != DHCP6_RELEASE) {
+       if (state->send->type != DHCP6_RELEASE &&
+           state->send->type != DHCP6_DECLINE)
+       {
                if (fqdn != FQDN_DISABLE) {
                        o_lenp = NEXTLEN;
                        COPYIN1(D6_OPTION_FQDN, 0);
@@ -1431,6 +1451,13 @@ dhcp6_sendconfirm(void *arg)
        dhcp6_sendmessage(arg, dhcp6_sendconfirm);
 }
 
+static void
+dhcp6_senddecline(void *arg)
+{
+
+       dhcp6_sendmessage(arg, dhcp6_senddecline);
+}
+
 static void
 dhcp6_sendrelease(void *arg)
 {
@@ -1496,54 +1523,60 @@ dhcp6_dadcallback(void *arg)
        struct ipv6_addr *ia = arg;
        struct interface *ifp;
        struct dhcp6_state *state;
-       int wascompleted, valid;
+       struct ipv6_addr *ia2;
+       bool completed, valid, oneduplicated;
 
-       wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED);
+       completed = (ia->flags & IPV6_AF_DADCOMPLETED);
        ia->flags |= IPV6_AF_DADCOMPLETED;
-       if (ia->flags & IPV6_AF_DUPLICATED) {
-               /* XXX FIXME
-                * We should decline the address */
+       if (ia->addr_flags & IN6_IFF_DUPLICATED)
                logwarnx("%s: DAD detected %s", ia->iface->name, ia->saddr);
-       }
 
-       if (!wascompleted) {
-               ifp = ia->iface;
+#ifdef ND6_ADVERTISE
+       else
+               ipv6nd_advertise(ia);
+#endif
+       if (completed)
+               return;
 
-               state = D6_STATE(ifp);
-               if (state->state == DH6S_BOUND ||
-                   state->state == DH6S_DELEGATED)
-               {
-                       struct ipv6_addr *ia2;
+       ifp = ia->iface;
+       state = D6_STATE(ifp);
+       if (state->state != DH6S_BOUND && state->state != DH6S_DELEGATED)
+               return;
 
 #ifdef SMALL
-                       valid = true;
+       valid = true;
 #else
-                       valid = (ia->delegating_prefix == NULL);
-#endif
-                       TAILQ_FOREACH(ia2, &state->addrs, next) {
-                               if (ia2->flags & IPV6_AF_ADDED &&
-                                   !(ia2->flags & IPV6_AF_DADCOMPLETED))
-                               {
-                                       wascompleted = 1;
-                                       break;
-                               }
-                       }
-                       if (!wascompleted) {
-                               logdebugx("%s: DHCPv6 DAD completed",
-                                   ifp->name);
-                               script_runreason(ifp,
-#ifndef SMALL
-                                   ia->delegating_prefix ? "DELEGATED6" :
-#endif
-                                   state->reason);
-                               if (valid)
-                                       dhcpcd_daemonise(ifp->ctx);
-                       }
-#ifdef ND6_ADVERTISE
-                       ipv6nd_advertise(ia);
+       valid = (ia->delegating_prefix == NULL);
 #endif
+       completed = true;
+       oneduplicated = false;
+       TAILQ_FOREACH(ia2, &state->addrs, next) {
+               if (ia2->flags & IPV6_AF_ADDED &&
+                   !(ia2->flags & IPV6_AF_DADCOMPLETED))
+               {
+                       completed = false;
+                       break;
                }
+               if (DECLINE_IA(ia))
+                       oneduplicated = true;
        }
+       if (!completed)
+               return;
+
+       logdebugx("%s: DHCPv6 DAD completed", ifp->name);
+
+       if (oneduplicated && state->state == DH6S_BOUND) {
+               dhcp6_startdecline(ifp);
+               return;
+       }
+
+       script_runreason(ifp,
+#ifndef SMALL
+           ia->delegating_prefix ? "DELEGATED6" :
+#endif
+           state->reason);
+       if (valid)
+               dhcpcd_daemonise(ifp->ctx);
 }
 
 static void
@@ -1682,7 +1715,7 @@ dhcp6_leaseextend(struct interface *ifp)
 }
 
 static void
-dhcp6_fail(struct interfaceifp)
+dhcp6_fail(struct interface *ifp)
 {
        struct dhcp6_state *state = D6_STATE(ifp);
 
@@ -1867,8 +1900,18 @@ static void
 dhcp6_startconfirm(struct interface *ifp)
 {
        struct dhcp6_state *state;
+       struct ipv6_addr *ia;
 
        state = D6_STATE(ifp);
+
+       TAILQ_FOREACH(ia, &state->addrs, next) {
+               if (!DECLINE_IA(ia))
+                       continue;
+               logerrx("%s: prior DHCPv6 has a duplicated address", ifp->name);
+               dhcp6_startdecline(ifp);
+               return;
+       }
+
        state->state = DH6S_CONFIRM;
        state->RTC = 0;
        state->IMD = CNF_MAX_DELAY;
@@ -1877,6 +1920,7 @@ dhcp6_startconfirm(struct interface *ifp)
        state->MRC = CNF_MAX_RC;
 
        loginfox("%s: confirming prior DHCPv6 lease", ifp->name);
+
        if (dhcp6_makemessage(ifp) == -1) {
                logerr("%s: %s", __func__, ifp->name);
                return;
@@ -1898,6 +1942,36 @@ dhcp6_startexpire(void *arg)
        dhcp6_fail(ifp);
 }
 
+static void
+dhcp6_faildecline(void *arg)
+{
+       struct interface *ifp = arg;
+
+       logerrx("%s: failed to decline duplicated DHCPv6 addresses", ifp->name);
+       dhcp6_fail(ifp);
+}
+
+static void
+dhcp6_startdecline(struct interface *ifp)
+{
+       struct dhcp6_state *state;
+
+       state = D6_STATE(ifp);
+       loginfox("%s: declining failed DHCPv6 addresses", ifp->name);
+       state->state = DH6S_DECLINE;
+       state->RTC = 0;
+       state->IMD = 0;
+       state->IRT = DEC_TIMEOUT;
+       state->MRT = 0;
+       state->MRC = DEC_MAX_RC;
+       state->MRCcallback = dhcp6_faildecline;
+
+       if (dhcp6_makemessage(ifp) == -1)
+               logerr("%s: %s", __func__, ifp->name);
+       else
+               dhcp6_senddecline(ifp);
+}
+
 static void
 dhcp6_finishrelease(void *arg)
 {
@@ -3350,6 +3424,13 @@ dhcp6_recvif(struct interface *ifp, const char *sfrom,
                        if (state->state == DH6S_DISCOVER)
                                state->state = DH6S_REQUEST;
                        break;
+               case DH6S_DECLINE:
+                       /* This isnt really a failure, but an
+                        * acknowledgement of one. */
+                       loginfox("%s: %s acknowledged DECLINE6",
+                           ifp->name, sfrom);
+                       dhcp6_fail(ifp);
+                       return;
                default:
                        valid_op = false;
                        break;
index 73341cde459bdbe3cff1ee387991b243ab5f2f49..d099837573cae449d270c74ef1aed21a610cc6b7 100644 (file)
@@ -168,9 +168,10 @@ enum DH6S {
        DH6S_INFORMED,
        DH6S_RENEW_REQUESTED,
        DH6S_PROBE,
+       DH6S_DECLINE,
        DH6S_DELEGATED,
        DH6S_RELEASE,
-       DH6S_RELEASED
+       DH6S_RELEASED,
 };
 
 struct dhcp6_state {
index f6734e93437024c656f44ecc63b6a04e7a5dfa01..c039e7e6b72c6839441794bafa000ccd0f57a142 100644 (file)
@@ -1635,7 +1635,7 @@ ipv6_staticdadcallback(void *arg)
 
        wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED);
        ia->flags |= IPV6_AF_DADCOMPLETED;
-       if (ia->flags & IPV6_AF_DUPLICATED)
+       if (ia->addr_flags & IN6_IFF_DUPLICATED)
                logwarnx("%s: DAD detected %s", ia->iface->name,
                    ia->saddr);
        else if (!wascompleted) {
@@ -1838,16 +1838,13 @@ ipv6_handleifa_addrs(int cmd,
                        }
                        break;
                case RTM_NEWADDR:
+                       ia->addr_flags = addr->addr_flags;
                        /* Safety - ignore tentative announcements */
-                       if (addr->addr_flags &
+                       if (ia->addr_flags &
                            (IN6_IFF_DETACHED | IN6_IFF_TENTATIVE))
                                break;
                        if ((ia->flags & IPV6_AF_DADCOMPLETED) == 0) {
                                found++;
-                               if (addr->addr_flags & IN6_IFF_DUPLICATED)
-                                       ia->flags |= IPV6_AF_DUPLICATED;
-                               else
-                                       ia->flags &= ~IPV6_AF_DUPLICATED;
                                if (ia->dadcallback)
                                        ia->dadcallback(ia);
                                /* We need to set this here in-case the
@@ -1890,7 +1887,7 @@ ipv6_tempdadcallback(void *arg)
 {
        struct ipv6_addr *ia = arg;
 
-       if (ia->flags & IPV6_AF_DUPLICATED) {
+       if (ia->addr_flags & IN6_IFF_DUPLICATED) {
                struct ipv6_addr *ia1;
                struct timespec tv;
 
index d311b374d1db4808722d664c71afbaa784def70a..005735d7c1b80f3d0883244b764e01feb1378ea6 100644 (file)
@@ -216,18 +216,17 @@ struct ipv6_addr {
 #define        IPV6_AF_STALE           (1U << 2)
 #define        IPV6_AF_ADDED           (1U << 3)
 #define        IPV6_AF_AUTOCONF        (1U << 4)
-#define        IPV6_AF_DUPLICATED      (1U << 5)
-#define        IPV6_AF_DADCOMPLETED    (1U << 6)
-#define        IPV6_AF_DELEGATED       (1U << 7)
-#define        IPV6_AF_DELEGATEDPFX    (1U << 8)
-#define        IPV6_AF_NOREJECT        (1U << 9)
-#define        IPV6_AF_REQUEST         (1U << 10)
-#define        IPV6_AF_STATIC          (1U << 11)
-#define        IPV6_AF_DELEGATEDLOG    (1U << 12)
-#define        IPV6_AF_RAPFX           (1U << 13)
-#define        IPV6_AF_EXTENDED        (1U << 14)
+#define        IPV6_AF_DADCOMPLETED    (1U << 5)
+#define        IPV6_AF_DELEGATED       (1U << 6)
+#define        IPV6_AF_DELEGATEDPFX    (1U << 7)
+#define        IPV6_AF_NOREJECT        (1U << 8)
+#define        IPV6_AF_REQUEST         (1U << 9)
+#define        IPV6_AF_STATIC          (1U << 10)
+#define        IPV6_AF_DELEGATEDLOG    (1U << 11)
+#define        IPV6_AF_RAPFX           (1U << 12)
+#define        IPV6_AF_EXTENDED        (1U << 13)
 #ifdef IPV6_MANAGETEMPADDR
-#define        IPV6_AF_TEMPORARY       (1U << 15)
+#define        IPV6_AF_TEMPORARY       (1U << 14)
 #endif
 
 struct ll_callback {
index f1f83d5f7b8a19039e34102eb10c9a905cd59fda..faf31561131f1a1fcae84e8f77875eefe1916e5c 100644 (file)
@@ -927,7 +927,7 @@ ipv6nd_dadcallback(void *arg)
        ifp = ia->iface;
        wascompleted = (ia->flags & IPV6_AF_DADCOMPLETED);
        ia->flags |= IPV6_AF_DADCOMPLETED;
-       if (ia->flags & IPV6_AF_DUPLICATED) {
+       if (ia->addr_flags & IN6_IFF_DUPLICATED) {
                ia->dadcounter++;
                logwarnx("%s: DAD detected %s", ifp->name, ia->saddr);