svn commit: r333309 - in head/sys: net netinet netinet6

Matt Macy mmacy at FreeBSD.org
Sun May 6 20:34:15 UTC 2018


Author: mmacy
Date: Sun May  6 20:34:13 2018
New Revision: 333309
URL: https://svnweb.freebsd.org/changeset/base/333309

Log:
  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

Modified:
  head/sys/net/if.c
  head/sys/net/if_var.h
  head/sys/netinet/igmp.c
  head/sys/netinet/in.c
  head/sys/netinet/in_mcast.c
  head/sys/netinet/in_var.h
  head/sys/netinet6/in6_ifattach.c
  head/sys/netinet6/in6_mcast.c
  head/sys/netinet6/in6_var.h
  head/sys/netinet6/mld6.c

Modified: head/sys/net/if.c
==============================================================================
--- head/sys/net/if.c	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/net/if.c	Sun May  6 20:34:13 2018	(r333309)
@@ -254,7 +254,6 @@ struct mbuf *(*tbr_dequeue_ptr)(struct ifaltq *, int) 
 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, 
  * 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 ifmultiad
 	}
 
 	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 ifmultiad
 	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 ifmultiad
 			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.

Modified: head/sys/net/if_var.h
==============================================================================
--- head/sys/net/if_var.h	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/net/if_var.h	Sun May  6 20:34:13 2018	(r333309)
@@ -596,6 +596,12 @@ VNET_DECLARE(struct ifnet *, loif);	/* first loopback 
 #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);

Modified: head/sys/netinet/igmp.c
==============================================================================
--- head/sys/netinet/igmp.c	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/netinet/igmp.c	Sun May  6 20:34:13 2018	(r333309)
@@ -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);

Modified: head/sys/netinet/in.c
==============================================================================
--- head/sys/netinet/in.c	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/netinet/in.c	Sun May  6 20:34:13 2018	(r333309)
@@ -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);

Modified: head/sys/netinet/in_mcast.c
==============================================================================
--- head/sys/netinet/in_mcast.c	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/netinet/in_mcast.c	Sun May  6 20:34:13 2018	(r333309)
@@ -112,6 +112,8 @@ MTX_SYSINIT(in_multi_free_mtx, &in_multi_free_mtx, "in
 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
 	}
 
  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
 	} else {
 		*pinm = inm;
 	}
+	IN_MULTI_LIST_UNLOCK();
 
 	return (error);
 }
@@ -1350,7 +1382,9 @@ in_leavegroup_locked(struct in_multi *inm, /*const*/ s
 	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)

Modified: head/sys/netinet/in_var.h
==============================================================================
--- head/sys/netinet/in_var.h	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/netinet/in_var.h	Sun May  6 20:34:13 2018	(r333309)
@@ -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_
 
 	if (--inm->inm_refcount == 0) {
 		MPASS(inmh != NULL);
+		inm_disconnect(inm);
 		inm->inm_ifma->ifma_protospec = NULL;
 		SLIST_INSERT_HEAD(inmh, inm, inm_nrele);
 	}

Modified: head/sys/netinet6/in6_ifattach.c
==============================================================================
--- head/sys/netinet6/in6_ifattach.c	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/netinet6/in6_ifattach.c	Sun May  6 20:34:13 2018	(r333309)
@@ -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();

Modified: head/sys/netinet6/in6_mcast.c
==============================================================================
--- head/sys/netinet6/in6_mcast.c	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/netinet6/in6_mcast.c	Sun May  6 20:34:13 2018	(r333309)
@@ -190,6 +190,7 @@ static SYSCTL_NODE(_net_inet6_ip6_mcast, OID_AUTO, fil
     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*/
 
 	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);

Modified: head/sys/netinet6/in6_var.h
==============================================================================
--- head/sys/netinet6/in6_var.h	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/netinet6/in6_var.h	Sun May  6 20:34:13 2018	(r333309)
@@ -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);
 	}
 }

Modified: head/sys/netinet6/mld6.c
==============================================================================
--- head/sys/netinet6/mld6.c	Sun May  6 20:32:47 2018	(r333308)
+++ head/sys/netinet6/mld6.c	Sun May  6 20:34:13 2018	(r333309)
@@ -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
 		 * 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 ver
 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);
 }
 


More information about the svn-src-head mailing list