Index: sys/netinet6/nd6.h =================================================================== --- sys/netinet6/nd6.h +++ sys/netinet6/nd6.h @@ -428,6 +428,7 @@ void nd6_ns_output(struct ifnet *, const struct in6_addr *, const struct in6_addr *, struct llentry *, int); caddr_t nd6_ifptomac(struct ifnet *); +void nd6_dad_init(void); void nd6_dad_start(struct ifaddr *, int); void nd6_dad_stop(struct ifaddr *); Index: sys/netinet6/nd6.c =================================================================== --- sys/netinet6/nd6.c +++ sys/netinet6/nd6.c @@ -153,6 +153,8 @@ 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 Index: sys/netinet6/nd6_nbr.c =================================================================== --- sys/netinet6/nd6_nbr.c +++ sys/netinet6/nd6_nbr.c @@ -51,6 +51,7 @@ #include #include #include +#include #include #include @@ -81,6 +82,7 @@ static struct dadq *nd6_dad_find(struct ifaddr *); 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 *); @@ -1167,6 +1169,7 @@ int dad_na_icount; struct callout dad_timer_ch; struct vnet *dad_vnet; + u_int dad_refcnt; }; static VNET_DEFINE(TAILQ_HEAD(, dadq), dadq); @@ -1174,9 +1177,6 @@ #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) @@ -1186,9 +1186,9 @@ nd6_dad_add(struct dadq *dp) { - ifa_ref(dp->dad_ifa); /* just for safety */ + refcount_acquire(&dp->dad_refcnt); DADQ_WLOCK(); - TAILQ_INSERT_TAIL(&V_dadq, (struct dadq *)dp, dad_list); + TAILQ_INSERT_TAIL(&V_dadq, dp, dad_list); DADQ_WUNLOCK(); } @@ -1196,10 +1196,10 @@ nd6_dad_del(struct dadq *dp) { - ifa_free(dp->dad_ifa); DADQ_WLOCK(); - TAILQ_REMOVE(&V_dadq, (struct dadq *)dp, dad_list); + TAILQ_REMOVE(&V_dadq, dp, dad_list); DADQ_WUNLOCK(); + nd6_dad_rele(dp); } static struct dadq * @@ -1209,8 +1209,10 @@ DADQ_RLOCK(); TAILQ_FOREACH(dp, &V_dadq, dad_list) - if (dp->dad_ifa == ifa) + if (dp->dad_ifa == ifa) { + refcount_acquire(&dp->dad_refcnt); break; + } DADQ_RUNLOCK(); return (dp); @@ -1228,7 +1230,25 @@ 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); } /* @@ -1241,11 +1261,6 @@ 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: @@ -1275,8 +1290,9 @@ } if (ND_IFINFO(ifa->ifa_ifp)->flags & ND6_IFF_IFDISABLED) return; - if (nd6_dad_find(ifa) != NULL) { + if ((dp = nd6_dad_find(ifa)) != NULL) { /* DAD already in progress */ + nd6_dad_rele(dp); return; } @@ -1303,9 +1319,11 @@ * (re)initialization. */ dp->dad_ifa = ifa; + 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; + refcount_init(&dp->dad_refcnt, 0); nd6_dad_add(dp); if (delay == 0) { nd6_dad_ns_output(dp, ifa); @@ -1324,8 +1342,6 @@ { struct dadq *dp; - if (DADQ_LOCK_INITIALIZED() == 0) - return; dp = nd6_dad_find(ifa); if (!dp) { /* DAD wasn't started yet */ @@ -1334,8 +1350,16 @@ nd6_dad_stoptimer(dp); + /* + * 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) + return; + nd6_dad_del(dp); - free(dp, M_IP6NDP); + nd6_dad_rele(dp); } static void @@ -1350,42 +1374,34 @@ /* 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) { nd6log((LOG_INFO, "%s: could not run DAD, driver problem?\n", if_name(ifa->ifa_ifp))); - - nd6_dad_del(dp); - free(dp, M_IP6NDP); - dp = NULL; - goto done; + goto err; } /* Need more checks? */ @@ -1396,33 +1412,16 @@ 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) { - /* - * the check is in nd6_dad_na_input(), - * but just in case - */ - 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() */ + 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); - dp = NULL; - } else { + else { /* * We are done with DAD. No NA came, no NS came. * No duplicate address found. Check IFDISABLED flag @@ -1436,18 +1435,15 @@ "%s: DAD complete for %s - no duplicates found\n", if_name(ifa->ifa_ifp), ip6_sprintf(ip6buf, &ia->ia_addr.sin6_addr))); - - nd6_dad_del(dp); - free(dp, M_IP6NDP); - dp = NULL; } } - +err: + nd6_dad_del(dp); done: CURVNET_RESTORE(); } -void +static void nd6_dad_duplicated(struct ifaddr *ifa, struct dadq *dp) { struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa; @@ -1462,9 +1458,6 @@ 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)); @@ -1505,9 +1498,6 @@ break; } } - - nd6_dad_del(dp); - free(dp, M_IP6NDP); } static void @@ -1535,7 +1525,6 @@ struct ifnet *ifp; const struct in6_addr *taddr6; struct dadq *dp; - int duplicate; if (ifa == NULL) panic("ifa == NULL in nd6_dad_ns_input"); @@ -1543,8 +1532,9 @@ ia = (struct in6_ifaddr *)ifa; ifp = ifa->ifa_ifp; taddr6 = &ia->ia_addr.sin6_addr; - duplicate = 0; dp = nd6_dad_find(ifa); + if (dp == NULL) + return; /* Quickhack - completely ignore DAD NS packets */ if (V_dad_ignore_ns) { @@ -1556,26 +1546,10 @@ 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) { - nd6_dad_duplicated(ifa, dp); - dp = NULL; /* will be freed in nd6_dad_duplicated() */ - } 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 @@ -1587,9 +1561,8 @@ panic("ifa == NULL in nd6_dad_na_input"); dp = nd6_dad_find(ifa); - if (dp) + if (dp != NULL) { dp->dad_na_icount++; - - /* remove the address. */ - nd6_dad_duplicated(ifa, dp); + nd6_dad_rele(dp); + } }