--- sys/netinet6/in6_ifattach.c (revision 343111) +++ sys/netinet6/in6_ifattach.c (working copy) @@ -854,36 +854,22 @@ static void in6_purgemaddrs(struct ifnet *ifp) { - struct in6_multi_head purgeinms; - struct in6_multi *inm; - struct ifmultiaddr *ifma, *next; + struct in6_multi_head inmh; - SLIST_INIT(&purgeinms); + SLIST_INIT(&inmh); IN6_MULTI_LOCK(); IN6_MULTI_LIST_LOCK(); - IF_ADDR_WLOCK(ifp); + mld_ifdetach(ifp, &inmh); + IN6_MULTI_LIST_UNLOCK(); + IN6_MULTI_UNLOCK(); + in6m_release_list_deferred(&inmh); + /* - * Extract list of in6_multi associated with the detaching ifp - * which the PF_INET6 layer is about to release. + * Make sure all multicast deletions invoking if_ioctl() are + * completed before returning. Else we risk accessing a freed + * ifnet structure pointer. */ - restart: - CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - ifma->ifma_protospec == NULL) - continue; - inm = (struct in6_multi *)ifma->ifma_protospec; - in6m_disconnect(inm); - in6m_rele_locked(&purgeinms, inm); - if (__predict_false(ifma6_restart)) { - ifma6_restart = false; - goto restart; - } - } - IF_ADDR_WUNLOCK(ifp); - mld_ifdetach(ifp); - IN6_MULTI_LIST_UNLOCK(); - IN6_MULTI_UNLOCK(); - in6m_release_list_deferred(&purgeinms); + in6m_release_wait(); } void --- sys/netinet6/in6_mcast.c (revision 343111) +++ sys/netinet6/in6_mcast.c (working copy) @@ -190,7 +190,6 @@ CTLFLAG_RD | CTLFLAG_MPSAFE, sysctl_ip6_mcast_filters, "Per-interface stack-wide source filters"); -int ifma6_restart = 0; #ifdef KTR /* * Inline function which wraps assertions for a valid ifp. @@ -405,6 +404,7 @@ in6_getmulti(struct ifnet *ifp, const struct in6_addr *group, struct in6_multi **pinm) { + struct epoch_tracker et; struct sockaddr_in6 gsin6; struct ifmultiaddr *ifma; struct in6_multi *inm; @@ -420,7 +420,10 @@ IN6_MULTI_LOCK_ASSERT(); IN6_MULTI_LIST_LOCK(); IF_ADDR_WLOCK(ifp); + NET_EPOCH_ENTER(et); inm = in6m_lookup_locked(ifp, group); + NET_EPOCH_EXIT(et); + if (inm != NULL) { /* * If we already joined this group, just bump the @@ -497,6 +500,7 @@ inm->in6m_mli = MLD_IFINFO(ifp); inm->in6m_ifma = ifma; inm->in6m_refcount = 1; + in6m_trace("alloc", inm); inm->in6m_state = MLD_NOT_MEMBER; mbufq_init(&inm->in6m_scq, MLD_MAX_STATE_CHANGES); @@ -525,6 +529,8 @@ struct ifmultiaddr *ifma; struct ifnet *ifp; + in6m_trace("release", inm); + CTR2(KTR_MLD, "%s: refcount is %d", __func__, inm->in6m_refcount); MPASS(inm->in6m_refcount == 0); @@ -572,7 +578,6 @@ in6m_init, NULL); #endif - void in6m_release_list_deferred(struct in6_multi_head *inmh) { @@ -585,8 +590,15 @@ } void -in6m_disconnect(struct in6_multi *inm) +in6m_release_wait(void) { + /* wait for all jobs to complete */ + gtaskqueue_drain_all(free_gtask.gt_taskqueue); +} + +void +in6m_disconnect_locked(struct in6_multi_head *inmh, struct in6_multi *inm) +{ struct ifnet *ifp; struct ifaddr *ifa; struct in6_ifaddr *ifa6; @@ -593,10 +605,12 @@ struct in6_multi_mship *imm, *imm_tmp; struct ifmultiaddr *ifma, *ll_ifma; + IN6_MULTI_LIST_LOCK_ASSERT(); + ifp = inm->in6m_ifp; + if (ifp == NULL) + return; /* already called */ - if (ifp == NULL) - return; inm->in6m_ifp = NULL; IF_ADDR_WLOCK_ASSERT(ifp); ifma = inm->in6m_ifma; @@ -615,7 +629,6 @@ MPASS(ll_ifma->ifma_llifma == NULL); MPASS(ll_ifma->ifma_ifp == ifp); if (--ll_ifma->ifma_refcount == 0) { - ifma6_restart = true; 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; @@ -633,28 +646,12 @@ if (inm == imm->i6mm_maddr) { LIST_REMOVE(imm, i6mm_chain); free(imm, M_IP6MADDR); + in6m_rele_locked(inmh, inm); } } } } -void -in6m_release_deferred(struct in6_multi *inm) -{ - struct in6_multi_head tmp; - - IN6_MULTI_LIST_LOCK_ASSERT(); - KASSERT(inm->in6m_refcount > 0, ("refcount == %d inm: %p", inm->in6m_refcount, inm)); - if (--inm->in6m_refcount == 0) { - MPASS(inm->in6m_ifp == NULL); - SLIST_INIT(&tmp); - inm->in6m_ifma->ifma_protospec = NULL; - MPASS(inm->in6m_ifma->ifma_llifma == NULL); - SLIST_INSERT_HEAD(&tmp, inm, in6m_nrele); - in6m_release_list_deferred(&tmp); - } -} - static void in6m_release_task(void *arg __unused) { @@ -1254,6 +1251,7 @@ /*const*/ struct in6_mfilter *imf, struct in6_multi **pinm, const int delay) { + struct in6_multi_head inmh; struct in6_mfilter timf; struct in6_multi *inm; struct ifmultiaddr *ifma; @@ -1261,6 +1259,7 @@ #ifdef KTR char ip6tbuf[INET6_ADDRSTRLEN]; #endif + SLIST_INIT(&inmh); /* * Sanity: Check scope zone ID was set for ifp, if and @@ -1324,13 +1323,14 @@ break; } } - in6m_disconnect(inm); - in6m_release_deferred(inm); + in6m_disconnect_locked(&inmh, inm); + in6m_rele_locked(&inmh, inm); NET_EPOCH_EXIT(et); } else { *pinm = inm; } IN6_MULTI_LIST_UNLOCK(); + in6m_release_list_deferred(&inmh); return (error); } @@ -1364,6 +1364,7 @@ int in6_leavegroup_locked(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf) { + struct in6_multi_head inmh; struct in6_mfilter timf; struct ifnet *ifp; int error; @@ -1371,6 +1372,7 @@ char ip6tbuf[INET6_ADDRSTRLEN]; #endif + SLIST_INIT(&inmh); error = 0; IN6_MULTI_LOCK_ASSERT(); @@ -1413,17 +1415,18 @@ CTR2(KTR_MLD, "%s: dropping ref on %p", __func__, inm); if (ifp) IF_ADDR_WLOCK(ifp); - if (inm->in6m_refcount == 1 && inm->in6m_ifp != NULL) - in6m_disconnect(inm); - in6m_release_deferred(inm); + + in6m_trace("leave group locked", inm); + if (inm->in6m_refcount == 1) + in6m_disconnect_locked(&inmh, inm); + in6m_rele_locked(&inmh, inm); if (ifp) IF_ADDR_WUNLOCK(ifp); IN6_MULTI_LIST_UNLOCK(); - + in6m_release_list_deferred(&inmh); return (error); } - /* * Block or unblock an ASM multicast source on an inpcb. * This implements the delta-based API described in RFC 3678. @@ -1933,6 +1936,7 @@ static int in6p_join_group(struct inpcb *inp, struct sockopt *sopt) { + struct in6_multi_head inmh; struct group_source_req gsr; sockunion_t *gsa, *ssa; struct ifnet *ifp; @@ -1943,6 +1947,7 @@ size_t idx; int error, is_new; + SLIST_INIT(&inmh); ifp = NULL; imf = NULL; lims = NULL; @@ -2178,7 +2183,10 @@ IN6_MULTI_UNLOCK(); goto out_im6o_free; } - in6m_acquire(inm); + /* + * NOTE: Refcount from in6_joingroup_locked() + * is protecting membership. + */ imo->im6o_membership[idx] = inm; } else { CTR1(KTR_MLD, "%s: merge inm state", __func__); @@ -2216,7 +2224,7 @@ inm = imo->im6o_membership[idx]; if (inm != NULL) { IN6_MULTI_LIST_LOCK(); - in6m_release_deferred(inm); + in6m_rele_locked(&inmh, inm); IN6_MULTI_LIST_UNLOCK(); } imo->im6o_membership[idx] = NULL; @@ -2225,6 +2233,7 @@ out_in6p_locked: INP_WUNLOCK(inp); + in6m_release_list_deferred(&inmh); return (error); } @@ -2869,10 +2878,9 @@ IN6_MULTI_LIST_LOCK(); NET_EPOCH_ENTER(et); CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - ifma->ifma_protospec == NULL) + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) continue; - inm = (struct in6_multi *)ifma->ifma_protospec; if (!IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, &mcaddr)) continue; fmode = inm->in6m_st[1].iss_fmode; --- sys/netinet6/in6_var.h (revision 343111) +++ sys/netinet6/in6_var.h (working copy) @@ -645,6 +645,7 @@ /* New fields for MLDv2 follow. */ struct mld_ifsoftc *in6m_mli; /* MLD info */ SLIST_ENTRY(in6_multi) in6m_nrele; /* to-be-released by MLD */ + SLIST_ENTRY(in6_multi) in6m_defer; /* deferred MLDv1 */ struct ip6_msource_tree in6m_srcs; /* tree of sources */ u_long in6m_nsrc; /* # of tree entries */ @@ -670,8 +671,17 @@ } in6m_st[2]; /* state at t0, t1 */ }; -void in6m_disconnect(struct in6_multi *inm); -extern int ifma6_restart; +void in6m_disconnect_locked(struct in6_multi_head *inmh, struct in6_multi *inm); + +#include + +static inline void +in6m_trace(const char *desc, struct in6_multi *inm) +{ + printf("INM=%p REF=%d %s\n", inm, inm->in6m_refcount, desc); + kdb_backtrace(); +} + /* * Helper function to derive the filter mode on a source entry * from its internal counters. Predicates are: @@ -713,13 +723,25 @@ #define IN6_MULTI_LOCK_ASSERT() sx_assert(&in6_multi_sx, SA_XLOCKED) #define IN6_MULTI_UNLOCK_ASSERT() sx_assert(&in6_multi_sx, SA_XUNLOCKED) +/* + * Get the in6_multi pointer from a ifmultiaddr. + * Returns NULL if ifmultiaddr is no longer valid. + */ +static __inline struct in6_multi * +in6m_ifmultiaddr_get_inm(struct ifmultiaddr *ifma) +{ + NET_EPOCH_ASSERT(); + + return ((ifma->ifma_addr->sa_family != AF_INET6 || + (ifma->ifma_flags & IFMA_F_ENQUEUED) == 0) ? NULL : + ifma->ifma_protospec); +} + /* * Look up an in6_multi record for an IPv6 multicast address * on the interface ifp. * If no record found, return NULL. - * - * SMPng: The IN6_MULTI_LOCK and IF_ADDR_LOCK on ifp must be held. */ static __inline struct in6_multi * in6m_lookup_locked(struct ifnet *ifp, const struct in6_addr *mcaddr) @@ -727,18 +749,14 @@ struct ifmultiaddr *ifma; struct in6_multi *inm; - inm = NULL; - CK_STAILQ_FOREACH(ifma, &((ifp)->if_multiaddrs), ifma_link) { - if (ifma->ifma_addr->sa_family == AF_INET6) { - inm = (struct in6_multi *)ifma->ifma_protospec; - if (inm == NULL) - continue; - if (IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, mcaddr)) - break; - inm = NULL; - } + CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) + continue; + if (IN6_ARE_ADDR_EQUAL(&inm->in6m_addr, mcaddr)) + return (inm); } - return (inm); + return (NULL); } /* @@ -768,6 +786,8 @@ IN6_MULTI_LIST_LOCK_ASSERT(); ++inm->in6m_refcount; + + in6m_trace("post acquire", inm); } static __inline void @@ -784,6 +804,8 @@ KASSERT(inm->in6m_refcount > 0, ("refcount == %d inm: %p", inm->in6m_refcount, inm)); IN6_MULTI_LIST_LOCK_ASSERT(); + in6m_trace("pre release", inm); + if (--inm->in6m_refcount == 0) { MPASS(inm->in6m_ifp == NULL); inm->in6m_ifma->ifma_protospec = NULL; @@ -809,8 +831,8 @@ void in6m_commit(struct in6_multi *); void in6m_print(const struct in6_multi *); int in6m_record_source(struct in6_multi *, const struct in6_addr *); -void in6m_release_deferred(struct in6_multi *); void in6m_release_list_deferred(struct in6_multi_head *); +void in6m_release_wait(void); void ip6_freemoptions(struct ip6_moptions *); int ip6_getmoptions(struct inpcb *, struct sockopt *); int ip6_setmoptions(struct inpcb *, struct sockopt *); --- sys/netinet6/mld6.c (revision 343111) +++ sys/netinet6/mld6.c (working copy) @@ -110,7 +110,7 @@ static void mld_dispatch_packet(struct mbuf *); static void mld_dispatch_queue(struct mbufq *, int); static void mld_final_leave(struct in6_multi *, struct mld_ifsoftc *); -static void mld_fasttimo_vnet(void); +static void mld_fasttimo_vnet(struct in6_multi_head *inmh); static int mld_handle_state_change(struct in6_multi *, struct mld_ifsoftc *); static int mld_initial_join(struct in6_multi *, struct mld_ifsoftc *, @@ -243,6 +243,10 @@ SYSCTL_INT(_net_inet6_mld, OID_AUTO, v1enable, CTLFLAG_RWTUN, &mld_v1enable, 0, "Enable fallback to MLDv1"); +static int mld_v2enable = 1; +SYSCTL_INT(_net_inet6_mld, OID_AUTO, v2enable, CTLFLAG_RWTUN, + &mld_v2enable, 0, "Enable MLDv2"); + static int mld_use_allow = 1; SYSCTL_INT(_net_inet6_mld, OID_AUTO, use_allow, CTLFLAG_RWTUN, &mld_use_allow, 0, "Use ALLOW/BLOCK for RFC 4604 SSM joins/leaves"); @@ -533,45 +537,48 @@ * XXX This routine is also bitten by unlocked ifma_protospec access. */ void -mld_ifdetach(struct ifnet *ifp) +mld_ifdetach(struct ifnet *ifp, struct in6_multi_head *inmh) { + struct epoch_tracker et; struct mld_ifsoftc *mli; - struct ifmultiaddr *ifma, *next; + struct ifmultiaddr *ifma; struct in6_multi *inm; - struct in6_multi_head inmh; CTR3(KTR_MLD, "%s: called for ifp %p(%s)", __func__, ifp, if_name(ifp)); - SLIST_INIT(&inmh); IN6_MULTI_LIST_LOCK_ASSERT(); MLD_LOCK(); mli = MLD_IFINFO(ifp); - if (mli->mli_version == MLD_VERSION_2) { - IF_ADDR_WLOCK(ifp); - restart: - CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - ifma->ifma_protospec == NULL) - continue; - inm = (struct in6_multi *)ifma->ifma_protospec; + IF_ADDR_WLOCK(ifp); + /* + * Extract list of in6_multi associated with the detaching ifp + * which the PF_INET6 layer is about to release. + */ + NET_EPOCH_ENTER(et); + CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) + continue; + in6m_disconnect_locked(inmh, inm); + + if (mli->mli_version == MLD_VERSION_2) { + in6m_clear_recorded(inm); + + /* + * We need to release the final reference held + * for issuing the INCLUDE {}. + */ if (inm->in6m_state == MLD_LEAVING_MEMBER) { - in6m_disconnect(inm); - in6m_rele_locked(&inmh, inm); - ifma->ifma_protospec = NULL; + inm->in6m_state = MLD_NOT_MEMBER; + in6m_rele_locked(inmh, inm); } - in6m_clear_recorded(inm); - if (__predict_false(ifma6_restart)) { - ifma6_restart = false; - goto restart; - } } - IF_ADDR_WUNLOCK(ifp); } - + NET_EPOCH_EXIT(et); + IF_ADDR_WUNLOCK(ifp); MLD_UNLOCK(); - in6m_release_list_deferred(&inmh); } /* @@ -705,10 +712,9 @@ CTR2(KTR_MLD, "process v1 general query on ifp %p(%s)", ifp, if_name(ifp)); CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - ifma->ifma_protospec == NULL) + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) continue; - inm = (struct in6_multi *)ifma->ifma_protospec; mld_v1_update_group(inm, timer); } } else { @@ -819,6 +825,13 @@ is_general_query = 0; + if (!mld_v2enable) { + CTR3(KTR_MLD, "ignore v2 query %s on ifp %p(%s)", + ip6_sprintf(ip6tbuf, &mld->mld_addr), + ifp, if_name(ifp)); + return (0); + } + /* * RFC3810 Section 6.2: MLD queries must originate from * a router's link-local address. @@ -1313,15 +1326,19 @@ void mld_fasttimo(void) { + struct in6_multi_head inmh; VNET_ITERATOR_DECL(vnet_iter); + SLIST_INIT(&inmh); + VNET_LIST_RLOCK_NOSLEEP(); VNET_FOREACH(vnet_iter) { CURVNET_SET(vnet_iter); - mld_fasttimo_vnet(); + mld_fasttimo_vnet(&inmh); CURVNET_RESTORE(); } VNET_LIST_RUNLOCK_NOSLEEP(); + in6m_release_list_deferred(&inmh); } /* @@ -1330,15 +1347,15 @@ * VIMAGE: Assume caller has set up our curvnet. */ static void -mld_fasttimo_vnet(void) +mld_fasttimo_vnet(struct in6_multi_head *inmh) { + struct epoch_tracker et; struct mbufq scq; /* State-change packets */ struct mbufq qrq; /* Query response packets */ struct ifnet *ifp; struct mld_ifsoftc *mli; - struct ifmultiaddr *ifma, *next; - struct in6_multi *inm, *tinm; - struct in6_multi_head inmh; + struct ifmultiaddr *ifma; + struct in6_multi *inm; int uri_fasthz; uri_fasthz = 0; @@ -1353,7 +1370,6 @@ !V_state_change_timers_running6) return; - SLIST_INIT(&inmh); IN6_MULTI_LIST_LOCK(); MLD_LOCK(); @@ -1399,25 +1415,20 @@ } IF_ADDR_WLOCK(ifp); - restart: - CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - ifma->ifma_protospec == NULL) + NET_EPOCH_ENTER(et); + CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) continue; - inm = (struct in6_multi *)ifma->ifma_protospec; switch (mli->mli_version) { case MLD_VERSION_1: - mld_v1_process_group_timer(&inmh, inm); + mld_v1_process_group_timer(inmh, inm); break; case MLD_VERSION_2: - mld_v2_process_group_timers(&inmh, &qrq, + mld_v2_process_group_timers(inmh, &qrq, &scq, inm, uri_fasthz); break; } - if (__predict_false(ifma6_restart)) { - ifma6_restart = false; - goto restart; - } } IF_ADDR_WUNLOCK(ifp); @@ -1431,9 +1442,8 @@ * IF_ADDR_LOCK internally as well as * ip6_output() to transmit a packet. */ - SLIST_FOREACH_SAFE(inm, &inmh, in6m_nrele, tinm) { - SLIST_REMOVE_HEAD(&inmh, - in6m_nrele); + while ((inm = SLIST_FIRST(inmh)) != NULL) { + SLIST_REMOVE_HEAD(inmh, in6m_defer); (void)mld_v1_transmit_report(inm, MLD_LISTENER_REPORT); } @@ -1441,14 +1451,9 @@ case MLD_VERSION_2: mld_dispatch_queue(&qrq, 0); mld_dispatch_queue(&scq, 0); - - /* - * Free the in_multi reference(s) for - * this lifecycle. - */ - in6m_release_list_deferred(&inmh); break; } + NET_EPOCH_EXIT(et); } out_locked: @@ -1488,8 +1493,7 @@ case MLD_REPORTING_MEMBER: if (report_timer_expired) { inm->in6m_state = MLD_IDLE_MEMBER; - in6m_disconnect(inm); - in6m_rele_locked(inmh, inm); + SLIST_INSERT_HEAD(inmh, inm, in6m_defer); } break; case MLD_G_QUERY_PENDING_MEMBER: @@ -1613,7 +1617,7 @@ if (inm->in6m_state == MLD_LEAVING_MEMBER && inm->in6m_scrv == 0) { inm->in6m_state = MLD_NOT_MEMBER; - in6m_disconnect(inm); + in6m_disconnect_locked(inmh, inm); in6m_rele_locked(inmh, inm); } } @@ -1658,10 +1662,11 @@ static void mld_v2_cancel_link_timers(struct mld_ifsoftc *mli) { - struct ifmultiaddr *ifma, *next; + struct epoch_tracker et; + struct in6_multi_head inmh; + struct ifmultiaddr *ifma; struct ifnet *ifp; struct in6_multi *inm; - struct in6_multi_head inmh; CTR3(KTR_MLD, "%s: cancel v2 timers on ifp %p(%s)", __func__, mli->mli_ifp, if_name(mli->mli_ifp)); @@ -1684,12 +1689,11 @@ ifp = mli->mli_ifp; IF_ADDR_WLOCK(ifp); - restart: - CK_STAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - ifma->ifma_protospec == NULL) + NET_EPOCH_ENTER(et); + CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) continue; - inm = (struct in6_multi *)ifma->ifma_protospec; switch (inm->in6m_state) { case MLD_NOT_MEMBER: case MLD_SILENT_MEMBER: @@ -1704,9 +1708,7 @@ * version, we need to release the final * reference held for issuing the INCLUDE {}. */ - in6m_disconnect(inm); in6m_rele_locked(&inmh, inm); - ifma->ifma_protospec = NULL; /* FALLTHROUGH */ case MLD_G_QUERY_PENDING_MEMBER: case MLD_SG_QUERY_PENDING_MEMBER: @@ -1722,11 +1724,8 @@ mbufq_drain(&inm->in6m_scq); break; } - if (__predict_false(ifma6_restart)) { - ifma6_restart = false; - goto restart; - } } + NET_EPOCH_EXIT(et); IF_ADDR_WUNLOCK(ifp); in6m_release_list_deferred(&inmh); } @@ -1802,7 +1801,7 @@ IN6_MULTI_LIST_LOCK_ASSERT(); MLD_LOCK_ASSERT(); - + ifp = in6m->in6m_ifp; /* in process of being freed */ if (ifp == NULL) @@ -1899,6 +1898,14 @@ error = 0; /* + * Check if the in6_multi has already been disconnected. + */ + if (inm->in6m_ifp == NULL) { + CTR1(KTR_MLD, "%s: inm is disconnected", __func__); + return (0); + } + + /* * Try to detect if the upper layer just asked us to change state * for an interface which has now gone away. */ @@ -2007,7 +2014,9 @@ */ if (mli->mli_version == MLD_VERSION_2 && inm->in6m_state == MLD_LEAVING_MEMBER) { + in6m_trace("pre leaving member", inm); inm->in6m_refcount--; + MPASS(inm->in6m_refcount > 0); } inm->in6m_state = MLD_REPORTING_MEMBER; @@ -3012,11 +3021,9 @@ NET_EPOCH_ENTER(et); CK_STAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) { - if (ifma->ifma_addr->sa_family != AF_INET6 || - ifma->ifma_protospec == NULL) + inm = in6m_ifmultiaddr_get_inm(ifma); + if (inm == NULL) continue; - - inm = (struct in6_multi *)ifma->ifma_protospec; KASSERT(ifp == inm->in6m_ifp, ("%s: inconsistent ifp", __func__)); --- sys/netinet6/mld6_var.h (revision 343111) +++ sys/netinet6/mld6_var.h (working copy) @@ -160,12 +160,13 @@ #define MLD_IFINFO(ifp) \ (((struct in6_ifextra *)(ifp)->if_afdata[AF_INET6])->mld_ifinfo) +struct in6_multi_head; int mld_change_state(struct in6_multi *, const int); struct mld_ifsoftc * mld_domifattach(struct ifnet *); void mld_domifdetach(struct ifnet *); void mld_fasttimo(void); -void mld_ifdetach(struct ifnet *); +void mld_ifdetach(struct ifnet *, struct in6_multi_head *); int mld_input(struct mbuf *, int, int); void mld_slowtimo(void);