Index | Thread | Search

From:
Alexander Bluhm <bluhm@openbsd.org>
Subject:
unlock igmp and mld6 fast timer
To:
tech@openbsd.org
Date:
Tue, 2 Dec 2025 23:01:36 +0100

Download raw body.

Thread
Hi,

After moving around code for a while, I think a rwlock to protect
if_maddrlist is the next step.  The malloc(M_WAITOK) in rti_fill()
prevents us from using a mutex here.

So protect the TAILQ if_maddrlist with rwlock if_maddrlock.  Also
struct in_multi and in6_multi use this lock for their state and
timer.  Sleeps in malloc and IP output are possible.

This allows to run IGMP and MLD6 fast timeout with shared net lock.

ok?

bluhm

Index: net/if.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/net/if.c,v
diff -u -p -r1.753 if.c
--- net/if.c	21 Nov 2025 04:44:26 -0000	1.753
+++ net/if.c	2 Dec 2025 20:36:56 -0000
@@ -646,6 +646,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.142 if_var.h
--- net/if_var.h	17 Nov 2025 09:19:45 -0000	1.142
+++ net/if_var.h	2 Dec 2025 20:34:11 -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.
@@ -148,8 +149,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 */
@@ -266,10 +268,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.93 igmp.c
--- netinet/igmp.c	2 Dec 2025 15:52:04 -0000	1.93
+++ netinet/igmp.c	2 Dec 2025 20:37:23 -0000
@@ -341,6 +341,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;
@@ -353,6 +354,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);
@@ -372,6 +374,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;
@@ -400,6 +403,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 					}
 				}
 			}
+			rw_exit_write(&ifp->if_maddrlock);
 		}
 
 		break;
@@ -436,6 +440,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;
@@ -456,6 +461,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 				break;
 			}
 		}
+		rw_exit_write(&ifp->if_maddrlock);
 
 		break;
 
@@ -504,6 +510,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;
@@ -520,6 +527,7 @@ igmp_input_if(struct ifnet *ifp, struct 
 				break;
 			}
 		}
+		rw_exit_write(&ifp->if_maddrlock);
 
 		break;
 
@@ -542,6 +550,8 @@ igmp_joingroup(struct in_multi *inm, str
 {
 	int i, running = 0;
 
+	rw_assert_wrlock(&ifp->if_maddrlock);
+
 	inm->inm_state = IGMP_IDLE_MEMBER;
 
 	if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
@@ -564,6 +574,8 @@ igmp_joingroup(struct in_multi *inm, str
 void
 igmp_leavegroup(struct in_multi *inm, struct ifnet *ifp)
 {
+	rw_assert_anylock(&ifp->if_maddrlock);
+
 	switch (inm->inm_state) {
 	case IGMP_DELAYING_MEMBER:
 	case IGMP_IDLE_MEMBER:
@@ -598,7 +610,7 @@ igmp_fasttimo(void)
 		return;
 	membar_consumer();
 
-	NET_LOCK();
+	NET_LOCK_SHARED();
 
 	TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
 		if (igmp_checktimer(ifp))
@@ -608,7 +620,7 @@ igmp_fasttimo(void)
 	membar_producer();
 	atomic_store_int(&igmp_timers_are_running, running);
 
-	NET_UNLOCK();
+	NET_UNLOCK_SHARED();
 }
 
 int
@@ -618,8 +630,7 @@ igmp_checktimer(struct ifnet *ifp)
 	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;
@@ -640,6 +651,7 @@ igmp_checktimer(struct ifnet *ifp)
 			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.190 in.c
--- netinet/in.c	2 Dec 2025 15:52:04 -0000	1.190
+++ netinet/in.c	2 Dec 2025 20:37:11 -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,54 +871,67 @@ 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 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.
-		 */
-		igmp_joingroup(inm, ifp);
-	}
+	TAILQ_INSERT_HEAD(&ifp->if_maddrlist, &inm->inm_ifma, ifma_list);
+
+	/*
+	 * Let IGMP know that we have joined a new IP multicast group.
+	 */
+	igmp_joingroup(inm, ifp);
+	rw_exit_write(&ifp->if_maddrlock);
+
+	return (inm);
 
+ found:
+	refcnt_take(&inm->inm_refcnt);
+	rw_exit_write(&ifp->if_maddrlock);
+ out:
+	free(new_inm, M_IPMADDR, sizeof(*inm));
 	return (inm);
 }
 
@@ -931,19 +944,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.
 		 */
 		igmp_leavegroup(inm, ifp);
 
+		TAILQ_REMOVE(&ifp->if_maddrlist, &inm->inm_ifma, ifma_list);
+		rw_exit_write(&ifp->if_maddrlock);
+
 		/*
 		 * Notify the network driver to update its multicast
 		 * reception filter.
@@ -956,9 +971,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));
 }
@@ -973,8 +987,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.44 in_var.h
--- netinet/in_var.h	2 Dec 2025 15:52:04 -0000	1.44
+++ netinet/in_var.h	2 Dec 2025 20:34:11 -0000
@@ -87,11 +87,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.275 in6.c
--- netinet6/in6.c	13 Nov 2025 23:30:01 -0000	1.275
+++ netinet6/in6.c	2 Dec 2025 21:36:52 -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,62 +1026,71 @@ 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	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;
+
+        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);
+	
+	TAILQ_INSERT_HEAD(&ifp->if_maddrlist, &in6m->in6m_ifma, ifma_list);
 
-		TAILQ_INSERT_HEAD(&ifp->if_maddrlist, &in6m->in6m_ifma,
-		    ifma_list);
+	/*
+	 * Let MLD6 know that we have joined a new IP6 multicast group.
+	 */
+	mld6_start_listening(in6m, ifp);
+	rw_exit_write(&ifp->if_maddrlock);
 
-		/*
-		 * Let MLD6 know that we have joined a new IP6 multicast
-		 * group.
-		 */
-		mld6_start_listening(in6m);
-	}
+	return (in6m);
 
+ found:
+	refcnt_take(&in6m->in6m_refcnt);
+	rw_exit_write(&ifp->if_maddrlock);
+ out:
+	free(new_in6m, M_IPMADDR, sizeof(*in6m));
 	return (in6m);
 }
 
@@ -1094,36 +1103,37 @@ in6_delmulti(struct in6_multi *in6m)
 	struct	in6_ifreq ifr;
 	struct	ifnet *ifp;
 
-	NET_ASSERT_LOCKED();
+	if (refcnt_rele(&in6m->in6m_refcnt) == 0)
+		return;
 
-	if (refcnt_rele(&in6m->in6m_refcnt) != 0) {
+	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.
 		 */
-		mld6_stop_listening(in6m);
-		ifp = if_get(in6m->in6m_ifidx);
+		mld6_stop_listening(in6m, ifp);
+
+		TAILQ_REMOVE(&ifp->if_maddrlist, &in6m->in6m_ifma, ifma_list);
+		rw_exit_write(&ifp->if_maddrlock);
 
 		/*
 		 * Notify the network driver to update its multicast
 		 * reception filter.
 		 */
-		if (ifp != NULL) {
-			bzero(&ifr.ifr_addr, sizeof(struct sockaddr_in6));
-			ifr.ifr_addr.sin6_len = sizeof(struct sockaddr_in6);
-			ifr.ifr_addr.sin6_family = AF_INET6;
-			ifr.ifr_addr.sin6_addr = in6m->in6m_addr;
-			KERNEL_LOCK();
-			(*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)&ifr);
-			KERNEL_UNLOCK();
+		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;
+		KERNEL_LOCK();
+		(*ifp->if_ioctl)(ifp, SIOCDELMULTI, (caddr_t)&ifr);
+		KERNEL_UNLOCK();
 
-			TAILQ_REMOVE(&ifp->if_maddrlist, &in6m->in6m_ifma,
-			    ifma_list);
-		}
 		if_put(ifp);
-
-		free(in6m, M_IPMADDR, sizeof(*in6m));
 	}
+
+	free(in6m, M_IPMADDR, sizeof(*in6m));
 }
 
 /*
@@ -1136,8 +1146,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.82 in6_var.h
--- netinet6/in6_var.h	12 Nov 2025 11:37:08 -0000	1.82
+++ netinet6/in6_var.h	2 Dec 2025 21:30:09 -0000
@@ -316,11 +316,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.70 mld6.c
--- netinet6/mld6.c	12 Nov 2025 19:11:10 -0000	1.70
+++ netinet6/mld6.c	2 Dec 2025 21:21:04 -0000
@@ -112,12 +112,14 @@ mld6_init(void)
 }
 
 void
-mld6_start_listening(struct in6_multi *in6m)
+mld6_start_listening(struct in6_multi *in6m, struct ifnet *ifp)
 {
 	/* XXX: These are necessary for KAME's link-local hack */
 	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
@@ -147,12 +149,14 @@ mld6_start_listening(struct in6_multi *i
 }
 
 void
-mld6_stop_listening(struct in6_multi *in6m)
+mld6_stop_listening(struct in6_multi *in6m, struct ifnet *ifp)
 {
 	/* XXX: These are necessary for KAME's link-local hack */
 	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);
@@ -254,6 +258,7 @@ mld6_input(struct mbuf *m, int off)
 			timer = 1;
 		all_nodes.s6_addr16[1] = htons(ifp->if_index);
 
+		rw_enter_write(&ifp->if_maddrlock);
 		TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
 			if (ifma->ifma_addr->sa_family != AF_INET6)
 				continue;
@@ -281,6 +286,7 @@ mld6_input(struct mbuf *m, int off)
 				}
 			}
 		}
+		rw_exit_write(&ifp->if_maddrlock);
 
 		if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
 			mldh->mld_addr.s6_addr16[1] = 0; /* XXX */
@@ -308,11 +314,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 */
 		}
+		rw_exit_write(&ifp->if_maddrlock);
 
 		if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
 			mldh->mld_addr.s6_addr16[1] = 0; /* XXX */
@@ -355,7 +363,7 @@ mld6_fasttimo(void)
 		return;
 	membar_consumer();
 
-	NET_LOCK();
+	NET_LOCK_SHARED();
 
 	TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
 		if (mld6_checktimer(ifp))
@@ -365,7 +373,7 @@ mld6_fasttimo(void)
 	membar_producer();
 	atomic_store_int(&mld6_timers_are_running, running);
 
-	NET_UNLOCK();
+	NET_UNLOCK_SHARED();
 }
 
 int
@@ -375,8 +383,7 @@ mld6_checktimer(struct ifnet *ifp)
 	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_INET6)
 			continue;
@@ -390,6 +397,7 @@ mld6_checktimer(struct ifnet *ifp)
 			running = 1;
 		}
 	}
+	rw_exit_write(&ifp->if_maddrlock);
 
 	return (running);
 }
Index: netinet6/mld6_var.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/mld6_var.h,v
diff -u -p -r1.7 mld6_var.h
--- netinet6/mld6_var.h	12 Nov 2025 19:11:10 -0000	1.7
+++ netinet6/mld6_var.h	2 Dec 2025 20:58:44 -0000
@@ -45,8 +45,8 @@
 
 void	mld6_init(void);
 void	mld6_input(struct mbuf *, int);
-void	mld6_start_listening(struct in6_multi *);
-void	mld6_stop_listening(struct in6_multi *);
+void	mld6_start_listening(struct in6_multi *, struct ifnet *);
+void	mld6_stop_listening(struct in6_multi *, struct ifnet *);
 void	mld6_fasttimo(void);
 #endif /* _KERNEL */