Download raw body.
unlock igmp and mld6
On Sun, Mar 01, 2026 at 06:38:16PM +0100, Alexander Bluhm wrote:
> 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?
>
Sorry for delay. ok mvs@
> 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);
> }
unlock igmp and mld6