From: Roy Marples Date: Wed, 7 May 2014 13:31:32 +0000 (+0000) Subject: Poll kernel neighbour reachability (SIOCGNBRINFO_IN6) for each router X-Git-Tag: v6.4.0~57 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a3ee6b2392dee5ed1fb5860a6166333a06ad2673;p=thirdparty%2Fdhcpcd.git Poll kernel neighbour reachability (SIOCGNBRINFO_IN6) for each router 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]. --- diff --git a/if-bsd.c b/if-bsd.c index 474ec038..bc4cd6e0 100644 --- 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) { diff --git a/if-linux.c b/if-linux.c index 866da57e..2ee561f7 100644 --- a/if-linux.c +++ b/if-linux.c @@ -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 cf7ea051..ddf10fc2 100644 --- 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) diff --git a/ipv6nd.c b/ipv6nd.c index d4e52250..07df8ab6 100644 --- 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); } } diff --git a/ipv6nd.h b/ipv6nd.h index 8c606919..222cacf3 100644 --- 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; };