Module Name:    src
Committed By:   roy
Date:           Tue Sep 15 10:05:37 UTC 2020

Modified Files:
        src/sys/net: nd.c nd.h
        src/sys/netinet: if_arp.c
        src/sys/netinet6: nd6.c

Log Message:
Implement RFC 7048, making Neighbor Unreachability Detection less impatient

RFC 7048 Section 3 says in the UNREACHABLE state packets continue to be
sent to the link-layer address and then backoff exponentially.
We adjust this slightly and move to the INCOMPLETE state after
`nd_mmaxtries` probes and then start backing off.

This results in simpler code whilst providing a more robust model which
doubles the time to failure over what we did before.
We don't want to be back to the old ARP model where no unreachability
errors are returned because very few applications would look at
unreachability hints provided such as ND_LLINFO_UNREACHABLE or RTM_MISS.


To generate a diff of this commit:
cvs rdiff -u -r1.2 -r1.3 src/sys/net/nd.c src/sys/net/nd.h
cvs rdiff -u -r1.296 -r1.297 src/sys/netinet/if_arp.c
cvs rdiff -u -r1.273 -r1.274 src/sys/netinet6/nd6.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/net/nd.c
diff -u src/sys/net/nd.c:1.2 src/sys/net/nd.c:1.3
--- src/sys/net/nd.c:1.2	Mon Sep 14 15:09:57 2020
+++ src/sys/net/nd.c	Tue Sep 15 10:05:36 2020
@@ -1,4 +1,4 @@
-/* $NetBSD: */
+/*	$NetBSD: nd.c,v 1.3 2020/09/15 10:05:36 roy Exp $	*/
 
 /*
  * Copyright (c) 2020 The NetBSD Foundation, Inc.
@@ -28,7 +28,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: nd.c,v 1.2 2020/09/14 15:09:57 roy Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nd.c,v 1.3 2020/09/15 10:05:36 roy Exp $");
 
 #include <sys/callout.h>
 #include <sys/mbuf.h>
@@ -56,7 +56,8 @@ nd_timer(void *arg)
 	struct ifnet *ifp = NULL;
 	struct psref psref;
 	struct mbuf *m = NULL;
-	bool send_ns = false, missed = false;
+	bool send_ns = false;
+	uint16_t missed = 0;
 	union l3addr taddr, *daddrp = NULL;
 
 	SOFTNET_KERNEL_LOCK_UNLESS_NET_MPSAFE();
@@ -84,10 +85,9 @@ nd_timer(void *arg)
 		break;
 
 	case ND_LLINFO_INCOMPLETE:
-		if (ln->ln_asked++ < nd->nd_mmaxtries) {
-			send_ns = true;
+		send_ns = true;
+		if (ln->ln_asked++ < nd->nd_mmaxtries)
 			break;
-		}
 
 		if (ln->ln_hold) {
 			struct mbuf *m0, *mnxt;
@@ -107,12 +107,8 @@ nd_timer(void *arg)
 			ln->ln_hold = NULL;
 		}
 
-		missed = true;
+		missed = ND_LLINFO_INCOMPLETE;
 		ln->ln_state = ND_LLINFO_WAITDELETE;
-		if (ln->ln_asked == nd->nd_mmaxtries)
-			nd_set_timer(ln, ND_TIMER_RETRANS);
-		else
-			send_ns = true;
 		break;
 
 	case ND_LLINFO_REACHABLE:
@@ -144,23 +140,50 @@ nd_timer(void *arg)
 		break;
 
 	case ND_LLINFO_PROBE:
-		if (ln->ln_asked < nd->nd_umaxtries) {
-			ln->ln_asked++;
-			send_ns = true;
+		send_ns = true;
+		if (ln->ln_asked++ < nd->nd_umaxtries) {
 			daddrp = &taddr;
 		} else {
-			LLE_REMREF(ln);
-			nd->nd_free(ln, 0);
-			ln = NULL;
+			ln->ln_state = ND_LLINFO_UNREACHABLE;
+			ln->ln_asked = 1;
+			missed = ND_LLINFO_PROBE;
+			/* nd_missed() consumers can use missed to know if
+			 * they need to send ICMP UNREACHABLE or not. */
 		}
 		break;
+	case ND_LLINFO_UNREACHABLE:
+		/*
+		 * RFC 7048 Section 3 says in the UNREACHABLE state
+		 * packets continue to be sent to the link-layer address and
+		 * then backoff exponentially.
+		 * We adjust this slightly and move to the INCOMPLETE state
+		 * after nd_mmaxtries probes and then start backing off.
+		 *
+		 * This results in simpler code whilst providing a more robust
+		 * model which doubles the time to failure over what we did
+		 * before. We don't want to be back to the old ARP model where
+		 * no unreachability errors are returned because very
+		 * few applications would look at unreachability hints provided
+		 * such as ND_LLINFO_UNREACHABLE or RTM_MISS.
+		 */
+		send_ns = true;
+		if (ln->ln_asked++ < nd->nd_mmaxtries)
+			break;
+
+		missed = ND_LLINFO_UNREACHABLE;
+		ln->ln_state = ND_LLINFO_WAITDELETE;
+		ln->la_flags &= ~LLE_VALID;
+		break;
 	}
 
 	if (send_ns) {
 		uint8_t lladdr[255], *lladdrp;
 		union l3addr src, *psrc;
 
-		nd_set_timer(ln, ND_TIMER_RETRANS);
+		if (ln->ln_state == ND_LLINFO_WAITDELETE)
+			nd_set_timer(ln, ND_TIMER_RETRANS_BACKOFF);
+		else
+			nd_set_timer(ln, ND_TIMER_RETRANS);
 		if (ln->ln_state > ND_LLINFO_INCOMPLETE &&
 		    ln->la_flags & LLE_VALID)
 		{
@@ -181,7 +204,7 @@ out:
 	SOFTNET_KERNEL_UNLOCK_UNLESS_NET_MPSAFE();
 
 	if (missed)
-		nd->nd_missed(ifp, &taddr, m);
+		nd->nd_missed(ifp, &taddr, missed, m);
 	if (ifp != NULL)
 		if_release(ifp, &psref);
 }
@@ -241,6 +264,22 @@ nd_set_timer(struct llentry *ln, int typ
 	case ND_TIMER_RETRANS:
 		xtick = nd->nd_retrans(ifp) * hz / 1000;
 		break;
+	case ND_TIMER_RETRANS_BACKOFF:
+	{
+		unsigned int retrans = nd->nd_retrans(ifp);
+		unsigned int attempts = ln->ln_asked - nd->nd_mmaxtries;
+
+		xtick = retrans;
+		while (attempts-- != 0) {
+			xtick *= nd->nd_retransmultiple;
+			if (xtick > nd->nd_maxretrans || xtick < retrans) {
+				xtick = nd->nd_maxretrans;
+				break;
+			}
+		}
+		xtick = xtick * hz / 1000;
+		break;
+	}
 	case ND_TIMER_REACHABLE:
 		xtick = nd->nd_reachable(ifp) * hz / 1000;
 		break;
Index: src/sys/net/nd.h
diff -u src/sys/net/nd.h:1.2 src/sys/net/nd.h:1.3
--- src/sys/net/nd.h:1.2	Mon Sep 14 15:09:57 2020
+++ src/sys/net/nd.h	Tue Sep 15 10:05:36 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd.h,v 1.2 2020/09/14 15:09:57 roy Exp $	*/
+/*	$NetBSD: nd.h,v 1.3 2020/09/15 10:05:36 roy Exp $	*/
 
 /*
  * Copyright (c) 2020 The NetBSD Foundation, Inc.
@@ -39,6 +39,7 @@
 #define	ND_LLINFO_STALE		2
 #define	ND_LLINFO_DELAY		3
 #define	ND_LLINFO_PROBE		4
+#define	ND_LLINFO_UNREACHABLE	5
 
 #ifdef _KERNEL
 #define	ND_IS_LLINFO_PROBREACH(ln)	\
@@ -47,18 +48,21 @@
 	(((ln)->ln_expire == 0) && ((ln)->ln_state > ND_LLINFO_INCOMPLETE))
 
 /* ND timer types */
-#define	ND_TIMER_IMMEDIATE	0
-#define	ND_TIMER_TICK		1
-#define	ND_TIMER_REACHABLE	2
-#define	ND_TIMER_RETRANS	3
-#define	ND_TIMER_EXPIRE		4
-#define	ND_TIMER_DELAY		5
-#define	ND_TIMER_GC		6
+#define	ND_TIMER_IMMEDIATE		0
+#define	ND_TIMER_TICK			1
+#define	ND_TIMER_REACHABLE		2
+#define	ND_TIMER_RETRANS		3
+#define	ND_TIMER_RETRANS_BACKOFF	4
+#define	ND_TIMER_EXPIRE			5
+#define	ND_TIMER_DELAY			6
+#define	ND_TIMER_GC			7
 
 /* node constants */
 #define	MAX_REACHABLE_TIME		3600000	/* msec */
 #define	REACHABLE_TIME			30000	/* msec */
 #define	RETRANS_TIMER			1000	/* msec */
+#define	MAX_RETRANS_TIMER		60000	/* msec */
+#define	BACKOFF_MULTIPLE		3
 #define	MIN_RANDOM_FACTOR		512	/* 1024 * 0.5 */
 #define	MAX_RANDOM_FACTOR		1536	/* 1024 * 1.5 */
 #define	ND_COMPUTE_RTIME(x) \
@@ -70,6 +74,8 @@ struct nd_domain {
 	int nd_delay;		/* delay first probe time in seconds */
 	int nd_mmaxtries;	/* maximum multicast query */
 	int nd_umaxtries;	/* maximum unicast query */
+	int nd_retransmultiple;	/* retransmission multiplier for backoff */
+	int nd_maxretrans;	/* maximum retransmission time in msec */
 	int nd_maxnudhint;	/* max # of subsequent upper layer hints */
 	int nd_maxqueuelen;	/* max # of packets in unresolved ND entries */
 	bool (*nd_nud_enabled)(struct ifnet *);
@@ -78,7 +84,8 @@ struct nd_domain {
 	union l3addr *(*nd_holdsrc)(struct llentry *, union l3addr *);
 	void (*nd_output)(struct ifnet *, const union l3addr *,
 	    const union l3addr *, const uint8_t *, const union l3addr *);
-	void (*nd_missed)(struct ifnet *, const union l3addr *, struct mbuf *);
+	void (*nd_missed)(struct ifnet *, const union l3addr *,
+	    int16_t, struct mbuf *);
 	void (*nd_free)(struct llentry *, int);
 };
 

Index: src/sys/netinet/if_arp.c
diff -u src/sys/netinet/if_arp.c:1.296 src/sys/netinet/if_arp.c:1.297
--- src/sys/netinet/if_arp.c:1.296	Mon Sep 14 15:09:57 2020
+++ src/sys/netinet/if_arp.c	Tue Sep 15 10:05:36 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: if_arp.c,v 1.296 2020/09/14 15:09:57 roy Exp $	*/
+/*	$NetBSD: if_arp.c,v 1.297 2020/09/15 10:05:36 roy Exp $	*/
 
 /*
  * Copyright (c) 1998, 2000, 2008 The NetBSD Foundation, Inc.
@@ -68,7 +68,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: if_arp.c,v 1.296 2020/09/14 15:09:57 roy Exp $");
+__KERNEL_RCSID(0, "$NetBSD: if_arp.c,v 1.297 2020/09/15 10:05:36 roy Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_ddb.h"
@@ -145,7 +145,7 @@ static union l3addr *arp_llinfo_holdsrc(
 static void arp_llinfo_output(struct ifnet *, const union l3addr *,
     const union l3addr *, const uint8_t *, const union l3addr *);
 static void arp_llinfo_missed(struct ifnet *, const union l3addr *,
-    struct mbuf *);
+    int16_t, struct mbuf *);
 static void arp_free(struct llentry *, int);
 
 static struct nd_domain arp_nd_domain = {
@@ -153,6 +153,8 @@ static struct nd_domain arp_nd_domain = 
 	.nd_delay = 5,		/* delay first probe time 5 second */
 	.nd_mmaxtries = 3,	/* maximum broadcast query */
 	.nd_umaxtries = 3,	/* maximum unicast query */
+	.nd_retransmultiple = BACKOFF_MULTIPLE,
+	.nd_maxretrans = MAX_RETRANS_TIMER,
 	.nd_maxnudhint = 0,	/* max # of subsequent upper layer hints */
 	.nd_maxqueuelen = 1,	/* max # of packets in unresolved ND entries */
 	.nd_nud_enabled = arp_nud_enabled,
@@ -1374,7 +1376,7 @@ arp_llinfo_output(struct ifnet *ifp, __u
 
 static void
 arp_llinfo_missed(struct ifnet *ifp, const union l3addr *taddr,
-    struct mbuf *m)
+    __unused int16_t type, struct mbuf *m)
 {
 	struct in_addr mdaddr = zeroin_addr;
 	struct sockaddr_in dsin, tsin;

Index: src/sys/netinet6/nd6.c
diff -u src/sys/netinet6/nd6.c:1.273 src/sys/netinet6/nd6.c:1.274
--- src/sys/netinet6/nd6.c:1.273	Mon Sep 14 15:09:57 2020
+++ src/sys/netinet6/nd6.c	Tue Sep 15 10:05:36 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: nd6.c,v 1.273 2020/09/14 15:09:57 roy Exp $	*/
+/*	$NetBSD: nd6.c,v 1.274 2020/09/15 10:05:36 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.273 2020/09/14 15:09:57 roy Exp $");
+__KERNEL_RCSID(0, "$NetBSD: nd6.c,v 1.274 2020/09/15 10:05:36 roy Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_compat_netbsd.h"
@@ -111,7 +111,7 @@ static union l3addr *nd6_llinfo_holdsrc(
 static void nd6_llinfo_output(struct ifnet *, const union l3addr *,
     const union l3addr *, const uint8_t *, const union l3addr *);
 static void nd6_llinfo_missed(struct ifnet *, const union l3addr *,
-    struct mbuf *);
+    int16_t, struct mbuf *);
 static void nd6_timer(void *);
 static void nd6_timer_work(struct work *, void *);
 static struct nd_opt_hdr *nd6_option(union nd_opts *);
@@ -126,6 +126,8 @@ struct nd_domain nd6_nd_domain = {
 	.nd_delay = 5,		/* delay first probe time 5 second */
 	.nd_mmaxtries = 3,	/* maximum unicast query */
 	.nd_umaxtries = 3,	/* maximum multicast query */
+	.nd_retransmultiple = BACKOFF_MULTIPLE,
+	.nd_maxretrans = MAX_RETRANS_TIMER,
 	.nd_maxnudhint = 0,	/* max # of subsequent upper layer hints */
 	.nd_maxqueuelen = 1,	/* max # of packets in unresolved ND entries */
 	.nd_nud_enabled = nd6_nud_enabled,
@@ -411,15 +413,25 @@ nd6_llinfo_retrans(struct ifnet *ifp)
 }
 
 static void
-nd6_llinfo_missed(struct ifnet *ifp, const union l3addr *taddr, struct mbuf *m)
+nd6_llinfo_missed(struct ifnet *ifp, const union l3addr *taddr,
+    int16_t type, struct mbuf *m)
 {
 	struct in6_addr mdaddr6 = zeroin6_addr;
 	struct sockaddr_in6 dsin6, tsin6;
 	struct sockaddr *sa;
 
-	if (m != NULL)
-		icmp6_error2(m, ICMP6_DST_UNREACH,
-		    ICMP6_DST_UNREACH_ADDR, 0, ifp, &mdaddr6);
+	if (m != NULL) {
+		if (type == ND_LLINFO_PROBE) {
+			struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *);
+
+			/* XXX pullup? */
+			if (sizeof(*ip6) < m->m_len)
+				mdaddr6 = ip6->ip6_src;
+			m_freem(m);
+		} else
+			icmp6_error2(m, ICMP6_DST_UNREACH,
+			    ICMP6_DST_UNREACH_ADDR, 0, ifp, &mdaddr6);
+	}
 	if (!IN6_IS_ADDR_UNSPECIFIED(&mdaddr6)) {
 		sockaddr_in6_init(&dsin6, &mdaddr6, 0, 0, 0);
 		sa = sin6tosa(&dsin6);

Reply via email to