tech-net archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

IPv6 NS looback avoidance - RFC7527 support



Hi

Following on from my prior email, this new patch removes the limitation of only checking the most recent nonce sent. It can store 3 nonces (configurable at compile time) and if the user wants to send more probes, it will store the last 3. Currently we default to sending 1 probe and the storage for an extra 2 is just a nice to have.

Commentary and ideas on how to test it welcome.

Roy
Index: sys/netinet/icmp6.h
===================================================================
RCS file: /cvsroot/src/sys/netinet/icmp6.h,v
retrieving revision 1.49
diff -u -p -r1.49 icmp6.h
--- sys/netinet/icmp6.h	23 Jan 2018 10:55:38 -0000	1.49
+++ sys/netinet/icmp6.h	2 Mar 2018 12:29:07 -0000
@@ -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: sys/netinet6/nd6.c
===================================================================
RCS file: /cvsroot/src/sys/netinet6/nd6.c,v
retrieving revision 1.245
diff -u -p -r1.245 nd6.c
--- sys/netinet6/nd6.c	29 Jan 2018 19:51:15 -0000	1.245
+++ sys/netinet6/nd6.c	2 Mar 2018 12:29:08 -0000
@@ -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",
@@ -560,7 +561,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:
@@ -2426,7 +2427,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: sys/netinet6/nd6.h
===================================================================
RCS file: /cvsroot/src/sys/netinet6/nd6.h,v
retrieving revision 1.85
diff -u -p -r1.85 nd6.h
--- sys/netinet6/nd6.h	22 Jun 2017 09:24:02 -0000	1.85
+++ sys/netinet6/nd6.h	2 Mar 2018 12:29:08 -0000
@@ -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: sys/netinet6/nd6_nbr.c
===================================================================
RCS file: /cvsroot/src/sys/netinet6/nd6_nbr.c,v
retrieving revision 1.148
diff -u -p -r1.148 nd6_nbr.c
--- sys/netinet6/nd6_nbr.c	24 Feb 2018 07:53:15 -0000	1.148
+++ sys/netinet6/nd6_nbr.c	2 Mar 2018 12:29:08 -0000
@@ -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,15 @@ 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 */
+	uint8_t dad_nonce[ND_OPT_NONCE_STORE][ND_OPT_NONCE_LEN];
 };
 
 static struct dadq_head dadq;
@@ -1068,17 +1089,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 +1195,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 +1224,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 +1251,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 +1387,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 +1444,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 +1461,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 +1484,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 +1516,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++;
 


Home | Main Index | Thread Index | Old Index