Module Name:    src
Committed By:   roy
Date:           Tue Mar  6 10:57:00 UTC 2018

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

Log Message:
nd6: add a nonce to DaD probes in-case they are looped back to us

This implements RFC 7527, based a similar change in FreeBSD.


To generate a diff of this commit:
cvs rdiff -u -r1.49 -r1.50 src/sys/netinet/icmp6.h
cvs rdiff -u -r1.246 -r1.247 src/sys/netinet6/nd6.c
cvs rdiff -u -r1.85 -r1.86 src/sys/netinet6/nd6.h
cvs rdiff -u -r1.148 -r1.149 src/sys/netinet6/nd6_nbr.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/netinet/icmp6.h
diff -u src/sys/netinet/icmp6.h:1.49 src/sys/netinet/icmp6.h:1.50
--- src/sys/netinet/icmp6.h:1.49	Tue Jan 23 10:55:38 2018
+++ src/sys/netinet/icmp6.h	Tue Mar  6 10:57:00 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: icmp6.h,v 1.49 2018/01/23 10:55:38 maxv Exp $	*/
+/*	$NetBSD: icmp6.h,v 1.50 2018/03/06 10:57:00 roy Exp $	*/
 /*	$KAME: icmp6.h,v 1.84 2003/04/23 10:26:51 itojun Exp $	*/
 
 
@@ -305,10 +305,12 @@ struct nd_opt_hdr {		/* Neighbor discove
 #define ND_OPT_HOMEAGENT_INFO		8
 #define ND_OPT_SOURCE_ADDRLIST		9
 #define ND_OPT_TARGET_ADDRLIST		10
+#define ND_OPT_NONCE			14	/* RFC 3971 */
 #define ND_OPT_MAP			23	/* RFC 5380 */
 #define ND_OPT_ROUTE_INFO		24	/* RFC 4191 */
 #define ND_OPT_RDNSS			25	/* RFC 6016 */
 #define ND_OPT_DNSSL			31	/* RFC 6016 */
+#define ND_OPT_MAX			31
 
 struct nd_opt_route_info {	/* route info */
 	u_int8_t	nd_opt_rti_type;
@@ -348,6 +350,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 {
+	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_rdnss {		/* RDNSS option RFC 6106 */
 	u_int8_t	nd_opt_rdnss_type;
 	u_int8_t	nd_opt_rdnss_len;

Index: src/sys/netinet6/nd6.c
diff -u src/sys/netinet6/nd6.c:1.246 src/sys/netinet6/nd6.c:1.247
--- src/sys/netinet6/nd6.c:1.246	Tue Mar  6 07:24:01 2018
+++ src/sys/netinet6/nd6.c	Tue Mar  6 10:57:00 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6.c,v 1.246 2018/03/06 07:24:01 ozaki-r Exp $	*/
+/*	$NetBSD: nd6.c,v 1.247 2018/03/06 10:57:00 roy Exp $	*/
 /*	$KAME: nd6.c,v 1.279 2002/06/08 11:16:51 itojun Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nd6.c,v 1.246 2018/03/06 07:24:01 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nd6.c,v 1.247 2018/03/06 10:57:00 roy Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_net_mpsafe.h"
@@ -347,6 +347,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",
@@ -554,7 +555,7 @@ nd6_llinfo_timer(void *arg)
 		psrc = nd6_llinfo_get_holdsrc(ln, &src);
 		LLE_FREE_LOCKED(ln);
 		ln = NULL;
-		nd6_ns_output(ifp, daddr6, taddr6, psrc, 0);
+		nd6_ns_output(ifp, daddr6, taddr6, psrc, NULL);
 	}
 
 out:
@@ -2420,7 +2421,7 @@ nd6_resolve(struct ifnet *ifp, const str
 		psrc = nd6_llinfo_get_holdsrc(ln, &src);
 		LLE_WUNLOCK(ln);
 		ln = NULL;
-		nd6_ns_output(ifp, NULL, &dst->sin6_addr, psrc, 0);
+		nd6_ns_output(ifp, NULL, &dst->sin6_addr, psrc, NULL);
 	} else {
 		/* We did the lookup so we need to do the unlock here. */
 		LLE_WUNLOCK(ln);

Index: src/sys/netinet6/nd6.h
diff -u src/sys/netinet6/nd6.h:1.85 src/sys/netinet6/nd6.h:1.86
--- src/sys/netinet6/nd6.h:1.85	Thu Jun 22 09:24:02 2017
+++ src/sys/netinet6/nd6.h	Tue Mar  6 10:57:00 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6.h,v 1.85 2017/06/22 09:24:02 ozaki-r Exp $	*/
+/*	$NetBSD: nd6.h,v 1.86 2018/03/06 10:57:00 roy Exp $	*/
 /*	$KAME: nd6.h,v 1.95 2002/06/08 11:31:06 itojun Exp $	*/
 
 /*
@@ -397,7 +397,7 @@ extern int ip6_temp_regen_advance; /* se
 extern int nd6_numroutes;
 
 union nd_opts {
-	struct nd_opt_hdr *nd_opt_array[8];
+	struct nd_opt_hdr *nd_opt_array[16];	/* max = ND_OPT_NONCE */
 	struct {
 		struct nd_opt_hdr *zero;
 		struct nd_opt_hdr *src_lladdr;
@@ -405,6 +405,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;
@@ -417,6 +427,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
@@ -454,7 +465,7 @@ void nd6_na_output(struct ifnet *, const
 	const struct in6_addr *, u_long, int, const 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 in6_addr *, int);
+	const struct in6_addr *, struct in6_addr *, uint8_t *);
 const void *nd6_ifptomac(const struct ifnet *);
 void nd6_dad_start(struct ifaddr *, int);
 void nd6_dad_stop(struct ifaddr *);

Index: src/sys/netinet6/nd6_nbr.c
diff -u src/sys/netinet6/nd6_nbr.c:1.148 src/sys/netinet6/nd6_nbr.c:1.149
--- src/sys/netinet6/nd6_nbr.c:1.148	Sat Feb 24 07:53:15 2018
+++ src/sys/netinet6/nd6_nbr.c	Tue Mar  6 10:57:00 2018
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6_nbr.c,v 1.148 2018/02/24 07:53:15 ozaki-r Exp $	*/
+/*	$NetBSD: nd6_nbr.c,v 1.149 2018/03/06 10:57:00 roy Exp $	*/
 /*	$KAME: nd6_nbr.c,v 1.61 2001/02/10 16:06:14 jinmei Exp $	*/
 
 /*
@@ -31,7 +31,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nd6_nbr.c,v 1.148 2018/02/24 07:53:15 ozaki-r Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nd6_nbr.c,v 1.149 2018/03/06 10:57:00 roy Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_inet.h"
@@ -52,6 +52,7 @@ __KERNEL_RCSID(0, "$NetBSD: nd6_nbr.c,v 
 #include <sys/syslog.h>
 #include <sys/queue.h>
 #include <sys/callout.h>
+#include <sys/cprng.h>
 
 #include <net/if.h>
 #include <net/if_types.h>
@@ -77,16 +78,15 @@ __KERNEL_RCSID(0, "$NetBSD: nd6_nbr.c,v 
 #include <net/net_osdep.h>
 
 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_starttimer(struct dadq *, int);
 static void nd6_dad_destroytimer(struct dadq *);
 static void nd6_dad_timer(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_dad_duplicated(struct dadq *);
 
-static int dad_ignore_ns = 0;	/* ignore NS in DAD - specwise incorrect*/
 static int dad_maxtry = 15;	/* max # of *tries* to transmit DAD packet */
 
 /*
@@ -309,7 +309,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);
 		ifa_release(ifa, &psref_ia);
 		ifa = NULL;
 
@@ -374,7 +374,7 @@ void
 nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6,
     const struct in6_addr *taddr6,
     struct in6_addr *hsrc,
-    int dad			/* duplicate address detection */)
+    uint8_t *nonce		/* duplicate address detection */)
 {
 	struct mbuf *m;
 	struct ip6_hdr *ip6;
@@ -441,7 +441,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) {
 		int s;
 		/*
 		 * RFC2461 7.2.2:
@@ -512,7 +512,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... */
@@ -527,12 +527,30 @@ nd6_ns_output(struct ifnet *ifp, const s
 		memcpy((void *)(nd_opt + 1), mac, ifp->if_addrlen);
 	}
 
+	/* Add a nonce option (RFC 3971) to detect looped back NS messages.
+	 * This behavior is documented in RFC 7527. */
+	if (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;
+		memset(nd_opt, 0, optlen);
+		nd_opt->nd_opt_type = ND_OPT_NONCE;
+		nd_opt->nd_opt_len = optlen >> 3;
+		memcpy(nd_opt + 1, nonce, ND_OPT_NONCE_LEN);
+	}
+
 	ip6->ip6_plen = htons((u_int16_t)icmp6len);
 	nd_ns->nd_ns_cksum = 0;
 	nd_ns->nd_ns_cksum =
 	    in6_cksum(m, IPPROTO_ICMPV6, sizeof(*ip6), icmp6len);
 
-	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);
 	ICMP6_STATINC(ICMP6_STAT_OUTHIST + ND_NEIGHBOR_SOLICIT);
@@ -1055,12 +1073,24 @@ TAILQ_HEAD(dadq_head, dadq);
 struct dadq {
 	TAILQ_ENTRY(dadq) dad_list;
 	struct ifaddr *dad_ifa;
-	int dad_count;		/* max NS to send */
-	int dad_ns_tcount;	/* # of trials to send NS */
-	int dad_ns_ocount;	/* NS sent so far */
+	int dad_count;			/* max NS to send */
+	int dad_ns_tcount;		/* # of trials to send NS */
+	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;
+#define	ND_OPT_NONCE_STORE	3	/* dad_count should not exceed this */
+	/*
+	 * The default ip6_dad_count is 1 as specified by RFC 4862 and
+	 * practically must users won't exceed this.
+	 * A storage of 3 is defaulted to here, in-case the administrator wants
+	 * to match the equivalent behaviour in our ARP implementation.
+	 * This constraint could be removed by sending the on wire nonce as
+	 * hmac(key, dad_ns_ocount), but that would increase the nonce size
+	 * sent on the wire.
+	 */
+	uint8_t dad_nonce[ND_OPT_NONCE_STORE][ND_OPT_NONCE_LEN];
 };
 
 static struct dadq_head dadq;
@@ -1068,17 +1098,42 @@ static int dad_init = 0;
 static kmutex_t nd6_dad_lock;
 
 static struct dadq *
-nd6_dad_find(struct ifaddr *ifa)
+nd6_dad_find(struct ifaddr *ifa, struct nd_opt_nonce *nonce)
 {
 	struct dadq *dp;
+	int i, nonce_max;
 
 	KASSERT(mutex_owned(&nd6_dad_lock));
 
 	TAILQ_FOREACH(dp, &dadq, dad_list) {
-		if (dp->dad_ifa == ifa)
-			return dp;
+		if (dp->dad_ifa != ifa)
+			continue;
+
+		if (nonce == NULL ||
+		    nonce->nd_opt_nonce_len != (ND_OPT_NONCE_LEN + 2) / 8)
+			break;
+
+		nonce_max = MIN(dp->dad_ns_ocount, ND_OPT_NONCE_STORE);
+		for (i = 0; i < nonce_max; i++) {
+			if (memcmp(nonce->nd_opt_nonce,
+			    dp->dad_nonce[i],
+			    ND_OPT_NONCE_LEN) == 0)
+				break;
+		}
+		if (i < nonce_max) {
+			char ip6buf[INET6_ADDRSTRLEN];
+
+			log(LOG_DEBUG,
+			    "%s: detected a looped back NS message for %s\n",
+			    ifa->ifa_ifp ? if_name(ifa->ifa_ifp) : "???",
+			    IN6_PRINT(ip6buf, IFA_IN6(ifa)));
+			dp->dad_ns_lcount++;
+			continue;
+		}
+
+		break;
 	}
-	return NULL;
+	return dp;
 }
 
 static void
@@ -1149,7 +1204,7 @@ nd6_dad_start(struct ifaddr *ifa, int xt
 	dp = kmem_intr_alloc(sizeof(*dp), KM_NOSLEEP);
 
 	mutex_enter(&nd6_dad_lock);
-	if (nd6_dad_find(ifa) != NULL) {
+	if (nd6_dad_find(ifa, NULL) != NULL) {
 		mutex_exit(&nd6_dad_lock);
 		/* DAD already in progress */
 		if (dp != NULL)
@@ -1178,6 +1233,7 @@ nd6_dad_start(struct ifaddr *ifa, int xt
 	dp->dad_count = 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;
 	TAILQ_INSERT_TAIL(&dadq, (struct dadq *)dp, dad_list);
 
 	nd6log(LOG_DEBUG, "%s: starting DAD for %s\n", if_name(ifa->ifa_ifp),
@@ -1204,7 +1260,7 @@ nd6_dad_stop(struct ifaddr *ifa)
 		return;
 
 	mutex_enter(&nd6_dad_lock);
-	dp = nd6_dad_find(ifa);
+	dp = nd6_dad_find(ifa, NULL);
 	if (dp == NULL) {
 		mutex_exit(&nd6_dad_lock);
 		/* DAD wasn't started yet */
@@ -1340,9 +1396,10 @@ nd6_dad_duplicated(struct dadq *dp)
 	ifp = ifa->ifa_ifp;
 	ia = (struct in6_ifaddr *)ifa;
 	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(ifp), IN6_PRINT(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;
@@ -1396,6 +1453,7 @@ 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;
 
 	dp->dad_ns_tcount++;
 	if ((ifp->if_flags & IFF_UP) == 0) {
@@ -1412,12 +1470,15 @@ nd6_dad_ns_output(struct dadq *dp, struc
 	}
 
 	dp->dad_ns_tcount = 0;
+	nonce = dp->dad_nonce[dp->dad_ns_ocount % ND_OPT_NONCE_STORE];
+	cprng_fast(nonce, ND_OPT_NONCE_LEN);
 	dp->dad_ns_ocount++;
-	nd6_ns_output(ifp, NULL, &ia->ia_addr.sin6_addr, NULL, 1);
+
+	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 *nonce)
 {
 	struct in6_ifaddr *ia;
 	const struct in6_addr *taddr6;
@@ -1432,16 +1493,7 @@ nd6_dad_ns_input(struct ifaddr *ifa)
 	duplicate = 0;
 
 	mutex_enter(&nd6_dad_lock);
-	dp = nd6_dad_find(ifa);
-
-	/* Quickhack - completely ignore DAD NS packets */
-	if (dad_ignore_ns) {
-		char ip6buf[INET6_ADDRSTRLEN];
-		nd6log(LOG_INFO, "ignoring DAD NS packet for "
-		    "address %s(%s)\n", IN6_PRINT(ip6buf, taddr6),
-		    if_name(ifa->ifa_ifp));
-		return;
-	}
+	dp = nd6_dad_find(ifa, nonce);
 
 	/*
 	 * if I'm yet to start DAD, someone else started using this address
@@ -1473,7 +1525,7 @@ nd6_dad_na_input(struct ifaddr *ifa)
 	KASSERT(ifa != NULL);
 
 	mutex_enter(&nd6_dad_lock);
-	dp = nd6_dad_find(ifa);
+	dp = nd6_dad_find(ifa, NULL);
 	if (dp)
 		dp->dad_na_icount++;
 

Reply via email to