Index: sys/net/if.c =================================================================== --- sys/net/if.c +++ sys/net/if.c @@ -1048,6 +1048,8 @@ while (!CK_STAILQ_EMPTY(&ifp->if_multiaddrs)) { ifma = CK_STAILQ_FIRST(&ifp->if_multiaddrs); CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ifma, ifmultiaddr, ifma_link); + ifma->ifma_flags &= ~IFMA_F_ENQUEUED; + MCDPRINTF("removed ifma: %p from %s\n", ifma, ifp->if_xname); if_delmulti_locked(ifp, ifma, 1); } IF_ADDR_WUNLOCK(ifp); @@ -3421,6 +3423,7 @@ ifma->ifma_addr = dupsa; ifma->ifma_ifp = ifp; + if_ref(ifp); ifma->ifma_refcount = 1; ifma->ifma_protospec = NULL; @@ -3454,15 +3457,15 @@ if_freemulti_internal(struct ifmultiaddr *ifma) { +#ifdef MCAST_VERBOSE + kdb_backtrace(); + printf("%s freeing ifma: %p\n", __func__, ifma); +#endif KASSERT(ifma->ifma_refcount == 0, ("if_freemulti: refcount %d", ifma->ifma_refcount)); if (ifma->ifma_lladdr != NULL) free(ifma->ifma_lladdr, M_IFMADDR); -#ifdef MCAST_VERBOSE - kdb_backtrace(); - printf("%s freeing ifma: %p\n", __func__, ifma); -#endif free(ifma->ifma_addr, M_IFMADDR); free(ifma, M_IFMADDR); } @@ -3575,7 +3578,8 @@ ll_ifma = if_allocmulti(ifp, llsa, NULL, M_NOWAIT); if (ll_ifma == NULL) { --ifma->ifma_refcount; - if_freemulti(ifma); + if (ifma->ifma_refcount == 0) + if_freemulti(ifma); error = ENOMEM; goto free_llsa_out; } @@ -3593,6 +3597,7 @@ * ifnet address list. */ ifma->ifma_flags |= IFMA_F_ENQUEUED; + MCDPRINTF("insert ifma: %p in %s\n", ifma, ifp->if_xname); CK_STAILQ_INSERT_HEAD(&ifp->if_multiaddrs, ifma, ifma_link); if (retifma != NULL) @@ -3761,6 +3766,7 @@ if_delmulti_locked(struct ifnet *ifp, struct ifmultiaddr *ifma, int detaching) { struct ifmultiaddr *ll_ifma; + struct ifmultiaddr *ifmatmp; if (ifp != NULL && ifma->ifma_ifp != NULL) { KASSERT(ifma->ifma_ifp == ifp, @@ -3768,61 +3774,55 @@ IF_ADDR_WLOCK_ASSERT(ifp); } - ifp = ifma->ifma_ifp; + MPASS(ifma->ifma_ifp == NULL || ifma->ifma_ifp == ifp); MCDPRINTF("%s freeing %p from %s \n", __func__, ifma, ifp ? ifp->if_xname : ""); - /* - * If the ifnet is detaching, null out references to ifnet, - * so that upper protocol layers will notice, and not attempt - * to obtain locks for an ifnet which no longer exists. The - * routing socket announcement must happen before the ifnet - * instance is detached from the system. - */ - if (detaching) { -#ifdef DIAGNOSTIC - printf("%s: detaching ifnet instance %p\n", __func__, ifp); -#endif + if (ifp) { + if (ifma->ifma_flags & IFMA_F_ENQUEUED) { + MCDPRINTF("removed ifma: %p from %s\n", ifma, ifp->if_xname); + CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ifma, ifmultiaddr, ifma_link); + ifma->ifma_flags &= ~IFMA_F_ENQUEUED; + } /* - * ifp may already be nulled out if we are being reentered - * to delete the ll_ifma. + * If this is a link level interface, we need to ensure that we've cleared any + * dangling references */ - if (ifp != NULL) { - rt_newmaddrmsg(RTM_DELMADDR, ifma); - ifma->ifma_ifp = NULL; + CK_STAILQ_FOREACH(ifmatmp, &ifp->if_multiaddrs, ifma_link) { + if (ifmatmp->ifma_llifma == ifma) + ifmatmp->ifma_llifma = NULL; } + rt_newmaddrmsg(RTM_DELMADDR, ifma); + if_rele(ifp); + ifma->ifma_ifp = NULL; } - + ifma->ifma_llifma = NULL; if (--ifma->ifma_refcount > 0) return 0; - if (ifp != NULL && detaching == 0 && (ifma->ifma_flags & IFMA_F_ENQUEUED)) { - CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ifma, ifmultiaddr, ifma_link); - ifma->ifma_flags &= ~IFMA_F_ENQUEUED; - } /* * If this ifma is a network-layer ifma, a link-layer ifma may * have been associated with it. Release it first if so. */ ll_ifma = ifma->ifma_llifma; - if (ll_ifma != NULL) { + if (ifp && ll_ifma != NULL) { + ifp = ll_ifma->ifma_ifp; + if (ll_ifma->ifma_flags & IFMA_F_ENQUEUED) { + CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ll_ifma, ifmultiaddr, + ifma_link); + ll_ifma->ifma_flags &= ~IFMA_F_ENQUEUED; + } KASSERT(ifma->ifma_lladdr != NULL, ("%s: llifma w/o lladdr", __func__)); - if (detaching) + ifma->ifma_llifma = NULL; + if (ifp) { + if_rele(ifp); ll_ifma->ifma_ifp = NULL; /* XXX */ - if (--ll_ifma->ifma_refcount == 0) { - if (ifp != NULL) { - if (ll_ifma->ifma_flags & IFMA_F_ENQUEUED) { - CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ll_ifma, ifmultiaddr, - ifma_link); - ll_ifma->ifma_flags &= ~IFMA_F_ENQUEUED; - } - } - if_freemulti(ll_ifma); } + if (ll_ifma->ifma_refcount == 0) + if_freemulti(ll_ifma); } #ifdef INVARIANTS if (ifp) { - struct ifmultiaddr *ifmatmp; CK_STAILQ_FOREACH(ifmatmp, &ifp->if_multiaddrs, ifma_link) MPASS(ifma != ifmatmp); Index: sys/netinet/igmp.c =================================================================== --- sys/netinet/igmp.c +++ sys/netinet/igmp.c @@ -628,8 +628,10 @@ ifma->ifma_protospec == NULL) continue; inm = (struct in_multi *)ifma->ifma_protospec; - if (inm->inm_state == IGMP_LEAVING_MEMBER) + if (inm->inm_state == IGMP_LEAVING_MEMBER) { inm_rele_locked(&inm_free_tmp, inm); + ifma->ifma_protospec = NULL; + } inm_clear_recorded(inm); if (__predict_false(ifma_restart)) { ifma_restart = false; @@ -856,6 +858,7 @@ "process v2 query 0x%08x on ifp %p(%s)", ntohl(igmp->igmp_group.s_addr), ifp, ifp->if_xname); igmp_v2_update_group(inm, timer); + inm_release_deferred(inm); } } @@ -932,7 +935,7 @@ /*const*/ struct igmpv3 *igmpv3) { struct igmp_ifsoftc *igi; - struct in_multi *inm; + struct in_multi *inm = NULL; int is_general_query; uint32_t maxresp, nsrc, qqi; uint16_t timer; @@ -1081,6 +1084,8 @@ out_locked: IGMP_UNLOCK(); + if (inm) + inm_release_deferred(inm); IN_MULTI_LIST_UNLOCK(); return (0); @@ -1296,6 +1301,9 @@ } out_locked: + if (inm) + inm_release_deferred(inm); + IN_MULTI_LIST_UNLOCK(); return (0); @@ -1406,8 +1414,9 @@ break; } } - out_locked: + if (inm) + inm_release_deferred(inm); IN_MULTI_LIST_UNLOCK(); return (0); @@ -2043,6 +2052,7 @@ * transition to NOT would lose the leave and race. */ inm_rele_locked(&inm_free_tmp, inm); + ifma->ifma_protospec = NULL; /* FALLTHROUGH */ case IGMP_G_QUERY_PENDING_MEMBER: case IGMP_SG_QUERY_PENDING_MEMBER: @@ -2274,6 +2284,9 @@ */ KASSERT(inm->inm_ifma != NULL, ("%s: no ifma", __func__)); ifp = inm->inm_ifma->ifma_ifp; + /* The interface address has gone away */ + if (ifp == NULL) + return (0); /* * Sanity check that netinet's notion of ifp is the * same as net's. Index: sys/netinet/in.c =================================================================== --- sys/netinet/in.c +++ sys/netinet/in.c @@ -1034,9 +1034,10 @@ ifma->ifma_protospec == NULL) continue; inm = (struct in_multi *)ifma->ifma_protospec; + ifma->ifma_protospec = NULL; inm_rele_locked(&purgeinms, inm); if (__predict_false(ifma_restart)) { - ifma_restart = true; + ifma_restart = false; goto restart; } } Index: sys/netinet/in_mcast.c =================================================================== --- sys/netinet/in_mcast.c +++ sys/netinet/in_mcast.c @@ -266,9 +266,9 @@ if (ifma->ifma_flags & IFMA_F_ENQUEUED) { CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ifma, ifmultiaddr, ifma_link); ifma->ifma_flags &= ~IFMA_F_ENQUEUED; + MCDPRINTF("removed ifma: %p from %s\n", ifma, ifp->if_xname); } - MCDPRINTF("removed ifma: %p from %s\n", ifma, ifp->if_xname); - if ((ll_ifma = ifma->ifma_llifma) != NULL) { + if (ifma->ifma_ifp != NULL && (ll_ifma = ifma->ifma_llifma) != NULL) { MPASS(ifma != ll_ifma); ifma->ifma_llifma = NULL; MPASS(ll_ifma->ifma_llifma == NULL); @@ -277,8 +277,8 @@ if (ll_ifma->ifma_flags & IFMA_F_ENQUEUED) { CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ll_ifma, ifmultiaddr, ifma_link); ll_ifma->ifma_flags &= ~IFMA_F_ENQUEUED; + MCDPRINTF("removed ll_ifma: %p from %s\n", ll_ifma, ifp->if_xname); } - MCDPRINTF("removed ll_ifma: %p from %s\n", ll_ifma, ifp->if_xname); if_freemulti(ll_ifma); ifma_restart = true; } @@ -292,7 +292,9 @@ IN_MULTI_LIST_LOCK_ASSERT(); MPASS(inm->inm_refcount > 0); + MCDPRINTF("inm: %p refcount: %d\n", inm, inm->inm_refcount); if (--inm->inm_refcount == 0) { + MCDPRINTF("freeing %p \n", inm); SLIST_INIT(&tmp); inm_disconnect(inm); inm->inm_ifma->ifma_protospec = NULL; @@ -357,6 +359,8 @@ break; inm = NULL; } + if (inm) + inm_acquire_locked(inm); return (inm); } @@ -560,13 +564,8 @@ IN_MULTI_LIST_LOCK(); inm = inm_lookup(ifp, *group); if (inm != NULL) { - /* - * If we already joined this group, just bump the - * refcount and return it. - */ - KASSERT(inm->inm_refcount >= 1, + KASSERT(inm->inm_refcount > 1, ("%s: bad refcount %d", __func__, inm->inm_refcount)); - inm_acquire_locked(inm); *pinm = inm; } IN_MULTI_LIST_UNLOCK(); @@ -638,7 +637,9 @@ inm->inm_ifp = ifp; inm->inm_igi = ii->ii_igmp; inm->inm_ifma = ifma; - inm->inm_refcount = 1; + ifma->ifma_refcount++; + /* One reference for the caller and one for the ifma */ + inm->inm_refcount = 2; inm->inm_state = IGMP_NOT_MEMBER; mbufq_init(&inm->inm_scq, IGMP_MAX_STATE_CHANGES); inm->inm_st[0].iss_fmode = MCAST_UNDEFINED; @@ -1314,6 +1315,7 @@ if (error) { CTR2(KTR_IGMPV3, "%s: dropping ref on %p", __func__, inm); + /* drop the callers reference */ inm_release_deferred(inm); } else { *pinm = inm; @@ -2220,14 +2222,8 @@ if (error) goto out_inp_locked; } - /* - * Allocate the new slot upfront so we can deal with - * grafting the new source filter in same code path - * as for join-source on existing membership. - */ idx = imo->imo_num_memberships; imo->imo_membership[idx] = NULL; - imo->imo_num_memberships++; KASSERT(imo->imo_mfilters != NULL, ("%s: imf_mfilters vector was not allocated", __func__)); imf = &imo->imo_mfilters[idx]; @@ -2280,13 +2276,14 @@ error = in_joingroup_locked(ifp, &gsa->sin.sin_addr, imf, &inm); if (error) { - CTR1(KTR_IGMPV3, "%s: in_joingroup_locked failed", - __func__); - IN_MULTI_LIST_UNLOCK(); - goto out_imo_free; + CTR1(KTR_IGMPV3, "%s: in_joingroup_locked failed", + __func__); + goto out_in_multi_locked; } - inm_acquire(inm); + /* joingroup returns with a reference held */ imo->imo_membership[idx] = inm; + MPASS(idx == imo->imo_num_memberships); + imo->imo_num_memberships++; } else { CTR1(KTR_IGMPV3, "%s: merge inm state", __func__); IN_MULTI_LIST_LOCK(); @@ -2308,10 +2305,12 @@ } out_in_multi_locked: + if (imo->imo_num_memberships) + MPASS(imo->imo_membership[0]); IN_MULTI_UNLOCK(); INP_WLOCK(inp); - if (in_pcbrele_wlocked(inp)) + if (in_pcbrele_wlocked(inp)) return (ENXIO); if (error) { imf_rollback(imf); @@ -2332,7 +2331,6 @@ IN_MULTI_LIST_UNLOCK(); } imo->imo_membership[idx] = NULL; - --imo->imo_num_memberships; } out_inp_locked: Index: sys/netinet/in_pcb.c =================================================================== --- sys/netinet/in_pcb.c +++ sys/netinet/in_pcb.c @@ -1809,6 +1809,8 @@ imo->imo_membership[i]; } imo->imo_num_memberships -= gap; + if (imo->imo_num_memberships) + MPASS(imo->imo_membership[0]); } INP_WUNLOCK(inp); } Index: sys/netinet/in_var.h =================================================================== --- sys/netinet/in_var.h +++ sys/netinet/in_var.h @@ -361,16 +361,18 @@ IN_MULTI_LIST_UNLOCK(); } +extern void kdb_backtrace(void); static __inline void inm_rele_locked(struct in_multi_head *inmh, struct in_multi *inm) { MPASS(inm->inm_refcount > 0); IN_MULTI_LIST_LOCK_ASSERT(); - + MCDPRINTF("inm: %p refcount: %d\n", inm, inm->inm_refcount); if (--inm->inm_refcount == 0) { + MCDPRINTF("freeing %p \n", inm); MPASS(inmh != NULL); inm_disconnect(inm); - inm->inm_ifma->ifma_protospec = NULL; + MPASS(inm->inm_ifma->ifma_protospec == NULL); SLIST_INSERT_HEAD(inmh, inm, inm_nrele); } } Index: sys/netinet/ip_carp.c =================================================================== --- sys/netinet/ip_carp.c +++ sys/netinet/ip_carp.c @@ -1418,16 +1418,13 @@ bzero(&in6, sizeof(in6)); in6.s6_addr16[0] = htons(0xff02); in6.s6_addr8[15] = 0x12; - if ((error = in6_setscope(&in6, ifp, NULL)) != 0) { - free(im6o->im6o_membership, M_CARP); - break; - } + if ((error = in6_setscope(&in6, ifp, NULL)) != 0) + goto error_out; in6m = NULL; - if ((error = in6_joingroup(ifp, &in6, NULL, &in6m, 0)) != 0) { - free(im6o->im6o_membership, M_CARP); - break; - } - in6m_acquire(in6m); + if ((error = in6_joingroup(ifp, &in6, NULL, &in6m, 0)) != 0) + goto error_out; + + /* reference acquired by joingroup */ im6o->im6o_membership[0] = in6m; im6o->im6o_num_memberships++; @@ -1438,21 +1435,22 @@ in6.s6_addr32[2] = htonl(1); in6.s6_addr32[3] = 0; in6.s6_addr8[12] = 0xff; - if ((error = in6_setscope(&in6, ifp, NULL)) != 0) { - in6_leavegroup(im6o->im6o_membership[0], NULL); - free(im6o->im6o_membership, M_CARP); - break; - } + if ((error = in6_setscope(&in6, ifp, NULL)) != 0) + goto error_out; in6m = NULL; - if ((error = in6_joingroup(ifp, &in6, NULL, &in6m, 0)) != 0) { - in6_leavegroup(im6o->im6o_membership[0], NULL); - free(im6o->im6o_membership, M_CARP); - break; - } - in6m_acquire(in6m); + if ((error = in6_joingroup(ifp, &in6, NULL, &in6m, 0)) != 0) + goto error_out; + /* reference acquired by joingroup */ im6o->im6o_membership[1] = in6m; im6o->im6o_num_memberships++; break; + + error_out: + if (im6o->im6o_membership[0]) + in6_leavegroup(im6o->im6o_membership[0], NULL); + free(im6o->im6o_membership, M_CARP); + + break; } #endif } Index: sys/netinet/ip_var.h =================================================================== --- sys/netinet/ip_var.h +++ sys/netinet/ip_var.h @@ -93,8 +93,8 @@ u_long imo_multicast_vif; /* vif num outgoing multicasts */ u_char imo_multicast_ttl; /* TTL for outgoing multicasts */ u_char imo_multicast_loop; /* 1 => hear sends if a member */ - u_short imo_num_memberships; /* no. memberships this socket */ u_short imo_max_memberships; /* max memberships this socket */ + int imo_num_memberships; /* no. memberships this socket */ struct in_multi **imo_membership; /* group memberships */ struct in_mfilter *imo_mfilters; /* source filters */ struct epoch_context imo_epoch_ctx; Index: sys/netinet6/icmp6.c =================================================================== --- sys/netinet6/icmp6.c +++ sys/netinet6/icmp6.c @@ -443,6 +443,8 @@ in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_discard); goto freeit; } + in6m_release_deferred(inm); + } /* Index: sys/netinet6/in6_mcast.c =================================================================== --- sys/netinet6/in6_mcast.c +++ sys/netinet6/in6_mcast.c @@ -431,7 +431,7 @@ */ KASSERT(inm->in6m_refcount >= 1, ("%s: bad refcount %d", __func__, inm->in6m_refcount)); - in6m_acquire_locked(inm); + /* reference acquired by lookup */ *pinm = inm; goto out_locked; } @@ -473,6 +473,7 @@ panic("%s: ifma %p is inconsistent with %p (%p)", __func__, ifma, inm, group); #endif + /* add caller reference */ in6m_acquire_locked(inm); *pinm = inm; goto out_locked; @@ -499,7 +500,9 @@ inm->in6m_ifp = ifp; inm->in6m_mli = MLD_IFINFO(ifp); inm->in6m_ifma = ifma; - inm->in6m_refcount = 1; + ifma->ifma_refcount++; + /* one reference for the ifma and one for the caller */ + inm->in6m_refcount = 2; inm->in6m_state = MLD_NOT_MEMBER; mbufq_init(&inm->in6m_scq, MLD_MAX_STATE_CHANGES); @@ -587,6 +590,18 @@ GROUPTASK_ENQUEUE(&free_gtask); } +void +in6m_release_deferred(struct in6_multi *inm) +{ + IN6_MULTI_LIST_LOCK_ASSERT(); + if (--inm->in6m_refcount == 0) { + mtx_lock(&in6_multi_free_mtx); + SLIST_INSERT_HEAD(&in6m_free_list, inm, in6m_nrele); + mtx_unlock(&in6_multi_free_mtx); + GROUPTASK_ENQUEUE(&free_gtask); + } +} + void in6m_release_wait(void) { @@ -599,9 +614,6 @@ in6m_disconnect_locked(struct in6_multi_head *inmh, struct in6_multi *inm) { struct ifnet *ifp; - struct ifaddr *ifa; - struct in6_ifaddr *ifa6; - struct in6_multi_mship *imm, *imm_tmp; struct ifmultiaddr *ifma, *ll_ifma; IN6_MULTI_LIST_LOCK_ASSERT(); @@ -622,7 +634,7 @@ ifma->ifma_flags &= ~IFMA_F_ENQUEUED; } MCDPRINTF("removed ifma: %p from %s\n", ifma, ifp->if_xname); - if ((ll_ifma = ifma->ifma_llifma) != NULL) { + if (ifma->ifma_ifp != NULL && (ll_ifma = ifma->ifma_llifma) != NULL) { MPASS(ifma != ll_ifma); ifma->ifma_llifma = NULL; MPASS(ll_ifma->ifma_llifma == NULL); @@ -631,11 +643,25 @@ if (ll_ifma->ifma_flags & IFMA_F_ENQUEUED) { CK_STAILQ_REMOVE(&ifp->if_multiaddrs, ll_ifma, ifmultiaddr, ifma_link); ll_ifma->ifma_flags &= ~IFMA_F_ENQUEUED; + MCDPRINTF("removed ll_ifma: %p from %s -- from in6m -- \n", ll_ifma, ifp->if_xname); + } else { + MCDPRINTF("did not remove ll_ifma: %p from %s - not queued\n", ll_ifma, ifp->if_xname); } - MCDPRINTF("removed ll_ifma: %p from %s\n", ll_ifma, ifp->if_xname); if_freemulti(ll_ifma); } } +} + +int +in6m_remove_members(struct in6_multi_head *inmh, struct in6_multi *inm) +{ + struct in6_ifaddr *ifa6; + struct ifaddr *ifa; + struct in6_multi_mship *imm, *imm_tmp; + struct ifnet *ifp; + int released = 0; + + ifp = inm->in6m_ifp; CK_STAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) { if (ifa->ifa_addr->sa_family != AF_INET6) continue; @@ -644,13 +670,16 @@ i6mm_chain, imm_tmp) { if (inm == imm->i6mm_maddr) { LIST_REMOVE(imm, i6mm_chain); - free(imm, M_IP6MADDR); in6m_rele_locked(inmh, inm); + released = (inm->in6m_refcount == 0); + free(imm, M_IP6MADDR); } } } + return (released); } + static void in6m_release_task(void *arg __unused) { @@ -1322,8 +1351,8 @@ break; } } - in6m_disconnect_locked(&inmh, inm); - in6m_rele_locked(&inmh, inm); + if (in6m_remove_members(&inmh, inm) == 0) + in6m_rele_locked(&inmh, inm); NET_EPOCH_EXIT(et); } else { *pinm = inm; @@ -1415,9 +1444,8 @@ IF_ADDR_WLOCK(ifp); SLIST_INIT(&inmh); - if (inm->in6m_refcount == 1) - in6m_disconnect_locked(&inmh, inm); - in6m_rele_locked(&inmh, inm); + if (in6m_remove_members(&inmh, inm) == 0) + in6m_rele_locked(&inmh, inm); if (ifp) IF_ADDR_WUNLOCK(ifp); IN6_MULTI_LIST_UNLOCK(); @@ -2126,7 +2154,6 @@ */ idx = imo->im6o_num_memberships; imo->im6o_membership[idx] = NULL; - imo->im6o_num_memberships++; KASSERT(imo->im6o_mfilters != NULL, ("%s: im6f_mfilters vector was not allocated", __func__)); imf = &imo->im6o_mfilters[idx]; @@ -2180,13 +2207,16 @@ &inm, 0); if (error) { IN6_MULTI_UNLOCK(); + INP_WLOCK(inp); goto out_im6o_free; } /* * NOTE: Refcount from in6_joingroup_locked() * is protecting membership. */ + MPASS(idx == imo->im6o_num_memberships); imo->im6o_membership[idx] = inm; + imo->im6o_num_memberships++; } else { CTR1(KTR_MLD, "%s: merge inm state", __func__); IN6_MULTI_LIST_LOCK(); Index: sys/netinet6/in6_var.h =================================================================== --- sys/netinet6/in6_var.h +++ sys/netinet6/in6_var.h @@ -729,6 +729,38 @@ ifma->ifma_protospec); } +/* Acquire an in6_multi record. */ +static __inline void +in6m_acquire_locked(struct in6_multi *inm) +{ + + IN6_MULTI_LIST_LOCK_ASSERT(); + ++inm->in6m_refcount; +} + +static __inline void +in6m_acquire(struct in6_multi *inm) +{ + IN6_MULTI_LIST_LOCK(); + in6m_acquire_locked(inm); + IN6_MULTI_LIST_UNLOCK(); +} + +static __inline void +in6m_rele_locked(struct in6_multi_head *inmh, struct in6_multi *inm) +{ + KASSERT(inm->in6m_refcount > 0, ("refcount == %d inm: %p", inm->in6m_refcount, inm)); + IN6_MULTI_LIST_LOCK_ASSERT(); + + if (--inm->in6m_refcount == 0) { + in6m_disconnect_locked(inmh, inm); + MPASS(inm->in6m_ifp == NULL); + inm->in6m_ifma->ifma_protospec = NULL; + MPASS(inm->in6m_ifma->ifma_llifma == NULL); + SLIST_INSERT_HEAD(inmh, inm, in6m_nrele); + } +} + /* * Look up an in6_multi record for an IPv6 multicast address * on the interface ifp. @@ -744,12 +776,15 @@ inm = in6m_ifmultiaddr_get_inm(ifma); if (inm == NULL) continue; - if (IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, mcaddr)) + if (IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, mcaddr)) { + in6m_acquire_locked(inm); return (inm); + } } return (NULL); } +int in6m_remove_members(struct in6_multi_head *inmh, struct in6_multi *inm); /* * Wrapper for in6m_lookup_locked(). * @@ -770,37 +805,6 @@ return (inm); } -/* Acquire an in6_multi record. */ -static __inline void -in6m_acquire_locked(struct in6_multi *inm) -{ - - IN6_MULTI_LIST_LOCK_ASSERT(); - ++inm->in6m_refcount; -} - -static __inline void -in6m_acquire(struct in6_multi *inm) -{ - IN6_MULTI_LIST_LOCK(); - in6m_acquire_locked(inm); - IN6_MULTI_LIST_UNLOCK(); -} - -static __inline void -in6m_rele_locked(struct in6_multi_head *inmh, struct in6_multi *inm) -{ - KASSERT(inm->in6m_refcount > 0, ("refcount == %d inm: %p", inm->in6m_refcount, inm)); - IN6_MULTI_LIST_LOCK_ASSERT(); - - if (--inm->in6m_refcount == 0) { - MPASS(inm->in6m_ifp == NULL); - inm->in6m_ifma->ifma_protospec = NULL; - MPASS(inm->in6m_ifma->ifma_llifma == NULL); - SLIST_INSERT_HEAD(inmh, inm, in6m_nrele); - } -} - struct ip6_moptions; struct sockopt; struct inpcbinfo; @@ -819,6 +823,7 @@ void in6m_print(const struct in6_multi *); int in6m_record_source(struct in6_multi *, const struct in6_addr *); void in6m_release_list_deferred(struct in6_multi_head *); +void in6m_release_deferred(struct in6_multi *); void in6m_release_wait(void); void ip6_freemoptions(struct ip6_moptions *); int ip6_getmoptions(struct inpcb *, struct sockopt *); Index: sys/netinet6/mld6.c =================================================================== --- sys/netinet6/mld6.c +++ sys/netinet6/mld6.c @@ -541,7 +541,7 @@ { struct epoch_tracker et; struct mld_ifsoftc *mli; - struct ifmultiaddr *ifma; + struct ifmultiaddr *ifma, *tifma; struct in6_multi *inm; CTR3(KTR_MLD, "%s: called for ifp %p(%s)", __func__, ifp, @@ -556,13 +556,15 @@ * Extract list of in6_multi associated with the detaching ifp * which the PF_INET6 layer is about to release. */ + // restart: NET_EPOCH_ENTER(et); - CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, tifma) { + int released; + inm = in6m_ifmultiaddr_get_inm(ifma); if (inm == NULL) continue; - in6m_disconnect_locked(inmh, inm); - + released = in6m_remove_members(inmh, inm); if (mli->mli_version == MLD_VERSION_2) { in6m_clear_recorded(inm); @@ -572,7 +574,8 @@ */ if (inm->in6m_state == MLD_LEAVING_MEMBER) { inm->in6m_state = MLD_NOT_MEMBER; - in6m_rele_locked(inmh, inm); + if (!released) + in6m_rele_locked(inmh, inm); } } } @@ -729,6 +732,7 @@ ip6_sprintf(ip6tbuf, &mld->mld_addr), ifp, if_name(ifp)); mld_v1_update_group(inm, timer); + in6m_release_deferred(inm); } /* XXX Clear embedded scope ID as userland won't expect it. */ in6_clearscope(&mld->mld_addr); @@ -814,7 +818,7 @@ { struct mld_ifsoftc *mli; struct mldv2_query *mld; - struct in6_multi *inm; + struct in6_multi *inm = NULL; uint32_t maxdelay, nsrc, qqi; int is_general_query; uint16_t timer; @@ -981,6 +985,8 @@ } out_locked: + if (inm) + in6m_release_deferred(inm); MLD_UNLOCK(); IN6_MULTI_LIST_UNLOCK(); @@ -1114,7 +1120,7 @@ struct in6_addr src, dst; struct epoch_tracker et; struct in6_ifaddr *ia; - struct in6_multi *inm; + struct in6_multi *inm = NULL; #ifdef KTR char ip6tbuf[INET6_ADDRSTRLEN]; #endif @@ -1236,6 +1242,8 @@ } out_locked: + if (inm) + in6m_release_deferred(inm); NET_EPOCH_EXIT(et); MLD_UNLOCK(); IN6_MULTI_LIST_UNLOCK(); @@ -1617,8 +1625,8 @@ if (inm->in6m_state == MLD_LEAVING_MEMBER && inm->in6m_scrv == 0) { inm->in6m_state = MLD_NOT_MEMBER; - in6m_disconnect_locked(inmh, inm); - in6m_rele_locked(inmh, inm); + if (in6m_remove_members(inmh, inm) == 0) + in6m_rele_locked(inmh, inm); } } break;