From 36585b5662f434aefc7d52793679791f0ad73a3e Mon Sep 17 00:00:00 2001 From: hiren Date: Fri, 8 May 2015 08:35:06 +0000 Subject: [PATCH] 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 Sponsored by: Limelight Networks git-svn-id: svn://svn.freebsd.org/base/stable/10@282622 ccf9f872-aa2e-dd11-9fc8-001c23d0bc1f --- sbin/ifconfig/af_inet6.c | 4 + sbin/ifconfig/af_nd6.c | 3 +- sbin/ifconfig/ifconfig.8 | 16 +- sys/netinet/icmp6.h | 12 ++ sys/netinet6/in6.c | 3 +- sys/netinet6/nd6.c | 408 +++++++++++++++++-------------------- sys/netinet6/nd6.h | 28 ++- sys/netinet6/nd6_nbr.c | 425 ++++++++++++++++++++++----------------- 8 files changed, 481 insertions(+), 418 deletions(-) diff --git a/sbin/ifconfig/af_inet6.c b/sbin/ifconfig/af_inet6.c index 0f8688a8b..321bf5b8b 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 b3db0a820..3a510a557 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 ff97c85c5..82835fad1 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 15f4c2dfa..979c8fd65 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 609efa728..9a5a6ed5e 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 8052e8f15..94add9a97 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 25d8c5d1f..d664a94f9 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 8e317af31..d5f40f9ca 100644 --- a/sys/netinet6/nd6_nbr.c +++ b/sys/netinet6/nd6_nbr.c @@ -40,6 +40,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -48,9 +49,11 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include +#include #include #include @@ -60,6 +63,7 @@ __FBSDID("$FreeBSD$"); #ifdef RADIX_MPATH #include #endif +#include #include #include @@ -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); + } } -- 2.45.0