aboutsummaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorMatt Macy <mmacy@FreeBSD.org>2018-05-06 20:34:13 +0000
committerMatt Macy <mmacy@FreeBSD.org>2018-05-06 20:34:13 +0000
commitb6f6f88018f35e44a181d2b38ae57bc5d67451f6 (patch)
tree46beeae0db0f675aae02e05960323f4daca94c45 /sys
parent7edd877a1eb6cb6eda87d52bf04582215d066d43 (diff)
downloadsrc-b6f6f88018f35e44a181d2b38ae57bc5d67451f6.tar.gz
src-b6f6f88018f35e44a181d2b38ae57bc5d67451f6.zip
r333175 introduced deferred deletion of multicast addresses in order to permit the driver ioctl
to sleep on commands to the NIC when updating multicast filters. More generally this permitted driver's to use an sx as a softc lock. Unfortunately this change introduced a race whereby a a multicast update would still be queued for deletion when ifconfig deleted the interface thus calling down in to _purgemaddrs and synchronously deleting _all_ of the multicast addresses on the interface. Synchronously remove all external references to a multicast address before enqueueing for delete. Reported by: lwhsu Approved by: sbruno
Notes
Notes: svn path=/head/; revision=333309
Diffstat (limited to 'sys')
-rw-r--r--sys/net/if.c36
-rw-r--r--sys/net/if_var.h8
-rw-r--r--sys/netinet/igmp.c30
-rw-r--r--sys/netinet/in.c17
-rw-r--r--sys/netinet/in_mcast.c40
-rw-r--r--sys/netinet/in_var.h4
-rw-r--r--sys/netinet6/in6_ifattach.c15
-rw-r--r--sys/netinet6/in6_mcast.c66
-rw-r--r--sys/netinet6/in6_var.h22
-rw-r--r--sys/netinet6/mld6.c41
10 files changed, 195 insertions, 84 deletions
diff --git a/sys/net/if.c b/sys/net/if.c
index 588081a9bbff..fb09a4f4e208 100644
--- a/sys/net/if.c
+++ b/sys/net/if.c
@@ -254,7 +254,6 @@ struct mbuf *(*tbr_dequeue_ptr)(struct ifaltq *, int) = NULL;
static void if_attachdomain(void *);
static void if_attachdomain1(struct ifnet *);
static int ifconf(u_long, caddr_t);
-static void if_freemulti(struct ifmultiaddr *);
static void if_grow(void);
static void if_input_default(struct ifnet *, struct mbuf *);
static int if_requestencap_default(struct ifnet *, struct if_encap_req *);
@@ -3395,7 +3394,10 @@ if_allocmulti(struct ifnet *ifp, struct sockaddr *sa, struct sockaddr *llsa,
* counting, notifying the driver, handling routing messages, and releasing
* any dependent link layer state.
*/
-static void
+#ifdef MCAST_VERBOSE
+extern void kdb_backtrace(void);
+#endif
+void
if_freemulti(struct ifmultiaddr *ifma)
{
@@ -3404,6 +3406,10 @@ if_freemulti(struct ifmultiaddr *ifma)
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);
}
@@ -3610,6 +3616,12 @@ if_delallmulti(struct ifnet *ifp)
IF_ADDR_WUNLOCK(ifp);
}
+void
+if_delmulti_ifma(struct ifmultiaddr *ifma)
+{
+ if_delmulti_ifma_flags(ifma, 0);
+}
+
/*
* Delete a multicast group membership by group membership pointer.
* Network-layer protocol domains must use this routine.
@@ -3617,11 +3629,11 @@ if_delallmulti(struct ifnet *ifp)
* It is safe to call this routine if the ifp disappeared.
*/
void
-if_delmulti_ifma(struct ifmultiaddr *ifma)
+if_delmulti_ifma_flags(struct ifmultiaddr *ifma, int flags)
{
struct ifnet *ifp;
int lastref;
-
+ MCDPRINTF("%s freeing ifma: %p\n", __func__, ifma);
#ifdef INET
IN_MULTI_LIST_UNLOCK_ASSERT();
#endif
@@ -3649,7 +3661,7 @@ if_delmulti_ifma(struct ifmultiaddr *ifma)
if (ifp != NULL)
IF_ADDR_WLOCK(ifp);
- lastref = if_delmulti_locked(ifp, ifma, 0);
+ lastref = if_delmulti_locked(ifp, ifma, flags);
if (ifp != NULL) {
/*
@@ -3683,6 +3695,7 @@ if_delmulti_locked(struct ifnet *ifp, struct ifmultiaddr *ifma, int detaching)
}
ifp = ifma->ifma_ifp;
+ MCDPRINTF("%s freeing %p from %s \n", __func__, ifma, ifp ? ifp->if_xname : "");
/*
* If the ifnet is detaching, null out references to ifnet,
@@ -3708,6 +3721,9 @@ if_delmulti_locked(struct ifnet *ifp, struct ifmultiaddr *ifma, int detaching)
if (--ifma->ifma_refcount > 0)
return 0;
+ if (ifp != NULL && detaching == 0)
+ TAILQ_REMOVE(&ifp->if_multiaddrs, ifma, ifma_link);
+
/*
* If this ifma is a network-layer ifma, a link-layer ifma may
* have been associated with it. Release it first if so.
@@ -3726,11 +3742,15 @@ if_delmulti_locked(struct ifnet *ifp, struct ifmultiaddr *ifma, int detaching)
if_freemulti(ll_ifma);
}
}
- if (ifp != NULL && detaching == 0)
- TAILQ_REMOVE(&ifp->if_multiaddrs, ifma, ifma_link);
+#ifdef INVARIANTS
+ if (ifp) {
+ struct ifmultiaddr *ifmatmp;
+ TAILQ_FOREACH(ifmatmp, &ifp->if_multiaddrs, ifma_link)
+ MPASS(ifma != ifmatmp);
+ }
+#endif
if_freemulti(ifma);
-
/*
* The last reference to this instance of struct ifmultiaddr
* was released; the hardware should be notified of this change.
diff --git a/sys/net/if_var.h b/sys/net/if_var.h
index c9e50a880724..782b9c706b7b 100644
--- a/sys/net/if_var.h
+++ b/sys/net/if_var.h
@@ -596,6 +596,12 @@ VNET_DECLARE(struct ifnet *, loif); /* first loopback interface */
#define V_if_index VNET(if_index)
#define V_loif VNET(loif)
+#ifdef MCAST_VERBOSE
+#define MCDPRINTF printf
+#else
+#define MCDPRINTF(...)
+#endif
+
int if_addgroup(struct ifnet *, const char *);
int if_delgroup(struct ifnet *, const char *);
int if_addmulti(struct ifnet *, struct sockaddr *, struct ifmultiaddr **);
@@ -605,12 +611,14 @@ void if_attach(struct ifnet *);
void if_dead(struct ifnet *);
int if_delmulti(struct ifnet *, struct sockaddr *);
void if_delmulti_ifma(struct ifmultiaddr *);
+void if_delmulti_ifma_flags(struct ifmultiaddr *, int flags);
void if_detach(struct ifnet *);
void if_purgeaddrs(struct ifnet *);
void if_delallmulti(struct ifnet *);
void if_down(struct ifnet *);
struct ifmultiaddr *
if_findmulti(struct ifnet *, const struct sockaddr *);
+void if_freemulti(struct ifmultiaddr *ifma);
void if_free(struct ifnet *);
void if_initname(struct ifnet *, const char *, int);
void if_link_state_change(struct ifnet *, int);
diff --git a/sys/netinet/igmp.c b/sys/netinet/igmp.c
index 4389d169ec6d..2f4f5242cd12 100644
--- a/sys/netinet/igmp.c
+++ b/sys/netinet/igmp.c
@@ -610,7 +610,7 @@ void
igmp_ifdetach(struct ifnet *ifp)
{
struct igmp_ifsoftc *igi;
- struct ifmultiaddr *ifma;
+ struct ifmultiaddr *ifma, *next;
struct in_multi *inm;
struct in_multi_head inm_free_tmp;
CTR3(KTR_IGMPV3, "%s: called for ifp %p(%s)", __func__, ifp,
@@ -621,21 +621,22 @@ igmp_ifdetach(struct ifnet *ifp)
igi = ((struct in_ifinfo *)ifp->if_afdata[AF_INET])->ii_igmp;
if (igi->igi_version == IGMP_VERSION_3) {
- IF_ADDR_RLOCK(ifp);
- TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ IF_ADDR_WLOCK(ifp);
+ restart:
+ TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
if (ifma->ifma_addr->sa_family != AF_INET ||
ifma->ifma_protospec == NULL)
continue;
-#if 0
- KASSERT(ifma->ifma_protospec != NULL,
- ("%s: ifma_protospec is NULL", __func__));
-#endif
inm = (struct in_multi *)ifma->ifma_protospec;
if (inm->inm_state == IGMP_LEAVING_MEMBER)
inm_rele_locked(&inm_free_tmp, inm);
inm_clear_recorded(inm);
+ if (__predict_false(ifma_restart)) {
+ ifma_restart = false;
+ goto restart;
+ }
}
- IF_ADDR_RUNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
inm_release_list_deferred(&inm_free_tmp);
}
IGMP_UNLOCK();
@@ -1631,7 +1632,7 @@ igmp_fasttimo_vnet(void)
struct mbufq qrq; /* Query response packets */
struct ifnet *ifp;
struct igmp_ifsoftc *igi;
- struct ifmultiaddr *ifma;
+ struct ifmultiaddr *ifma, *next;
struct in_multi *inm;
struct in_multi_head inm_free_tmp;
int loop, uri_fasthz;
@@ -1695,8 +1696,9 @@ igmp_fasttimo_vnet(void)
mbufq_init(&scq, IGMP_MAX_STATE_CHANGE_PACKETS);
}
- IF_ADDR_RLOCK(ifp);
- TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ IF_ADDR_WLOCK(ifp);
+ restart:
+ TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
if (ifma->ifma_addr->sa_family != AF_INET ||
ifma->ifma_protospec == NULL)
continue;
@@ -1712,8 +1714,12 @@ igmp_fasttimo_vnet(void)
&scq, inm, uri_fasthz);
break;
}
+ if (__predict_false(ifma_restart)) {
+ ifma_restart = false;
+ goto restart;
+ }
}
- IF_ADDR_RUNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
if (igi->igi_version == IGMP_VERSION_3) {
igmp_dispatch_queue(&qrq, 0, loop);
diff --git a/sys/netinet/in.c b/sys/netinet/in.c
index c17e3bd93e12..5563bc8a088f 100644
--- a/sys/netinet/in.c
+++ b/sys/netinet/in.c
@@ -1012,7 +1012,7 @@ in_purgemaddrs(struct ifnet *ifp)
{
struct in_multi_head purgeinms;
struct in_multi *inm;
- struct ifmultiaddr *ifma;
+ struct ifmultiaddr *ifma, *next;
SLIST_INIT(&purgeinms);
IN_MULTI_LIST_LOCK();
@@ -1023,19 +1023,20 @@ in_purgemaddrs(struct ifnet *ifp)
* We need to do this as IF_ADDR_LOCK() may be re-acquired
* by code further down.
*/
- IF_ADDR_RLOCK(ifp);
- TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ IF_ADDR_WLOCK(ifp);
+ restart:
+ TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
if (ifma->ifma_addr->sa_family != AF_INET ||
ifma->ifma_protospec == NULL)
continue;
-#if 0
- KASSERT(ifma->ifma_protospec != NULL,
- ("%s: ifma_protospec is NULL", __func__));
-#endif
inm = (struct in_multi *)ifma->ifma_protospec;
inm_rele_locked(&purgeinms, inm);
+ if (__predict_false(ifma_restart)) {
+ ifma_restart = true;
+ goto restart;
+ }
}
- IF_ADDR_RUNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
inm_release_list_deferred(&purgeinms);
igmp_ifdetach(ifp);
diff --git a/sys/netinet/in_mcast.c b/sys/netinet/in_mcast.c
index afca84e68039..5a8c923ec508 100644
--- a/sys/netinet/in_mcast.c
+++ b/sys/netinet/in_mcast.c
@@ -112,6 +112,8 @@ MTX_SYSINIT(in_multi_free_mtx, &in_multi_free_mtx, "in_multi_free_mtx", MTX_DEF)
struct sx in_multi_sx;
SX_SYSINIT(in_multi_sx, &in_multi_sx, "in_multi_sx");
+int ifma_restart;
+
/*
* Functions with non-static linkage defined in this file should be
* declared in in_var.h:
@@ -252,6 +254,33 @@ inm_release_list_deferred(struct in_multi_head *inmh)
}
void
+inm_disconnect(struct in_multi *inm)
+{
+ struct ifnet *ifp;
+ struct ifmultiaddr *ifma, *ll_ifma;
+
+ ifp = inm->inm_ifp;
+ IF_ADDR_WLOCK_ASSERT(ifp);
+ ifma = inm->inm_ifma;
+
+ if_ref(ifp);
+ TAILQ_REMOVE(&ifp->if_multiaddrs, ifma, ifma_link);
+ MCDPRINTF("removed ifma: %p from %s\n", ifma, ifp->if_xname);
+ if ((ll_ifma = ifma->ifma_llifma) != NULL) {
+ MPASS(ifma != ll_ifma);
+ ifma->ifma_llifma = NULL;
+ MPASS(ll_ifma->ifma_llifma == NULL);
+ MPASS(ll_ifma->ifma_ifp == ifp);
+ if (--ll_ifma->ifma_refcount == 0) {
+ TAILQ_REMOVE(&ifp->if_multiaddrs, ll_ifma, ifma_link);
+ MCDPRINTF("removed ll_ifma: %p from %s\n", ll_ifma, ifp->if_xname);
+ if_freemulti(ll_ifma);
+ ifma_restart = true;
+ }
+ }
+}
+
+void
inm_release_deferred(struct in_multi *inm)
{
struct in_multi_head tmp;
@@ -260,6 +289,7 @@ inm_release_deferred(struct in_multi *inm)
MPASS(inm->inm_refcount > 0);
if (--inm->inm_refcount == 0) {
SLIST_INIT(&tmp);
+ inm_disconnect(inm);
inm->inm_ifma->ifma_protospec = NULL;
SLIST_INSERT_HEAD(&tmp, inm, inm_nrele);
inm_release_list_deferred(&tmp);
@@ -643,9 +673,11 @@ inm_release(struct in_multi *inm)
inm_purge(inm);
free(inm, M_IPMADDR);
- if_delmulti_ifma(ifma);
- if (ifp)
+ if_delmulti_ifma_flags(ifma, 1);
+ if (ifp) {
CURVNET_RESTORE();
+ if_rele(ifp);
+ }
}
/*
@@ -1270,7 +1302,6 @@ in_joingroup_locked(struct ifnet *ifp, const struct in_addr *gina,
}
out_inm_release:
- IN_MULTI_LIST_UNLOCK();
if (error) {
CTR2(KTR_IGMPV3, "%s: dropping ref on %p", __func__, inm);
@@ -1278,6 +1309,7 @@ in_joingroup_locked(struct ifnet *ifp, const struct in_addr *gina,
} else {
*pinm = inm;
}
+ IN_MULTI_LIST_UNLOCK();
return (error);
}
@@ -1350,7 +1382,9 @@ in_leavegroup_locked(struct in_multi *inm, /*const*/ struct in_mfilter *imf)
CTR1(KTR_IGMPV3, "%s: doing igmp downcall", __func__);
CURVNET_SET(inm->inm_ifp->if_vnet);
error = igmp_change_state(inm);
+ IF_ADDR_WLOCK(inm->inm_ifp);
inm_release_deferred(inm);
+ IF_ADDR_WUNLOCK(inm->inm_ifp);
IN_MULTI_LIST_UNLOCK();
CURVNET_RESTORE();
if (error)
diff --git a/sys/netinet/in_var.h b/sys/netinet/in_var.h
index 9788e8eddccf..89bab81b5e51 100644
--- a/sys/netinet/in_var.h
+++ b/sys/netinet/in_var.h
@@ -343,6 +343,9 @@ extern struct sx in_multi_sx;
#define IN_MULTI_LOCK_ASSERT() sx_assert(&in_multi_sx, SA_XLOCKED)
#define IN_MULTI_UNLOCK_ASSERT() sx_assert(&in_multi_sx, SA_XUNLOCKED)
+void inm_disconnect(struct in_multi *inm);
+extern int ifma_restart;
+
/* Acquire an in_multi record. */
static __inline void
inm_acquire_locked(struct in_multi *inm)
@@ -368,6 +371,7 @@ inm_rele_locked(struct in_multi_head *inmh, struct in_multi *inm)
if (--inm->inm_refcount == 0) {
MPASS(inmh != NULL);
+ inm_disconnect(inm);
inm->inm_ifma->ifma_protospec = NULL;
SLIST_INSERT_HEAD(inmh, inm, inm_nrele);
}
diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c
index 87fc501e39d3..baff523207ec 100644
--- a/sys/netinet6/in6_ifattach.c
+++ b/sys/netinet6/in6_ifattach.c
@@ -848,26 +848,29 @@ in6_purgemaddrs(struct ifnet *ifp)
{
struct in6_multi_head purgeinms;
struct in6_multi *inm;
- struct ifmultiaddr *ifma;
+ struct ifmultiaddr *ifma, *next;
SLIST_INIT(&purgeinms);
IN6_MULTI_LOCK();
IN6_MULTI_LIST_LOCK();
+ IF_ADDR_WLOCK(ifp);
/*
* Extract list of in6_multi associated with the detaching ifp
* which the PF_INET6 layer is about to release.
- * We need to do this as IF_ADDR_LOCK() may be re-acquired
- * by code further down.
*/
- IF_ADDR_RLOCK(ifp);
- TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ restart:
+ TAILQ_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_rele_locked(&purgeinms, inm);
+ if (__predict_false(ifma6_restart)) {
+ ifma6_restart = false;
+ goto restart;
+ }
}
- IF_ADDR_RUNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
mld_ifdetach(ifp);
IN6_MULTI_LIST_UNLOCK();
IN6_MULTI_UNLOCK();
diff --git a/sys/netinet6/in6_mcast.c b/sys/netinet6/in6_mcast.c
index 023e3a751327..fa8093cd5c3c 100644
--- a/sys/netinet6/in6_mcast.c
+++ b/sys/netinet6/in6_mcast.c
@@ -190,6 +190,7 @@ static SYSCTL_NODE(_net_inet6_ip6_mcast, OID_AUTO, filters,
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.
@@ -531,6 +532,7 @@ in6m_release(struct in6_multi *inm)
ifma = inm->in6m_ifma;
ifp = inm->in6m_ifp;
+ MPASS(ifma->ifma_llifma == NULL);
/* XXX this access is not covered by IF_ADDR_LOCK */
CTR2(KTR_MLD, "%s: purging ifma %p", __func__, ifma);
@@ -542,9 +544,11 @@ in6m_release(struct in6_multi *inm)
in6m_purge(inm);
free(inm, M_IP6MADDR);
- if_delmulti_ifma(ifma);
- if (ifp)
+ if_delmulti_ifma_flags(ifma, 1);
+ if (ifp) {
CURVNET_RESTORE();
+ if_rele(ifp);
+ }
}
static struct grouptask free_gtask;
@@ -572,30 +576,58 @@ in6m_release_list_deferred(struct in6_multi_head *inmh)
}
void
-in6m_release_deferred(struct in6_multi *inm)
+in6m_disconnect(struct in6_multi *inm)
{
- struct in6_multi_head tmp;
struct ifnet *ifp;
struct ifaddr *ifa;
struct in6_ifaddr *ifa6;
struct in6_multi_mship *imm;
+ struct ifmultiaddr *ifma, *ll_ifma;
+
+ ifp = inm->in6m_ifp;
+ IF_ADDR_WLOCK_ASSERT(ifp);
+ ifma = inm->in6m_ifma;
+
+ if_ref(ifp);
+ TAILQ_REMOVE(&ifp->if_multiaddrs, ifma, ifma_link);
+ MCDPRINTF("removed ifma: %p from %s\n", ifma, ifp->if_xname);
+ if ((ll_ifma = ifma->ifma_llifma) != NULL) {
+ MPASS(ifma != ll_ifma);
+ ifma->ifma_llifma = NULL;
+ MPASS(ll_ifma->ifma_llifma == NULL);
+ MPASS(ll_ifma->ifma_ifp == ifp);
+ if (--ll_ifma->ifma_refcount == 0) {
+ ifma6_restart = true;
+ TAILQ_REMOVE(&ifp->if_multiaddrs, ll_ifma, ifma_link);
+ MCDPRINTF("removed ll_ifma: %p from %s\n", ll_ifma, ifp->if_xname);
+ if_freemulti(ll_ifma);
+ }
+ }
+ TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
+ if (ifa->ifa_addr->sa_family != AF_INET6)
+ continue;
+ ifa6 = (void *)ifa;
+ LIST_FOREACH(imm, &ifa6->ia6_memberships, i6mm_chain) {
+ if (inm == imm->i6mm_maddr) {
+ LIST_REMOVE(imm, i6mm_chain);
+ free(imm, M_IP6MADDR);
+ }
+ }
+ }
+}
+
+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) {
- ifp = inm->in6m_ifp;
- IF_ADDR_LOCK_ASSERT(ifp);
- TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
- if (ifa->ifa_addr->sa_family != AF_INET6)
- continue;
- ifa6 = (void *)ifa;
- LIST_FOREACH(imm, &ifa6->ia6_memberships, i6mm_chain) {
- if (inm == imm->i6mm_maddr)
- imm->i6mm_maddr = NULL;
- }
- }
+ in6m_disconnect(inm);
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);
}
@@ -1355,10 +1387,10 @@ in6_leavegroup_locked(struct in6_multi *inm, /*const*/ struct in6_mfilter *imf)
CTR2(KTR_MLD, "%s: dropping ref on %p", __func__, inm);
if (ifp)
- IF_ADDR_RLOCK(ifp);
+ IF_ADDR_WLOCK(ifp);
in6m_release_deferred(inm);
if (ifp)
- IF_ADDR_RUNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
IN6_MULTI_LIST_UNLOCK();
return (error);
diff --git a/sys/netinet6/in6_var.h b/sys/netinet6/in6_var.h
index 7a98fc7c0c7c..ddf37d75869c 100644
--- a/sys/netinet6/in6_var.h
+++ b/sys/netinet6/in6_var.h
@@ -670,6 +670,8 @@ struct in6_multi {
} in6m_st[2]; /* state at t0, t1 */
};
+void in6m_disconnect(struct in6_multi *inm);
+extern int ifma6_restart;
/*
* Helper function to derive the filter mode on a source entry
* from its internal counters. Predicates are:
@@ -711,6 +713,7 @@ extern struct sx in6_multi_sx;
#define IN6_MULTI_LOCK_ASSERT() sx_assert(&in6_multi_sx, SA_XLOCKED)
#define IN6_MULTI_UNLOCK_ASSERT() sx_assert(&in6_multi_sx, SA_XUNLOCKED)
+
/*
* Look up an in6_multi record for an IPv6 multicast address
* on the interface ifp.
@@ -779,28 +782,13 @@ in6m_acquire(struct in6_multi *inm)
static __inline void
in6m_rele_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;
-
KASSERT(inm->in6m_refcount > 0, ("refcount == %d inm: %p", inm->in6m_refcount, inm));
IN6_MULTI_LIST_LOCK_ASSERT();
if (--inm->in6m_refcount == 0) {
- ifp = inm->in6m_ifp;
- IF_ADDR_LOCK_ASSERT(ifp);
- TAILQ_FOREACH(ifa, &ifp->if_addrhead, ifa_link) {
- if (ifa->ifa_addr->sa_family != AF_INET6)
- continue;
-
- ifa6 = (void *)ifa;
- LIST_FOREACH(imm, &ifa6->ia6_memberships, i6mm_chain) {
- if (inm == imm->i6mm_maddr)
- imm->i6mm_maddr = NULL;
- }
- }
+ in6m_disconnect(inm);
inm->in6m_ifma->ifma_protospec = NULL;
+ MPASS(inm->in6m_ifma->ifma_llifma == NULL);
SLIST_INSERT_HEAD(inmh, inm, in6m_nrele);
}
}
diff --git a/sys/netinet6/mld6.c b/sys/netinet6/mld6.c
index bba7dce96e41..567fedd1ec38 100644
--- a/sys/netinet6/mld6.c
+++ b/sys/netinet6/mld6.c
@@ -536,7 +536,7 @@ void
mld_ifdetach(struct ifnet *ifp)
{
struct mld_ifsoftc *mli;
- struct ifmultiaddr *ifma;
+ struct ifmultiaddr *ifma, *next;
struct in6_multi *inm;
struct in6_multi_head inmh;
@@ -549,8 +549,9 @@ mld_ifdetach(struct ifnet *ifp)
mli = MLD_IFINFO(ifp);
if (mli->mli_version == MLD_VERSION_2) {
- IF_ADDR_RLOCK(ifp);
- TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ IF_ADDR_WLOCK(ifp);
+ restart:
+ TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
if (ifma->ifma_addr->sa_family != AF_INET6 ||
ifma->ifma_protospec == NULL)
continue;
@@ -560,8 +561,12 @@ mld_ifdetach(struct ifnet *ifp)
ifma->ifma_protospec = NULL;
}
in6m_clear_recorded(inm);
+ if (__predict_false(ifma6_restart)) {
+ ifma6_restart = false;
+ goto restart;
+ }
}
- IF_ADDR_RUNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
}
MLD_UNLOCK();
@@ -696,7 +701,7 @@ mld_v1_input_query(struct ifnet *ifp, const struct ip6_hdr *ip6,
* interface, kick the report timer.
*/
CTR2(KTR_MLD, "process v1 general query on ifp %p(%s)",
- ifp, if_name(ifp));
+ ifp, if_name(ifp));
TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
if (ifma->ifma_addr->sa_family != AF_INET6 ||
ifma->ifma_protospec == NULL)
@@ -1326,7 +1331,7 @@ mld_fasttimo_vnet(void)
struct mbufq qrq; /* Query response packets */
struct ifnet *ifp;
struct mld_ifsoftc *mli;
- struct ifmultiaddr *ifma;
+ struct ifmultiaddr *ifma, *next;
struct in6_multi *inm, *tinm;
struct in6_multi_head inmh;
int uri_fasthz;
@@ -1388,8 +1393,9 @@ mld_fasttimo_vnet(void)
mbufq_init(&scq, MLD_MAX_STATE_CHANGE_PACKETS);
}
- IF_ADDR_RLOCK(ifp);
- TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ IF_ADDR_WLOCK(ifp);
+ restart:
+ TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
if (ifma->ifma_addr->sa_family != AF_INET6 ||
ifma->ifma_protospec == NULL)
continue;
@@ -1403,8 +1409,12 @@ mld_fasttimo_vnet(void)
&scq, inm, uri_fasthz);
break;
}
+ if (__predict_false(ifma6_restart)) {
+ ifma6_restart = false;
+ goto restart;
+ }
}
- IF_ADDR_RUNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
switch (mli->mli_version) {
case MLD_VERSION_1:
@@ -1641,7 +1651,7 @@ mld_set_version(struct mld_ifsoftc *mli, const int version)
static void
mld_v2_cancel_link_timers(struct mld_ifsoftc *mli)
{
- struct ifmultiaddr *ifma;
+ struct ifmultiaddr *ifma, *next;
struct ifnet *ifp;
struct in6_multi *inm;
struct in6_multi_head inmh;
@@ -1666,8 +1676,9 @@ mld_v2_cancel_link_timers(struct mld_ifsoftc *mli)
ifp = mli->mli_ifp;
- IF_ADDR_RLOCK(ifp);
- TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) {
+ IF_ADDR_WLOCK(ifp);
+ restart:
+ TAILQ_FOREACH_SAFE(ifma, &ifp->if_multiaddrs, ifma_link, next) {
if (ifma->ifma_addr->sa_family != AF_INET6)
continue;
inm = (struct in6_multi *)ifma->ifma_protospec;
@@ -1702,8 +1713,12 @@ mld_v2_cancel_link_timers(struct mld_ifsoftc *mli)
mbufq_drain(&inm->in6m_scq);
break;
}
+ if (__predict_false(ifma6_restart)) {
+ ifma6_restart = false;
+ goto restart;
+ }
}
- IF_ADDR_RUNLOCK(ifp);
+ IF_ADDR_WUNLOCK(ifp);
in6m_release_list_deferred(&inmh);
}