]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Poll kernel neighbour reachability (SIOCGNBRINFO_IN6) for each router
authorRoy Marples <roy@marples.name>
Wed, 7 May 2014 13:31:32 +0000 (13:31 +0000)
committerRoy Marples <roy@marples.name>
Wed, 7 May 2014 13:31:32 +0000 (13:31 +0000)
instead of sending and listening for Neighbour Soliciation/Advertisement
packets. The kernel is privy to a lot more reachability information than
userland is.
Fixes [bb6153d18b].

if-bsd.c
if-linux.c
if.h
ipv6nd.c
ipv6nd.h

index 474ec038babd1227239a2b57e23f0e0e8b59392b..bc4cd6e0fec6bd19670d4f8b5056cd35fc3055cb 100644 (file)
--- a/if-bsd.c
+++ b/if-bsd.c
@@ -972,6 +972,36 @@ eexit:
        return error;
 }
 
+int
+if_nd6reachable(const char *ifname, struct in6_addr *addr)
+{
+       int s, r;
+       struct in6_nbrinfo nbi;
+
+       if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
+               return -1;
+
+       memset(&nbi, 0, sizeof(nbi));
+       strlcpy(nbi.ifname, ifname, sizeof(nbi.ifname));
+       nbi.addr = *addr;
+       if (ioctl(s, SIOCGNBRINFO_IN6, &nbi) == -1)
+               r = -1;
+       else {
+               switch(nbi.state) {
+               case ND6_LLINFO_REACHABLE:
+               case ND6_LLINFO_STALE:
+               case ND6_LLINFO_DELAY:
+               case ND6_LLINFO_PROBE:
+                       r = 1;
+                       break;
+               default:
+                       r = 0;
+               }
+       }
+       close(s);
+       return r;
+}
+
 void
 if_rarestore(struct dhcpcd_ctx *ctx)
 {
index 866da57ec7e029b351ac22523b1f588e566a0b16..2ee561f774d67d7443ccef8df175e3f51ecbf643 100644 (file)
@@ -1163,6 +1163,14 @@ if_addrflags6(const char *ifname, const struct in6_addr *addr)
        return -1;
 }
 
+int
+if_nd6reachable(__unused const char *ifname, __unused struct in6_addr *addr)
+{
+
+       /* Assume reachable until I work out how to obtain reachability */
+       return 1;
+}
+
 static const char *prefix = "/proc/sys/net/ipv6/conf";
 
 void
diff --git a/if.h b/if.h
index cf7ea051061fbc3afc6863bd3e333157fab202d9..ddf10fc2c880b6eed848cf8b0b0d36e5cecd82c5 100644 (file)
--- a/if.h
+++ b/if.h
@@ -124,6 +124,7 @@ int if_route(const struct rt *rt, int);
 #ifdef INET6
 int if_checkipv6(struct dhcpcd_ctx *ctx, const char *, int);
 void if_rarestore(struct dhcpcd_ctx *);
+int if_nd6reachable(const char *ifname, struct in6_addr *addr);
 
 int if_address6(const struct ipv6_addr *, int);
 #define if_addaddress6(a) if_address6(a, 1)
index d4e52250857b569d0979de308bfac7dc74fdea81..07df8ab6e310ea2e86e3186ac6061ce4dcf15c27 100644 (file)
--- a/ipv6nd.c
+++ b/ipv6nd.c
@@ -51,6 +51,7 @@
 #include "dhcpcd.h"
 #include "dhcp6.h"
 #include "eloop.h"
+#include "if.h"
 #include "ipv6.h"
 #include "ipv6nd.h"
 #include "script.h"
@@ -116,12 +117,13 @@ struct nd_opt_dnssl {             /* DNSSL option RFC 6106 */
 #define IPV6_ADDR_INT16_MLL     0x02ff
 #endif
 
+#define ND6REACHABLE_TIMER     1
+
 /* Debugging Neighbor Solicitations is a lot of spam, so disable it */
 //#define DEBUG_NS
 //
 
 static void ipv6nd_handledata(void *);
-static void ipv6nd_startproberouter(struct ra *);
 
 /*
  * Android ships buggy ICMP6 filter headers.
@@ -314,6 +316,41 @@ ipv6nd_sendrsprobe(void *arg)
                syslog(LOG_WARNING, "%s: no IPv6 Routers available", ifp->name);
 }
 
+static void
+ipv6nd_checkreachablerouters(void *arg)
+{
+       struct dhcpcd_ctx *ctx = arg;
+       struct ra *rap;
+
+       TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) {
+               if (if_nd6reachable(rap->iface->name, &rap->from) == 1) {
+                       if (rap->lifetime && rap->expired) {
+                               syslog(LOG_INFO, "%s: %s is reachable again",
+                                   rap->iface->name, rap->sfrom);
+                               rap->expired = 0;
+                               ipv6_buildroutes(ctx);
+                               /* XXX Not really an RA */
+                               script_runreason(rap->iface, "ROUTERADVERT");
+                       }
+               } else {
+                       /* Any error means it's really gone from the kernel
+                        * neighbour database */
+                       if (rap->lifetime && !rap->expired) {
+                               syslog(LOG_WARNING,
+                                   "%s: %s is unreachable, expiring it",
+                                   rap->iface->name, rap->sfrom);
+                               rap->expired = 1;
+                               ipv6_buildroutes(ctx);
+                               /* XXX Not really an RA */
+                               script_runreason(rap->iface, "ROUTERADVERT");
+                       }
+               }
+       }
+
+       eloop_timeout_add_sec(ctx->eloop, ND6REACHABLE_TIMER,
+           ipv6nd_checkreachablerouters, ctx);
+}
+
 static void
 ipv6nd_free_opts(struct ra *rap)
 {
@@ -353,11 +390,14 @@ void ipv6nd_freedrop_ra(struct ra *rap, int drop)
        eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap);
        if (!drop)
                TAILQ_REMOVE(rap->iface->ctx->ipv6->ra_routers, rap, next);
+       if (TAILQ_FIRST(rap->iface->ctx->ipv6->ra_routers) == NULL)
+               eloop_timeout_delete(rap->iface->ctx->eloop,
+                   ipv6nd_checkreachablerouters, rap->iface->ctx);
        ipv6_freedrop_addrs(&rap->addrs, drop, NULL);
        ipv6nd_free_opts(rap);
        free(rap->data);
-       free(rap->ns);
        free(rap);
+
 }
 
 ssize_t
@@ -617,9 +657,6 @@ ipv6nd_handlera(struct ipv6_ctx *ctx, struct interface *ifp,
                if (rap) {
                        free(rap->data);
                        rap->data_len = 0;
-                       free(rap->ns);
-                       rap->ns = NULL;
-                       rap->nslen = 0;
                }
                new_data = 1;
        } else
@@ -926,16 +963,6 @@ ipv6nd_handlera(struct ipv6_ctx *ctx, struct interface *ifp,
        eloop_timeout_delete(ifp->ctx->eloop, NULL, ifp);
        eloop_timeout_delete(ifp->ctx->eloop, NULL, rap); /* reachable timer */
 
-       /* If we're owning the RA then we need to try and ensure the
-        * router is actually reachable */
-       if (ifp->options->options & DHCPCD_IPV6RA_OWN ||
-           ifp->options->options & DHCPCD_IPV6RA_OWN_DEFAULT)
-       {
-               rap->nsprobes = 0;
-               if (rap->lifetime)
-                       ipv6nd_startproberouter(rap);
-       }
-
 handle_flag:
        if (rap->flags & ND_RA_FLAG_MANAGED) {
                if (new_data && dhcp6_start(ifp, DH6S_INIT) == -1)
@@ -955,6 +982,9 @@ handle_flag:
 
        /* Expire should be called last as the rap object could be destroyed */
        ipv6nd_expirera(ifp);
+
+       /* Start our reachability tests now */
+       ipv6nd_checkreachablerouters(ifp->ctx);
 }
 
 int
@@ -1092,132 +1122,6 @@ ipv6nd_handleifa(struct dhcpcd_ctx *ctx, int cmd, const char *ifname,
        }
 }
 
-static void
-ipv6nd_unreachable(void *arg)
-{
-       struct ra *rap = arg;
-
-       /* We could add an unreachable flag and persist the information,
-        * but that is more effort than it's probably worth. */
-       syslog(LOG_WARNING, "%s: %s is unreachable, expiring it",
-           rap->iface->name, rap->sfrom);
-       rap->expired = 1;
-       ipv6_buildroutes(rap->iface->ctx);
-       script_runreason(rap->iface, "ROUTERADVERT"); /* XXX not RA */
-}
-
-static void
-ipv6nd_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;
-       struct ipv6_ctx *ctx;
-       struct timeval tv;
-
-       if (ipv6nd_open(rap->iface->ctx) == -1) {
-               syslog(LOG_ERR, "%s: ipv6nd_open: %m", __func__);
-               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));
-       dst.sin6_family = AF_INET6;
-#ifdef SIN6_LEN
-       dst.sin6_len = sizeof(dst);
-#endif
-       memcpy(&dst.sin6_addr, &rap->from, sizeof(dst.sin6_addr));
-       dst.sin6_scope_id = rap->iface->index;
-
-       ctx = rap->iface->ctx->ipv6;
-       ctx->sndhdr.msg_name = (caddr_t)&dst;
-       ctx->sndhdr.msg_iov[0].iov_base = rap->ns;
-       ctx->sndhdr.msg_iov[0].iov_len = rap->nslen;
-
-       /* Set the outbound interface */
-       cm = CMSG_FIRSTHDR(&ctx->sndhdr);
-       if (cm == NULL) /* unlikely */
-               return;
-       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 = rap->iface->index;
-       memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
-
-#ifdef DEBUG_NS
-       syslog(LOG_INFO, "%s: sending IPv6 NS for %s",
-           rap->iface->name, rap->sfrom);
-#endif
-       if (sendmsg(ctx->nd_fd, &ctx->sndhdr, 0) == -1) {
-               syslog(LOG_ERR, "%s: %s: sendmsg: %m",
-                   rap->iface->name, __func__);
-               return;
-       }
-
-       if (rap->nsprobes++ == 0)
-               eloop_timeout_add_sec(rap->iface->ctx->eloop,
-                   DELAY_FIRST_PROBE_TIME,
-                   ipv6nd_proberouter, rap);
-       else {
-               /* MAX_UNICAST_PROBES applies to this retrans loop,
-                * so take one away for the above DELAY probe */
-               ms_to_tv(&tv, rap->retrans ? rap->retrans :  RETRANS_TIMER);
-               eloop_timeout_add_tv(rap->iface->ctx->eloop, &tv,
-                   rap->nsprobes <= MAX_UNICAST_SOLICIT ?
-                   ipv6nd_proberouter : ipv6nd_unreachable,
-                   rap);
-       }
-}
-
-static void
-ipv6nd_cancelproberouter(struct ra *rap)
-{
-
-       eloop_timeout_delete(rap->iface->ctx->eloop, ipv6nd_proberouter, rap);
-       eloop_timeout_delete(rap->iface->ctx->eloop, ipv6nd_unreachable, rap);
-}
-
-
-static void
-ipv6nd_startproberouter(struct ra *rap)
-{
-       struct timeval tv, rtv;
-
-       ipv6nd_cancelproberouter(rap);
-       rap->nsprobes = 0;
-
-       ms_to_tv(&tv, rap->reachable ? rap->reachable : REACHABLE_TIME);
-       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(rap->iface->ctx->eloop,
-           &tv, ipv6nd_proberouter, rap);
-}
-
 void
 ipv6nd_expirera(void *arg)
 {
@@ -1246,7 +1150,6 @@ ipv6nd_expirera(void *arg)
                                            "%s: %s: router expired",
                                            ifp->name, rap->sfrom);
                                        rap->expired = expired = 1;
-                                       ipv6nd_cancelproberouter(rap);
                                }
                        } else {
                                valid = 1;
@@ -1397,7 +1300,6 @@ ipv6nd_handlena(struct ipv6_ctx *ctx, struct interface *ifp,
                syslog(LOG_INFO, "%s: %s is no longer a router",
                    ifp->name, ctx->sfrom);
                rap->expired = 1;
-               ipv6nd_cancelproberouter(rap);
                ipv6_buildroutes(ifp->ctx);
                script_runreason(ifp, "ROUTERADVERT");
                return;
@@ -1411,7 +1313,6 @@ ipv6nd_handlena(struct ipv6_ctx *ctx, struct interface *ifp,
                        ipv6_buildroutes(ifp->ctx);
                        script_runreason(rap->iface, "ROUTERADVERT"); /* XXX */
                }
-               ipv6nd_startproberouter(rap);
        }
 }
 
index 8c606919bcf6f031ab2ae12b0634b856f82bb714..222cacf35decc9a845998746f9b93cdc030b9ecb 100644 (file)
--- a/ipv6nd.h
+++ b/ipv6nd.h
@@ -57,11 +57,6 @@ struct ra {
        uint32_t mtu;
        struct ipv6_addrhead addrs;
        TAILQ_HEAD(, ra_opt) options;
-
-       unsigned char *ns;
-       size_t nslen;
-       int nsprobes;
-
        int expired;
 };