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.
This commit is contained in:
roy 2020-09-15 10:05:36 +00:00
parent af1644547e
commit e53a363e2b
4 changed files with 99 additions and 39 deletions

View File

@ -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 type)
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;

View File

@ -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);
};

View File

@ -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(struct llentry *, union l3addr *);
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, __unused const union l3addr *daddr,
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;

View File

@ -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(struct llentry *, union l3addr *);
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);