diff --git a/sys/netinet6/nd6.c b/sys/netinet6/nd6.c index 5e1f25ff3236..5c0f56b2939f 100644 --- a/sys/netinet6/nd6.c +++ b/sys/netinet6/nd6.c @@ -153,6 +153,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 diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h index 17db6183320d..9253820d7cc7 100644 --- a/sys/netinet6/nd6.h +++ b/sys/netinet6/nd6.h @@ -428,6 +428,7 @@ 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); caddr_t nd6_ifptomac(struct ifnet *); +void nd6_dad_init(void); void nd6_dad_start(struct ifaddr *, int); void nd6_dad_stop(struct ifaddr *); diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c index a917b76ce369..39a36e875c23 100644 --- a/sys/netinet6/nd6_nbr.c +++ b/sys/netinet6/nd6_nbr.c @@ -51,6 +51,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include @@ -81,6 +82,7 @@ struct dadq; 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 @@ struct dadq { 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 @@ 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) @@ -1186,9 +1186,8 @@ static void nd6_dad_add(struct dadq *dp) { - ifa_ref(dp->dad_ifa); /* just for safety */ DADQ_WLOCK(); - TAILQ_INSERT_TAIL(&V_dadq, (struct dadq *)dp, dad_list); + TAILQ_INSERT_TAIL(&V_dadq, dp, dad_list); DADQ_WUNLOCK(); } @@ -1196,10 +1195,10 @@ static void 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 +1208,10 @@ nd6_dad_find(struct ifaddr *ifa) 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 +1229,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); } /* @@ -1241,11 +1260,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: @@ -1275,12 +1289,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) { /* 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", @@ -1288,7 +1303,6 @@ 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; @@ -1303,9 +1317,11 @@ nd6_dad_start(struct ifaddr *ifa, int delay) * (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, 1); nd6_dad_add(dp); if (delay == 0) { nd6_dad_ns_output(dp, ifa); @@ -1324,8 +1340,6 @@ nd6_dad_stop(struct ifaddr *ifa) { struct dadq *dp; - if (DADQ_LOCK_INITIALIZED() == 0) - return; dp = nd6_dad_find(ifa); if (!dp) { /* DAD wasn't started yet */ @@ -1334,8 +1348,16 @@ nd6_dad_stop(struct ifaddr *ifa) 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 +1372,34 @@ 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) { 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 +1410,16 @@ 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) { - /* - * 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 +1433,15 @@ 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))); - - 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 +1456,6 @@ nd6_dad_duplicated(struct ifaddr *ifa, struct dadq *dp) 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 +1496,6 @@ nd6_dad_duplicated(struct ifaddr *ifa, struct dadq *dp) break; } } - - nd6_dad_del(dp); - free(dp, M_IP6NDP); } static void @@ -1535,7 +1523,6 @@ nd6_dad_ns_input(struct ifaddr *ifa) 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 +1530,9 @@ 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); + if (dp == NULL) + return; /* Quickhack - completely ignore DAD NS packets */ if (V_dad_ignore_ns) { @@ -1556,26 +1544,10 @@ nd6_dad_ns_input(struct ifaddr *ifa) 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 +1559,8 @@ nd6_dad_na_input(struct ifaddr *ifa) 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); + } }