Index | Thread | Search

From:
Alexander Bluhm <bluhm@openbsd.org>
Subject:
Re: unlock igmp and mld6 fast timer
To:
Vitaliy Makkoveev <mvs@openbsd.org>
Cc:
tech@openbsd.org
Date:
Fri, 6 Feb 2026 20:39:11 -0500

Download raw body.

Thread
On Sun, Jan 04, 2026 at 11:36:44AM +0300, Vitaliy Makkoveev wrote:
> On Sat, Jan 03, 2026 at 11:43:09PM +0100, Alexander Bluhm wrote:
> > On Fri, Jan 02, 2026 at 01:58:09PM +0100, Alexander Bluhm wrote:
> > > On Fri, Jan 02, 2026 at 06:32:31AM +0000, Vitaliy Makkoveev wrote:
> > > > On Mon, Dec 29, 2025 at 01:20:05PM +0100, Alexander Bluhm wrote:
> > > > > On Tue, Dec 02, 2025 at 11:01:36PM +0100, Alexander Bluhm wrote:
> > > > > > 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.
> > > > > 
> > > > > I would like to proceed with MP multicast.
> > > > > 
> > > > > OK? objections?
> > > > >
> > > > 
> > > > Just for curiosity, why have you replaced mutex with rwlock?
> > > 
> > > There is this call path, I found the sleeping point during testing.
> > > 
> > > in_addmulti()
> > > rw_enter_write(&ifp->if_maddrlock)
> > > igmp_joingroup()
> > > rti_fill()
> > > malloc(M_WAITOK)
> > > 
> > > Maybe I can pull the M_WAITOK out of the lock later.  Sleeping for
> > > memory with a rwlock held is not optimal.  But moving the malloc
> > > and replacing the lock with a mutex requires more refacotring.  Not
> > > sure if it is worth it.
> > > 
> > > IPv6 is different, it uses M_NOWAIT.  But that means a system call
> > > can fail due to low memory situations.  Also not good, sleeping
> > > would be better.
> > > 
> > > > in{,6}_var.h have missing locks description. Can you add something
> > > > like of 'if_maddrlock rwlock of parent interface' for [m]? And
> > > > description for [I] too.
> > > 
> > > done
> > > 
> > > > The rest looks good to me.
> > 
> > The diff was not quite right.  syzkaller found that in_addmulti ->
> > igmp_joingroup -> igmp_sendpkt -> ip_output -> in_hasmulti is a bad
> > idea.  The lock was taken twice.  I have backed it out.
> > 
> > https://syzkaller.appspot.com/bug?extid=de6bcf8e746b8a631885
> > 
> > Fortunately I was working on a follow up diff.  Idea is that
> > igmp_sendpkt() is called after the lock has been released.  Struct
> > igmp_pktinfo contains everything that is needed to send an IGMP
> > packet later.  In case of timeout loops a bunch of such packets are
> > queued.
> > 
> > Diff below contains the previous locking diff with igmp_sendpkt()
> > on top.  MLD6 is analog to IGMP.  If it helps review, I can also
> > split it up.
> > 
> > ok?
> > 
> 
> Yes please.

Not sure if that refers to the whole diff or the suggestion to split
it up.

Here it the part that delays igmp_sendpkt() so that it can run after
the lock.  This has to be commited first.

ok?

bluhm

Index: netinet/igmp.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/igmp.c,v
diff -u -p -r1.95 igmp.c
--- netinet/igmp.c	3 Jan 2026 14:10:04 -0000	1.95
+++ netinet/igmp.c	6 Feb 2026 22:36:20 -0000
@@ -118,8 +118,7 @@ static LIST_HEAD(, router_info) rti_head
 static struct mbuf *router_alert;
 struct cpumem *igmpcounters;
 
-int igmp_checktimer(struct ifnet *);
-void igmp_sendpkt(struct ifnet *, struct in_multi *, int, in_addr_t);
+int igmp_checktimer(struct ifnet *, struct igmp_pktlist *);
 int rti_fill(struct in_multi *);
 int rti_reset(struct ifnet *);
 int igmp_input_if(struct ifnet *, struct mbuf **, int *, int, int,
@@ -538,16 +537,20 @@ igmp_input_if(struct ifnet *ifp, struct 
 }
 
 void
-igmp_joingroup(struct in_multi *inm, struct ifnet *ifp)
+igmp_joingroup(struct in_multi *inm, struct ifnet *ifp,
+    struct igmp_pktinfo *pkt)
 {
-	int i, running = 0;
+	int running = 0;
 
 	inm->inm_state = IGMP_IDLE_MEMBER;
 
 	if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
 	    (ifp->if_flags & IFF_LOOPBACK) == 0) {
-		i = rti_fill(inm);
-		igmp_sendpkt(ifp, inm, i, 0);
+		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);
@@ -562,17 +565,20 @@ igmp_joingroup(struct in_multi *inm, str
 }
 
 void
-igmp_leavegroup(struct in_multi *inm, struct ifnet *ifp)
+igmp_leavegroup(struct in_multi *inm, struct ifnet *ifp,
+    struct igmp_pktinfo *pkt)
 {
 	switch (inm->inm_state) {
 	case IGMP_DELAYING_MEMBER:
 	case IGMP_IDLE_MEMBER:
 		if (!IN_LOCAL_GROUP(inm->inm_addr.s_addr) &&
 		    (ifp->if_flags & IFF_LOOPBACK) == 0)
-			if (inm->inm_rti->rti_type != IGMP_v1_ROUTER)
-				igmp_sendpkt(ifp, inm,
-				    IGMP_HOST_LEAVE_MESSAGE,
-				    INADDR_ALLROUTERS_GROUP);
+			if (inm->inm_rti->rti_type != IGMP_v1_ROUTER) {
+				pkt->ipi_addr.s_addr = INADDR_ALLROUTERS_GROUP;
+				pkt->ipi_rdomain = ifp->if_rdomain;
+				pkt->ipi_ifidx = inm->inm_ifidx;
+				pkt->ipi_type = IGMP_HOST_LEAVE_MESSAGE;
+			}
 		break;
 	case IGMP_LAZY_MEMBER:
 	case IGMP_AWAKENING_MEMBER:
@@ -584,6 +590,7 @@ igmp_leavegroup(struct in_multi *inm, st
 void
 igmp_fasttimo(void)
 {
+	struct igmp_pktlist pktlist;
 	struct ifnet *ifp;
 	int running = 0;
 
@@ -600,19 +607,29 @@ igmp_fasttimo(void)
 
 	NET_LOCK();
 
+	STAILQ_INIT(&pktlist);
 	TAILQ_FOREACH(ifp, &ifnetlist, if_list) {
-		if (igmp_checktimer(ifp))
+		if (igmp_checktimer(ifp, &pktlist))
 			running = 1;
 	}
 
 	membar_producer();
 	atomic_store_int(&igmp_timers_are_running, running);
 
+	while (!STAILQ_EMPTY(&pktlist)) {
+		struct igmp_pktinfo *pkt;
+
+		pkt = STAILQ_FIRST(&pktlist);
+		STAILQ_REMOVE_HEAD(&pktlist, ipi_list);
+		igmp_sendpkt(pkt);
+		free(pkt, M_MRTABLE, sizeof(*pkt));
+	}
+
 	NET_UNLOCK();
 }
 
 int
-igmp_checktimer(struct ifnet *ifp)
+igmp_checktimer(struct ifnet *ifp, struct igmp_pktlist *pktlist)
 {
 	struct in_multi *inm;
 	struct ifmaddr *ifma;
@@ -628,13 +645,20 @@ igmp_checktimer(struct ifnet *ifp)
 			/* do nothing */
 		} else if (--inm->inm_timer == 0) {
 			if (inm->inm_state == IGMP_DELAYING_MEMBER) {
-				if (inm->inm_rti->rti_type == IGMP_v1_ROUTER)
-					igmp_sendpkt(ifp, inm,
-					    IGMP_v1_HOST_MEMBERSHIP_REPORT, 0);
-				else
-					igmp_sendpkt(ifp, inm,
-					    IGMP_v2_HOST_MEMBERSHIP_REPORT, 0);
+				struct igmp_pktinfo *pkt;
+
 				inm->inm_state = IGMP_IDLE_MEMBER;
+				pkt = malloc(sizeof(*pkt), M_MRTABLE, M_NOWAIT);
+				if (pkt == NULL)
+					continue;
+				pkt->ipi_addr = inm->inm_addr;
+				pkt->ipi_rdomain = ifp->if_rdomain;
+				pkt->ipi_ifidx = inm->inm_ifidx;
+				pkt->ipi_type =
+				    inm->inm_rti->rti_type == IGMP_v1_ROUTER ?
+				    IGMP_v1_HOST_MEMBERSHIP_REPORT :
+				    IGMP_v2_HOST_MEMBERSHIP_REPORT;
+				STAILQ_INSERT_TAIL(pktlist, pkt, ipi_list);
 			}
 		} else {
 			running = 1;
@@ -660,8 +684,7 @@ igmp_slowtimo(void)
 }
 
 void
-igmp_sendpkt(struct ifnet *ifp, struct in_multi *inm, int type,
-    in_addr_t addr)
+igmp_sendpkt(struct igmp_pktinfo *pkt)
 {
 	struct mbuf *m;
 	struct igmp *igmp;
@@ -686,25 +709,21 @@ igmp_sendpkt(struct ifnet *ifp, struct i
 	ip->ip_off = 0;
 	ip->ip_p = IPPROTO_IGMP;
 	ip->ip_src.s_addr = INADDR_ANY;
-	if (addr) {
-		ip->ip_dst.s_addr = addr;
-	} else {
-		ip->ip_dst = inm->inm_addr;
-	}
+	ip->ip_dst = pkt->ipi_addr;
 
 	m->m_data += sizeof(struct ip);
 	m->m_len -= sizeof(struct ip);
 	igmp = mtod(m, struct igmp *);
-	igmp->igmp_type = type;
+	igmp->igmp_type = pkt->ipi_type;
 	igmp->igmp_code = 0;
-	igmp->igmp_group = inm->inm_addr;
+	igmp->igmp_group = pkt->ipi_addr;
 	igmp->igmp_cksum = 0;
 	igmp->igmp_cksum = in_cksum(m, IGMP_MINLEN);
 	m->m_data -= sizeof(struct ip);
 	m->m_len += sizeof(struct ip);
 
-	m->m_pkthdr.ph_rtableid = ifp->if_rdomain;
-	imo.imo_ifidx = inm->inm_ifidx;
+	m->m_pkthdr.ph_rtableid = pkt->ipi_rdomain;
+	imo.imo_ifidx = pkt->ipi_ifidx;
 	imo.imo_ttl = 1;
 
 	/*
@@ -712,7 +731,7 @@ igmp_sendpkt(struct ifnet *ifp, struct i
 	 * router, so that the process-level routing daemon can hear it.
 	 */
 #ifdef MROUTING
-	imo.imo_loop = (ip_mrouter[ifp->if_rdomain] != NULL);
+	imo.imo_loop = (ip_mrouter[pkt->ipi_rdomain] != NULL);
 #else
 	imo.imo_loop = 0;
 #endif /* MROUTING */
Index: netinet/igmp_var.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/igmp_var.h,v
diff -u -p -r1.16 igmp_var.h
--- netinet/igmp_var.h	2 Mar 2025 21:28:32 -0000	1.16
+++ netinet/igmp_var.h	6 Feb 2026 22:35:48 -0000
@@ -105,12 +105,26 @@ igmpstat_inc(enum igmpstat_counters c)
  */
 #define	IGMP_RANDOM_DELAY(X)	(arc4random_uniform(X) + 1)
 
+struct igmp_pktinfo {
+	STAILQ_ENTRY(igmp_pktinfo)	ipi_list;
+	struct in_addr			ipi_addr;
+	unsigned int			ipi_rdomain;
+	unsigned int			ipi_ifidx;
+	int				ipi_type;
+};
+STAILQ_HEAD(igmp_pktlist, igmp_pktinfo);
+
 void	igmp_init(void);
 int	igmp_input(struct mbuf **, int *, int, int, struct netstack *);
-void	igmp_joingroup(struct in_multi *, struct ifnet *);
-void	igmp_leavegroup(struct in_multi *, struct ifnet *);
+void	igmp_joingroup(struct in_multi *, struct ifnet *,
+	    struct igmp_pktinfo *);
+void	igmp_leavegroup(struct in_multi *, struct ifnet *,
+	    struct igmp_pktinfo *);
 void	igmp_fasttimo(void);
 void	igmp_slowtimo(void);
 int	igmp_sysctl(int *, u_int, void *, size_t *, void *, size_t);
+void	igmp_sendpkt(struct igmp_pktinfo *);
+
 #endif /* _KERNEL */
+
 #endif /* _NETINET_IGMP_VAR_H_ */
Index: netinet/in.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/in.c,v
diff -u -p -r1.192 in.c
--- netinet/in.c	3 Jan 2026 14:10:04 -0000	1.192
+++ netinet/in.c	6 Feb 2026 22:40:20 -0000
@@ -872,6 +872,7 @@ struct in_multi *
 in_addmulti(const struct in_addr *addr, struct ifnet *ifp)
 {
 	struct in_multi *inm;
+	struct igmp_pktinfo pkt;
 	struct ifreq ifr;
 
 	/*
@@ -916,7 +917,10 @@ in_addmulti(const struct in_addr *addr, 
 		/*
 		 * Let IGMP know that we have joined a new IP multicast group.
 		 */
-		igmp_joingroup(inm, ifp);
+		pkt.ipi_ifidx = 0;
+		igmp_joingroup(inm, ifp, &pkt);
+		if (pkt.ipi_ifidx)
+			igmp_sendpkt(&pkt);
 	}
 
 	return (inm);
@@ -928,6 +932,7 @@ in_addmulti(const struct in_addr *addr, 
 void
 in_delmulti(struct in_multi *inm)
 {
+	struct igmp_pktinfo pkt;
 	struct ifreq ifr;
 	struct ifnet *ifp;
 
@@ -942,7 +947,10 @@ in_delmulti(struct in_multi *inm)
 		 * No remaining claims to this record; let IGMP know that
 		 * we are leaving the multicast group.
 		 */
-		igmp_leavegroup(inm, ifp);
+		pkt.ipi_ifidx = 0;
+		igmp_leavegroup(inm, ifp, &pkt);
+		if (pkt.ipi_ifidx)
+			igmp_sendpkt(&pkt);
 
 		/*
 		 * Notify the network driver to update its multicast
Index: netinet6/in6.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/in6.c,v
diff -u -p -r1.277 in6.c
--- netinet6/in6.c	3 Jan 2026 14:10:04 -0000	1.277
+++ netinet6/in6.c	6 Feb 2026 23:04:44 -0000
@@ -1026,6 +1026,7 @@ in6_lookupmulti(const struct in6_addr *a
 struct in6_multi *
 in6_addmulti(const struct in6_addr *addr, struct ifnet *ifp, int *errorp)
 {
+	struct	mld6_pktinfo pkt;
 	struct	in6_ifreq ifr;
 	struct	in6_multi *in6m;
 
@@ -1079,7 +1080,10 @@ in6_addmulti(const struct in6_addr *addr
 		 * Let MLD6 know that we have joined a new IP6 multicast
 		 * group.
 		 */
-		mld6_start_listening(in6m);
+		pkt.mpi_ifidx = 0;
+		mld6_start_listening(in6m, ifp, &pkt);
+		if (pkt.mpi_ifidx)
+			mld6_sendpkt(&pkt);
 	}
 
 	return (in6m);
@@ -1091,39 +1095,45 @@ in6_addmulti(const struct in6_addr *addr
 void
 in6_delmulti(struct in6_multi *in6m)
 {
+	struct	mld6_pktinfo pkt;
 	struct	in6_ifreq ifr;
 	struct	ifnet *ifp;
 
 	NET_ASSERT_LOCKED();
 
-	if (refcnt_rele(&in6m->in6m_refcnt) != 0) {
+	if (refcnt_rele(&in6m->in6m_refcnt) == 0)
+		return;
+
+	ifp = if_get(in6m->in6m_ifidx);
+	if (ifp != NULL) {
 		/*
 		 * 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);
+		pkt.mpi_ifidx = 0;
+		mld6_stop_listening(in6m, ifp, &pkt);
+		if (pkt.mpi_ifidx)
+			mld6_sendpkt(&pkt);
 
 		/*
 		 * 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();
+		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();
 
-			TAILQ_REMOVE(&ifp->if_maddrlist, &in6m->in6m_ifma,
-			    ifma_list);
-		}
-		if_put(ifp);
+		TAILQ_REMOVE(&ifp->if_maddrlist, &in6m->in6m_ifma,
+		    ifma_list);
 
-		free(in6m, M_IPMADDR, sizeof(*in6m));
+		if_put(ifp);
 	}
+
+	free(in6m, M_IPMADDR, sizeof(*in6m));
 }
 
 /*
Index: netinet6/mld6.c
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/mld6.c,v
diff -u -p -r1.72 mld6.c
--- netinet6/mld6.c	3 Jan 2026 14:10:04 -0000	1.72
+++ netinet6/mld6.c	6 Feb 2026 22:43:01 -0000
@@ -86,7 +86,6 @@ static struct ip6_pktopts ip6_opts;
 int	mld6_timers_are_running;	/* [a] shortcut for fast timer */
 
 int mld6_checktimer(struct ifnet *);
-static void mld6_sendpkt(struct in6_multi *, int, const struct in6_addr *);
 
 void
 mld6_init(void)
@@ -112,7 +111,8 @@ mld6_init(void)
 }
 
 void
-mld6_start_listening(struct in6_multi *in6m)
+mld6_start_listening(struct in6_multi *in6m, struct ifnet *ifp,
+    struct mld6_pktinfo *pkt)
 {
 	/* XXX: These are necessary for KAME's link-local hack */
 	struct in6_addr all_nodes = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
@@ -132,10 +132,12 @@ mld6_start_listening(struct in6_multi *i
 		in6m->in6m_timer = 0;
 		in6m->in6m_state = MLD_OTHERLISTENER;
 	} else {
-		mld6_sendpkt(in6m, MLD_LISTENER_REPORT, NULL);
+		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);
+		    MLD_RANDOM_DELAY(MLD_V1_MAX_RI * PR_FASTHZ);
 		in6m->in6m_state = MLD_IREPORTEDLAST;
 		running = 1;
 	}
@@ -147,7 +149,8 @@ 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,
+    struct mld6_pktinfo *pkt)
 {
 	/* XXX: These are necessary for KAME's link-local hack */
 	struct in6_addr all_nodes = IN6ADDR_LINKLOCAL_ALLNODES_INIT;
@@ -160,8 +163,12 @@ mld6_stop_listening(struct in6_multi *in
 	if (in6m->in6m_state == MLD_IREPORTEDLAST &&
 	    (!IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &all_nodes)) &&
 	    __IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) >
-	    __IPV6_ADDR_SCOPE_INTFACELOCAL)
-		mld6_sendpkt(in6m, MLD_LISTENER_DONE, &all_routers);
+	    __IPV6_ADDR_SCOPE_INTFACELOCAL) {
+		pkt->mpi_addr = all_routers;
+		pkt->mpi_rdomain = ifp->if_rdomain;
+		pkt->mpi_ifidx = in6m->in6m_ifidx;
+		pkt->mpi_type = MLD_LISTENER_DONE;
+	}
 }
 
 void
@@ -269,8 +276,13 @@ mld6_input(struct mbuf *m, int off)
 			{
 				if (timer == 0) {
 					/* send a report immediately */
-					mld6_sendpkt(in6m, MLD_LISTENER_REPORT,
-					    NULL);
+					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;
 				} else if (in6m->in6m_timer == 0 || /* idle */
@@ -373,10 +385,12 @@ mld6_checktimer(struct ifnet *ifp)
 {
 	struct in6_multi *in6m;
 	struct ifmaddr *ifma;
+	struct mld6_pktlist pktlist;
 	int running = 0;
 
 	NET_ASSERT_LOCKED();
 
+	STAILQ_INIT(&pktlist);
 	TAILQ_FOREACH(ifma, &ifp->if_maddrlist, ifma_list) {
 		if (ifma->ifma_addr->sa_family != AF_INET6)
 			continue;
@@ -384,18 +398,36 @@ mld6_checktimer(struct ifnet *ifp)
 		if (in6m->in6m_timer == 0) {
 			/* do nothing */
 		} else if (--in6m->in6m_timer == 0) {
-			mld6_sendpkt(in6m, MLD_LISTENER_REPORT, NULL);
+			struct mld6_pktinfo *pkt;
+
 			in6m->in6m_state = MLD_IREPORTEDLAST;
+			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 {
 			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));
+	}
+
 	return (running);
 }
 
-static void
-mld6_sendpkt(struct in6_multi *in6m, int type, const struct in6_addr *dst)
+void
+mld6_sendpkt(const struct mld6_pktinfo *pkt)
 {
 	struct mbuf *mh, *md;
 	struct mld_hdr *mldh;
@@ -405,7 +437,7 @@ mld6_sendpkt(struct in6_multi *in6m, int
 	struct ifnet *ifp;
 	int ignflags;
 
-	ifp = if_get(in6m->in6m_ifidx);
+	ifp = if_get(pkt->mpi_ifidx);
 	if (ifp == NULL)
 		return;
 
@@ -441,8 +473,7 @@ mld6_sendpkt(struct in6_multi *in6m, int
 	}
 	mh->m_next = md;
 
-	mh->m_pkthdr.ph_ifidx = 0;
-	mh->m_pkthdr.ph_rtableid = ifp->if_rdomain;
+	mh->m_pkthdr.ph_rtableid = pkt->mpi_rdomain;
 	mh->m_pkthdr.len = sizeof(struct ip6_hdr) + sizeof(struct mld_hdr);
 	mh->m_len = sizeof(struct ip6_hdr);
 	m_align(mh, sizeof(struct ip6_hdr));
@@ -456,25 +487,25 @@ mld6_sendpkt(struct in6_multi *in6m, int
 	ip6->ip6_nxt = IPPROTO_ICMPV6;
 	/* ip6_hlim will be set by im6o.im6o_hlim */
 	ip6->ip6_src = ia6 ? ia6->ia_addr.sin6_addr : in6addr_any;
-	ip6->ip6_dst = dst ? *dst : in6m->in6m_addr;
+	ip6->ip6_dst = pkt->mpi_addr;
 
 	/* fill in the MLD header */
 	md->m_len = sizeof(struct mld_hdr);
 	mldh = mtod(md, struct mld_hdr *);
-	mldh->mld_type = type;
+	mldh->mld_type = pkt->mpi_type;
 	mldh->mld_code = 0;
 	mldh->mld_cksum = 0;
 	/* XXX: we assume the function will not be called for query messages */
 	mldh->mld_maxdelay = 0;
 	mldh->mld_reserved = 0;
-	mldh->mld_addr = in6m->in6m_addr;
+	mldh->mld_addr = pkt->mpi_addr;
 	if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr))
 		mldh->mld_addr.s6_addr16[1] = 0; /* XXX */
 	mh->m_pkthdr.csum_flags |= M_ICMP_CSUM_OUT;
 
 	/* construct multicast option */
 	bzero(&im6o, sizeof(im6o));
-	im6o.im6o_ifidx = ifp->if_index;
+	im6o.im6o_ifidx = pkt->mpi_ifidx;
 	im6o.im6o_hlim = 1;
 
 	/*
@@ -482,10 +513,10 @@ mld6_sendpkt(struct in6_multi *in6m, int
 	 * router, so that the process-level routing daemon can hear it.
 	 */
 #ifdef MROUTING
-	im6o.im6o_loop = (ip6_mrouter[ifp->if_rdomain] != NULL);
+	im6o.im6o_loop = (ip6_mrouter[pkt->mpi_rdomain] != NULL);
 #endif
 	if_put(ifp);
 
-	icmp6stat_inc(icp6s_outhist + type);
+	icmp6stat_inc(icp6s_outhist + pkt->mpi_type);
 	ip6_output(mh, &ip6_opts, NULL, ia6 ? 0 : IPV6_UNSPECSRC, &im6o, NULL);
 }
Index: netinet6/mld6_var.h
===================================================================
RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/mld6_var.h,v
diff -u -p -r1.9 mld6_var.h
--- netinet6/mld6_var.h	3 Jan 2026 14:10:04 -0000	1.9
+++ netinet6/mld6_var.h	6 Feb 2026 22:43:14 -0000
@@ -43,11 +43,24 @@
 #define MLD_OTHERLISTENER			0
 #define MLD_IREPORTEDLAST			1
 
+struct mld6_pktinfo {
+	STAILQ_ENTRY(mld6_pktinfo)	mpi_list;
+	struct in6_addr			mpi_addr;
+	unsigned int			mpi_rdomain;
+	unsigned int			mpi_ifidx;
+	int				mpi_type;
+};
+STAILQ_HEAD(mld6_pktlist, mld6_pktinfo);
+
 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 *,
+	    struct mld6_pktinfo *);
+void	mld6_stop_listening(struct in6_multi *, struct ifnet *,
+	    struct mld6_pktinfo *);
 void	mld6_fasttimo(void);
+void	mld6_sendpkt(const struct mld6_pktinfo *);
+
 #endif /* _KERNEL */
 
 #endif /* _NETINET6_MLD6_VAR_H_ */