summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorhiren <hiren@FreeBSD.org>2015-05-08 08:35:06 +0000
committerhiren <hiren@FreeBSD.org>2015-05-08 08:35:06 +0000
commite545513c3ef78baddef748f9ea257633dad59fc9 (patch)
tree98338e19060cb1c8b08af2b15efd776dbe4afa7a
parentb48d3ee8e0b9264b5c609e743bda864f76948075 (diff)
downloadFreeBSD-src-e545513c3ef78baddef748f9ea257633dad59fc9.zip
FreeBSD-src-e545513c3ef78baddef748f9ea257633dad59fc9.tar.gz
MFC r261708, r261847, r268525, r274316, r274347, r275593,
r276844, r276847, r279531, r279559, r279564, r279676 A bunch of IPv6 fixes by melifaro, hrs and ae Major changes: Simplify nd6_output_lle() Add refcounting to DAD and fix races and other errors Implement Enhanced DAD algorithm for IPv6 Suggested by: ae Tested by: Jason Wolfe <j at nitrology.com> Sponsored by: Limelight Networks
-rw-r--r--sbin/ifconfig/af_inet6.c4
-rw-r--r--sbin/ifconfig/af_nd6.c3
-rw-r--r--sbin/ifconfig/ifconfig.816
-rw-r--r--sys/netinet/icmp6.h12
-rw-r--r--sys/netinet6/in6.c3
-rw-r--r--sys/netinet6/nd6.c408
-rw-r--r--sys/netinet6/nd6.h28
-rw-r--r--sys/netinet6/nd6_nbr.c425
8 files changed, 481 insertions, 418 deletions
diff --git a/sbin/ifconfig/af_inet6.c b/sbin/ifconfig/af_inet6.c
index 0f8688a..321bf5b 100644
--- a/sbin/ifconfig/af_inet6.c
+++ b/sbin/ifconfig/af_inet6.c
@@ -483,6 +483,10 @@ static struct cmd inet6_cmds[] = {
DEF_CMD("-auto_linklocal",-ND6_IFF_AUTO_LINKLOCAL,setnd6flags),
DEF_CMD("no_prefer_iface",ND6_IFF_NO_PREFER_IFACE,setnd6flags),
DEF_CMD("-no_prefer_iface",-ND6_IFF_NO_PREFER_IFACE,setnd6flags),
+ DEF_CMD("no_dad", ND6_IFF_NO_DAD, setnd6flags),
+ DEF_CMD("-no_dad", -ND6_IFF_NO_DAD, setnd6flags),
+ DEF_CMD("ignoreloop", ND6_IFF_IGNORELOOP, setnd6flags),
+ DEF_CMD("-ignoreloop", -ND6_IFF_IGNORELOOP, setnd6flags),
DEF_CMD_ARG("pltime", setip6pltime),
DEF_CMD_ARG("vltime", setip6vltime),
DEF_CMD("eui64", 0, setip6eui64),
diff --git a/sbin/ifconfig/af_nd6.c b/sbin/ifconfig/af_nd6.c
index b3db0a8..3a510a5 100644
--- a/sbin/ifconfig/af_nd6.c
+++ b/sbin/ifconfig/af_nd6.c
@@ -58,7 +58,8 @@ static const char rcsid[] =
#define MAX_SYSCTL_TRY 5
#define ND6BITS "\020\001PERFORMNUD\002ACCEPT_RTADV\003PREFER_SOURCE" \
"\004IFDISABLED\005DONT_SET_IFROUTE\006AUTO_LINKLOCAL" \
- "\007NO_RADR\010NO_PREFER_IFACE\020DEFAULTIF"
+ "\007NO_RADR\010NO_PREFER_IFACE\011IGNORELOOP\012NO_DAD" \
+ "\020DEFAULTIF"
static int isnd6defif(int);
void setnd6flags(const char *, int, int, const struct afswtch *);
diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8
index ff97c85..82835fa 100644
--- a/sbin/ifconfig/ifconfig.8
+++ b/sbin/ifconfig/ifconfig.8
@@ -28,7 +28,7 @@
.\" From: @(#)ifconfig.8 8.3 (Berkeley) 1/5/94
.\" $FreeBSD$
.\"
-.Dd September 9, 2014
+.Dd March 6, 2015
.Dt IFCONFIG 8
.Os
.Sh NAME
@@ -736,6 +736,20 @@ outgoing interface.
.It Cm -no_prefer_iface
Clear a flag
.Cm no_prefer_iface .
+.It Cm no_dad
+Set a flag to disable Duplicate Address Detection.
+.It Cm -no_dad
+Clear a flag
+.Cm no_dad .
+.It Cm ignoreloop
+Set a flag to disable loopback detection in Enhanced Duplicate Address
+Detection Algorithm.
+When this flag is set,
+Duplicate Address Detection will stop in a finite number of probings
+even if a loopback configuration is detected.
+.It Cm -ignoreloop
+Clear a flag
+.Cm ignoreloop .
.El
.Pp
The following parameters are specific for IPv6 addresses.
diff --git a/sys/netinet/icmp6.h b/sys/netinet/icmp6.h
index 15f4c2d..979c8fd 100644
--- a/sys/netinet/icmp6.h
+++ b/sys/netinet/icmp6.h
@@ -297,9 +297,11 @@ struct nd_opt_hdr { /* Neighbor discovery option header */
#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;
diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c
index 609efa7..9a5a6ed 100644
--- a/sys/netinet6/in6.c
+++ b/sys/netinet6/in6.c
@@ -2370,7 +2370,8 @@ in6if_do_dad(struct ifnet *ifp)
if ((ifp->if_flags & IFF_LOOPBACK) != 0)
return (0);
- if (ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED)
+ if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) ||
+ (ND_IFINFO(ifp)->flags & ND6_IFF_NO_DAD))
return (0);
switch (ifp->if_type) {
diff --git a/sys/netinet6/nd6.c b/sys/netinet6/nd6.c
index 8052e8f..94add9a 100644
--- a/sys/netinet6/nd6.c
+++ b/sys/netinet6/nd6.c
@@ -133,6 +133,10 @@ static int regen_tmpaddr(struct in6_ifaddr *);
static struct llentry *nd6_free(struct llentry *, int);
static void nd6_llinfo_timer(void *);
static void clear_llinfo_pqueue(struct llentry *);
+static int nd6_output_lle(struct ifnet *, struct ifnet *, struct mbuf *,
+ struct sockaddr_in6 *);
+static int nd6_output_ifp(struct ifnet *, struct ifnet *, struct mbuf *,
+ struct sockaddr_in6 *);
static VNET_DEFINE(struct callout, nd6_slowtimo_ch);
#define V_nd6_slowtimo_ch VNET(nd6_slowtimo_ch)
@@ -152,6 +156,8 @@ nd6_init(void)
callout_init(&V_nd6_slowtimo_ch, 0);
callout_reset(&V_nd6_slowtimo_ch, ND6_SLOWTIMER_INTERVAL * hz,
nd6_slowtimo, curvnet);
+
+ nd6_dad_init();
}
#ifdef VIMAGE
@@ -365,6 +371,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",
@@ -519,7 +526,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;
@@ -566,7 +573,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 */
@@ -578,7 +585,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);
@@ -1662,42 +1669,8 @@ nd6_cache_lladdr(struct ifnet *ifp, struct in6_addr *from, char *lladdr,
ln->ln_state = newstate;
if (ln->ln_state == ND6_LLINFO_STALE) {
- /*
- * XXX: since nd6_output() below will cause
- * state tansition to DELAY and reset the timer,
- * we must set the timer now, although it is actually
- * meaningless.
- */
- nd6_llinfo_settimer_locked(ln, (long)V_nd6_gctimer * hz);
-
- if (ln->la_hold) {
- struct mbuf *m_hold, *m_hold_next;
-
- /*
- * reset the la_hold in advance, to explicitly
- * prevent a la_hold lookup in nd6_output()
- * (wouldn't happen, though...)
- */
- for (m_hold = ln->la_hold, ln->la_hold = NULL;
- m_hold; m_hold = m_hold_next) {
- m_hold_next = m_hold->m_nextpkt;
- m_hold->m_nextpkt = NULL;
-
- /*
- * we assume ifp is not a p2p here, so
- * just set the 2nd argument as the
- * 1st one.
- */
- nd6_output_lle(ifp, ifp, m_hold, L3_ADDR_SIN6(ln), NULL, ln, &chain);
- }
- /*
- * If we have mbufs in the chain we need to do
- * deferred transmit. Copy the address from the
- * llentry before dropping the lock down below.
- */
- if (chain != NULL)
- memcpy(&sin6, L3_ADDR_SIN6(ln), sizeof(sin6));
- }
+ if (ln->la_hold != NULL)
+ nd6_grab_holdchain(ln, &chain, &sin6);
} else if (ln->ln_state == ND6_LLINFO_INCOMPLETE) {
/* probe right away */
nd6_llinfo_settimer_locked((void *)ln, 0);
@@ -1780,8 +1753,8 @@ nd6_cache_lladdr(struct ifnet *ifp, struct in6_addr *from, char *lladdr,
if (static_route)
ln = NULL;
}
- if (chain)
- nd6_output_flush(ifp, ifp, chain, &sin6, NULL);
+ if (chain != NULL)
+ nd6_flush_holdchain(ifp, ifp, chain, &sin6);
/*
* When the link-layer address of a router changes, select the
@@ -1849,55 +1822,159 @@ nd6_slowtimo(void *arg)
CURVNET_RESTORE();
}
-int
-nd6_output(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
- struct sockaddr_in6 *dst, struct rtentry *rt0)
+void
+nd6_grab_holdchain(struct llentry *ln, struct mbuf **chain,
+ struct sockaddr_in6 *sin6)
{
- return (nd6_output_lle(ifp, origifp, m0, dst, rt0, NULL, NULL));
+ LLE_WLOCK_ASSERT(ln);
+
+ *chain = ln->la_hold;
+ ln->la_hold = NULL;
+ memcpy(sin6, L3_ADDR_SIN6(ln), sizeof(*sin6));
+
+ if (ln->ln_state == ND6_LLINFO_STALE) {
+
+ /*
+ * The first time we send a packet to a
+ * neighbor whose entry is STALE, we have
+ * to change the state to DELAY and a sets
+ * a timer to expire in DELAY_FIRST_PROBE_TIME
+ * seconds to ensure do neighbor unreachability
+ * detection on expiration.
+ * (RFC 2461 7.3.3)
+ */
+ ln->la_asked = 0;
+ ln->ln_state = ND6_LLINFO_DELAY;
+ nd6_llinfo_settimer_locked(ln, (long)V_nd6_delay * hz);
+ }
}
+static int
+nd6_output_ifp(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m,
+ struct sockaddr_in6 *dst)
+{
+ int error;
+ int ip6len;
+ struct ip6_hdr *ip6;
+ struct m_tag *mtag;
+
+#ifdef MAC
+ mac_netinet6_nd6_send(ifp, m);
+#endif
+
+ /*
+ * If called from nd6_ns_output() (NS), nd6_na_output() (NA),
+ * icmp6_redirect_output() (REDIRECT) or from rip6_output() (RS, RA
+ * as handled by rtsol and rtadvd), mbufs will be tagged for SeND
+ * to be diverted to user space. When re-injected into the kernel,
+ * send_output() will directly dispatch them to the outgoing interface.
+ */
+ if (send_sendso_input_hook != NULL) {
+ mtag = m_tag_find(m, PACKET_TAG_ND_OUTGOING, NULL);
+ if (mtag != NULL) {
+ ip6 = mtod(m, struct ip6_hdr *);
+ ip6len = sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen);
+ /* Use the SEND socket */
+ error = send_sendso_input_hook(m, ifp, SND_OUT,
+ ip6len);
+ /* -1 == no app on SEND socket */
+ if (error == 0 || error != -1)
+ return (error);
+ }
+ }
+
+ m_clrprotoflags(m); /* Avoid confusing lower layers. */
+ IP_PROBE(send, NULL, NULL, mtod(m, struct ip6_hdr *), ifp, NULL,
+ mtod(m, struct ip6_hdr *));
+
+ if ((ifp->if_flags & IFF_LOOPBACK) == 0)
+ origifp = ifp;
+
+ error = (*ifp->if_output)(origifp, m, (struct sockaddr *)dst, NULL);
+ return (error);
+}
/*
- * Note that I'm not enforcing any global serialization
- * lle state or asked changes here as the logic is too
- * complicated to avoid having to always acquire an exclusive
- * lock
- * KMM
- *
+ * IPv6 packet output - light version.
+ * Checks if destination LLE exists and is in proper state
+ * (e.g no modification required). If not true, fall back to
+ * "heavy" version.
*/
-#define senderr(e) { error = (e); goto bad;}
-
int
-nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
- struct sockaddr_in6 *dst, struct rtentry *rt0, struct llentry *lle,
- struct mbuf **chain)
+nd6_output(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m,
+ struct sockaddr_in6 *dst, struct rtentry *rt0)
{
- struct mbuf *m = m0;
- struct m_tag *mtag;
- struct llentry *ln = lle;
- struct ip6_hdr *ip6;
- int error = 0;
- int flags = 0;
- int ip6len;
-
-#ifdef INVARIANTS
- if (lle != NULL) {
-
- LLE_WLOCK_ASSERT(lle);
+ struct llentry *ln = NULL;
- KASSERT(chain != NULL, (" lle locked but no mbuf chain pointer passed"));
+ /* discard the packet if IPv6 operation is disabled on the interface */
+ if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED)) {
+ m_freem(m);
+ return (ENETDOWN); /* better error? */
}
-#endif
+
if (IN6_IS_ADDR_MULTICAST(&dst->sin6_addr))
goto sendpkt;
if (nd6_need_cache(ifp) == 0)
goto sendpkt;
+ IF_AFDATA_RLOCK(ifp);
+ ln = nd6_lookup(&dst->sin6_addr, 0, ifp);
+ IF_AFDATA_RUNLOCK(ifp);
+
/*
- * next hop determination. This routine is derived from ether_output.
+ * Perform fast path for the following cases:
+ * 1) lle state is REACHABLE
+ * 2) lle state is DELAY (NS message sentNS message sent)
+ *
+ * Every other case involves lle modification, so we handle
+ * them separately.
*/
+ if (ln == NULL || (ln->ln_state != ND6_LLINFO_REACHABLE &&
+ ln->ln_state != ND6_LLINFO_DELAY)) {
+ /* Fall back to slow processing path */
+ if (ln != NULL)
+ LLE_RUNLOCK(ln);
+ return (nd6_output_lle(ifp, origifp, m, dst));
+ }
+
+sendpkt:
+ if (ln != NULL)
+ LLE_RUNLOCK(ln);
+
+ return (nd6_output_ifp(ifp, origifp, m, dst));
+}
+
+
+/*
+ * Output IPv6 packet - heavy version.
+ * Function assume that either
+ * 1) destination LLE does not exist, is invalid or stale, so
+ * ND6_EXCLUSIVE lock needs to be acquired
+ * 2) destination lle is provided (with ND6_EXCLUSIVE lock),
+ * in that case packets are queued in &chain.
+ *
+ */
+static int
+nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m,
+ struct sockaddr_in6 *dst)
+{
+ struct llentry *lle = NULL;
+ int flags = 0;
+
+ KASSERT(m != NULL, ("NULL mbuf, nothing to send"));
+ /* discard the packet if IPv6 operation is disabled on the interface */
+ if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED)) {
+ m_freem(m);
+ return (ENETDOWN); /* better error? */
+ }
+
+ if (IN6_IS_ADDR_MULTICAST(&dst->sin6_addr))
+ goto sendpkt;
+
+ if (nd6_need_cache(ifp) == 0)
+ goto sendpkt;
/*
* Address resolution or Neighbor Unreachability Detection
@@ -1905,48 +1982,43 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
* At this point, the destination of the packet must be a unicast
* or an anycast address(i.e. not a multicast).
*/
-
- flags = (lle != NULL) ? LLE_EXCLUSIVE : 0;
- if (ln == NULL) {
- retry:
+ if (lle == NULL) {
IF_AFDATA_RLOCK(ifp);
- ln = lla_lookup(LLTABLE6(ifp), flags, (struct sockaddr *)dst);
+ lle = nd6_lookup(&dst->sin6_addr, ND6_EXCLUSIVE, ifp);
IF_AFDATA_RUNLOCK(ifp);
- if ((ln == NULL) && nd6_is_addr_neighbor(dst, ifp)) {
+ if ((lle == NULL) && nd6_is_addr_neighbor(dst, ifp)) {
/*
* Since nd6_is_addr_neighbor() internally calls nd6_lookup(),
* the condition below is not very efficient. But we believe
* it is tolerable, because this should be a rare case.
*/
- flags = ND6_CREATE | (m ? ND6_EXCLUSIVE : 0);
+ flags = ND6_CREATE | ND6_EXCLUSIVE;
IF_AFDATA_LOCK(ifp);
- ln = nd6_lookup(&dst->sin6_addr, flags, ifp);
+ lle = nd6_lookup(&dst->sin6_addr, flags, ifp);
IF_AFDATA_UNLOCK(ifp);
}
}
- if (ln == NULL) {
+ if (lle == NULL) {
if ((ifp->if_flags & IFF_POINTOPOINT) == 0 &&
!(ND_IFINFO(ifp)->flags & ND6_IFF_PERFORMNUD)) {
char ip6buf[INET6_ADDRSTRLEN];
log(LOG_DEBUG,
"nd6_output: can't allocate llinfo for %s "
"(ln=%p)\n",
- ip6_sprintf(ip6buf, &dst->sin6_addr), ln);
- senderr(EIO); /* XXX: good error? */
+ ip6_sprintf(ip6buf, &dst->sin6_addr), lle);
+ m_freem(m);
+ return (ENOBUFS);
}
goto sendpkt; /* send anyway */
}
+ LLE_WLOCK_ASSERT(lle);
+
/* We don't have to do link-layer address resolution on a p2p link. */
if ((ifp->if_flags & IFF_POINTOPOINT) != 0 &&
- ln->ln_state < ND6_LLINFO_REACHABLE) {
- if ((flags & LLE_EXCLUSIVE) == 0) {
- flags |= LLE_EXCLUSIVE;
- LLE_RUNLOCK(ln);
- goto retry;
- }
- ln->ln_state = ND6_LLINFO_STALE;
- nd6_llinfo_settimer_locked(ln, (long)V_nd6_gctimer * hz);
+ lle->ln_state < ND6_LLINFO_REACHABLE) {
+ lle->ln_state = ND6_LLINFO_STALE;
+ nd6_llinfo_settimer_locked(lle, (long)V_nd6_gctimer * hz);
}
/*
@@ -1956,15 +2028,10 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
* neighbor unreachability detection on expiration.
* (RFC 2461 7.3.3)
*/
- if (ln->ln_state == ND6_LLINFO_STALE) {
- if ((flags & LLE_EXCLUSIVE) == 0) {
- flags |= LLE_EXCLUSIVE;
- LLE_RUNLOCK(ln);
- goto retry;
- }
- ln->la_asked = 0;
- ln->ln_state = ND6_LLINFO_DELAY;
- nd6_llinfo_settimer_locked(ln, (long)V_nd6_delay * hz);
+ if (lle->ln_state == ND6_LLINFO_STALE) {
+ lle->la_asked = 0;
+ lle->ln_state = ND6_LLINFO_DELAY;
+ nd6_llinfo_settimer_locked(lle, (long)V_nd6_delay * hz);
}
/*
@@ -1972,7 +2039,7 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
* (i.e. its link-layer address is already resolved), just
* send the packet.
*/
- if (ln->ln_state > ND6_LLINFO_INCOMPLETE)
+ if (lle->ln_state > ND6_LLINFO_INCOMPLETE)
goto sendpkt;
/*
@@ -1982,23 +2049,15 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
* does not exceed nd6_maxqueuelen. When it exceeds nd6_maxqueuelen,
* the oldest packet in the queue will be removed.
*/
- if (ln->ln_state == ND6_LLINFO_NOSTATE)
- ln->ln_state = ND6_LLINFO_INCOMPLETE;
-
- if ((flags & LLE_EXCLUSIVE) == 0) {
- flags |= LLE_EXCLUSIVE;
- LLE_RUNLOCK(ln);
- goto retry;
- }
-
- LLE_WLOCK_ASSERT(ln);
+ if (lle->ln_state == ND6_LLINFO_NOSTATE)
+ lle->ln_state = ND6_LLINFO_INCOMPLETE;
- if (ln->la_hold) {
+ if (lle->la_hold != NULL) {
struct mbuf *m_hold;
int i;
i = 0;
- for (m_hold = ln->la_hold; m_hold; m_hold = m_hold->m_nextpkt) {
+ for (m_hold = lle->la_hold; m_hold; m_hold = m_hold->m_nextpkt){
i++;
if (m_hold->m_nextpkt == NULL) {
m_hold->m_nextpkt = m;
@@ -2006,135 +2065,44 @@ nd6_output_lle(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *m0,
}
}
while (i >= V_nd6_maxqueuelen) {
- m_hold = ln->la_hold;
- ln->la_hold = ln->la_hold->m_nextpkt;
+ m_hold = lle->la_hold;
+ lle->la_hold = lle->la_hold->m_nextpkt;
m_freem(m_hold);
i--;
}
} else {
- ln->la_hold = m;
+ lle->la_hold = m;
}
/*
* If there has been no NS for the neighbor after entering the
* INCOMPLETE state, send the first solicitation.
*/
- if (!ND6_LLINFO_PERMANENT(ln) && ln->la_asked == 0) {
- ln->la_asked++;
+ if (!ND6_LLINFO_PERMANENT(lle) && lle->la_asked == 0) {
+ lle->la_asked++;
- nd6_llinfo_settimer_locked(ln,
+ nd6_llinfo_settimer_locked(lle,
(long)ND_IFINFO(ifp)->retrans * hz / 1000);
- LLE_WUNLOCK(ln);
- nd6_ns_output(ifp, NULL, &dst->sin6_addr, ln, 0);
- if (lle != NULL && ln == lle)
- LLE_WLOCK(lle);
-
- } else if (lle == NULL || ln != lle) {
- /*
- * We did the lookup (no lle arg) so we
- * need to do the unlock here.
- */
- LLE_WUNLOCK(ln);
+ LLE_WUNLOCK(lle);
+ 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);
}
return (0);
sendpkt:
- /* discard the packet if IPv6 operation is disabled on the interface */
- if ((ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED)) {
- error = ENETDOWN; /* better error? */
- goto bad;
- }
- /*
- * ln is valid and the caller did not pass in
- * an llentry
- */
- if ((ln != NULL) && (lle == NULL)) {
- if (flags & LLE_EXCLUSIVE)
- LLE_WUNLOCK(ln);
- else
- LLE_RUNLOCK(ln);
- }
+ if (lle != NULL)
+ LLE_WUNLOCK(lle);
-#ifdef MAC
- mac_netinet6_nd6_send(ifp, m);
-#endif
-
- /*
- * If called from nd6_ns_output() (NS), nd6_na_output() (NA),
- * icmp6_redirect_output() (REDIRECT) or from rip6_output() (RS, RA
- * as handled by rtsol and rtadvd), mbufs will be tagged for SeND
- * to be diverted to user space. When re-injected into the kernel,
- * send_output() will directly dispatch them to the outgoing interface.
- */
- if (send_sendso_input_hook != NULL) {
- mtag = m_tag_find(m, PACKET_TAG_ND_OUTGOING, NULL);
- if (mtag != NULL) {
- ip6 = mtod(m, struct ip6_hdr *);
- ip6len = sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen);
- /* Use the SEND socket */
- error = send_sendso_input_hook(m, ifp, SND_OUT,
- ip6len);
- /* -1 == no app on SEND socket */
- if (error == 0 || error != -1)
- return (error);
- }
- }
-
- /*
- * We were passed in a pointer to an lle with the lock held
- * this means that we can't call if_output as we will
- * recurse on the lle lock - so what we do is we create
- * a list of mbufs to send and transmit them in the caller
- * after the lock is dropped
- */
- if (lle != NULL) {
- if (*chain == NULL)
- *chain = m;
- else {
- struct mbuf *mb;
-
- /*
- * append mbuf to end of deferred chain
- */
- mb = *chain;
- while (mb->m_nextpkt != NULL)
- mb = mb->m_nextpkt;
- mb->m_nextpkt = m;
- }
- return (error);
- }
- m_clrprotoflags(m); /* Avoid confusing lower layers. */
- IP_PROBE(send, NULL, NULL, mtod(m, struct ip6_hdr *), ifp, NULL,
- mtod(m, struct ip6_hdr *));
- if ((ifp->if_flags & IFF_LOOPBACK) != 0) {
- return ((*ifp->if_output)(origifp, m, (struct sockaddr *)dst,
- NULL));
- }
- error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, NULL);
- return (error);
-
- bad:
- /*
- * ln is valid and the caller did not pass in
- * an llentry
- */
- if ((ln != NULL) && (lle == NULL)) {
- if (flags & LLE_EXCLUSIVE)
- LLE_WUNLOCK(ln);
- else
- LLE_RUNLOCK(ln);
- }
- if (m)
- m_freem(m);
- return (error);
+ return (nd6_output_ifp(ifp, origifp, m, dst));
}
-#undef senderr
int
-nd6_output_flush(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *chain,
- struct sockaddr_in6 *dst, struct route *ro)
+nd6_flush_holdchain(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *chain,
+ struct sockaddr_in6 *dst)
{
struct mbuf *m, *m_head;
struct ifnet *outifp;
@@ -2149,7 +2117,7 @@ nd6_output_flush(struct ifnet *ifp, struct ifnet *origifp, struct mbuf *chain,
while (m_head) {
m = m_head;
m_head = m_head->m_nextpkt;
- error = (*ifp->if_output)(ifp, m, (struct sockaddr *)dst, ro);
+ error = nd6_output_ifp(ifp, origifp, m, dst);
}
/*
diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h
index 25d8c5d..d664a94 100644
--- a/sys/netinet6/nd6.h
+++ b/sys/netinet6/nd6.h
@@ -87,6 +87,8 @@ struct nd_ifinfo {
#define ND6_IFF_AUTO_LINKLOCAL 0x20
#define ND6_IFF_NO_RADR 0x40
#define ND6_IFF_NO_PREFER_IFACE 0x80 /* XXX: not related to ND. */
+#define ND6_IFF_IGNORELOOP 0x100
+#define ND6_IFF_NO_DAD 0x200
#define ND6_CREATE LLE_CREATE
#define ND6_EXCLUSIVE LLE_EXCLUSIVE
@@ -359,7 +361,7 @@ VNET_DECLARE(int, ip6_temp_regen_advance); /* seconds */
#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 +369,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 +391,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
@@ -410,11 +423,10 @@ struct llentry *nd6_cache_lladdr(struct ifnet *, struct in6_addr *,
char *, int, int, int);
int nd6_output(struct ifnet *, struct ifnet *, struct mbuf *,
struct sockaddr_in6 *, struct rtentry *);
-int nd6_output_lle(struct ifnet *, struct ifnet *, struct mbuf *,
- struct sockaddr_in6 *, struct rtentry *, struct llentry *,
- struct mbuf **);
-int nd6_output_flush(struct ifnet *, struct ifnet *, struct mbuf *,
- struct sockaddr_in6 *, struct route *);
+void nd6_grab_holdchain(struct llentry *, struct mbuf **,
+ struct sockaddr_in6 *);
+int nd6_flush_holdchain(struct ifnet *, struct ifnet *, struct mbuf *,
+ struct sockaddr_in6 *);
int nd6_need_cache(struct ifnet *);
int nd6_storelladdr(struct ifnet *, struct mbuf *,
const struct sockaddr *, u_char *, struct llentry **);
@@ -425,11 +437,11 @@ void nd6_na_output(struct ifnet *, const struct in6_addr *,
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);
void nd6_dad_stop(struct ifaddr *);
-void nd6_dad_duplicated(struct ifaddr *);
/* nd6_rtr.c */
void nd6_rs_input(struct mbuf *, int, int);
diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c
index 8e317af..d5f40f9 100644
--- a/sys/netinet6/nd6_nbr.c
+++ b/sys/netinet6/nd6_nbr.c
@@ -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,9 +49,11 @@ __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>
+#include <sys/refcount.h>
#include <net/if.h>
#include <net/if_types.h>
@@ -60,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>
@@ -78,21 +82,32 @@ __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 *);
static void nd6_dad_starttimer(struct dadq *, int);
static void nd6_dad_stoptimer(struct dadq *);
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 void nd6_ns_output_fib(struct ifnet *, const struct in6_addr *,
+ const struct in6_addr *, struct llentry *, uint8_t *, u_int);
+
+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_ignore_ns) = 0; /* ignore NS in DAD
- - specwise incorrect */
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)
/*
@@ -316,7 +331,7 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
* 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;
}
@@ -377,12 +392,14 @@ nd6_ns_input(struct mbuf *m, int off, int icmp6len)
* 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)
+static void
+nd6_ns_output_fib(struct ifnet *ifp, const struct in6_addr *daddr6,
+ const struct in6_addr *taddr6, struct llentry *ln, uint8_t *nonce,
+ u_int fibnum)
{
struct mbuf *m;
struct m_tag *mtag;
@@ -404,12 +421,14 @@ nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6,
"%s: max_linkhdr + maxlen > MCLBYTES (%d + %d > %d)",
__func__, max_linkhdr, maxlen, MCLBYTES));
+
if (max_linkhdr + maxlen > MHLEN)
m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
else
m = m_gethdr(M_NOWAIT, MT_DATA);
if (m == NULL)
return;
+ M_SETFIB(m, fibnum);
bzero(&ro, sizeof(ro));
@@ -444,7 +463,7 @@ nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6,
if (in6_setscope(&ip6->ip6_dst, ifp, NULL) != 0)
goto bad;
}
- if (!dad) {
+ if (nonce == NULL) {
struct ifaddr *ifa;
/*
@@ -503,9 +522,8 @@ nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6,
NULL, &ro, NULL, &oifp, &src_in);
if (error) {
char ip6buf[INET6_ADDRSTRLEN];
- nd6log((LOG_DEBUG,
- "nd6_ns_output: source can't be "
- "determined: dst=%s, error=%d\n",
+ nd6log((LOG_DEBUG, "%s: source can't be "
+ "determined: dst=%s, error=%d\n", __func__,
ip6_sprintf(ip6buf, &dst_sa.sin6_addr),
error));
goto bad;
@@ -541,7 +559,7 @@ nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6,
* 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... */
@@ -555,7 +573,26 @@ nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6,
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 =
@@ -570,7 +607,8 @@ nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6,
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]);
@@ -588,6 +626,15 @@ nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6,
return;
}
+#ifndef BURN_BRIDGES
+void
+nd6_ns_output(struct ifnet *ifp, const struct in6_addr *daddr6,
+ const struct in6_addr *taddr6, struct llentry *ln, uint8_t *nonce)
+{
+
+ nd6_ns_output_fib(ifp, daddr6, taddr6, ln, nonce, RT_DEFAULT_FIB);
+}
+#endif
/*
* Neighbor advertisement input handling.
*
@@ -617,7 +664,6 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len)
struct llentry *ln = NULL;
union nd_opts ndopts;
struct mbuf *chain = NULL;
- struct m_tag *mtag;
struct sockaddr_in6 sin6;
char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
@@ -644,6 +690,7 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len)
is_router = ((flags & ND_NA_FLAG_ROUTER) != 0);
is_solicited = ((flags & ND_NA_FLAG_SOLICITED) != 0);
is_override = ((flags & ND_NA_FLAG_OVERRIDE) != 0);
+ memset(&sin6, 0, sizeof(sin6));
taddr6 = nd_na->nd_na_target;
if (in6_setscope(&taddr6, ifp, NULL))
@@ -882,43 +929,15 @@ nd6_na_input(struct mbuf *m, int off, int icmp6len)
* rt->rt_flags &= ~RTF_REJECT;
*/
ln->la_asked = 0;
- if (ln->la_hold) {
- struct mbuf *m_hold, *m_hold_next;
-
- /*
- * reset the la_hold in advance, to explicitly
- * prevent a la_hold lookup in nd6_output()
- * (wouldn't happen, though...)
- */
- for (m_hold = ln->la_hold, ln->la_hold = NULL;
- m_hold; m_hold = m_hold_next) {
- m_hold_next = m_hold->m_nextpkt;
- m_hold->m_nextpkt = NULL;
- /*
- * we assume ifp is not a loopback here, so just set
- * the 2nd argument as the 1st one.
- */
-
- if (send_sendso_input_hook != NULL) {
- mtag = m_tag_get(PACKET_TAG_ND_OUTGOING,
- sizeof(unsigned short), M_NOWAIT);
- if (mtag == NULL)
- goto bad;
- m_tag_prepend(m, mtag);
- }
-
- nd6_output_lle(ifp, ifp, m_hold, L3_ADDR_SIN6(ln), NULL, ln, &chain);
- }
- }
+ if (ln->la_hold != NULL)
+ nd6_grab_holdchain(ln, &chain, &sin6);
freeit:
- if (ln != NULL) {
- if (chain)
- memcpy(&sin6, L3_ADDR_SIN6(ln), sizeof(sin6));
+ if (ln != NULL)
LLE_WUNLOCK(ln);
- if (chain)
- nd6_output_flush(ifp, ifp, chain, &sin6, NULL);
- }
+ if (chain != NULL)
+ nd6_flush_holdchain(ifp, ifp, chain, &sin6);
+
if (checklink)
pfxlist_onlink_check();
@@ -1154,8 +1173,14 @@ 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 */
+ int dad_loopbackprobe; /* probing state for loopback detection */
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);
@@ -1163,23 +1188,54 @@ static VNET_DEFINE(struct rwlock, dad_rwlock);
#define V_dadq VNET(dadq)
#define V_dad_rwlock VNET(dad_rwlock)
-#define DADQ_LOCK_INIT() rw_init(&V_dad_rwlock, "nd6 DAD queue")
-#define DADQ_LOCK_DESTROY() rw_destroy(&V_dad_rwlock)
-#define DADQ_LOCK_INITIALIZED() rw_initialized(&V_dad_rwlock)
#define DADQ_RLOCK() rw_rlock(&V_dad_rwlock)
#define DADQ_RUNLOCK() rw_runlock(&V_dad_rwlock)
#define DADQ_WLOCK() rw_wlock(&V_dad_rwlock)
#define DADQ_WUNLOCK() rw_wunlock(&V_dad_rwlock)
+static void
+nd6_dad_add(struct dadq *dp)
+{
+
+ DADQ_WLOCK();
+ TAILQ_INSERT_TAIL(&V_dadq, dp, dad_list);
+ DADQ_WUNLOCK();
+}
+
+static void
+nd6_dad_del(struct dadq *dp)
+{
+
+ DADQ_WLOCK();
+ TAILQ_REMOVE(&V_dadq, dp, dad_list);
+ DADQ_WUNLOCK();
+ nd6_dad_rele(dp);
+}
+
static struct dadq *
-nd6_dad_find(struct ifaddr *ifa)
+nd6_dad_find(struct ifaddr *ifa, struct nd_opt_nonce *n)
{
struct dadq *dp;
DADQ_RLOCK();
- TAILQ_FOREACH(dp, &V_dadq, dad_list)
- if (dp->dad_ifa == ifa)
- 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) {
+ dp->dad_ns_lcount++;
+ continue;
+ }
+ refcount_acquire(&dp->dad_refcnt);
+ break;
+ }
DADQ_RUNLOCK();
return (dp);
@@ -1197,7 +1253,25 @@ static void
nd6_dad_stoptimer(struct dadq *dp)
{
- callout_stop(&dp->dad_timer_ch);
+ callout_drain(&dp->dad_timer_ch);
+}
+
+static void
+nd6_dad_rele(struct dadq *dp)
+{
+
+ if (refcount_release(&dp->dad_refcnt)) {
+ ifa_free(dp->dad_ifa);
+ free(dp, M_IP6NDP);
+ }
+}
+
+void
+nd6_dad_init(void)
+{
+
+ rw_init(&V_dad_rwlock, "nd6 DAD queue");
+ TAILQ_INIT(&V_dadq);
}
/*
@@ -1210,11 +1284,6 @@ nd6_dad_start(struct ifaddr *ifa, int delay)
struct dadq *dp;
char ip6buf[INET6_ADDRSTRLEN];
- if (DADQ_LOCK_INITIALIZED() == 0) {
- DADQ_LOCK_INIT();
- TAILQ_INIT(&V_dadq);
- }
-
/*
* If we don't need DAD, don't do it.
* There are several cases:
@@ -1244,12 +1313,13 @@ nd6_dad_start(struct ifaddr *ifa, int delay)
}
if (ND_IFINFO(ifa->ifa_ifp)->flags & ND6_IFF_IFDISABLED)
return;
- if (nd6_dad_find(ifa) != NULL) {
+ if ((dp = nd6_dad_find(ifa, NULL)) != NULL) {
/* DAD already in progress */
+ nd6_dad_rele(dp);
return;
}
- dp = malloc(sizeof(*dp), M_IP6NDP, M_NOWAIT);
+ dp = malloc(sizeof(*dp), M_IP6NDP, M_NOWAIT | M_ZERO);
if (dp == NULL) {
log(LOG_ERR, "nd6_dad_start: memory allocation failed for "
"%s(%s)\n",
@@ -1257,15 +1327,10 @@ nd6_dad_start(struct ifaddr *ifa, int delay)
ifa->ifa_ifp ? if_name(ifa->ifa_ifp) : "???");
return;
}
- bzero(dp, sizeof(*dp));
callout_init(&dp->dad_timer_ch, 0);
#ifdef VIMAGE
dp->dad_vnet = curvnet;
#endif
- DADQ_WLOCK();
- TAILQ_INSERT_TAIL(&V_dadq, (struct dadq *)dp, dad_list);
- DADQ_WUNLOCK();
-
nd6log((LOG_DEBUG, "%s: starting DAD for %s\n", if_name(ifa->ifa_ifp),
ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr)));
@@ -1276,10 +1341,13 @@ nd6_dad_start(struct ifaddr *ifa, int delay)
* (re)initialization.
*/
dp->dad_ifa = ifa;
- ifa_ref(ifa); /* just for safety */
+ ifa_ref(dp->dad_ifa);
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 = dp->dad_loopbackprobe = 0;
+ refcount_init(&dp->dad_refcnt, 1);
+ nd6_dad_add(dp);
if (delay == 0) {
nd6_dad_ns_output(dp, ifa);
nd6_dad_starttimer(dp,
@@ -1297,9 +1365,7 @@ nd6_dad_stop(struct ifaddr *ifa)
{
struct dadq *dp;
- if (DADQ_LOCK_INITIALIZED() == 0)
- return;
- dp = nd6_dad_find(ifa);
+ dp = nd6_dad_find(ifa, NULL);
if (!dp) {
/* DAD wasn't started yet */
return;
@@ -1307,12 +1373,16 @@ nd6_dad_stop(struct ifaddr *ifa)
nd6_dad_stoptimer(dp);
- DADQ_WLOCK();
- TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list);
- DADQ_WUNLOCK();
- free(dp, M_IP6NDP);
- dp = NULL;
- ifa_free(ifa);
+ /*
+ * The DAD queue entry may have been removed by nd6_dad_timer() while
+ * we were waiting for it to stop, so re-do the lookup.
+ */
+ nd6_dad_rele(dp);
+ if (nd6_dad_find(ifa, NULL) == NULL)
+ return;
+
+ nd6_dad_del(dp);
+ nd6_dad_rele(dp);
}
static void
@@ -1327,45 +1397,36 @@ nd6_dad_timer(struct dadq *dp)
/* Sanity check */
if (ia == NULL) {
log(LOG_ERR, "nd6_dad_timer: called with null parameter\n");
- goto done;
+ goto err;
}
if (ND_IFINFO(ifp)->flags & ND6_IFF_IFDISABLED) {
/* Do not need DAD for ifdisabled interface. */
- TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list);
log(LOG_ERR, "nd6_dad_timer: cancel DAD on %s because of "
"ND6_IFF_IFDISABLED.\n", ifp->if_xname);
- free(dp, M_IP6NDP);
- dp = NULL;
- ifa_free(ifa);
- goto done;
+ goto err;
}
if (ia->ia6_flags & IN6_IFF_DUPLICATED) {
log(LOG_ERR, "nd6_dad_timer: called with duplicated address "
"%s(%s)\n",
ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr),
ifa->ifa_ifp ? if_name(ifa->ifa_ifp) : "???");
- goto done;
+ goto err;
}
if ((ia->ia6_flags & IN6_IFF_TENTATIVE) == 0) {
log(LOG_ERR, "nd6_dad_timer: called with non-tentative address "
"%s(%s)\n",
ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr),
ifa->ifa_ifp ? if_name(ifa->ifa_ifp) : "???");
- goto done;
+ goto err;
}
- /* timeouted with IFF_{RUNNING,UP} check */
- if (dp->dad_ns_tcount > V_dad_maxtry) {
+ /* Stop DAD if the interface is down even after dad_maxtry attempts. */
+ if ((dp->dad_ns_tcount > V_dad_maxtry) &&
+ (((ifp->if_flags & IFF_UP) == 0) ||
+ ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0))) {
nd6log((LOG_INFO, "%s: could not run DAD, driver problem?\n",
if_name(ifa->ifa_ifp)));
-
- DADQ_WLOCK();
- TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list);
- DADQ_WUNLOCK();
- free(dp, M_IP6NDP);
- dp = NULL;
- ifa_free(ifa);
- goto done;
+ goto err;
}
/* Need more checks? */
@@ -1376,32 +1437,50 @@ nd6_dad_timer(struct dadq *dp)
nd6_dad_ns_output(dp, ifa);
nd6_dad_starttimer(dp,
(long)ND_IFINFO(ifa->ifa_ifp)->retrans * hz / 1000);
+ goto done;
} else {
/*
* We have transmitted sufficient number of DAD packets.
* See what we've got.
*/
- int duplicate;
-
- duplicate = 0;
-
- if (dp->dad_na_icount) {
+ if (dp->dad_ns_icount > 0 || dp->dad_na_icount > 0)
+ /* We've seen NS or NA, means DAD has failed. */
+ nd6_dad_duplicated(ifa, dp);
+ else if (V_dad_enhanced != 0 &&
+ dp->dad_ns_lcount > 0 &&
+ dp->dad_ns_lcount > dp->dad_loopbackprobe) {
/*
- * the check is in nd6_dad_na_input(),
- * but just in case
+ * A looped back probe is detected,
+ * Sec. 4.1 in draft-ietf-6man-enhanced-dad-13
+ * requires transmission of additional probes until
+ * the loopback condition becomes clear.
*/
- duplicate++;
- }
-
- if (dp->dad_ns_icount) {
- /* We've seen NS, means DAD has failed. */
- duplicate++;
- }
-
- if (duplicate) {
- /* (*dp) will be freed in nd6_dad_duplicated() */
- dp = NULL;
- nd6_dad_duplicated(ifa);
+ log(LOG_ERR, "%s: a looped back NS message is "
+ "detected during DAD for %s. "
+ "Another DAD probes are being sent.\n",
+ if_name(ifa->ifa_ifp),
+ ip6_sprintf(ip6buf, IFA_IN6(ifa)));
+ dp->dad_loopbackprobe = dp->dad_ns_lcount;
+ /*
+ * An interface with IGNORELOOP is one which a
+ * loopback is permanently expected while regular
+ * traffic works. In that case, stop DAD after
+ * MAX_MULTICAST_SOLICIT number of NS messages
+ * regardless of the number of received loopback NS
+ * by increasing dad_loopbackprobe in advance.
+ */
+ if (ND_IFINFO(ifa->ifa_ifp)->flags & ND6_IFF_IGNORELOOP)
+ dp->dad_loopbackprobe += V_nd6_mmaxtries;
+ /*
+ * Send an NS immediately and increase dad_count by
+ * V_nd6_mmaxtries - 1.
+ */
+ nd6_dad_ns_output(dp, ifa);
+ dp->dad_count =
+ dp->dad_ns_ocount + V_nd6_mmaxtries - 1;
+ nd6_dad_starttimer(dp,
+ (long)ND_IFINFO(ifa->ifa_ifp)->retrans * hz / 1000);
+ goto done;
} else {
/*
* We are done with DAD. No NA came, no NS came.
@@ -1416,45 +1495,36 @@ nd6_dad_timer(struct dadq *dp)
"%s: DAD complete for %s - no duplicates found\n",
if_name(ifa->ifa_ifp),
ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr)));
-
- DADQ_WLOCK();
- TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list);
- DADQ_WUNLOCK();
- free(dp, M_IP6NDP);
- dp = NULL;
- ifa_free(ifa);
+ if (dp->dad_ns_lcount > 0)
+ log(LOG_ERR, "%s: DAD completed while "
+ "a looped back NS message is detected "
+ "during DAD for %s.\n",
+ if_name(ifa->ifa_ifp),
+ ip6_sprintf(ip6buf, IFA_IN6(ifa)));
}
}
-
+err:
+ nd6_dad_del(dp);
done:
CURVNET_RESTORE();
}
-void
-nd6_dad_duplicated(struct ifaddr *ifa)
+static void
+nd6_dad_duplicated(struct ifaddr *ifa, struct dadq *dp)
{
struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa;
struct ifnet *ifp;
- struct dadq *dp;
char ip6buf[INET6_ADDRSTRLEN];
- dp = nd6_dad_find(ifa);
- if (dp == NULL) {
- log(LOG_ERR, "nd6_dad_duplicated: DAD structure not found\n");
- return;
- }
-
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;
- /* We are done with DAD, with duplicate address found. (failure) */
- nd6_dad_stoptimer(dp);
-
ifp = ifa->ifa_ifp;
log(LOG_ERR, "%s: DAD complete for %s - duplicate found\n",
if_name(ifp), ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr));
@@ -1495,13 +1565,6 @@ nd6_dad_duplicated(struct ifaddr *ifa)
break;
}
}
-
- DADQ_WLOCK();
- TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list);
- DADQ_WUNLOCK();
- free(dp, M_IP6NDP);
- dp = NULL;
- ifa_free(ifa);
}
static void
@@ -1509,6 +1572,7 @@ nd6_dad_ns_output(struct dadq *dp, struct ifaddr *ifa)
{
struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa;
struct ifnet *ifp = ifa->ifa_ifp;
+ int i;
dp->dad_ns_tcount++;
if ((ifp->if_flags & IFF_UP) == 0) {
@@ -1519,17 +1583,29 @@ nd6_dad_ns_output(struct dadq *dp, struct ifaddr *ifa)
}
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();
+ /*
+ * 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.
+ */
+ }
+ nd6_ns_output(ifp, NULL, &ia->ia_addr.sin6_addr, NULL,
+ (uint8_t *)&dp->dad_nonce[0]);
}
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;
const struct in6_addr *taddr6;
struct dadq *dp;
- int duplicate;
if (ifa == NULL)
panic("ifa == NULL in nd6_dad_ns_input");
@@ -1537,39 +1613,15 @@ nd6_dad_ns_input(struct ifaddr *ifa)
ia = (struct in6_ifaddr *)ifa;
ifp = ifa->ifa_ifp;
taddr6 = &ia->ia_addr.sin6_addr;
- duplicate = 0;
- dp = nd6_dad_find(ifa);
-
- /* 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)));
+ /* 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;
- }
-
- /*
- * if I'm yet to start DAD, someone else started using this address
- * first. I have a duplicate and you win.
- */
- if (dp == NULL || dp->dad_ns_ocount == 0)
- duplicate++;
-
- /* XXX more checks for loopback situation - see nd6_dad_timer too */
- if (duplicate) {
- dp = NULL; /* will be freed in nd6_dad_duplicated() */
- nd6_dad_duplicated(ifa);
- } else {
- /*
- * not sure if I got a duplicate.
- * increment ns count and see what happens.
- */
- if (dp)
- dp->dad_ns_icount++;
- }
+ dp->dad_ns_icount++;
+ nd6_dad_rele(dp);
}
static void
@@ -1580,10 +1632,9 @@ nd6_dad_na_input(struct ifaddr *ifa)
if (ifa == NULL)
panic("ifa == NULL in nd6_dad_na_input");
- dp = nd6_dad_find(ifa);
- if (dp)
+ dp = nd6_dad_find(ifa, NULL);
+ if (dp != NULL) {
dp->dad_na_icount++;
-
- /* remove the address. */
- nd6_dad_duplicated(ifa);
+ nd6_dad_rele(dp);
+ }
}
OpenPOWER on IntegriCloud