changeset 2481:06928b7119de draft

Support RTM_GETNEIGH on Linux as well as the polling for NUD changes on BSD. Fixes [bb6153d18b].
author Roy Marples <roy@marples.name>
date Thu, 08 May 2014 23:35:30 +0000
parents 800509a3b25b
children 46cb55263209
files if-bsd.c if-linux.c if.h ipv6nd.c ipv6nd.h
diffstat 5 files changed, 127 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/if-bsd.c	Wed May 07 13:31:32 2014 +0000
+++ b/if-bsd.c	Thu May 08 23:35:30 2014 +0000
@@ -975,7 +975,7 @@
 int
 if_nd6reachable(const char *ifname, struct in6_addr *addr)
 {
-	int s, r;
+	int s, flags;
 	struct in6_nbrinfo nbi;
 
 	if ((s = socket(AF_INET6, SOCK_DGRAM, 0)) < 0)
@@ -985,21 +985,22 @@
 	strlcpy(nbi.ifname, ifname, sizeof(nbi.ifname));
 	nbi.addr = *addr;
 	if (ioctl(s, SIOCGNBRINFO_IN6, &nbi) == -1)
-		r = -1;
+		flags = -1;
 	else {
+		flags = 0;
 		switch(nbi.state) {
 		case ND6_LLINFO_REACHABLE:
 		case ND6_LLINFO_STALE:
 		case ND6_LLINFO_DELAY:
 		case ND6_LLINFO_PROBE:
-			r = 1;
+			flags |= IPV6ND_REACHABLE;
 			break;
-		default:
-			r = 0;
 		}
+		if (nbi.isrouter)
+			flags |= IPV6ND_ROUTER;
 	}
 	close(s);
-	return r;
+	return flags;
 }
 
 void
--- a/if-linux.c	Wed May 07 13:31:32 2014 +0000
+++ b/if-linux.c	Thu May 08 23:35:30 2014 +0000
@@ -75,6 +75,7 @@
 #include "if.h"
 #include "ipv4.h"
 #include "ipv6.h"
+#include "ipv6nd.h"
 
 #define bpf_insn		sock_filter
 #define BPF_SKIPTYPE
@@ -275,7 +276,7 @@
 	snl.nl_groups |= RTMGRP_IPV4_ROUTE | RTMGRP_IPV4_IFADDR;
 #endif
 #ifdef INET6
-	snl.nl_groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR;
+	snl.nl_groups |= RTMGRP_IPV6_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_NEIGH;
 #endif
 
 	return _open_link_socket(&snl);
@@ -557,6 +558,50 @@
 	return 0;
 }
 
+#ifdef INET6
+static int
+link_neigh(struct dhcpcd_ctx *ctx, struct nlmsghdr *nlm)
+{
+	struct ndmsg *r;
+	struct rtattr *rta;
+	size_t len;
+	struct in6_addr addr6;
+	int flags;
+
+	if (nlm->nlmsg_type != RTM_NEWNEIGH && nlm->nlmsg_type != RTM_DELNEIGH)
+		return 0;
+	if (nlm->nlmsg_len < sizeof(*r))
+		return -1;
+
+	r = NLMSG_DATA(nlm);
+	rta = (struct rtattr *)RTM_RTA(r);
+	len = RTM_PAYLOAD(nlm);
+        if (r->ndm_family == AF_INET6) {
+		flags = 0;
+		if (r->ndm_flags & NTF_ROUTER)
+			flags |= IPV6ND_ROUTER;
+		if (nlm->nlmsg_type == RTM_NEWNEIGH &&
+		    r->ndm_state &
+		    (NUD_REACHABLE | NUD_STALE | NUD_DELAY | NUD_PROBE |
+		     NUD_PERMANENT))
+		        flags |= IPV6ND_REACHABLE;
+		memset(&addr6, 0, sizeof(addr6));
+		while (RTA_OK(rta, len)) {
+			switch (rta->rta_type) {
+			case NDA_DST:
+				memcpy(&addr6.s6_addr, RTA_DATA(rta),
+				       sizeof(addr6.s6_addr));
+				break;
+			}
+			rta = RTA_NEXT(rta, len);
+		}
+		ipv6nd_neighbour(ctx, &addr6, flags);
+	}
+
+	return 1;
+}
+#endif
+
 static int
 link_netlink(struct dhcpcd_ctx *ctx, struct nlmsghdr *nlm)
 {
@@ -573,6 +618,11 @@
 	r = link_addr(ctx, nlm);
 	if (r != 0)
 		return r;
+#ifdef INET6
+	r = link_neigh(ctx, nlm);
+	if (r != 0)
+		return r;
+#endif
 
 	if (nlm->nlmsg_type != RTM_NEWLINK && nlm->nlmsg_type != RTM_DELLINK)
 		return 0;
@@ -1163,14 +1213,6 @@
 	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
--- a/if.h	Wed May 07 13:31:32 2014 +0000
+++ b/if.h	Thu May 08 23:35:30 2014 +0000
@@ -48,6 +48,13 @@
 # endif
 #endif
 
+/* Neighbour reachability and router updates */
+#ifndef HAVE_RTM_GETNEIGH
+# ifdef __linux__
+#  define HAVE_RTM_GETNEIGH
+# endif
+#endif
+
 #define EUI64_ADDR_LEN			8
 #define INFINIBAND_ADDR_LEN		20
 
--- a/ipv6nd.c	Wed May 07 13:31:32 2014 +0000
+++ b/ipv6nd.c	Thu May 08 23:35:30 2014 +0000
@@ -317,39 +317,69 @@
 }
 
 static void
+ipv6nd_reachable(struct ra *rap, int flags)
+{
+
+	if (flags & IPV6ND_REACHABLE) {
+		if (rap->lifetime && rap->expired) {
+			syslog(LOG_INFO, "%s: %s is reachable again",
+			    rap->iface->name, rap->sfrom);
+			rap->expired = 0;
+			ipv6_buildroutes(rap->iface->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(rap->iface->ctx);
+			/* XXX Not really an RA */
+			script_runreason(rap->iface, "ROUTERADVERT");
+		}
+	}
+}
+
+#ifdef HAVE_RTM_GETNEIGH
+void
+ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, int flags)
+{
+	struct ra *rap;
+
+	TAILQ_FOREACH(rap, ctx->ipv6->ra_routers, next) {
+		if (IN6_ARE_ADDR_EQUAL(&rap->from, addr)) {
+			ipv6nd_reachable(rap, flags);
+			break;
+		}
+	}
+}
+
+#else
+
+static void
 ipv6nd_checkreachablerouters(void *arg)
 {
 	struct dhcpcd_ctx *ctx = arg;
 	struct ra *rap;
+	int flags;
 
 	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");
-			}
+		flags = if_nd6reachable(rap->iface->name, &rap->from);
+		if (flags == -1) {
+			/* An error occured, so it's unreachable */
+			flags = 0;
 		}
+		ipv6nd_reachable(rap, flags);
 	}
 
 	eloop_timeout_add_sec(ctx->eloop, ND6REACHABLE_TIMER,
 	    ipv6nd_checkreachablerouters, ctx);
 }
+#endif
 
 static void
 ipv6nd_free_opts(struct ra *rap)
@@ -390,9 +420,11 @@
 	eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap);
 	if (!drop)
 		TAILQ_REMOVE(rap->iface->ctx->ipv6->ra_routers, rap, next);
+#ifndef HAVE_RTM_GETNEIGH
 	if (TAILQ_FIRST(rap->iface->ctx->ipv6->ra_routers) == NULL)
 		eloop_timeout_delete(rap->iface->ctx->eloop,
 		    ipv6nd_checkreachablerouters, rap->iface->ctx);
+#endif
 	ipv6_freedrop_addrs(&rap->addrs, drop, NULL);
 	ipv6nd_free_opts(rap);
 	free(rap->data);
@@ -983,8 +1015,10 @@
 	/* Expire should be called last as the rap object could be destroyed */
 	ipv6nd_expirera(ifp);
 
+#ifndef HAVE_RTM_GETNEIGH
 	/* Start our reachability tests now */
 	ipv6nd_checkreachablerouters(ifp->ctx);
+#endif
 }
 
 int
--- a/ipv6nd.h	Wed May 07 13:31:32 2014 +0000
+++ b/ipv6nd.h	Thu May 08 23:35:30 2014 +0000
@@ -78,6 +78,9 @@
 #define RETRANS_TIMER			1000	/* milliseconds */
 #define DELAY_FIRST_PROBE_TIME		5	/* seconds */
 
+#define IPV6ND_REACHABLE		(1 << 0)
+#define IPV6ND_ROUTER			(1 << 1)
+
 #ifdef INET6
 void ipv6nd_startrs(struct interface *);
 ssize_t ipv6nd_env(char **, const char *, const struct interface *);
@@ -91,6 +94,10 @@
 void ipv6nd_handleifa(struct dhcpcd_ctx *, int,
     const char *, const struct in6_addr *, int);
 void ipv6nd_drop(struct interface *);
+
+#ifdef HAVE_RTM_GETNEIGH
+void ipv6nd_neighbour(struct dhcpcd_ctx *, struct in6_addr *, int);
+#endif
 #else
 #define ipv6nd_startrs(a) {}
 #define ipv6nd_addrexists(a, b) (0)