changeset 1921:e385beb3a4d7 draft

Use the kernel DAD for IPv6 addresses and finish the action once each address DAD completes. BSD kernels will require a patch as noted within the README. The linux netlink part still needs to be written.
author Roy Marples <roy@marples.name>
date Wed, 15 May 2013 10:27:36 +0000
parents 89ec7542c11e
children bf410e0a9173
files README defs.h dhcp6.c dhcp6.h dhcpcd.c if-bsd.c ipv6.c ipv6.h ipv6ns.c ipv6rs.c ipv6rs.h
diffstat 11 files changed, 213 insertions(+), 46 deletions(-) [+]
line wrap: on
line diff
--- a/README	Fri May 03 14:43:51 2013 +0000
+++ b/README	Wed May 15 10:27:36 2013 +0000
@@ -38,6 +38,16 @@
 BSD systems where this has been fixed are:
     NetBSD-5.0
 
+Some BSD systems announce IPv6 addresses to userland when the address has
+been added, not when it's actually ready to use. This is important because
+no kernel allows to send from the unspecified address which means userland
+cannot be RFC conformation when creating DAD messages so we need to rely on
+the kernel implementation.
+You can find the discussion here:
+    http://mail-index.netbsd.org/tech-net/2013/03/15/msg004019.html
+BSD systems where this will be fixed are:
+    NetBSD-7.0
+
 We try and detect how dhcpcd should interact with system services during the
 configure stage. If we cannot auto-detect how do to this, or it is wrong then
 you can change this by passing shell commands to --service-exists,
@@ -67,11 +77,6 @@
 dhcpcd-5.0 is only fully command line compatible with dhcpcd-4.0
 For compatibility with older versions, use dhcpcd-4.0
 
-dhcpcd no longer sends a default ClientID for ethernet interfaces.
-This is so we can re-use the address the kernel DHCP client found.
-To retain the old behaviour of sending a default ClientID based on the
-hardware address for interface, simply add the keyword clientid to dhcpcd.conf.
-
 
 ChangeLog
 ---------
--- a/defs.h	Fri May 03 14:43:51 2013 +0000
+++ b/defs.h	Wed May 15 10:27:36 2013 +0000
@@ -28,7 +28,7 @@
 #define CONFIG_H
 
 #define PACKAGE			"dhcpcd"
-#define VERSION			"5.99.5"
+#define VERSION			"5.99.6"
 
 #ifndef CONFIG
 # define CONFIG			SYSCONFDIR "/" PACKAGE ".conf"
--- a/dhcp6.c	Fri May 03 14:43:51 2013 +0000
+++ b/dhcp6.c	Wed May 15 10:27:36 2013 +0000
@@ -1559,6 +1559,7 @@
 	if (k) {
 		syslog(LOG_INFO, "%s: adding delegated prefixes", ifp->name);
 		state = D6_STATE(ifp);
+		state->state = DH6S_DELEGATED;
 		ipv6ns_probeaddrs(&state->addrs);
 		ipv6_buildroutes();
 	}
@@ -1577,7 +1578,6 @@
 	struct dhcp6_message *r;
 	struct dhcp6_state *state;
 	const struct dhcp6_option *o;
-	const char *reason;
 	const struct dhcp_opt *opt;
 	const struct if_options *ifo;
 	const struct ipv6_addr *ap;
@@ -1638,7 +1638,8 @@
 	    r->xid[2] != state->send->xid[2])
 	{
 		syslog(LOG_ERR,
-		    "%s: wrong xid 0x%02x%02x%02x (expecting 0x%02x%02x%02x) from %s",
+		    "%s: wrong xid 0x%02x%02x%02x"
+		    " (expecting 0x%02x%02x%02x) from %s",
 		    ifp->name,
 		    r->xid[0], r->xid[1], r->xid[2],
 		    state->send->xid[0], state->send->xid[1],
@@ -1746,7 +1747,7 @@
 recv:
 	syslog(LOG_INFO, "%s: %s received from %s", ifp->name, op, sfrom);
 
-	reason = NULL;
+	state->reason = NULL;
 	eloop_timeout_delete(NULL, ifp);
 	switch(state->state) {
 	case DH6S_INFORM:
@@ -1754,22 +1755,22 @@
 		state->rebind = 0;
 		state->expire = ~0U;
 		state->lowpl = ~0U;
-		reason = "INFORM6";
+		state->reason = "INFORM6";
 		break;
 	case DH6S_REQUEST:
-		if (reason == NULL)
-			reason = "BOUND6";
+		if (state->reason == NULL)
+			state->reason = "BOUND6";
 		/* FALLTHROUGH */
 	case DH6S_RENEW:
-		if (reason == NULL)
-			reason = "RENEW6";
+		if (state->reason == NULL)
+			state->reason = "RENEW6";
 		/* FALLTHROUGH */
 	case DH6S_REBIND:
-		if (reason == NULL)
-			reason = "REBIND6";
+		if (state->reason == NULL)
+			state->reason = "REBIND6";
 	case DH6S_CONFIRM:
-		if (reason == NULL)
-			reason = "REBOOT6";
+		if (state->reason == NULL)
+			state->reason = "REBOOT6";
 		if (state->renew == 0) {
 			if (state->expire == ~0U)
 				state->renew = ~0U;
@@ -1784,7 +1785,7 @@
 		}
 		break;
 	default:
-		reason = "UNKNOWN6";
+		state->reason = "UNKNOWN6";
 		break;
 	}
 
@@ -1798,7 +1799,11 @@
 		state->recv_len = 0;
 	}
 
-	if (!(options & DHCPCD_TEST)) {
+	if (options & DHCPCD_TEST)
+		script_runreason(ifp, "TEST");
+	else {
+		if (state->state == DH6S_INFORM)
+			script_runreason(ifp, state->reason);
 		state->state = DH6S_BOUND;
 		if (state->renew)
 			eloop_timeout_add_sec(state->renew,
@@ -1818,9 +1823,23 @@
 			    ifp->name, state->renew, state->rebind);
 		ipv6_buildroutes();
 		dhcp6_writelease(ifp);
+
+		len = 1;
+		/* If all addresses have completed DAD run the script */
+		TAILQ_FOREACH(ap, &state->addrs, next) {
+			if (ap->dadcompleted == 0) {
+				len = 0;
+				break;
+			}
+		}
+		if (len) {
+			script_runreason(ifp, state->reason);
+			daemonise();
+		} else
+			syslog(LOG_DEBUG, "%s: waiting for RA DAD to complete",
+			    ifp->name);
 	}
 
-	script_runreason(ifp, options & DHCPCD_TEST ? "TEST" : reason);
 	if (options & DHCPCD_TEST ||
 	    (ifp->options->options & DHCPCD_INFORM &&
 	    !(options & DHCPCD_MASTER)))
@@ -1830,7 +1849,6 @@
 #endif
 		exit(EXIT_SUCCESS);
 	}
-	daemonise();
 }
 
 static int
@@ -1920,10 +1938,8 @@
 		return -1;
 
 	TAILQ_INIT(&state->addrs);
-	if (dhcp6_find_delegates(ifp)) {
-		state->state = DH6S_DELEGATED;
+	if (dhcp6_find_delegates(ifp))
 		return 0;
-	}
 
 	syslog(LOG_INFO, "%s: %s", ifp->name,
 	    manage ? "soliciting DHCPv6 address" :
@@ -1997,6 +2013,27 @@
 	dhcp6_freedrop(ifp, 0, NULL);
 }
 
+void
+dhcp6_handleifa(int cmd, const char *ifname, const struct in6_addr *addr)
+{
+	struct interface *ifp;
+	struct dhcp6_state *state;
+	int found;
+
+	TAILQ_FOREACH(ifp, ifaces, next) {
+		state = D6_STATE(ifp);
+		if (state == NULL || strcmp(ifp->name, ifname))
+			continue;
+		found = ipv6_handleifa_addrs(cmd, &state->addrs, addr);
+		if (found && state->state == DH6S_BOUND) {
+			syslog(LOG_DEBUG, "%s: DHCPv6 DAD completed",
+			    ifp->name);
+			script_runreason(ifp, state->reason);
+			daemonise();
+		}
+	}
+}
+
 ssize_t
 dhcp6_env(char **env, const char *prefix, const struct interface *ifp,
     const struct dhcp6_message *m, ssize_t mlen)
--- a/dhcp6.h	Fri May 03 14:43:51 2013 +0000
+++ b/dhcp6.h	Wed May 15 10:27:36 2013 +0000
@@ -1,4 +1,4 @@
-/* 
+/*
  * dhcpcd - DHCP client daemon
  * Copyright (c) 2006-2013 Roy Marples <roy@marples.name>
  * All rights reserved
@@ -180,6 +180,7 @@
 	struct ipv6_addrhead addrs;
 	uint32_t lowpl;
 	char leasefile[PATH_MAX];
+	const char *reason;
 };
 
 #define D6_STATE(ifp)							       \
@@ -212,6 +213,7 @@
 ssize_t dhcp6_env(char **, const char *, const struct interface *,
     const struct dhcp6_message *, ssize_t);
 void dhcp6_free(struct interface *);
+void dhcp6_handleifa(int, const char *, const struct in6_addr *addr);
 void dhcp6_drop(struct interface *, const char *);
 #else
 #define dhcp6_printoptions()
--- a/dhcpcd.c	Fri May 03 14:43:51 2013 +0000
+++ b/dhcpcd.c	Wed May 15 10:27:36 2013 +0000
@@ -457,7 +457,10 @@
 	configure_interface(ifp, argc, argv);
 	ifo = ifp->options;
 
-	if (ifo->options & DHCPCD_LINK && linkfd == -1) {
+	/* RTM_NEWADDR goes through the link socket as well which we
+	 * need for IPv6 DAD, so we check for DHCPCD_LINK in handle_carrier
+	 * instead */
+	if (linkfd == -1) {
 		linkfd = open_link_socket();
 		if (linkfd == -1) {
 			syslog(LOG_ERR, "open_link_socket: %m");
--- a/if-bsd.c	Fri May 03 14:43:51 2013 +0000
+++ b/if-bsd.c	Wed May 15 10:27:36 2013 +0000
@@ -71,11 +71,14 @@
 #define RT_ADVANCE(x, n) (x += RT_ROUNDUP((n)->sa_len))
 #endif
 
-/* FIXME: Why do we need to check for sa_family 255 */
 #define COPYOUT(sin, sa)						      \
 	sin.s_addr = ((sa) != NULL) ?					      \
 	    (((struct sockaddr_in *)(void *)sa)->sin_addr).s_addr : 0
 
+#define COPYOUT6(sin, sa)						      \
+	sin.s6_addr = ((sa) != NULL) ?					      \
+	    (((struct sockaddr_in6 *)(void *)sa)->sin6_addr).s6_addr : 0
+
 static int r_fd = -1;
 static char *link_buf;
 static ssize_t link_buflen;
@@ -478,6 +481,10 @@
 	struct sockaddr_dl sdl;
 	unsigned char *hwaddr;
 #endif
+#ifdef INET6
+	struct in6_addr ia6;
+	struct sockaddr_in6 *sin6;
+#endif
 
 	for (;;) {
 		if (ioctl(fd, FIONREAD, &len) == -1)
@@ -599,6 +606,17 @@
 					    &rt.dest, &rt.net, &rt.gate);
 					break;
 #endif
+#ifdef INET6
+				case AF_INET6:
+					sin6 = (struct sockaddr_in6*)
+					    rti_info[RTAX_IFA];
+					memcpy(ia6.s6_addr,
+					    sin6->sin6_addr.s6_addr,
+					    sizeof(ia6.s6_addr));
+					ipv6_handleifa(rtm->rtm_type, ifname,
+					    &ia6);
+					break;
+#endif
 				}
 				break;
 			}
--- a/ipv6.c	Fri May 03 14:43:51 2013 +0000
+++ b/ipv6.c	Wed May 15 10:27:36 2013 +0000
@@ -28,8 +28,14 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 
+#include <net/route.h>
 #include <netinet/in.h>
 
+#ifdef __linux__
+#  include <asm/types.h> /* for systems with broken headers */
+#  include <linux/rtnetlink.h>
+#endif
+
 #include <errno.h>
 #include <ifaddrs.h>
 #include <stdlib.h>
@@ -285,6 +291,49 @@
 	return i;
 }
 
+void
+ipv6_handleifa(int cmd, const char *ifname, const struct in6_addr *addr)
+{
+
+	ipv6rs_handleifa(cmd, ifname, addr);
+	dhcp6_handleifa(cmd, ifname, addr);
+}
+
+int
+ipv6_handleifa_addrs(int cmd,
+    struct ipv6_addrhead *addrs, const struct in6_addr *addr)
+{
+	struct ipv6_addr *ap, *apn;
+	uint8_t found, alldadcompleted;
+
+	alldadcompleted = 1;
+	found = 0;
+	TAILQ_FOREACH_SAFE(ap, addrs, next, apn) {
+		if (memcmp(addr->s6_addr, ap->addr.s6_addr,
+		    sizeof(addr->s6_addr)))
+		{
+			if (ap->dadcompleted == 0)
+				alldadcompleted = 0;
+			continue;
+		}
+		switch (cmd) {
+		case RTM_DELADDR:
+			syslog(LOG_INFO, "%s: deleted address %s",
+			    ap->iface->name, ap->saddr);
+			TAILQ_REMOVE(addrs, ap, next);
+			free(ap);
+			break;
+		case RTM_NEWADDR:
+			if (!ap->dadcompleted) {
+				found++;
+				ap->dadcompleted = 1;
+			}
+		}
+	}
+
+	return alldadcompleted ? found : 0;
+}
+
 static struct rt6 *
 find_route6(struct rt6head *rts, const struct rt6 *r)
 {
--- a/ipv6.h	Fri May 03 14:43:51 2013 +0000
+++ b/ipv6.h	Wed May 15 10:27:36 2013 +0000
@@ -84,6 +84,8 @@
 int ipv6_prefixlen(const struct in6_addr *);
 int ipv6_addaddr(struct ipv6_addr *);
 ssize_t ipv6_addaddrs(struct ipv6_addrhead *);
+void ipv6_handleifa(int, const char *, const struct in6_addr *);
+int ipv6_handleifa_addrs(int, struct ipv6_addrhead *, const struct in6_addr *);
 int ipv6_removesubnet(const struct interface *, struct ipv6_addr *);
 void ipv6_buildroutes(void);
 void ipv6_drop(struct interface *);
--- a/ipv6ns.c	Fri May 03 14:43:51 2013 +0000
+++ b/ipv6ns.c	Wed May 15 10:27:36 2013 +0000
@@ -55,8 +55,8 @@
 #include "ipv6ns.h"
 #include "script.h"
 
-#define MIN_RANDOM_FACTOR	500				/* milliseconds */
-#define MAX_RANDOM_FACTOR	1500				/* milliseconds */
+#define MIN_RANDOM_FACTOR	500				/* millisecs */
+#define MAX_RANDOM_FACTOR	1500				/* millisecs */
 #define MIN_RANDOM_FACTOR_U	MIN_RANDOM_FACTOR * 1000	/* usecs */
 #define MAX_RANDOM_FACTOR_U	MAX_RANDOM_FACTOR * 1000	/* usecs */
 
--- a/ipv6rs.c	Fri May 03 14:43:51 2013 +0000
+++ b/ipv6rs.c	Wed May 15 10:27:36 2013 +0000
@@ -410,6 +410,50 @@
 #endif
 }
 
+static void
+ipv6rs_scriptrun(const struct ra *rap)
+{
+	int alldadcomplete, hasdns;
+	const struct ipv6_addr *ap;
+	const struct ra_opt *rao;
+
+	/* If all addresses have completed DAD run the script */
+	alldadcomplete = 1;
+	TAILQ_FOREACH(ap, &rap->addrs, next) {
+		if (ap->dadcompleted == 0)
+			return;
+	}
+
+	/* If we don't require RDNSS then set hasdns = 1 so we fork */
+	if (!(rap->iface->options->options & DHCPCD_IPV6RA_REQRDNSS))
+		hasdns = 1;
+	else {
+		hasdns = 0;
+		TAILQ_FOREACH(rao, &rap->options, next) {
+			if (rao->type == ND_OPT_RDNSS &&
+			    rao->option &&
+			    timerisset(&rao->expire))
+			{
+				hasdns = 1;
+				break;
+			}
+		}
+	}
+
+	script_runreason(rap->iface, "ROUTERADVERT");
+	if (hasdns)
+		daemonise();
+#if 0
+	else if (options & DHCPCD_DAEMONISE &&
+	    !(options & DHCPCD_DAEMONISED) && new_data)
+		syslog(LOG_WARNING,
+		    "%s: did not fork due to an absent"
+		    " RDNSS option in the RA",
+		    ifp->name);
+}
+#endif
+}
+
 /* ARGSUSED */
 static void
 ipv6rs_handledata(__unused void *arg)
@@ -437,7 +481,7 @@
 	struct ipv6_addr *ap;
 	char *opt, *tmp;
 	struct timeval expire;
-	uint8_t has_dns, new_rap, new_data;
+	uint8_t new_rap, new_data;
 
 	len = recvmsg(sock, &rcvhdr, 0);
 	if (len == -1) {
@@ -578,7 +622,6 @@
 	p = ((uint8_t *)icp) + sizeof(struct nd_router_advert);
 	olen = 0;
 	lifetime = ~0U;
-	has_dns = 0;
 	for (olen = 0; len > 0; p += olen, len -= olen) {
 		if ((size_t)len < sizeof(struct nd_opt_hdr)) {
 			syslog(LOG_ERR, "%s: Short option", ifp->name);
@@ -746,8 +789,6 @@
 						l -= (m + 1);
 						tmp += m;
 						*tmp++ = ' ';
-						if (lifetime > 0)
-							has_dns = 1;
 					}
 				}
 				if (tmp != opt)
@@ -836,23 +877,13 @@
 	if (options & DHCPCD_IPV6RA_OWN)
 		ipv6ns_probeaddrs(&rap->addrs);
 	ipv6_buildroutes();
+
 	/* We will get run by the expire function */
 	if (rap->lifetime)
-		script_runreason(ifp, "ROUTERADVERT");
-
-	/* If we don't require RDNSS then set has_dns = 1 so we fork */
-	if (!(ifp->options->options & DHCPCD_IPV6RA_REQRDNSS))
-		has_dns = 1;
+		ipv6rs_scriptrun(rap);
 
 	eloop_timeout_delete(NULL, ifp);
 	eloop_timeout_delete(NULL, rap); /* reachable timer */
-	if (has_dns)
-		daemonise();
-	else if (options & DHCPCD_DAEMONISE &&
-	    !(options & DHCPCD_DAEMONISED) && new_data)
-		syslog(LOG_WARNING,
-		    "%s: did not fork due to an absent RDNSS option in the RA",
-		    ifp->name);
 
 	/* If we're owning the RA then we need to try and ensure the
 	 * router is actually reachable */
@@ -1051,6 +1082,25 @@
 }
 
 void
+ipv6rs_handleifa(int cmd, const char *ifname, const struct in6_addr *addr)
+{
+	struct ra *rap;
+	int found;
+
+	TAILQ_FOREACH(rap, &ipv6_routers, next) {
+		if (strcmp(rap->iface->name, ifname))
+			continue;
+		found = ipv6_handleifa_addrs(cmd, &rap->addrs, addr);
+		if (found && rap->lifetime) {
+			syslog(LOG_DEBUG,
+			    "%s: IPv6 Router Advertisement DAD completed",
+			    rap->iface->name);
+			ipv6rs_scriptrun(rap);
+		}
+	}
+}
+
+void
 ipv6rs_expire(void *arg)
 {
 	struct interface *ifp;
--- a/ipv6rs.h	Fri May 03 14:43:51 2013 +0000
+++ b/ipv6rs.h	Wed May 15 10:27:36 2013 +0000
@@ -86,6 +86,7 @@
 ssize_t ipv6rs_free(struct interface *);
 void ipv6rs_expire(void *arg);
 int ipv6rs_has_ra(const struct interface *);
+void ipv6rs_handleifa(int, const char *, const struct in6_addr *);
 void ipv6rs_drop(struct interface *);
 #else
 #define ipv6rs_start(a) {}