Index: sys/netinet6/nd6_nbr.c =================================================================== --- sys/netinet6/nd6_nbr.c +++ sys/netinet6/nd6_nbr.c @@ -84,9 +84,9 @@ 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, int); +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_timer(void *); static void nd6_dad_duplicated(struct ifaddr *, struct dadq *); static void nd6_dad_ns_output(struct dadq *); static void nd6_dad_ns_input(struct ifaddr *, struct nd_opt_nonce *); @@ -1135,26 +1135,31 @@ #define V_dadq VNET(dadq) #define V_dad_rwlock VNET(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) +#define DADQ_LOCKPTR() (&V_dad_rwlock) +#define DADQ_LOCK_INIT() rw_init(DADQ_LOCKPTR(), "nd6 DAD queue") +#define DADQ_RLOCK() rw_rlock(DADQ_LOCKPTR()) +#define DADQ_RUNLOCK() rw_runlock(DADQ_LOCKPTR()) +#define DADQ_WLOCK() rw_wlock(DADQ_LOCKPTR()) +#define DADQ_WUNLOCK() rw_wunlock(DADQ_LOCKPTR()) + +#define DADQ_LOCK_ASSERT() rw_assert(DADQ_LOCKPTR(), RA_LOCKED); +#define DADQ_RLOCK_ASSERT() rw_assert(DADQ_LOCKPTR(), RA_RLOCKED); +#define DADQ_WLOCK_ASSERT() rw_assert(DADQ_LOCKPTR(), RA_WLOCKED); static void nd6_dad_add(struct dadq *dp) { + DADQ_WLOCK_ASSERT(); - DADQ_WLOCK(); TAILQ_INSERT_TAIL(&V_dadq, dp, dad_list); dp->dad_ondadq = true; - DADQ_WUNLOCK(); } static void nd6_dad_del(struct dadq *dp) { + DADQ_WLOCK_ASSERT(); - DADQ_WLOCK(); if (dp->dad_ondadq) { /* * Remove dp from the dadq and release the dadq's @@ -1162,10 +1167,8 @@ */ TAILQ_REMOVE(&V_dadq, dp, dad_list); dp->dad_ondadq = false; - DADQ_WUNLOCK(); nd6_dad_rele(dp); - } else - DADQ_WUNLOCK(); + } } static struct dadq * @@ -1173,10 +1176,12 @@ { struct dadq *dp; - DADQ_RLOCK(); + DADQ_LOCK_ASSERT(); + 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 @@ -1185,42 +1190,35 @@ 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) { + ND_OPT_NONCE_LEN) == 0) { dp->dad_ns_lcount++; continue; } - refcount_acquire(&dp->dad_refcnt); break; } - DADQ_RUNLOCK(); return (dp); } static void -nd6_dad_starttimer(struct dadq *dp, int ticks, int send_ns) +nd6_dad_starttimer(struct dadq *dp, int ticks) { + DADQ_WLOCK_ASSERT(); - NET_EPOCH_ASSERT(); - - if (send_ns != 0) - nd6_dad_ns_output(dp); - callout_reset(&dp->dad_timer_ch, ticks, - (void (*)(void *))nd6_dad_timer, (void *)dp); + callout_reset(&dp->dad_timer_ch, ticks, nd6_dad_timer, dp); } static void nd6_dad_stoptimer(struct dadq *dp) { - callout_drain(&dp->dad_timer_ch); } static void nd6_dad_rele(struct dadq *dp) { - if (refcount_release(&dp->dad_refcnt)) { + KASSERT(!dp->dad_ondadq, ("dp %p still on DAD queue", dp)); ifa_free(dp->dad_ifa); free(dp, M_IP6NDP); } @@ -1229,8 +1227,7 @@ void nd6_dad_init(void) { - - rw_init(&V_dad_rwlock, "nd6 DAD queue"); + DADQ_LOCK_INIT(); TAILQ_INIT(&V_dadq); } @@ -1243,7 +1240,6 @@ struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa; struct dadq *dp; char ip6buf[INET6_ADDRSTRLEN]; - struct epoch_tracker et; KASSERT((ia->ia6_flags & IN6_IFF_TENTATIVE) != 0, ("starting DAD on non-tentative address %p", ifa)); @@ -1265,12 +1261,13 @@ (ND_IFINFO(ifa->ifa_ifp)->flags & ND6_IFF_IFDISABLED) != 0) return; + DADQ_WLOCK(); if ((dp = nd6_dad_find(ifa, NULL)) != NULL) { /* * DAD is already in progress. Let the existing entry * finish it. */ - nd6_dad_rele(dp); + DADQ_WUNLOCK(); return; } @@ -1282,7 +1279,8 @@ ifa->ifa_ifp ? if_name(ifa->ifa_ifp) : "???"); return; } - callout_init(&dp->dad_timer_ch, 0); + callout_init_rw(&dp->dad_timer_ch, DADQ_LOCKPTR(), + CALLOUT_RETURNUNLOCKED); #ifdef VIMAGE dp->dad_vnet = curvnet; #endif @@ -1305,9 +1303,8 @@ /* Add this to the dadq and add a reference for the dadq. */ refcount_init(&dp->dad_refcnt, 1); nd6_dad_add(dp); - NET_EPOCH_ENTER(et); - nd6_dad_starttimer(dp, delay, 0); - NET_EPOCH_EXIT(et); + nd6_dad_starttimer(dp, delay); + DADQ_WUNLOCK(); } /* @@ -1318,29 +1315,36 @@ { struct dadq *dp; + DADQ_WLOCK(); dp = nd6_dad_find(ifa, NULL); - if (!dp) { + if (dp == NULL) { + DADQ_WUNLOCK(); /* DAD wasn't started yet */ return; } - nd6_dad_stoptimer(dp); + /* + * Acquire a temporary reference so that we can safely stop the callout. + */ + (void)refcount_acquire(&dp->dad_refcnt); nd6_dad_del(dp); + DADQ_WUNLOCK(); - /* Release this function's reference, acquired by nd6_dad_find(). */ + nd6_dad_stoptimer(dp); nd6_dad_rele(dp); } static void -nd6_dad_timer(struct dadq *dp) +nd6_dad_timer(void *arg) { - CURVNET_SET(dp->dad_vnet); + struct dadq *dp = arg; struct ifaddr *ifa = dp->dad_ifa; struct ifnet *ifp = dp->dad_ifa->ifa_ifp; struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa; char ip6buf[INET6_ADDRSTRLEN]; struct epoch_tracker et; + CURVNET_SET(dp->dad_vnet); KASSERT(ia != NULL, ("DAD entry %p with no address", dp)); NET_EPOCH_ENTER(et); @@ -1381,17 +1385,18 @@ * We have more NS to go. Send NS packet for DAD. */ nd6_dad_starttimer(dp, - (long)ND_IFINFO(ifa->ifa_ifp)->retrans * hz / 1000, 1); + (long)ND_IFINFO(ifa->ifa_ifp)->retrans * hz / 1000); + nd6_dad_ns_output(dp); goto done; } else { /* * We have transmitted sufficient number of DAD packets. * See what we've got. */ - if (dp->dad_ns_icount > 0 || dp->dad_na_icount > 0) + 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 && + } else if (V_dad_enhanced != 0 && dp->dad_ns_lcount > 0 && dp->dad_ns_lcount > dp->dad_loopbackprobe) { /* @@ -1412,8 +1417,8 @@ dp->dad_count = dp->dad_ns_ocount + V_nd6_mmaxtries - 1; nd6_dad_starttimer(dp, - (long)ND_IFINFO(ifa->ifa_ifp)->retrans * hz / 1000, - 1); + (long)ND_IFINFO(ifa->ifa_ifp)->retrans * hz / 1000); + nd6_dad_ns_output(dp); goto done; } else { /* @@ -1439,6 +1444,7 @@ } err: nd6_dad_del(dp); + DADQ_WUNLOCK(); done: NET_EPOCH_EXIT(et); CURVNET_RESTORE(); @@ -1498,6 +1504,10 @@ } } +/* + * Transmit a neighbour solicitation for the purpose of DAD. Returns with the + * DAD queue unlocked. + */ static void nd6_dad_ns_output(struct dadq *dp) { @@ -1505,11 +1515,15 @@ struct ifnet *ifp = dp->dad_ifa->ifa_ifp; int i; + DADQ_WLOCK_ASSERT(); + dp->dad_ns_tcount++; if ((ifp->if_flags & IFF_UP) == 0) { + DADQ_WUNLOCK(); return; } if ((ifp->if_drv_flags & IFF_DRV_RUNNING) == 0) { + DADQ_WUNLOCK(); return; } @@ -1526,6 +1540,7 @@ * should work well in almost all cases. */ } + DADQ_WUNLOCK(); nd6_ns_output(ifp, NULL, NULL, &ia->ia_addr.sin6_addr, (uint8_t *)&dp->dad_nonce[0]); } @@ -1541,12 +1556,11 @@ /* Ignore Nonce option when Enhanced DAD is disabled. */ if (V_dad_enhanced == 0) ndopt_nonce = NULL; + DADQ_RLOCK(); dp = nd6_dad_find(ifa, ndopt_nonce); - if (dp == NULL) - return; - - dp->dad_ns_icount++; - nd6_dad_rele(dp); + if (dp != NULL) + dp->dad_ns_icount++; + DADQ_RUNLOCK(); } static void @@ -1557,9 +1571,9 @@ if (ifa == NULL) panic("ifa == NULL in nd6_dad_na_input"); + DADQ_RLOCK(); dp = nd6_dad_find(ifa, NULL); - if (dp != NULL) { + if (dp != NULL) dp->dad_na_icount++; - nd6_dad_rele(dp); - } + DADQ_RUNLOCK(); }