svn commit: r279531 - in head/sys: netinet netinet6

Hiroki Sato hrs at FreeBSD.org
Mon Mar 2 17:30:28 UTC 2015


Author: hrs
Date: Mon Mar  2 17:30:26 2015
New Revision: 279531
URL: https://svnweb.freebsd.org/changeset/base/279531

Log:
  Implement Enhanced DAD algorithm for IPv6 described in
  draft-ietf-6man-enhanced-dad-13.
  
  This basically adds a random nonce option (RFC 3971) to NS messages
  for DAD probe to detect a looped back packet.  This looped back packet
  prevented DAD on some pseudo-interfaces which aggregates multiple L2 links
  such as lagg(4).
  
  The length of the nonce is set to 6 bytes.  This algorithm can be disabled by
  setting net.inet6.ip6.dad_enhanced sysctl to 0 in a per-vnet basis.
  
  Reported by:		hiren
  Reviewed by:		ae
  Differential Revision:	https://reviews.freebsd.org/D1835

Modified:
  head/sys/netinet/icmp6.h
  head/sys/netinet6/nd6.c
  head/sys/netinet6/nd6.h
  head/sys/netinet6/nd6_nbr.c

Modified: head/sys/netinet/icmp6.h
==============================================================================
--- head/sys/netinet/icmp6.h	Mon Mar  2 17:25:03 2015	(r279530)
+++ head/sys/netinet/icmp6.h	Mon Mar  2 17:30:26 2015	(r279531)
@@ -297,9 +297,11 @@ struct nd_opt_hdr {		/* Neighbor discove
 #define ND_OPT_PREFIX_INFORMATION	3
 #define ND_OPT_REDIRECTED_HEADER	4
 #define ND_OPT_MTU			5
+#define ND_OPT_NONCE			14	/* RFC 3971 */
 #define ND_OPT_ROUTE_INFO		24	/* RFC 4191 */
 #define ND_OPT_RDNSS			25	/* RFC 6106 */
 #define ND_OPT_DNSSL			31	/* RFC 6106 */
+#define ND_OPT_MAX			31
 
 struct nd_opt_prefix_info {	/* prefix information */
 	u_int8_t	nd_opt_pi_type;
@@ -330,6 +332,16 @@ struct nd_opt_mtu {		/* MTU option */
 	u_int32_t	nd_opt_mtu_mtu;
 } __packed;
 
+#define	ND_OPT_NONCE_LEN	((1 * 8) - 2)
+#if ((ND_OPT_NONCE_LEN + 2) % 8) != 0
+#error "(ND_OPT_NONCE_LEN + 2) must be a multiple of 8."
+#endif 
+struct nd_opt_nonce {		/* nonce option */
+	u_int8_t	nd_opt_nonce_type;
+	u_int8_t	nd_opt_nonce_len;
+	u_int8_t	nd_opt_nonce[ND_OPT_NONCE_LEN];
+} __packed;
+
 struct nd_opt_route_info {	/* route info */
 	u_int8_t	nd_opt_rti_type;
 	u_int8_t	nd_opt_rti_len;

Modified: head/sys/netinet6/nd6.c
==============================================================================
--- head/sys/netinet6/nd6.c	Mon Mar  2 17:25:03 2015	(r279530)
+++ head/sys/netinet6/nd6.c	Mon Mar  2 17:30:26 2015	(r279531)
@@ -372,6 +372,7 @@ nd6_options(union nd_opts *ndopts)
 		case ND_OPT_TARGET_LINKADDR:
 		case ND_OPT_MTU:
 		case ND_OPT_REDIRECTED_HEADER:
+		case ND_OPT_NONCE:
 			if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) {
 				nd6log((LOG_INFO,
 				    "duplicated ND6 option found (type=%d)\n",
@@ -526,7 +527,7 @@ nd6_llinfo_timer(void *arg)
 			ln->la_asked++;
 			nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000);
 			LLE_WUNLOCK(ln);
-			nd6_ns_output(ifp, NULL, dst, ln, 0);
+			nd6_ns_output(ifp, NULL, dst, ln, NULL);
 			LLE_WLOCK(ln);
 		} else {
 			struct mbuf *m = ln->la_hold;
@@ -573,7 +574,7 @@ nd6_llinfo_timer(void *arg)
 			ln->ln_state = ND6_LLINFO_PROBE;
 			nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000);
 			LLE_WUNLOCK(ln);
-			nd6_ns_output(ifp, dst, dst, ln, 0);
+			nd6_ns_output(ifp, dst, dst, ln, NULL);
 			LLE_WLOCK(ln);
 		} else {
 			ln->ln_state = ND6_LLINFO_STALE; /* XXX */
@@ -585,7 +586,7 @@ nd6_llinfo_timer(void *arg)
 			ln->la_asked++;
 			nd6_llinfo_settimer_locked(ln, (long)ndi->retrans * hz / 1000);
 			LLE_WUNLOCK(ln);
-			nd6_ns_output(ifp, dst, dst, ln, 0);
+			nd6_ns_output(ifp, dst, dst, ln, NULL);
 			LLE_WLOCK(ln);
 		} else {
 			EVENTHANDLER_INVOKE(lle_event, ln, LLENTRY_EXPIRED);
@@ -2084,7 +2085,7 @@ nd6_output_lle(struct ifnet *ifp, struct
 		nd6_llinfo_settimer_locked(lle,
 		    (long)ND_IFINFO(ifp)->retrans * hz / 1000);
 		LLE_WUNLOCK(lle);
-		nd6_ns_output(ifp, NULL, &dst->sin6_addr, lle, 0);
+		nd6_ns_output(ifp, NULL, &dst->sin6_addr, lle, NULL);
 	} else {
 		/* We did the lookup so we need to do the unlock here. */
 		LLE_WUNLOCK(lle);

Modified: head/sys/netinet6/nd6.h
==============================================================================
--- head/sys/netinet6/nd6.h	Mon Mar  2 17:25:03 2015	(r279530)
+++ head/sys/netinet6/nd6.h	Mon Mar  2 17:30:26 2015	(r279531)
@@ -359,7 +359,7 @@ VNET_DECLARE(int, ip6_temp_regen_advance
 #define	V_ip6_temp_regen_advance	VNET(ip6_temp_regen_advance)
 
 union nd_opts {
-	struct nd_opt_hdr *nd_opt_array[8];	/* max = target address list */
+	struct nd_opt_hdr *nd_opt_array[16];	/* max = ND_OPT_NONCE */
 	struct {
 		struct nd_opt_hdr *zero;
 		struct nd_opt_hdr *src_lladdr;
@@ -367,6 +367,16 @@ union nd_opts {
 		struct nd_opt_prefix_info *pi_beg; /* multiple opts, start */
 		struct nd_opt_rd_hdr *rh;
 		struct nd_opt_mtu *mtu;
+		struct nd_opt_hdr *__res6;
+		struct nd_opt_hdr *__res7;
+		struct nd_opt_hdr *__res8;
+		struct nd_opt_hdr *__res9;
+		struct nd_opt_hdr *__res10;
+		struct nd_opt_hdr *__res11;
+		struct nd_opt_hdr *__res12;
+		struct nd_opt_hdr *__res13;
+		struct nd_opt_nonce *nonce;
+		struct nd_opt_hdr *__res15;
 		struct nd_opt_hdr *search;	/* multiple opts */
 		struct nd_opt_hdr *last;	/* multiple opts */
 		int done;
@@ -379,6 +389,7 @@ union nd_opts {
 #define nd_opts_pi_end		nd_opt_each.pi_end
 #define nd_opts_rh		nd_opt_each.rh
 #define nd_opts_mtu		nd_opt_each.mtu
+#define nd_opts_nonce		nd_opt_each.nonce
 #define nd_opts_search		nd_opt_each.search
 #define nd_opts_last		nd_opt_each.last
 #define nd_opts_done		nd_opt_each.done
@@ -425,7 +436,7 @@ void nd6_na_output(struct ifnet *, const
 	const struct in6_addr *, u_long, int, struct sockaddr *);
 void nd6_ns_input(struct mbuf *, int, int);
 void nd6_ns_output(struct ifnet *, const struct in6_addr *,
-	const struct in6_addr *, struct llentry *, int);
+	const struct in6_addr *, struct llentry *, uint8_t *);
 caddr_t nd6_ifptomac(struct ifnet *);
 void nd6_dad_init(void);
 void nd6_dad_start(struct ifaddr *, int);

Modified: head/sys/netinet6/nd6_nbr.c
==============================================================================
--- head/sys/netinet6/nd6_nbr.c	Mon Mar  2 17:25:03 2015	(r279530)
+++ head/sys/netinet6/nd6_nbr.c	Mon Mar  2 17:30:26 2015	(r279531)
@@ -40,6 +40,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/param.h>
 #include <sys/systm.h>
 #include <sys/malloc.h>
+#include <sys/libkern.h>
 #include <sys/lock.h>
 #include <sys/rwlock.h>
 #include <sys/mbuf.h>
@@ -48,6 +49,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/time.h>
 #include <sys/kernel.h>
 #include <sys/errno.h>
+#include <sys/sysctl.h>
 #include <sys/syslog.h>
 #include <sys/queue.h>
 #include <sys/callout.h>
@@ -61,6 +63,7 @@ __FBSDID("$FreeBSD$");
 #ifdef RADIX_MPATH
 #include <net/radix_mpath.h>
 #endif
+#include <net/vnet.h>
 
 #include <netinet/in.h>
 #include <netinet/in_var.h>
@@ -79,7 +82,7 @@ __FBSDID("$FreeBSD$");
 #define SDL(s) ((struct sockaddr_dl *)s)
 
 struct dadq;
-static struct dadq *nd6_dad_find(struct ifaddr *);
+static struct dadq *nd6_dad_find(struct ifaddr *, struct nd_opt_nonce *);
 static void nd6_dad_add(struct dadq *dp);
 static void nd6_dad_del(struct dadq *dp);
 static void nd6_dad_rele(struct dadq *);
@@ -88,16 +91,21 @@ static void nd6_dad_stoptimer(struct dad
 static void nd6_dad_timer(struct dadq *);
 static void nd6_dad_duplicated(struct ifaddr *, struct dadq *);
 static void nd6_dad_ns_output(struct dadq *, struct ifaddr *);
-static void nd6_dad_ns_input(struct ifaddr *);
+static void nd6_dad_ns_input(struct ifaddr *, struct nd_opt_nonce *);
 static void nd6_dad_na_input(struct ifaddr *);
 static void nd6_na_output_fib(struct ifnet *, const struct in6_addr *,
     const struct in6_addr *, u_long, int, struct sockaddr *, u_int);
 
-static VNET_DEFINE(int, dad_ignore_ns) = 0;	/* ignore NS in DAD
-						   - specwise incorrect */
+static VNET_DEFINE(int, dad_enhanced) = 1;
+#define	V_dad_enhanced			VNET(dad_enhanced)
+
+SYSCTL_DECL(_net_inet6_ip6);
+SYSCTL_INT(_net_inet6_ip6, OID_AUTO, dad_enhanced, CTLFLAG_VNET | CTLFLAG_RW,
+    &VNET_NAME(dad_enhanced), 0,
+    "Enable Enhanced DAD, which adds a random nonce to NS messages for DAD.");
+
 static VNET_DEFINE(int, dad_maxtry) = 15;	/* max # of *tries* to
 						   transmit DAD packet */
-#define	V_dad_ignore_ns			VNET(dad_ignore_ns)
 #define	V_dad_maxtry			VNET(dad_maxtry)
 
 /*
@@ -321,7 +329,7 @@ nd6_ns_input(struct mbuf *m, int off, in
 		 * silently ignore it.
 		 */
 		if (IN6_IS_ADDR_UNSPECIFIED(&saddr6))
-			nd6_dad_ns_input(ifa);
+			nd6_dad_ns_input(ifa, ndopts.nd_opts_nonce);
 
 		goto freeit;
 	}
@@ -382,12 +390,13 @@ nd6_ns_input(struct mbuf *m, int off, in
  * Based on RFC 2461
  * Based on RFC 2462 (duplicate address detection)
  *
- *   ln - for source address determination
- *  dad - duplicate address detection
+ *    ln - for source address determination
+ * nonce - If non-NULL, NS is used for duplicate address detection and
+ *         the value (length is ND_OPT_NONCE_LEN) is used as a random nonce.
  */
 void
 nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6, 
-    const struct in6_addr *taddr6, struct llentry *ln, int dad)
+    const struct in6_addr *taddr6, struct llentry *ln, uint8_t *nonce)
 {
 	struct mbuf *m;
 	struct m_tag *mtag;
@@ -453,7 +462,7 @@ nd6_ns_output(struct ifnet *ifp, const s
 		if (in6_setscope(&ip6->ip6_dst, ifp, NULL) != 0)
 			goto bad;
 	}
-	if (!dad) {
+	if (nonce == NULL) {
 		struct ifaddr *ifa;
 
 		/*
@@ -550,7 +559,7 @@ nd6_ns_output(struct ifnet *ifp, const s
 	 *	Multicast NS		MUST add one	add the option
 	 *	Unicast NS		SHOULD add one	add the option
 	 */
-	if (!dad && (mac = nd6_ifptomac(ifp))) {
+	if (nonce == NULL && (mac = nd6_ifptomac(ifp))) {
 		int optlen = sizeof(struct nd_opt_hdr) + ifp->if_addrlen;
 		struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd_ns + 1);
 		/* 8 byte alignments... */
@@ -564,7 +573,26 @@ nd6_ns_output(struct ifnet *ifp, const s
 		nd_opt->nd_opt_len = optlen >> 3;
 		bcopy(mac, (caddr_t)(nd_opt + 1), ifp->if_addrlen);
 	}
+	/*
+	 * Add a Nonce option (RFC 3971) to detect looped back NS messages.
+	 * This behavior is documented as Enhanced Duplicate Address
+	 * Detection in draft-ietf-6man-enhanced-dad-13.
+	 * net.inet6.ip6.dad_enhanced=0 disables this.
+	 */
+	if (V_dad_enhanced != 0 && nonce != NULL) {
+		int optlen = sizeof(struct nd_opt_hdr) + ND_OPT_NONCE_LEN;
+		struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd_ns + 1);
+		/* 8-byte alignment is required. */
+		optlen = (optlen + 7) & ~7;
 
+		m->m_pkthdr.len += optlen;
+		m->m_len += optlen;
+		icmp6len += optlen;
+		bzero((caddr_t)nd_opt, optlen);
+		nd_opt->nd_opt_type = ND_OPT_NONCE;
+		nd_opt->nd_opt_len = optlen >> 3;
+		bcopy(nonce, (caddr_t)(nd_opt + 1), ND_OPT_NONCE_LEN);
+	}
 	ip6->ip6_plen = htons((u_short)icmp6len);
 	nd_ns->nd_ns_cksum = 0;
 	nd_ns->nd_ns_cksum =
@@ -579,7 +607,8 @@ nd6_ns_output(struct ifnet *ifp, const s
 		m_tag_prepend(m, mtag);
 	}
 
-	ip6_output(m, NULL, &ro, dad ? IPV6_UNSPECSRC : 0, &im6o, NULL, NULL);
+	ip6_output(m, NULL, &ro, (nonce != NULL) ? IPV6_UNSPECSRC : 0,
+	    &im6o, NULL, NULL);
 	icmp6_ifstat_inc(ifp, ifs6_out_msg);
 	icmp6_ifstat_inc(ifp, ifs6_out_neighborsolicit);
 	ICMP6STAT_INC(icp6s_outhist[ND_NEIGHBOR_SOLICIT]);
@@ -1139,9 +1168,13 @@ struct dadq {
 	int dad_ns_ocount;	/* NS sent so far */
 	int dad_ns_icount;
 	int dad_na_icount;
+	int dad_ns_lcount;	/* looped back NS */
 	struct callout dad_timer_ch;
 	struct vnet *dad_vnet;
 	u_int dad_refcnt;
+#define	ND_OPT_NONCE_LEN32 \
+		((ND_OPT_NONCE_LEN + sizeof(uint32_t) - 1)/sizeof(uint32_t))
+	uint32_t dad_nonce[ND_OPT_NONCE_LEN32];
 };
 
 static VNET_DEFINE(TAILQ_HEAD(, dadq), dadq);
@@ -1174,16 +1207,34 @@ nd6_dad_del(struct dadq *dp)
 }
 
 static struct dadq *
-nd6_dad_find(struct ifaddr *ifa)
+nd6_dad_find(struct ifaddr *ifa, struct nd_opt_nonce *n)
 {
 	struct dadq *dp;
+	char ip6buf[INET6_ADDRSTRLEN];
 
 	DADQ_RLOCK();
-	TAILQ_FOREACH(dp, &V_dadq, dad_list)
-		if (dp->dad_ifa == ifa) {
-			refcount_acquire(&dp->dad_refcnt);
-			break;
+	TAILQ_FOREACH(dp, &V_dadq, dad_list) {
+		if (dp->dad_ifa != ifa)
+			continue;
+		/*
+		 * Skip if the nonce matches the received one.
+		 * +2 in the length is required because of type and
+		 * length fields are included in a header.
+		 */
+		if (n != NULL &&
+		    n->nd_opt_nonce_len == (ND_OPT_NONCE_LEN + 2) / 8 &&
+		    memcmp(&n->nd_opt_nonce[0], &dp->dad_nonce[0],
+		        ND_OPT_NONCE_LEN) == 0) {
+			log(LOG_ERR, "%s: a looped back NS message is "
+			    "detected during DAD for %s.\n",
+			    if_name(ifa->ifa_ifp),
+			    ip6_sprintf(ip6buf, IFA_IN6(ifa)));
+			dp->dad_ns_lcount++;
+			continue;
 		}
+		refcount_acquire(&dp->dad_refcnt);
+		break;
+	}
 	DADQ_RUNLOCK();
 
 	return (dp);
@@ -1261,7 +1312,7 @@ nd6_dad_start(struct ifaddr *ifa, int de
 	}
 	if (ND_IFINFO(ifa->ifa_ifp)->flags & ND6_IFF_IFDISABLED)
 		return;
-	if ((dp = nd6_dad_find(ifa)) != NULL) {
+	if ((dp = nd6_dad_find(ifa, NULL)) != NULL) {
 		/* DAD already in progress */
 		nd6_dad_rele(dp);
 		return;
@@ -1293,6 +1344,7 @@ nd6_dad_start(struct ifaddr *ifa, int de
 	dp->dad_count = V_ip6_dad_count;
 	dp->dad_ns_icount = dp->dad_na_icount = 0;
 	dp->dad_ns_ocount = dp->dad_ns_tcount = 0;
+	dp->dad_ns_lcount = 0;
 	refcount_init(&dp->dad_refcnt, 1);
 	nd6_dad_add(dp);
 	if (delay == 0) {
@@ -1312,7 +1364,7 @@ nd6_dad_stop(struct ifaddr *ifa)
 {
 	struct dadq *dp;
 
-	dp = nd6_dad_find(ifa);
+	dp = nd6_dad_find(ifa, NULL);
 	if (!dp) {
 		/* DAD wasn't started yet */
 		return;
@@ -1325,7 +1377,7 @@ nd6_dad_stop(struct ifaddr *ifa)
 	 * we were waiting for it to stop, so re-do the lookup.
 	 */
 	nd6_dad_rele(dp);
-	if (nd6_dad_find(ifa) == NULL)
+	if (nd6_dad_find(ifa, NULL) == NULL)
 		return;
 
 	nd6_dad_del(dp);
@@ -1421,9 +1473,10 @@ nd6_dad_duplicated(struct ifaddr *ifa, s
 	char ip6buf[INET6_ADDRSTRLEN];
 
 	log(LOG_ERR, "%s: DAD detected duplicate IPv6 address %s: "
-	    "NS in/out=%d/%d, NA in=%d\n",
+	    "NS in/out/loopback=%d/%d/%d, NA in=%d\n",
 	    if_name(ifa->ifa_ifp), ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr),
-	    dp->dad_ns_icount, dp->dad_ns_ocount, dp->dad_na_icount);
+	    dp->dad_ns_icount, dp->dad_ns_ocount, dp->dad_ns_lcount,
+	    dp->dad_na_icount);
 
 	ia->ia6_flags &= ~IN6_IFF_TENTATIVE;
 	ia->ia6_flags |= IN6_IFF_DUPLICATED;
@@ -1475,6 +1528,8 @@ nd6_dad_ns_output(struct dadq *dp, struc
 {
 	struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa;
 	struct ifnet *ifp = ifa->ifa_ifp;
+	uint8_t *nonce;
+	int i;
 
 	dp->dad_ns_tcount++;
 	if ((ifp->if_flags & IFF_UP) == 0) {
@@ -1485,11 +1540,25 @@ nd6_dad_ns_output(struct dadq *dp, struc
 	}
 
 	dp->dad_ns_ocount++;
-	nd6_ns_output(ifp, NULL, &ia->ia_addr.sin6_addr, NULL, 1);
+	if (V_dad_enhanced != 0) {
+		for (i = 0; i < ND_OPT_NONCE_LEN32; i++)
+			dp->dad_nonce[i] = arc4random();
+		nonce = (uint8_t *)&dp->dad_nonce[0];
+		/*
+		 * XXXHRS: Note that in the case that
+		 * DupAddrDetectTransmits > 1, multiple NS messages with
+		 * different nonces can be looped back in an unexpected
+		 * order.  The current implementation recognizes only
+		 * the latest nonce on the sender side.  Practically it
+		 * should work well in almost all cases.
+		 */
+	} else
+		nonce = NULL;
+	nd6_ns_output(ifp, NULL, &ia->ia_addr.sin6_addr, NULL, nonce);
 }
 
 static void
-nd6_dad_ns_input(struct ifaddr *ifa)
+nd6_dad_ns_input(struct ifaddr *ifa, struct nd_opt_nonce *ndopt_nonce)
 {
 	struct in6_ifaddr *ia;
 	struct ifnet *ifp;
@@ -1502,22 +1571,13 @@ nd6_dad_ns_input(struct ifaddr *ifa)
 	ia = (struct in6_ifaddr *)ifa;
 	ifp = ifa->ifa_ifp;
 	taddr6 = &ia->ia_addr.sin6_addr;
-	dp = nd6_dad_find(ifa);
+	/* Ignore Nonce option when Enhanced DAD is disabled. */
+	if (V_dad_enhanced == 0)
+		ndopt_nonce = NULL;
+	dp = nd6_dad_find(ifa, ndopt_nonce);
 	if (dp == NULL)
 		return;
 
-	/* Quickhack - completely ignore DAD NS packets */
-	if (V_dad_ignore_ns) {
-		char ip6buf[INET6_ADDRSTRLEN];
-		nd6log((LOG_INFO,
-		    "nd6_dad_ns_input: ignoring DAD NS packet for "
-		    "address %s(%s)\n", ip6_sprintf(ip6buf, taddr6),
-		    if_name(ifa->ifa_ifp)));
-		return;
-	}
-
-	/* XXX more checks for loopback situation - see nd6_dad_timer too */
-
 	dp->dad_ns_icount++;
 	nd6_dad_rele(dp);
 }
@@ -1530,7 +1590,7 @@ nd6_dad_na_input(struct ifaddr *ifa)
 	if (ifa == NULL)
 		panic("ifa == NULL in nd6_dad_na_input");
 
-	dp = nd6_dad_find(ifa);
+	dp = nd6_dad_find(ifa, NULL);
 	if (dp != NULL) {
 		dp->dad_na_icount++;
 		nd6_dad_rele(dp);


More information about the svn-src-head mailing list