Index | Thread | Search

From:
Alexander Bluhm <bluhm@openbsd.org>
Subject:
unlock igmp and mld6
To:
tech@openbsd.org
Cc:
Vitaliy Makkoveev <mvs@openbsd.org>
Date:
Sun, 1 Mar 2026 18:38:16 +0100

Download raw body.

Thread
  • Alexander Bluhm:

    unlock igmp and mld6

Hi,

After moving the code sending IGMP and ICMP6 packets out of the
critical section, I think we an introduve a mutlicast lock.  This
allows us to run igmp and mld6 timers with shared netlock.

ok?

bluhm

Index: net/if.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/net/if.c,v
diff -u -p -r1.762 if.c
--- net/if.c	3 Jan 2026 14:10:04 -0000	1.762
+++ net/if.c	1 Mar 2026 17:01:05 -0000
@@ -662,6 +662,7 @@ if_attach_common(struct ifnet *ifp)
 	TAILQ_INIT(&ifp->if_addrlist);
 	TAILQ_INIT(&ifp->if_maddrlist);
 	TAILQ_INIT(&ifp->if_groups);
+	rw_init(&ifp->if_maddrlock, "maddr");
 
 	if (!ISSET(ifp->if_xflags, IFXF_MPSAFE)) {
 		KASSERTMSG(ifp->if_qstart == NULL,
Index: net/if_var.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/net/if_var.h,v
diff -u -p -r1.147 if_var.h
--- net/if_var.h	3 Jan 2026 14:10:04 -0000	1.147
+++ net/if_var.h	1 Mar 2026 17:01:05 -0000
@@ -81,6 +81,7 @@
  *	K	kernel lock
  *	N	net lock
  *	T	if_tmplist_lock
+ *	m	interface multicast rwlock if_maddrlock
  *
  *  For SRP related structures that allow lock-free reads, the write lock
  *  is indicated below.
@@ -153,8 +154,9 @@ struct ifnet {				/* and the entries */
 	TAILQ_ENTRY(ifnet) if_list;	/* [NK] all struct ifnets are chained */
 	TAILQ_ENTRY(ifnet) if_tmplist;	/* [T] temporary list */
 	TAILQ_HEAD(, ifaddr) if_addrlist; /* [N] list of addresses per if */
-	TAILQ_HEAD(, ifmaddr) if_maddrlist; /* [N] list of multicast records */
+	TAILQ_HEAD(, ifmaddr) if_maddrlist; /* [m] list of multicast records */
 	TAILQ_HEAD(, ifg_list) if_groups; /* [N] list of groups per if */
+	struct rwlock if_maddrlock;
 	struct task_list if_addrhooks;	/* [I] address change callbacks */
 	struct task_list if_linkstatehooks; /* [I] link change callbacks*/
 	struct task_list if_detachhooks; /* [I] detach callbacks */
@@ -273,10 +275,10 @@ struct ifaddr {
  * Interface multicast address.
  */
 struct ifmaddr {
-	struct sockaddr		*ifma_addr;	/* Protocol address */
-	unsigned int		 ifma_ifidx;	/* Index of the interface */
+	TAILQ_ENTRY(ifmaddr)	 ifma_list;	/* [m] Per-interface list */
+	struct sockaddr		*ifma_addr;	/* [I] Protocol address */
 	struct refcnt		 ifma_refcnt;	/* Count of references */
-	TAILQ_ENTRY(ifmaddr)	 ifma_list;	/* Per-interface list */
+	unsigned int		 ifma_ifidx;	/* [I] Index of the interface */
 };
 
 /*
Index: netinet/igmp.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/igmp.c,v
diff -u -p -r1.96 igmp.c
--- netinet/igmp.c	26 Feb 2026 00:53:18 -0000	1.96
+++ netinet/igmp.c	1 Mar 2026 17:22:24 -0000
@@ -340,6 +340,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 			 * except those that are already running and those
 			 * that belong to a "local" group (224.0.0.X).
 			 */
+			rw_enter_write(&ifp->if_maddrlock);
 			TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
 				if (ifma->ifma_addr->sa_family != AF_INET)
 					continue;
@@ -352,6 +353,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 					running = 1;
 				}
 			}
+			rw_exit_write(&ifp->if_maddrlock);
 		} else {
 			if (!IN_MULTICAST(ip->ip_dst.s_addr)) {
 				igmpstat_inc(igps_rcv_badqueries);
@@ -371,6 +373,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 			 * timers already running, check if they need to be
 			 * reset.
 			 */
+			rw_enter_write(&ifp->if_maddrlock);
 			TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
 				if (ifma->ifma_addr->sa_family != AF_INET)
 					continue;
@@ -399,6 +402,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 					}
 				}
 			}
+			rw_exit_write(&ifp->if_maddrlock);
 		}
 
 		break;
@@ -435,6 +439,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 		 * If we belong to the group being reported, stop
 		 * our timer for that group.
 		 */
+		rw_enter_write(&ifp->if_maddrlock);
 		inm = in_lookupmulti(&igmp->igmp_group, ifp);
 		if (inm != NULL) {
 			inm->inm_timer = 0;
@@ -455,6 +460,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 				break;
 			}
 		}
+		rw_exit_write(&ifp->if_maddrlock);
 
 		break;
 
@@ -503,6 +509,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 		 * If we belong to the group being reported, stop
 		 * our timer for that group.
 		 */
+		rw_enter_write(&ifp->if_maddrlock);
 		inm = in_lookupmulti(&igmp->igmp_group, ifp);
 		if (inm != NULL) {
 			inm->inm_timer = 0;
@@ -519,6 +526,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 				break;
 			}
 		}
+		rw_exit_write(&ifp->if_maddrlock);
 
 		break;
 
@@ -542,18 +550,19 @@ igmp_joingroup(struct in_multi *inm, str
 {
 	int running = 0;
 
+	rw_assert_wrlock(&ifp->if_maddrlock);
+
 	inm->inm_state = IGMP_IDLE_MEMBER;
 
 	if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
 	    (ifp->if_flags & IFF_LOOPBACK) == 0) {
+		inm->inm_state = IGMP_DELAYING_MEMBER;
+		inm->inm_timer = IGMP_RANDOM_DELAY(
+		    IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
 		pkt->ipi_addr = inm->inm_addr;
 		pkt->ipi_rdomain = ifp->if_rdomain;
 		pkt->ipi_ifidx = inm->inm_ifidx;
 		pkt->ipi_type = rti_fill(inm);
-
-		inm->inm_state = IGMP_DELAYING_MEMBER;
-		inm->inm_timer = IGMP_RANDOM_DELAY(
-		    IGMP_MAX_HOST_REPORT_DELAY * PR_FASTHZ);
 		running = 1;
 	} else
 		inm->inm_timer = 0;
@@ -568,6 +577,8 @@ void
 igmp_leavegroup(struct in_multi *inm, struct ifnet *ifp,
     struct igmp_pktinfo *pkt)
 {
+	rw_assert_anylock(&ifp->if_maddrlock);
+
 	switch (inm->inm_state) {
 	case IGMP_DELAYING_MEMBER:
 	case IGMP_IDLE_MEMBER:
@@ -605,7 +616,7 @@ igmp_fasttimo(void)
 		return;
 	membar_consumer();
 
-	NET_LOCK();
+	NET_LOCK_SHARED();
 
 	STAILQ_INIT(&pktlist);
 	TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
@@ -625,7 +636,7 @@ igmp_fasttimo(void)
 		free(pkt, M_MRTABLE, sizeof(*pkt));
 	}
 
-	NET_UNLOCK();
+	NET_UNLOCK_SHARED();
 }
 
 int
@@ -635,8 +646,7 @@ igmp_checktimer(struct ifnet *ifp, struc
 	struct ifmaddr *ifma;
 	int running = 0;
 
-	NET_ASSERT_LOCKED();
-
+	rw_enter_write(&ifp->if_maddrlock);
 	TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
 		if (ifma->ifma_addr->sa_family != AF_INET)
 			continue;
@@ -664,6 +674,7 @@ igmp_checktimer(struct ifnet *ifp, struc
 			running = 1;
 		}
 	}
+	rw_exit_write(&ifp->if_maddrlock);
 
 	return (running);
 }
Index: netinet/in.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/in.c,v
diff -u -p -r1.193 in.c
--- netinet/in.c	26 Feb 2026 00:53:18 -0000	1.193
+++ netinet/in.c	1 Mar 2026 17:01:05 -0000
@@ -853,7 +853,7 @@ in_lookupmulti(const struct in_addr *add
 	struct in_multi *inm = NULL;
 	struct ifmaddr *ifma;
 
-	NET_ASSERT_LOCKED();
+	rw_assert_anylock(&ifp->if_maddrlock);
 
 	TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
 		if (ifma->ifma_addr->sa_family == AF_INET &&
@@ -871,58 +871,70 @@ in_lookupmulti(const struct in_addr *add
 struct in_multi *
 in_addmulti(const struct in_addr *addr, struct ifnet *ifp)
 {
-	struct in_multi *inm;
+	struct in_multi *inm, *new_inm = NULL;
 	struct igmp_pktinfo pkt;
 	struct ifreq ifr;
 
 	/*
 	 * See if address already in list.
 	 */
+	rw_enter_write(&ifp->if_maddrlock);
 	inm = in_lookupmulti(addr, ifp);
-	if (inm != NULL) {
-		/*
-		 * Found it; just increment the reference count.
-		 */
-		refcnt_take(&inm->inm_refcnt);
-	} else {
-		/*
-		 * New address; allocate a new multicast record
-		 * and link it into the interface's multicast list.
-		 */
-		inm = malloc(sizeof(*inm), M_IPMADDR, M_WAITOK | M_ZERO);
-		inm->inm_sin.sin_len = sizeof(struct sockaddr_in);
-		inm->inm_sin.sin_family = AF_INET;
-		inm->inm_sin.sin_addr = *addr;
-		refcnt_init_trace(&inm->inm_refcnt, DT_REFCNT_IDX_IFMADDR);
-		inm->inm_ifidx = ifp->if_index;
-		inm->inm_ifma.ifma_addr = sintosa(&inm->inm_sin);
+	if (inm != NULL)
+		goto found;
+	rw_exit_write(&ifp->if_maddrlock);
 
-		/*
-		 * Ask the network driver to update its multicast reception
-		 * filter appropriately for the new address.
-		 */
-		memset(&ifr, 0, sizeof(ifr));
-		memcpy(&ifr.ifr_addr, &inm->inm_sin, sizeof(inm->inm_sin));
-		KERNEL_LOCK();
-		if ((*ifp->if_ioctl)(ifp, SIOCADDMULTI,(caddr_t)&ifr) != 0) {
-			KERNEL_UNLOCK();
-			free(inm, M_IPMADDR, sizeof(*inm));
-			return (NULL);
-		}
+	/*
+	 * New address; allocate a new multicast record
+	 * and link it into the interface's multicast list.
+	 */
+	new_inm = malloc(sizeof(*inm), M_IPMADDR, M_WAITOK | M_ZERO);
+
+	/*
+	 * Ask the network driver to update its multicast reception
+	 * filter appropriately for the new address.
+	 */
+	memset(&ifr, 0, sizeof(ifr));
+	satosin(&ifr.ifr_addr)->sin_len = sizeof(struct sockaddr_in);
+	satosin(&ifr.ifr_addr)->sin_family = AF_INET;
+	satosin(&ifr.ifr_addr)->sin_addr = *addr;
+	KERNEL_LOCK();
+	if ((*ifp->if_ioctl)(ifp, SIOCADDMULTI,(caddr_t)&ifr) != 0) {
 		KERNEL_UNLOCK();
+		goto out;
+	}
+	KERNEL_UNLOCK();
 
-		TAILQ_INSERT_HEAD(&ifp->if_maddrlist, &inm->inm_ifma,
-		    ifma_list);
+	rw_enter_write(&ifp->if_maddrlock);
+	/* check again after unlock and lock */
+	inm = in_lookupmulti(addr, ifp);
+	if (inm != NULL)
+		goto found;
+	inm = new_inm;
+	inm->inm_sin.sin_len = sizeof(struct sockaddr_in);
+	inm->inm_sin.sin_family = AF_INET;
+	inm->inm_sin.sin_addr = *addr;
+	refcnt_init_trace(&inm->inm_refcnt, DT_REFCNT_IDX_IFMADDR);
+	inm->inm_ifidx = ifp->if_index;
+	inm->inm_ifma.ifma_addr = sintosa(&inm->inm_sin);
 
-		/*
-		 * Let IGMP know that we have joined a new IP multicast group.
-		 */
-		pkt.ipi_ifidx = 0;
-		igmp_joingroup(inm, ifp, &pkt);
-		if (pkt.ipi_ifidx)
-			igmp_sendpkt(&pkt);
-	}
+	/*
+	 * Let IGMP know that we have joined a new IP multicast group.
+	 */
+	TAILQ_INSERT_HEAD(&ifp->if_maddrlist, &inm->inm_ifma, ifma_list);
+	pkt.ipi_ifidx = 0;
+	igmp_joingroup(inm, ifp, &pkt);
+	rw_exit_write(&ifp->if_maddrlock);
+
+	if (pkt.ipi_ifidx)
+		igmp_sendpkt(&pkt);
+	return (inm);
 
+ found:
+	refcnt_take(&inm->inm_refcnt);
+	rw_exit_write(&ifp->if_maddrlock);
+ out:
+	free(new_inm, M_IPMADDR, sizeof(*inm));
 	return (inm);
 }
 
@@ -936,19 +948,21 @@ in_delmulti(struct in_multi *inm)
 	struct ifreq ifr;
 	struct ifnet *ifp;
 
-	NET_ASSERT_LOCKED();
-
 	if (refcnt_rele(&inm->inm_refcnt) == 0)
 		return;
 
 	ifp = if_get(inm->inm_ifidx);
 	if (ifp != NULL) {
+		rw_enter_write(&ifp->if_maddrlock);
 		/*
 		 * No remaining claims to this record; let IGMP know that
 		 * we are leaving the multicast group.
 		 */
 		pkt.ipi_ifidx = 0;
 		igmp_leavegroup(inm, ifp, &pkt);
+		TAILQ_REMOVE(&ifp->if_maddrlist, &inm->inm_ifma, ifma_list);
+		rw_exit_write(&ifp->if_maddrlock);
+
 		if (pkt.ipi_ifidx)
 			igmp_sendpkt(&pkt);
 
@@ -964,9 +978,8 @@ in_delmulti(struct in_multi *inm)
 		(*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)&ifr);
 		KERNEL_UNLOCK();
 
-		TAILQ_REMOVE(&ifp->if_maddrlist, &inm->inm_ifma, ifma_list);
+		if_put(ifp);
 	}
-	if_put(ifp);
 
 	free(inm, M_IPMADDR, sizeof(*inm));
 }
@@ -981,8 +994,10 @@ in_hasmulti(const struct in_addr *addr, 
 	struct in_multi *inm;
 	int joined;
 
+	rw_enter_read(&ifp->if_maddrlock);
 	inm = in_lookupmulti(addr, ifp);
 	joined = (inm != NULL);
+	rw_exit_read(&ifp->if_maddrlock);
 
 	return (joined);
 }
Index: netinet/in_var.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/in_var.h,v
diff -u -p -r1.46 in_var.h
--- netinet/in_var.h	3 Jan 2026 14:10:04 -0000	1.46
+++ netinet/in_var.h	1 Mar 2026 17:01:05 -0000
@@ -35,6 +35,12 @@
 #ifndef _NETINET_IN_VAR_H_
 #define _NETINET_IN_VAR_H_
 
+/*
+ *  Locks used to protect struct members in this file:
+ *	I	immutable after creation
+ *	m	multicast if_maddrlock rwlock of parent interface
+ */
+
 #include <sys/queue.h>
 
 #ifdef _KERNEL
@@ -87,11 +93,11 @@ struct in_multi {
 #define inm_refcnt		inm_ifma.ifma_refcnt
 #define inm_ifidx		inm_ifma.ifma_ifidx
 
-	struct sockaddr_in	inm_sin;   /* IPv4 multicast address */
+	struct sockaddr_in	inm_sin;   /* [I] IPv4 multicast address */
 #define inm_addr		inm_sin.sin_addr
 
-	u_int			inm_state; /* state of membership */
-	u_int			inm_timer; /* IGMP membership report timer */
+	u_int			inm_state; /* [m] state of membership */
+	u_int			inm_timer; /* [m] IGMP membership report */
 
 	struct router_info	*inm_rti;  /* router version info */
 };
Index: netinet6/in6.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/in6.c,v
diff -u -p -r1.278 in6.c
--- netinet6/in6.c	26 Feb 2026 00:53:18 -0000	1.278
+++ netinet6/in6.c	1 Mar 2026 17:01:05 -0000
@@ -1007,7 +1007,7 @@ in6_lookupmulti(const struct in6_addr *a
 	struct in6_multi *in6m = NULL;
 	struct ifmaddr *ifma;
 
-	NET_ASSERT_LOCKED();
+	rw_assert_anylock(&ifp->if_maddrlock);
 
 	TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
 		if (ifma->ifma_addr->sa_family == AF_INET6 &&
@@ -1026,66 +1026,75 @@ in6_lookupmulti(const struct in6_addr *a
 struct in6_multi *
 in6_addmulti(const struct in6_addr *addr, struct ifnet *ifp, int *errorp)
 {
+	struct	in6_multi *in6m, *new_in6m = NULL;
 	struct	mld6_pktinfo pkt;
 	struct	in6_ifreq ifr;
-	struct	in6_multi *in6m;
-
-	NET_ASSERT_LOCKED();
 
 	*errorp = 0;
 	/*
 	 * See if address already in list.
 	 */
+	rw_enter_write(&ifp->if_maddrlock);
 	in6m = in6_lookupmulti(addr, ifp);
-	if (in6m != NULL) {
-		/*
-		 * Found it; just increment the reference count.
-		 */
-		refcnt_take(&in6m->in6m_refcnt);
-	} else {
-		/*
-		 * New address; allocate a new multicast record
-		 * and link it into the interface's multicast list.
-		 */
-		in6m = malloc(sizeof(*in6m), M_IPMADDR, M_NOWAIT | M_ZERO);
-		if (in6m == NULL) {
-			*errorp = ENOBUFS;
-			return (NULL);
-		}
+	if (in6m != NULL)
+		goto found;
+	rw_exit_write(&ifp->if_maddrlock);
 
-		in6m->in6m_sin.sin6_len = sizeof(struct sockaddr_in6);
-		in6m->in6m_sin.sin6_family = AF_INET6;
-		in6m->in6m_sin.sin6_addr = *addr;
-		refcnt_init_trace(&in6m->in6m_refcnt, DT_REFCNT_IDX_IFMADDR);
-		in6m->in6m_ifidx = ifp->if_index;
-		in6m->in6m_ifma.ifma_addr = sin6tosa(&in6m->in6m_sin);
+	/*
+	 * New address; allocate a new multicast record
+	 * and link it into the interface's multicast list.
+	 */
+	new_in6m = malloc(sizeof(*in6m), M_IPMADDR, M_NOWAIT | M_ZERO);
+	if (new_in6m == NULL) {
+		*errorp = ENOBUFS;
+		return (NULL);
+	}
 
-		/*
-		 * Ask the network driver to update its multicast reception
-		 * filter appropriately for the new address.
-		 */
-		memcpy(&ifr.ifr_addr, &in6m->in6m_sin, sizeof(in6m->in6m_sin));
-		KERNEL_LOCK();
-		*errorp = (*ifp->if_ioctl)(ifp, SIOCADDMULTI, (caddr_t)&ifr);
-		KERNEL_UNLOCK();
-		if (*errorp) {
-			free(in6m, M_IPMADDR, sizeof(*in6m));
-			return (NULL);
-		}
+	/*
+	 * Ask the network driver to update its multicast reception
+	 * filter appropriately for the new address.
+	 */
+	memset(&ifr, 0, sizeof(ifr));
+	ifr.ifr_addr.sin6_len = sizeof(struct sockaddr_in6);
+	ifr.ifr_addr.sin6_family = AF_INET6;
+	ifr.ifr_addr.sin6_addr = *addr;
+	KERNEL_LOCK();
+	*errorp = (*ifp->if_ioctl)(ifp, SIOCADDMULTI, (caddr_t)&ifr);
+	KERNEL_UNLOCK();
+	if (*errorp)
+		goto out;
 
-		TAILQ_INSERT_HEAD(&ifp->if_maddrlist, &in6m->in6m_ifma,
-		    ifma_list);
+	rw_enter_write(&ifp->if_maddrlock);
+	/* check again after unlock and lock */
+	in6m = in6_lookupmulti(addr, ifp);
+	if (in6m != NULL)
+		goto found;
+	in6m = new_in6m;
+	in6m->in6m_sin.sin6_len = sizeof(struct sockaddr_in6);
+	in6m->in6m_sin.sin6_family = AF_INET6;
+	in6m->in6m_sin.sin6_addr = *addr;
+	refcnt_init_trace(&in6m->in6m_refcnt, DT_REFCNT_IDX_IFMADDR);
+	in6m->in6m_ifidx = ifp->if_index;
+	in6m->in6m_ifma.ifma_addr = sin6tosa(&in6m->in6m_sin);
 
-		/*
-		 * Let MLD6 know that we have joined a new IP6 multicast
-		 * group.
-		 */
-		pkt.mpi_ifidx = 0;
-		mld6_start_listening(in6m, ifp, &pkt);
-		if (pkt.mpi_ifidx)
-			mld6_sendpkt(&pkt);
-	}
+	/*
+	 * Let MLD6 know that we have joined a new IP6 multicast group.
+	 */
+	TAILQ_INSERT_HEAD(&ifp->if_maddrlist, &in6m->in6m_ifma, ifma_list);
+	pkt.mpi_ifidx = 0;
+	mld6_start_listening(in6m, ifp, &pkt);
+	rw_exit_write(&ifp->if_maddrlock);
+
+	if (pkt.mpi_ifidx)
+		mld6_sendpkt(&pkt);
+
+	return (in6m);
 
+ found:
+	refcnt_take(&in6m->in6m_refcnt);
+	rw_exit_write(&ifp->if_maddrlock);
+ out:
+	free(new_in6m, M_IPMADDR, sizeof(*in6m));
 	return (in6m);
 }
 
@@ -1099,19 +1108,21 @@ in6_delmulti(struct in6_multi *in6m)
 	struct	in6_ifreq ifr;
 	struct	ifnet *ifp;
 
-	NET_ASSERT_LOCKED();
-
 	if (refcnt_rele(&in6m->in6m_refcnt) == 0)
 		return;
 
 	ifp = if_get(in6m->in6m_ifidx);
 	if (ifp != NULL) {
+		rw_enter_write(&ifp->if_maddrlock);
 		/*
 		 * No remaining claims to this record; let MLD6 know
 		 * that we are leaving the multicast group.
 		 */
 		pkt.mpi_ifidx = 0;
 		mld6_stop_listening(in6m, ifp, &pkt);
+		TAILQ_REMOVE(&ifp->if_maddrlist, &in6m->in6m_ifma, ifma_list);
+		rw_exit_write(&ifp->if_maddrlock);
+
 		if (pkt.mpi_ifidx)
 			mld6_sendpkt(&pkt);
 
@@ -1119,7 +1130,7 @@ in6_delmulti(struct in6_multi *in6m)
 		 * Notify the network driver to update its multicast
 		 * reception filter.
 		 */
-		bzero(&ifr.ifr_addr, sizeof(struct sockaddr_in6));
+		memset(&ifr, 0, sizeof(ifr));
 		ifr.ifr_addr.sin6_len = sizeof(struct sockaddr_in6);
 		ifr.ifr_addr.sin6_family = AF_INET6;
 		ifr.ifr_addr.sin6_addr = in6m->in6m_addr;
@@ -1127,9 +1138,6 @@ in6_delmulti(struct in6_multi *in6m)
 		(*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)&ifr);
 		KERNEL_UNLOCK();
 
-		TAILQ_REMOVE(&ifp->if_maddrlist, &in6m->in6m_ifma,
-		    ifma_list);
-
 		if_put(ifp);
 	}
 
@@ -1146,8 +1154,10 @@ in6_hasmulti(const struct in6_addr *addr
 	struct in6_multi *in6m;
 	int joined;
 
+	rw_enter_read(&ifp->if_maddrlock);
 	in6m = in6_lookupmulti(addr, ifp);
 	joined = (in6m != NULL);
+	rw_exit_read(&ifp->if_maddrlock);
 
 	return (joined);
 }
Index: netinet6/in6_var.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/in6_var.h,v
diff -u -p -r1.84 in6_var.h
--- netinet6/in6_var.h	3 Jan 2026 14:10:04 -0000	1.84
+++ netinet6/in6_var.h	1 Mar 2026 17:01:05 -0000
@@ -65,6 +65,12 @@
 #define _NETINET6_IN6_VAR_H_
 
 /*
+ *  Locks used to protect struct members in this file:
+ *	I	immutable after creation
+ *	m	multicast if_maddrlock rwlock of parent interface
+ */
+
+/*
  * Interface address, Internet version.  One of these structures
  * is allocated for each interface with an Internet address.
  * The ifaddr structure contains the protocol-independent part
@@ -316,11 +322,11 @@ struct in6_multi {
 #define in6m_refcnt		in6m_ifma.ifma_refcnt
 #define in6m_ifidx		in6m_ifma.ifma_ifidx
 
-	struct sockaddr_in6	in6m_sin;   /* IPv6 multicast address */
+	struct sockaddr_in6	in6m_sin;   /* [I] IPv6 multicast address */
 #define in6m_addr		in6m_sin.sin6_addr
 
-	u_int			in6m_state; /* state of membership */
-	u_int			in6m_timer; /* MLD6 membership report timer */
+	u_int			in6m_state; /* [m] state of membership */
+	u_int			in6m_timer; /* [m] MLD6 membership report */
 };
 
 static __inline struct in6_multi *
Index: netinet6/mld6.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/mld6.c,v
diff -u -p -r1.73 mld6.c
--- netinet6/mld6.c	26 Feb 2026 00:53:18 -0000	1.73
+++ netinet6/mld6.c	1 Mar 2026 17:22:23 -0000
@@ -85,7 +85,7 @@
 static struct ip6_pktopts ip6_opts;
 int	mld6_timers_are_running;	/* [a] shortcut for fast timer */
 
-int mld6_checktimer(struct ifnet *);
+int mld6_checktimer(struct ifnet *, struct mld6_pktlist *);
 
 void
 mld6_init(void)
@@ -118,6 +118,8 @@ mld6_start_listening(struct in6_multi *i
 	struct in6_addr all_nodes = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
 	int running = 0;
 
+	rw_assert_wrlock(&ifp->if_maddrlock);
+
 	/*
 	 * RFC2710 page 10:
 	 * The node never sends a Report or Done for the link-scope all-nodes
@@ -129,16 +131,16 @@ mld6_start_listening(struct in6_multi *i
 	if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_nodes) ||
 	    __IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) <
 	    __IPV6_ADDR_SCOPE_LINKLOCAL) {
-		in6m->in6m_timer = 0;
 		in6m->in6m_state = MLD_OTHERLISTENER;
+		in6m->in6m_timer = 0;
 	} else {
+		in6m->in6m_state = MLD_IREPORTEDLAST;
+		in6m->in6m_timer =
+		    MLD_RANDOM_DELAY(MLD_V1_MAX_RI * PR_FASTHZ);
 		pkt->mpi_addr = in6m->in6m_addr;
 		pkt->mpi_rdomain = ifp->if_rdomain;
 		pkt->mpi_ifidx = in6m->in6m_ifidx;
 		pkt->mpi_type = MLD_LISTENER_REPORT;
-		in6m->in6m_timer =
-		    MLD_RANDOM_DELAY(MLD_V1_MAX_RI * PR_FASTHZ);
-		in6m->in6m_state = MLD_IREPORTEDLAST;
 		running = 1;
 	}
 
@@ -156,6 +158,8 @@ mld6_stop_listening(struct in6_multi *in
 	struct in6_addr all_nodes = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
 	struct in6_addr all_routers = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT;
 
+	rw_assert_anylock(&ifp->if_maddrlock);
+
 	all_nodes.s6_addr16[1] = htons(in6m->in6m_ifidx);
 	/* XXX: necessary when mrouting */
 	all_routers.s6_addr16[1] = htons(in6m->in6m_ifidx);
@@ -228,7 +232,9 @@ mld6_input(struct mbuf *m, int off)
 	 * if we sent the last report.
 	 */
 	switch(mldh->mld_type) {
-	case MLD_LISTENER_QUERY:
+	case MLD_LISTENER_QUERY: {
+		struct mld6_pktlist pktlist;
+
 		if (ifp->if_flags & IFF_LOOPBACK)
 			break;
 
@@ -261,6 +267,8 @@ mld6_input(struct mbuf *m, int off)
 			timer = 1;
 		all_nodes.s6_addr16[1] = htons(ifp->if_index);
 
+		rw_enter_write(&ifp->if_maddrlock);
+		STAILQ_INIT(&pktlist);
 		TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
 			if (ifma->ifma_addr->sa_family != AF_INET6)
 				continue;
@@ -276,15 +284,20 @@ mld6_input(struct mbuf *m, int off)
 			{
 				if (timer == 0) {
 					/* send a report immediately */
-					struct mld6_pktinfo pkt;
+					struct mld6_pktinfo *pkt;
 
-					pkt.mpi_addr = in6m->in6m_addr;
-					pkt.mpi_rdomain = ifp->if_rdomain;
-					pkt.mpi_ifidx = in6m->in6m_ifidx;
-					pkt.mpi_type = MLD_LISTENER_REPORT;
-					mld6_sendpkt(&pkt);
-					in6m->in6m_timer = 0; /* reset timer */
 					in6m->in6m_state = MLD_IREPORTEDLAST;
+					in6m->in6m_timer = 0; /* reset timer */
+					pkt = malloc(sizeof(*pkt), M_MRTABLE,
+					    M_NOWAIT);
+					if (pkt == NULL)
+						continue;
+					pkt->mpi_addr = in6m->in6m_addr;
+					pkt->mpi_rdomain = ifp->if_rdomain;
+					pkt->mpi_ifidx = in6m->in6m_ifidx;
+					pkt->mpi_type = MLD_LISTENER_REPORT;
+					STAILQ_INSERT_TAIL(&pktlist, pkt,
+					    mpi_list);
 				} else if (in6m->in6m_timer == 0 || /* idle */
 					in6m->in6m_timer > timer) {
 					in6m->in6m_timer =
@@ -293,10 +306,21 @@ mld6_input(struct mbuf *m, int off)
 				}
 			}
 		}
+		rw_exit_write(&ifp->if_maddrlock);
+
+		while (!STAILQ_EMPTY(&pktlist)) {
+			struct mld6_pktinfo *pkt;
+
+			pkt = STAILQ_FIRST(&pktlist);
+			STAILQ_REMOVE_HEAD(&pktlist, mpi_list);
+			mld6_sendpkt(pkt);
+			free(pkt, M_MRTABLE, sizeof(*pkt));
+		}
 
 		if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
 			mldh->mld_addr.s6_addr16[1] = 0; /* XXX */
 		break;
+	}
 	case MLD_LISTENER_REPORT:
 		/*
 		 * For fast leave to work, we have to know that we are the
@@ -320,11 +344,13 @@ mld6_input(struct mbuf *m, int off)
 		 * If we belong to the group being reported, stop
 		 * our timer for that group.
 		 */
+		rw_enter_write(&ifp->if_maddrlock);
 		in6m = in6_lookupmulti(&mldh->mld_addr, ifp);
 		if (in6m) {
-			in6m->in6m_timer = 0; /* transit to idle state */
 			in6m->in6m_state = MLD_OTHERLISTENER; /* clear flag */
+			in6m->in6m_timer = 0; /* transit to idle state */
 		}
+		rw_exit_write(&ifp->if_maddrlock);
 
 		if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
 			mldh->mld_addr.s6_addr16[1] = 0; /* XXX */
@@ -353,6 +379,7 @@ mld6_input(struct mbuf *m, int off)
 void
 mld6_fasttimo(void)
 {
+	struct mld6_pktlist pktlist;
 	struct ifnet *ifp;
 	int running = 0;
 
@@ -367,30 +394,37 @@ mld6_fasttimo(void)
 		return;
 	membar_consumer();
 
-	NET_LOCK();
+	NET_LOCK_SHARED();
 
+	STAILQ_INIT(&pktlist);
 	TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
-		if (mld6_checktimer(ifp))
+		if (mld6_checktimer(ifp, &pktlist))
 			running = 1;
 	}
 
 	membar_producer();
 	atomic_store_int(&mld6_timers_are_running, running);
 
-	NET_UNLOCK();
+	while (!STAILQ_EMPTY(&pktlist)) {
+		struct mld6_pktinfo *pkt;
+
+		pkt = STAILQ_FIRST(&pktlist);
+		STAILQ_REMOVE_HEAD(&pktlist, mpi_list);
+		mld6_sendpkt(pkt);
+		free(pkt, M_MRTABLE, sizeof(*pkt));
+	}
+
+	NET_UNLOCK_SHARED();
 }
 
 int
-mld6_checktimer(struct ifnet *ifp)
+mld6_checktimer(struct ifnet *ifp, struct mld6_pktlist *pktlist)
 {
 	struct in6_multi *in6m;
 	struct ifmaddr *ifma;
-	struct mld6_pktlist pktlist;
 	int running = 0;
 
-	NET_ASSERT_LOCKED();
-
-	STAILQ_INIT(&pktlist);
+	rw_enter_write(&ifp->if_maddrlock);
 	TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
 		if (ifma->ifma_addr->sa_family != AF_INET6)
 			continue;
@@ -408,20 +442,12 @@ mld6_checktimer(struct ifnet *ifp)
 			pkt->mpi_rdomain = ifp->if_rdomain;
 			pkt->mpi_ifidx = in6m->in6m_ifidx;
 			pkt->mpi_type = MLD_LISTENER_REPORT;
-			STAILQ_INSERT_TAIL(&pktlist, pkt, mpi_list);
+			STAILQ_INSERT_TAIL(pktlist, pkt, mpi_list);
 		} else {
 			running = 1;
 		}
 	}
-
-	while (!STAILQ_EMPTY(&pktlist)) {
-		struct mld6_pktinfo *pkt;
- 
-		pkt = STAILQ_FIRST(&pktlist);
-		STAILQ_REMOVE_HEAD(&pktlist, mpi_list);
-		mld6_sendpkt(pkt);
-		free(pkt, M_MRTABLE, sizeof(*pkt));
-	}
+	rw_exit_write(&ifp->if_maddrlock);
 
 	return (running);
 }