]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Add IPv6 DAD detection.
authorRoy Marples <roy@marples.name>
Fri, 3 May 2013 12:11:49 +0000 (12:11 +0000)
committerRoy Marples <roy@marples.name>
Fri, 3 May 2013 12:11:49 +0000 (12:11 +0000)
Sadly, it seems that userland cannot send from the unspecified address.
This means that for RFC conformancy, we have to rely on the kernel
performing DAD.

13 files changed:
dhcp6.c
if-bsd.c
if-options.c
if-options.h
ipv6.c
ipv6.h
ipv6ns.c
ipv6ns.h
ipv6rs.c
ipv6rs.h
platform-bsd.c
platform-linux.c
platform.h

diff --git a/dhcp6.c b/dhcp6.c
index c8fc8605bf0faf6864de181c3f73a938ca36ca15..1adeb4566c4f40c08e7e7f129fe1f95526abf441 100644 (file)
--- a/dhcp6.c
+++ b/dhcp6.c
@@ -25,6 +25,8 @@
  * SUCH DAMAGE.
  */
 
+/* TODO: We should decline dupliate addresses detected */
+
 #include <sys/stat.h>
 #include <sys/utsname.h>
 
@@ -50,6 +52,7 @@
 #include "dhcp6.h"
 #include "duid.h"
 #include "eloop.h"
+#include "ipv6ns.h"
 #include "ipv6rs.h"
 #include "platform.h"
 #include "script.h"
@@ -1030,6 +1033,24 @@ dhcp6_addrexists(const struct ipv6_addr *a)
        return 0;
 }
 
+static void
+dhcp6_dadcallback(void *arg)
+{
+       struct ipv6_addr *ap = arg;
+
+       ipv6ns_cancelprobeaddr(ap);
+       ap->dadcompleted = 1;
+       if (ap->dad)
+               /* XXX FIXME
+                * We should decline the address */
+               syslog(LOG_WARNING, "%s: DAD detected %s",
+                   ap->iface->name, ap->saddr);
+#ifdef IPV6_SEND_DAD
+       else
+               ipv6_addaddr(ap);
+#endif
+}
+
 static int
 dhcp6_findna(struct interface *ifp, const uint8_t *iaid,
     const uint8_t *d, size_t l)
@@ -1063,8 +1084,10 @@ dhcp6_findna(struct interface *ifp, const uint8_t *iaid,
                        syslog(LOG_ERR, "%s: %m", __func__);
                        break;
                }
+               a->iface = ifp;
                a->new = 1;
                a->onlink = 1; /* XXX: suprised no DHCP opt for this */
+               a->dadcallback = dhcp6_dadcallback;
                memcpy(a->iaid, iaid, sizeof(a->iaid));
                p = D6_COPTION_DATA(o);
                memcpy(&a->addr.s6_addr, p,
@@ -1135,8 +1158,10 @@ dhcp6_findpd(struct interface *ifp, const uint8_t *iaid,
                        syslog(LOG_ERR, "%s: %m", __func__);
                        break;
                }
+               a->iface = ifp;
                a->new = 1;
                a->onlink = 0;
+               a->dadcallback = dhcp6_dadcallback;
                memcpy(a->iaid, iaid, sizeof(a->iaid));
                p = D6_COPTION_DATA(o);
                memcpy(&u32, p, sizeof(u32));
@@ -1410,9 +1435,10 @@ dhcp6_delegate_addr(struct interface *ifp, const struct ipv6_addr *prefix,
                syslog(LOG_ERR, "%s: %m", __func__);
                return NULL;
        }
-
+       a->iface = ifp;
        a->new = 1;
        a->onlink = 1;
+       a->dadcallback = dhcp6_dadcallback;
        a->delegating_iface = ifs;
        memcpy(&a->iaid, &prefix->iaid, sizeof(a->iaid));
        a->prefix_pltime = prefix->prefix_pltime;
@@ -1486,7 +1512,7 @@ dhcp6_delegate_prefix(struct interface *ifp)
                }
                if (k) {
                        ifd_state = D6_STATE(ifd);
-                       ipv6_addaddrs(ifd, &ifd_state->addrs);
+                       ipv6ns_probeaddrs(&ifd_state->addrs);
                }
        }
 }
@@ -1531,7 +1557,7 @@ dhcp6_find_delegates(struct interface *ifp)
        if (k) {
                syslog(LOG_INFO, "%s: adding delegated prefixes", ifp->name);
                state = D6_STATE(ifp);
-               ipv6_addaddrs(ifp, &state->addrs);
+               ipv6ns_probeaddrs(&state->addrs);
                ipv6_buildroutes();
        }
        return k;
@@ -1783,7 +1809,7 @@ recv:
                            dhcp6_startexpire, ifp);
                if (ifp->options->ia_type == D6_OPTION_IA_PD)
                        dhcp6_delegate_prefix(ifp);
-               ipv6_addaddrs(ifp, &state->addrs);
+               ipv6ns_probeaddrs(&state->addrs);
                if (state->renew || state->rebind)
                        syslog(LOG_INFO,
                            "%s: renew in %u seconds, rebind in %u seconds",
index 6367be9d5e779ab0c7a1385fb0684372a22452d7..78425d0fa2bade407ceb6682f8b81d45c56272b3 100644 (file)
--- a/if-bsd.c
+++ b/if-bsd.c
@@ -308,6 +308,11 @@ if_address6(const struct interface *ifp, const struct ipv6_addr *a, int action)
                return -1;
        memset(&ifa, 0, sizeof(ifa));
        strlcpy(ifa.ifra_name, ifp->name, sizeof(ifa.ifra_name));
+       ifa.ifra_flags = IN6_IFF_TENTATIVE;
+#if 0
+       if (a->autoconf)
+               ifa.ifra_flags |= IN6_IFF_AUTOCONF;
+#endif
 
 #define ADDADDR(v, addr) {                                                   \
                (v)->sin6_family = AF_INET6;                                  \
index 33d85af4471d2efad671bd706932c5b6b14b7cd5..8d83cbd3424088cdcf6494eabf13b90c3b0a9955 100644 (file)
@@ -1091,6 +1091,7 @@ read_config(const char *file,
 #endif
 #ifdef INET6
        ifo->options |= DHCPCD_IPV6 | DHCPCD_IPV6RS | DHCPCD_IPV6RA_REQRDNSS;
+       ifo->dadtransmits = ipv6_dadtransmits(ifname);
 #endif
        ifo->timeout = DEFAULT_TIMEOUT;
        ifo->reboot = DEFAULT_REBOOT;
index 9a4a835b9458b765006b025ec5f39c5e6b80bde7..b4c03041061873c0b62b38a3b057f7b6ab137384 100644 (file)
@@ -146,6 +146,7 @@ struct if_options {
        uint16_t ia_type;
        size_t iaid_len;
        struct if_iaid *iaid;
+       int dadtransmits;
 #endif
 };
 
diff --git a/ipv6.c b/ipv6.c
index 042c2a522d0da1a45d175807bfbfce03a77135a4..e8752815f9e99d0b8e4ae7b994b434858150ab55 100644 (file)
--- a/ipv6.c
+++ b/ipv6.c
@@ -247,8 +247,28 @@ ipv6_prefixlen(const struct in6_addr *mask)
        return x * NBBY + y;
 }
 
+int
+ipv6_addaddr(struct ipv6_addr *ap)
+{
+
+       syslog(ap->new ? LOG_INFO : LOG_DEBUG,
+           "%s: adding address %s", ap->iface->name, ap->saddr);
+       if (add_address6(ap->iface, ap) == -1) {
+               syslog(LOG_ERR, "add_address6 %m");
+               return -1;
+       }
+       ap->new = 0;
+       ap->added = 1;
+       if (ipv6_removesubnet(ap->iface, ap) == -1)
+               syslog(LOG_ERR,"ipv6_removesubnet %m");
+       syslog(LOG_DEBUG,
+           "%s: pltime %d seconds, vltime %d seconds",
+           ap->iface->name, ap->prefix_pltime, ap->prefix_vltime);
+       return 0;
+}
+
 ssize_t
-ipv6_addaddrs(const struct interface *ifp, struct ipv6_addrhead *addrs)
+ipv6_addaddrs(struct ipv6_addrhead *addrs)
 {
        struct ipv6_addr *ap;
        ssize_t i;
@@ -258,20 +278,8 @@ ipv6_addaddrs(const struct interface *ifp, struct ipv6_addrhead *addrs)
                if (ap->prefix_vltime == 0 ||
                    IN6_IS_ADDR_UNSPECIFIED(&ap->addr))
                        continue;
-               syslog(ap->new ? LOG_INFO : LOG_DEBUG,
-                   "%s: adding address %s",
-                   ifp->name, ap->saddr);
-               if (add_address6(ifp, ap) == -1)
-                       syslog(LOG_ERR, "add_address6 %m");
-               else {
+               if (ipv6_addaddr(ap) == 0)
                        i++;
-                       if (ipv6_removesubnet(ifp, ap) == -1)
-                               syslog(LOG_ERR,"ipv6_removesubnet %m");
-                       syslog(LOG_DEBUG,
-                           "%s: pltime %d seconds, vltime %d seconds",
-                           ifp->name, ap->prefix_pltime,
-                           ap->prefix_vltime);
-               }
        }
 
        return i;
diff --git a/ipv6.h b/ipv6.h
index 67e8a3661a4fa8a6d9e26e9586d1ffb7b6bd5f3e..ad4b91a84c02111978b55816b4b050cc1ab94970 100644 (file)
--- a/ipv6.h
+++ b/ipv6.h
@@ -39,6 +39,7 @@
 
 struct ipv6_addr {
        TAILQ_ENTRY(ipv6_addr) next;
+       struct interface *iface;
        struct in6_addr prefix;
        int prefix_len;
        uint32_t prefix_vltime;
@@ -47,8 +48,17 @@ struct ipv6_addr {
        uint8_t onlink;
        uint8_t new;
        char saddr[INET6_ADDRSTRLEN];
+       uint8_t added;
+       uint8_t autoconf;
        uint8_t iaid[4];
        struct interface *delegating_iface;
+
+       void (*dadcallback)(void *);
+       uint8_t dad;
+       uint8_t dadcompleted;
+       uint8_t *ns;
+       size_t nslen;
+       int nsprobes;
 };
 TAILQ_HEAD(ipv6_addrhead, ipv6_addr);
 
@@ -72,7 +82,8 @@ int ipv6_makeaddr(struct in6_addr *, const char *, const struct in6_addr *, int)
 int ipv6_makeprefix(struct in6_addr *, const struct in6_addr *, int);
 int ipv6_mask(struct in6_addr *, int);
 int ipv6_prefixlen(const struct in6_addr *);
-ssize_t ipv6_addaddrs(const struct interface *, struct ipv6_addrhead *);
+int ipv6_addaddr(struct ipv6_addr *);
+ssize_t ipv6_addaddrs(struct ipv6_addrhead *);
 int ipv6_removesubnet(const struct interface *, struct ipv6_addr *);
 void ipv6_buildroutes(void);
 void ipv6_drop(struct interface *);
index 0541ca53af58c257437fa7aa5976eef5d4f6e623..09a37e55a23ba0b133f19f15090d4b8a964a2bef 100644 (file)
--- a/ipv6ns.c
+++ b/ipv6ns.c
 #define ELOOP_QUEUE 1
 #include "common.h"
 #include "dhcpcd.h"
+#include "dhcp6.h"
 #include "eloop.h"
 #include "ipv6.h"
 #include "ipv6ns.h"
 #include "script.h"
 
-#define MIN_RANDOM_FACTOR      (500 * 1000)    /* milliseconds in usecs */
-#define MAX_RANDOM_FACTOR      (1500 * 1000)   /* milliseconds in usecs */
+#define MIN_RANDOM_FACTOR      500                             /* milliseconds */
+#define MAX_RANDOM_FACTOR      1500                            /* milliseconds */
+#define MIN_RANDOM_FACTOR_U    MIN_RANDOM_FACTOR * 1000        /* usecs */
+#define MAX_RANDOM_FACTOR_U    MAX_RANDOM_FACTOR * 1000        /* usecs */
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define IPV6_ADDR_INT32_ONE     1
+#define IPV6_ADDR_INT16_MLL     0xff02
+#elif BYTE_ORDER == LITTLE_ENDIAN
+#define IPV6_ADDR_INT32_ONE     0x01000000
+#define IPV6_ADDR_INT16_MLL     0x02ff
+#endif
 
 /* Debugging Neighbor Solicitations is a lot of spam, so disable it */
 //#define DEBUG_NS
+//
+
+/* Currently, no known kernel allows us to send from the unspecified address
+ * which is required for DAD to work. This isn't that much of a problem as
+ * the kernel will do DAD for us correctly, however we don't know the exact
+ * randomness the kernel applies to the timeouts. So we just follow the same
+ * logic and have a little faith.
+ * This define is purely for completeness */
+// #define IPV6_SEND_DAD
 
 static int sock = -1;
+#ifdef IPV6_SEND_DAD
+static int unspec_sock = -1;
+#endif
 static struct sockaddr_in6 from;
 static struct msghdr sndhdr;
 static struct iovec sndiov[2];
@@ -89,6 +112,15 @@ ipv6ns_open(void)
        int on;
        int len;
        struct icmp6_filter filt;
+#ifdef IPV6_SEND_DAD
+       union {
+               struct sockaddr sa;
+               struct sockaddr_in6 sin;
+       } su;
+#endif
+
+       if (sock != -1)
+               return sock;
 
        sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
        if (sock == -1)
@@ -104,6 +136,24 @@ ipv6ns_open(void)
                goto eexit;
 
        ICMP6_FILTER_SETBLOCKALL(&filt);
+
+#ifdef IPV6_SEND_DAD
+       /* We send DAD requests from the unspecified address. */
+       unspec_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+       if (unspec_sock == -1)
+               goto eexit;
+       if (setsockopt(unspec_sock, IPPROTO_ICMPV6, ICMP6_FILTER,
+           &filt, sizeof(filt)) == -1)
+               goto eexit;
+       memset(&su, 0, sizeof(su));
+       su.sin.sin6_family = AF_INET6;
+#ifdef SIN6_LEN
+       su.sin.sin6_len = sizeof(su.sin);
+#endif
+       if (bind(unspec_sock, &su.sa, sizeof(su.sin)) == -1)
+               goto eexit;
+#endif
+
        ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filt);
        if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER,
            &filt, sizeof(filt)) == -1)
@@ -134,9 +184,12 @@ ipv6ns_open(void)
        rcvhdr.msg_controllen = len;
        rcviov[0].iov_base = ansbuf;
        rcviov[0].iov_len = sizeof(ansbuf);
+
+       eloop_event_add(sock, ipv6ns_handledata, NULL);
        return sock;
 
 eexit:
+       syslog(LOG_ERR, "%s: %m", __func__);
        close(sock);
        sock = -1;
        free(sndbuf);
@@ -146,30 +199,6 @@ eexit:
        return -1;
 }
 
-static int
-ipv6ns_makeprobe(struct ra *rap)
-{
-       struct nd_neighbor_solicit *ns;
-       struct nd_opt_hdr *nd;
-
-       free(rap->ns);
-       rap->nslen = sizeof(*ns) + ROUNDUP8(rap->iface->hwlen + 2);
-       rap->ns = calloc(1, rap->nslen);
-       if (rap->ns == NULL)
-               return -1;
-       ns = (struct nd_neighbor_solicit *)(void *)rap->ns;
-       ns->nd_ns_type = ND_NEIGHBOR_SOLICIT;
-       ns->nd_ns_cksum = 0;
-       ns->nd_ns_code = 0;
-       ns->nd_ns_reserved = 0;
-       ns->nd_ns_target = rap->from;
-       nd = (struct nd_opt_hdr *)(rap->ns + sizeof(*ns));
-       nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
-       nd->nd_opt_len = (ROUNDUP8(rap->iface->hwlen + 2)) >> 3;
-       memcpy(nd + 1, rap->iface->hwaddr, rap->iface->hwlen);
-       return 0;
-}
-
 static void
 ipv6ns_unreachable(void *arg)
 {
@@ -185,26 +214,194 @@ ipv6ns_unreachable(void *arg)
 }
 
 void
-ipv6ns_sendprobe(void *arg)
+ipv6ns_cancelprobeaddr(struct ipv6_addr *ap)
 {
-       struct ra *rap = arg;
+
+       eloop_timeout_delete(ipv6ns_probeaddr, ap);
+       if (ap->dadcallback)
+               eloop_timeout_delete(ap->dadcallback, ap);
+}
+
+void
+ipv6ns_probeaddr(void *arg)
+{
+       struct ipv6_addr *ap = arg;
+#ifdef IPV6_SEND_DAD
+       struct nd_neighbor_solicit *ns;
+       struct nd_opt_hdr *nd;
        struct sockaddr_in6 dst;
        struct cmsghdr *cm;
        struct in6_pktinfo pi;
        int hoplimit = HOPLIMIT;
+#else
+       struct timeval mtv;
+       int i;
+#endif
        struct timeval tv, rtv;
 
-       if (sock == -1) {
-               if (ipv6ns_open() == -1) {
-                       syslog(LOG_ERR, "%s: ipv6ns_open: %m", __func__);
+       if (ap->dadcallback &&
+           (ap->new == 0 || ap->nsprobes >= ap->iface->options->dadtransmits))
+       {
+#ifdef IPV6_SEND_DAD
+               ap->dadcallback(ap);
+#else
+               ipv6_addaddr(ap);
+#endif
+               return;
+       }
+
+       if (ipv6ns_open() == -1)
+               return;
+
+       ap->dadcompleted = 0;
+
+#ifdef IPV6_SEND_DAD
+       if (!ap->ns) {
+               ap->nslen = sizeof(*ns) + ROUNDUP8(ap->iface->hwlen + 2);
+               ap->ns = calloc(1, ap->nslen);
+               if (ap->ns == NULL) {
+                       syslog(LOG_ERR, "%s: %m", __func__);
                        return;
                }
-               eloop_event_add(sock, ipv6ns_handledata, NULL);
+               ns = (struct nd_neighbor_solicit *)(void *)ap->ns;
+               ns->nd_ns_type = ND_NEIGHBOR_SOLICIT;
+               //ns->nd_ns_cksum = 0;
+               //ns->nd_ns_code = 0;
+               //ns->nd_ns_reserved = 0;
+               ns->nd_ns_target = ap->addr;
+               nd = (struct nd_opt_hdr *)(ap->ns + sizeof(*ns));
+               nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+               nd->nd_opt_len = (ROUNDUP8(ap->iface->hwlen + 2)) >> 3;
+               memcpy(nd + 1, ap->iface->hwaddr, ap->iface->hwlen);
        }
 
-       if (!rap->ns && ipv6ns_makeprobe(rap) == -1) {
-               syslog(LOG_ERR, "%s: ipv6ns_makeprobe: %m", __func__);
+       memset(&dst, 0, sizeof(dst));
+       dst.sin6_family = AF_INET6;
+#ifdef SIN6_LEN
+       dst.sin6_len = sizeof(dst);
+#endif
+       dst.sin6_addr.s6_addr16[0] = IPV6_ADDR_INT16_MLL;
+       dst.sin6_addr.s6_addr16[1] = 0;
+       dst.sin6_addr.s6_addr32[1] = 0;
+       dst.sin6_addr.s6_addr32[2] = IPV6_ADDR_INT32_ONE;
+       dst.sin6_addr.s6_addr32[3] = ap->addr.s6_addr32[3];
+       dst.sin6_addr.s6_addr[12] = 0xff;
+
+       //memcpy(&dst.sin6_addr, &ap->addr, sizeof(dst.sin6_addr));
+       //dst.sin6_scope_id = ap->iface->index;
+
+       sndhdr.msg_name = (caddr_t)&dst;
+       sndhdr.msg_iov[0].iov_base = ap->ns;
+       sndhdr.msg_iov[0].iov_len = ap->nslen;
+
+       /* Set the outbound interface */
+       cm = CMSG_FIRSTHDR(&sndhdr);
+       cm->cmsg_level = IPPROTO_IPV6;
+       cm->cmsg_type = IPV6_PKTINFO;
+       cm->cmsg_len = CMSG_LEN(sizeof(pi));
+       memset(&pi, 0, sizeof(pi));
+       pi.ipi6_ifindex = ap->iface->index;
+       memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+
+       /* Hop limit */
+       cm = CMSG_NXTHDR(&sndhdr, cm);
+       cm->cmsg_level = IPPROTO_IPV6;
+       cm->cmsg_type = IPV6_HOPLIMIT;
+       cm->cmsg_len = CMSG_LEN(sizeof(hoplimit));
+       memcpy(CMSG_DATA(cm), &hoplimit, sizeof(hoplimit));
+
+#ifdef DEBUG_NS
+       syslog(LOG_INFO, "%s: sending IPv6 NS for %s",
+           ap->iface->name, ap->saddr);
+       if (ap->dadcallback == NULL)
+               syslog(LOG_WARNING, "%s: no callback!", ap->iface->name);
+#endif
+       if (sendmsg(unspec_sock, &sndhdr, 0) == -1)
+               syslog(LOG_ERR, "%s: %s: sendmsg: %m",
+                   __func__, ap->iface->name);
+
+       if (ap->dadcallback) {
+               ms_to_tv(&tv, RETRANS_TIMER);
+               ms_to_tv(&rtv, MIN_RANDOM_FACTOR);
+               timeradd(&tv, &rtv, &tv);
+               rtv.tv_sec = 0;
+               rtv.tv_usec = arc4random() %
+                   (MAX_RANDOM_FACTOR_U - MIN_RANDOM_FACTOR_U);
+               timeradd(&tv, &rtv, &tv);
+
+               eloop_timeout_add_tv(&tv,
+                   ++(ap->nsprobes) < ap->iface->options->dadtransmits ?
+                   ipv6ns_probeaddr : ap->dadcallback,
+                   ap);
+       }
+#else /* IPV6_SEND_DAD */
+       /* Let the kernel handle DAD.
+        * We don't know the timings, so just wait for the max */
+       ipv6_addaddr(ap);
+       if (ap->dadcallback) {
+               mtv.tv_sec = 0;
+               mtv.tv_usec = 0;
+               for (i = 0; i < ap->iface->options->dadtransmits; i++) {
+                       ms_to_tv(&tv, RETRANS_TIMER);
+                       ms_to_tv(&rtv, MAX_RANDOM_FACTOR);
+                       timeradd(&tv, &rtv, &tv);
+                       timeradd(&mtv, &tv, &mtv);
+               }
+               eloop_timeout_add_tv(&mtv, ap->dadcallback, ap);
+       }
+#endif /* IPV6_SEND_DAD */
+}
+
+ssize_t
+ipv6ns_probeaddrs(struct ipv6_addrhead *addrs)
+{
+       struct ipv6_addr *ap;
+       ssize_t i;
+
+       i = 0;
+       TAILQ_FOREACH(ap, addrs, next) {
+               if (ap->prefix_vltime == 0 ||
+                   IN6_IS_ADDR_UNSPECIFIED(&ap->addr))
+                       continue;
+               ipv6ns_probeaddr(ap);
+               i++;
+       }
+
+       return i;
+}
+
+void
+ipv6ns_proberouter(void *arg)
+{
+       struct ra *rap = arg;
+       struct nd_neighbor_solicit *ns;
+       struct nd_opt_hdr *nd;
+       struct sockaddr_in6 dst;
+       struct cmsghdr *cm;
+       struct in6_pktinfo pi;
+       int hoplimit = HOPLIMIT;
+       struct timeval tv, rtv;
+
+       if (ipv6ns_open() == -1)
                return;
+
+       if (!rap->ns) {
+               rap->nslen = sizeof(*ns) + ROUNDUP8(rap->iface->hwlen + 2);
+               rap->ns = calloc(1, rap->nslen);
+               if (rap->ns == NULL) {
+                       syslog(LOG_ERR, "%s: %m", __func__);
+                       return;
+               }
+               ns = (struct nd_neighbor_solicit *)(void *)rap->ns;
+               ns->nd_ns_type = ND_NEIGHBOR_SOLICIT;
+               //ns->nd_ns_cksum = 0;
+               //ns->nd_ns_code = 0;
+               //ns->nd_ns_reserved = 0;
+               ns->nd_ns_target = rap->from;
+               nd = (struct nd_opt_hdr *)(rap->ns + sizeof(*ns));
+               nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+               nd->nd_opt_len = (ROUNDUP8(rap->iface->hwlen + 2)) >> 3;
+               memcpy(nd + 1, rap->iface->hwaddr, rap->iface->hwlen);
        }
 
        memset(&dst, 0, sizeof(dst));
@@ -248,9 +445,9 @@ ipv6ns_sendprobe(void *arg)
        ms_to_tv(&rtv, MIN_RANDOM_FACTOR);
        timeradd(&tv, &rtv, &tv);
        rtv.tv_sec = 0;
-       rtv.tv_usec = arc4random() % (MAX_RANDOM_FACTOR - MIN_RANDOM_FACTOR);
+       rtv.tv_usec = arc4random() % (MAX_RANDOM_FACTOR_U - MIN_RANDOM_FACTOR_U);
        timeradd(&tv, &rtv, &tv);
-       eloop_timeout_add_tv(&tv, ipv6ns_sendprobe, rap);
+       eloop_timeout_add_tv(&tv, ipv6ns_proberouter, rap);
 
        if (rap->nsprobes++ == 0)
                eloop_timeout_add_sec(DELAY_FIRST_PROBE_TIME,
@@ -271,7 +468,12 @@ ipv6ns_handledata(__unused void *arg)
        struct nd_neighbor_advert *nd_na;
        struct ra *rap;
        int is_router, is_solicited;
+#ifdef DEBUG_NS
+       int found;
+#endif
        struct timeval tv;
+       struct dhcp6_state *d6state;
+       struct ipv6_addr *ap;
 
        len = recvmsg(sock, &rcvhdr, 0);
        if (len == -1) {
@@ -340,16 +542,51 @@ ipv6ns_handledata(__unused void *arg)
                return;
        }
 
+#ifdef DEBUG_NS
+       found = 0;
+#endif
        TAILQ_FOREACH(rap, &ipv6_routers, next) {
-               if (rap->iface == ifp &&
-                   memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr,
+               if (rap->iface != ifp)
+                       continue;
+               if (memcmp(rap->from.s6_addr, nd_na->nd_na_target.s6_addr,
                    sizeof(rap->from.s6_addr)) == 0)
                        break;
+               TAILQ_FOREACH(ap, &rap->addrs, next) {
+                       if (memcmp(ap->addr.s6_addr,
+                           nd_na->nd_na_target.s6_addr,
+                           sizeof(ap->addr.s6_addr)) == 0)
+                       {
+                               ap->dad = 1;
+                               if (ap->dadcallback)
+                                       ap->dadcallback(ap);
+#ifdef DEBUG_NS
+                               found++;
+#endif
+                       }
+               }
        }
        if (rap == NULL) {
+               d6state = D6_STATE(ifp);
+               if (d6state) {
+                       TAILQ_FOREACH(ap, &d6state->addrs, next) {
+                               if (memcmp(ap->addr.s6_addr,
+                                   nd_na->nd_na_target.s6_addr,
+                                   sizeof(ap->addr.s6_addr)) == 0)
+                               {
+                                       ap->dad = 1;
+                                       if (ap->dadcallback)
+                                               ap->dadcallback(ap);
 #ifdef DEBUG_NS
-               syslog(LOG_DEBUG, "%s: unexpected NA from %s",
-                   ifp->name, sfrom);
+                                       found++;
+#endif
+                               }
+                       }
+               }
+
+#ifdef DEBUG_NS
+               if (found == 0)
+                       syslog(LOG_DEBUG, "%s: unexpected NA from %s",
+                           ifp->name, sfrom);
 #endif
                return;
        }
@@ -377,21 +614,7 @@ ipv6ns_handledata(__unused void *arg)
                        tv.tv_sec = REACHABLE_TIME;
                        tv.tv_usec = 0;
                }
-               eloop_timeout_add_tv(&tv, ipv6ns_sendprobe, rap);
+               eloop_timeout_add_tv(&tv, ipv6ns_proberouter, rap);
                eloop_timeout_delete(ipv6ns_unreachable, rap);
        }
 }
-
-int
-ipv6ns_init(void)
-{
-       int fd;
-
-       fd = ipv6ns_open();
-       if (fd == -1) {
-               syslog(LOG_ERR, "ipv6ns_open: %m");
-               return -1;
-       }
-       eloop_event_add(fd, ipv6ns_handledata, NULL);
-       return 0;
-}
index e14856b29799123a431a539b5b0479903d9e2675..7b0a9e56975f0b99222024e69a31373cedcff41e 100644 (file)
--- a/ipv6ns.h
+++ b/ipv6ns.h
 
 #define MAX_REACHABLE_TIME     3600    /* seconds */
 #define REACHABLE_TIME         30      /* seconds */
-#define RETRANS_TIMER          1       /* second */
+#define RETRANS_TIMER          1000    /* milliseconds */
 #define DELAY_FIRST_PROBE_TIME 5       /* seconds */
 
-int ipv6ns_init(void);
-void ipv6ns_sendprobe(void *);
+void ipv6ns_probeaddr(void *);
+ssize_t ipv6ns_probeaddrs(struct ipv6_addrhead *);
+void ipv6ns_cancelprobeaddr(struct ipv6_addr *);
+void ipv6ns_proberouter(void *);
 #endif
index 363aaf87870d0276e442b85b61067fb56af2a815..79c646e5fdffdc5179a4d6df45435f5c38048661 100644 (file)
--- a/ipv6rs.c
+++ b/ipv6rs.c
@@ -308,7 +308,7 @@ ipv6rs_freedrop_addrs(struct ra *rap, int drop)
                /* Only drop the address if no other RAs have assigned it.
                 * This is safe because the RA is removed from the list
                 * before we are called. */
-               if (drop && (options & DHCPCD_IPV6RA_OWN) &&
+               if (drop && ap->added &&
                    !IN6_IS_ADDR_UNSPECIFIED(&ap->addr) &&
                    !ipv6rs_addrexists(ap) && !dhcp6_addrexists(ap))
                {
@@ -393,6 +393,23 @@ add_router(struct ra *router)
        TAILQ_INSERT_HEAD(&ipv6_routers, router, next);
 }
 
+static void
+ipv6rs_dadcallback(void *arg)
+{
+       struct ipv6_addr *ap = arg;
+
+       ipv6ns_cancelprobeaddr(ap);
+       ap->dadcompleted = 1;
+       if (ap->dad)
+               /* No idea what how to try and make another address :( */
+               syslog(LOG_WARNING, "%s: DAD detected %s",
+                   ap->iface->name, ap->saddr);
+#ifdef IPV6_SEND_DAD
+       else
+               ipv6_addaddr(ap);
+#endif
+}
+
 /* ARGSUSED */
 static void
 ipv6rs_handledata(__unused void *arg)
@@ -620,13 +637,15 @@ ipv6rs_handledata(__unused void *arg)
                                    !(pi->nd_opt_pi_flags_reserved &
                                    ND_OPT_PI_FLAG_ONLINK))
                                        break;
-                               ap = malloc(sizeof(*ap));
+                               ap = calloc(1, sizeof(*ap));
                                if (ap == NULL) {
                                        syslog(LOG_ERR, "%s: %m", __func__);
                                        break;
                                }
+                               ap->iface = rap->iface;
                                ap->new = 1;
                                ap->onlink = 0;
+                               ap->autoconf = 1;
                                ap->prefix_len = pi->nd_opt_pi_prefix_len;
                                memcpy(ap->prefix.s6_addr,
                                   pi->nd_opt_pi_prefix.s6_addr,
@@ -651,14 +670,16 @@ ipv6rs_handledata(__unused void *arg)
                                        memset(&ap->addr, 0, sizeof(ap->addr));
                                        ap->saddr[0] = '\0';
                                }
+                               ap->dadcallback = ipv6rs_dadcallback;
                                TAILQ_INSERT_TAIL(&rap->addrs, ap, next);
                        } else if (ap->prefix_vltime !=
                            ntohl(pi->nd_opt_pi_valid_time) ||
                            ap->prefix_pltime !=
-                           ntohl(pi->nd_opt_pi_preferred_time))
+                           ntohl(pi->nd_opt_pi_preferred_time) ||
+                           ap->dad)
+                       {
                                ap->new = 1;
-                       else
-                               ap->new = 0;
+                       }
                        if (pi->nd_opt_pi_flags_reserved &
                            ND_OPT_PI_FLAG_ONLINK)
                                ap->onlink = 1;
@@ -666,6 +687,8 @@ ipv6rs_handledata(__unused void *arg)
                            ntohl(pi->nd_opt_pi_valid_time);
                        ap->prefix_pltime =
                            ntohl(pi->nd_opt_pi_preferred_time);
+                       ap->nsprobes = 0;
+                       ap->dad = 0;
                        if (opt) {
                                l = strlen(opt);
                                tmp = realloc(opt,
@@ -806,12 +829,12 @@ ipv6rs_handledata(__unused void *arg)
 
        if (new_rap)
                add_router(rap);
-       if (options & DHCPCD_IPV6RA_OWN && !(options & DHCPCD_TEST))
-               ipv6_addaddrs(ifp, &rap->addrs);
        if (options & DHCPCD_TEST) {
                script_runreason(ifp, "TEST");
                goto handle_flag;
        }
+       if (options & DHCPCD_IPV6RA_OWN)
+               ipv6ns_probeaddrs(&rap->addrs);
        ipv6_buildroutes();
        /* We will get run by the expire function */
        if (rap->lifetime)
@@ -837,7 +860,7 @@ ipv6rs_handledata(__unused void *arg)
            ifp->options->options & DHCPCD_IPV6RA_OWN_DEFAULT)
        {
                rap->nsprobes = 0;
-               ipv6ns_sendprobe(rap);
+               ipv6ns_proberouter(rap);
        }
 
 handle_flag:
@@ -1074,6 +1097,7 @@ ipv6rs_expire(void *arg)
                                        syslog(LOG_INFO,
                                            "%s: %s: expired address",
                                            ifp->name, ap->saddr);
+                                       eloop_timeout_delete(NULL, ap);
                                        TAILQ_REMOVE(&rap->addrs, ap, next);
                                        free(ap);
                                        /* No need to delete it as the kernel
@@ -1200,26 +1224,3 @@ ipv6rs_drop(struct interface *ifp)
                script_runreason(ifp, "ROUTERADVERT");
        }
 }
-
-int
-ipv6rs_init(void)
-{
-       int fd;
-
-       fd = ipv6rs_open();
-       if (fd == -1) {
-               syslog(LOG_ERR, "ipv6rs: %m");
-               options &= ~(DHCPCD_IPV6RS |
-                       DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT);
-               return -1;
-       }
-
-       eloop_event_add(fd, ipv6rs_handledata, NULL);
-       // atexit(restore_rtadv);
-
-       if (options & DHCPCD_IPV6RA_OWN ||
-           options & DHCPCD_IPV6RA_OWN_DEFAULT)
-               return ipv6ns_init();
-
-       return 0;
-}
index 5cdb9410b4b8b24ecf3c21800cf87e74d1110d77..2b53d0cf9fad687cab68f3ddc466f099213afeff 100644 (file)
--- a/ipv6rs.h
+++ b/ipv6rs.h
@@ -57,7 +57,7 @@ struct ra {
        uint32_t mtu;
        struct ipv6_addrhead addrs;
        TAILQ_HEAD(, ra_opt) options;
-       
+
        unsigned char *ns;
        size_t nslen;
        int nsprobes;
@@ -76,7 +76,6 @@ struct rs_state {
 #define RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6RS])
 
 #ifdef INET6
-int ipv6rs_init(void);
 int ipv6rs_start(struct interface *);
 ssize_t ipv6rs_env(char **, const char *, const struct interface *);
 const struct ipv6_addr * ipv6rs_findprefix(const struct ipv6_addr *);
@@ -89,7 +88,6 @@ void ipv6rs_expire(void *arg);
 int ipv6rs_has_ra(const struct interface *);
 void ipv6rs_drop(struct interface *);
 #else
-#define ipv6rs_init() {}
 #define ipv6rs_start(a) {}
 #define ipv6rs_free(a)
 #define ipv6rs_has_ra(a) 0
index 93048fc1f53628ead120ac8bac08c851200054d1..19e4fed7a8b63a4c850d0aec1c647004a4c28125 100644 (file)
@@ -134,3 +134,12 @@ check_ipv6(const char *ifname)
 
        return 1;
 }
+
+int
+ipv6_dadtransmits(__unused const char *ifname)
+{
+       int r;
+
+       r = get_inet6_sysctl(IPV6CTL_DAD_COUNT);
+       return r < 0 ? 0 : r;
+}
index 323a3bcb8062c76a40e717052449d8dfec260399..a8a6195f87529ce1a5dd8a4c8a0ff43f683498cf 100644 (file)
@@ -245,3 +245,17 @@ forward:
        }
        return 1;
 }
+
+int
+ipv6_dadtransmits(const char *ifname)
+{
+       char path[256];
+       int r;
+
+       if (ifname == NULL)
+               ifname = "default";
+
+       snprintf(path, sizeof(path), "%s/%s/dad_transmits", prefix, ifname);
+       r = check_proc_int(path);
+       return r < 0 ? 0 : r;
+}
index 2c738797db4429cf1502a2c934e55d7c564518ae..38e90991b03c00188631bddfde70e441e2706080 100644 (file)
@@ -30,5 +30,6 @@
 
 char *hardware_platform(void);
 int check_ipv6(const char *);
+int ipv6_dadtransmits(const char *);
 
 #endif