Index | Thread | Search

From:
Alexander Bluhm <bluhm@openbsd.org>
Subject:
Re: use softnet for socket splicing
To:
David Gwynne <david@gwynne.id.au>
Cc:
tech@openbsd.org
Date:
Mon, 4 Aug 2025 13:50:31 +0200

Download raw body.

Thread
On Thu, Jul 31, 2025 at 07:09:21PM +0200, Alexander Bluhm wrote:
> On Sat, Jul 26, 2025 at 02:02:11PM +1000, David Gwynne wrote:
> > On Fri, Jul 25, 2025 at 02:07:57PM +0200, Alexander Bluhm wrote:
> > > Hi,
> > > 
> > > Currently socket splicing runs on one dedicated kernel thread.  This
> > > design is from a time when softnet was still a soft interrupt.
> > 
> > how time flies.
> > 
> > > Now with multiple softnet threads, I want to retire the sosplice
> > > thread.  Instead call sotask() with the softnet task queue.  For
> > > that I have to pass the queue down in struct netstack.  Basically
> > > sorwakeup() and sowwakeup() get an additional argument.  If netstack
> > > and softnet are available, I use this specific tasks queue.  Otherwise
> > > softnet thread 0 is sufficient.  The hot path receiving packets
> > > will distribute them over all softnet threads.
> > > 
> > > Keeping the same softnet means that we take a bunch of packets from
> > > the network driver, do input processing, store them in socket
> > > buffers.  Then the same thread handles the splicing task, calls
> > > somove() and does output processing.  There is no concurrent locking
> > > or scheduling, ideally packets stay on the same CPU.  Before I had
> > > a yield() in sotask() to allow accumulation of packets.  With the
> > > new design this is no longer necessary.
> > > 
> > > As we run on softnet task queue and add splice tasks there, task
> > > barrier causes deadlock.  I replaced them with reference count in
> > > task_add(), task_del(), and sotask().
> > 
> > hmm. you should be able to call taskq_barrier() from a task that's
> > running on the relevant taskq. i added it in src/sys/kern/kern_task.c
> > r1.28 for drm.
> > 
> > also, this idiom:
> > 
> > +			if (task_add(soback->so_splicequeue,
> > +			    &soback->so_splicetask))
> > +				soref(soback);
> > 
> > is unsafe.
> > 
> > you have to assume that it's possible (regardless of how unlikely it is)
> > that the task will run as soon as it's added, and before that soref is
> > called. if the you're holding the only ref before the task_add call, the
> > rele in the task itself can drop the count to zero and destroy the thing.
> > the safe pattern is:
> > 
> > +			soref(soback);
> > +			if (!task_add(soback->so_splicequeue,
> > +			    &soback->so_splicetask))
> > +				sorele(soback);
> > 
> > bouncing the refcnt around sucks though, which is why barriers can be
> > useful. alternatively, you can set it up so you have an extra ref to
> > mitigate some of the extra counting.
> 
> You are right, the non working taskq_barrier() was a bug in my
> previous diff.
> 
> Below is the diff that puts the splicing task on the network task
> queue.  so_splicequeue is used to remember the queue that was used
> first.  It is set atomically, all subsequent task_add(), task_del(),
> and taskq_barrier() will use that queue.  Delete is done in sounplice()
> with additional mutex protection to allow re-adding.  Barriers are
> in soclose().
> 
> I like this a bit more than the netstack socket queue as I can use
> the task infrastructure everywhere.
> 
> ok?

Anyone?

I would like to get parallel somove() commited.  Which approach is
best can be decided later.  I need something that runs in parallel
to identify more bottlenecks.

bluhm

> Index: kern/uipc_socket.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/kern/uipc_socket.c,v
> diff -u -p -r1.385 uipc_socket.c
> --- kern/uipc_socket.c	25 Jul 2025 08:58:44 -0000	1.385
> +++ kern/uipc_socket.c	31 Jul 2025 11:39:04 -0000
> @@ -52,6 +52,9 @@
>  #include <sys/time.h>
>  #include <sys/refcnt.h>
>  
> +#include <net/if.h>
> +#include <net/if_var.h>
> +
>  #ifdef DDB
>  #include <machine/db_machdep.h>
>  #endif
> @@ -117,14 +120,13 @@ int	sominconn = SOMINCONN;
>  struct pool socket_pool;
>  #ifdef SOCKET_SPLICE
>  struct pool sosplice_pool;
> -struct taskq *sosplice_taskq;
> -struct rwlock sosplice_lock = RWLOCK_INITIALIZER("sosplicelk");
>  
>  #define so_splicelen	so_sp->ssp_len
>  #define so_splicemax	so_sp->ssp_max
>  #define so_spliceidletv	so_sp->ssp_idletv
>  #define so_spliceidleto	so_sp->ssp_idleto
>  #define so_splicetask	so_sp->ssp_task
> +#define so_splicequeue	so_sp->ssp_queue
>  #endif
>  
>  void
> @@ -433,6 +435,7 @@ discard:
>  #ifdef SOCKET_SPLICE
>  	if (so->so_sp) {
>  		struct socket *soback;
> +		struct taskq *spq, *spqback;
>  
>  		sounlock_shared(so);
>  		/*
> @@ -461,7 +464,6 @@ discard:
>  			sounsplice(so->so_sp->ssp_soback, so, freeing);
>  		}
>  		sbunlock(&soback->so_rcv);
> -		sorele(soback);
>  
>  notsplicedback:
>  		sblock(&so->so_rcv, SBL_WAIT | SBL_NOINTR);
> @@ -474,9 +476,16 @@ notsplicedback:
>  		}
>  		sbunlock(&so->so_rcv);
>  
> -		timeout_del_barrier(&so->so_spliceidleto);
> -		task_del(sosplice_taskq, &so->so_splicetask);
> -		taskq_barrier(sosplice_taskq);
> +		timeout_barrier(&so->so_spliceidleto);
> +		spq = READ_ONCE(so->so_splicequeue);
> +		if (spq != NULL)
> +			taskq_barrier(spq);
> +		if (soback != NULL) {
> +			spqback = READ_ONCE(soback->so_splicequeue);
> +			if (spqback != NULL && spqback != spq)
> +				taskq_barrier(spqback);
> +			sorele(soback);
> +		}
>  
>  		solock_shared(so);
>  	}
> @@ -1290,7 +1299,6 @@ sosplice(struct socket *so, int fd, off_
>  {
>  	struct file	*fp;
>  	struct socket	*sosp;
> -	struct taskq	*tq;
>  	int		 error = 0;
>  
>  	if ((so->so_proto->pr_flags & PR_SPLICE) == 0)
> @@ -1312,25 +1320,6 @@ sosplice(struct socket *so, int fd, off_
>  		return (error);
>  	}
>  
> -	if (sosplice_taskq == NULL) {
> -		rw_enter_write(&sosplice_lock);
> -		if (sosplice_taskq == NULL) {
> -			tq = taskq_create("sosplice", 1, IPL_SOFTNET,
> -			    TASKQ_MPSAFE);
> -			if (tq == NULL) {
> -				rw_exit_write(&sosplice_lock);
> -				return (ENOMEM);
> -			}
> -			/* Ensure the taskq is fully visible to other CPUs. */
> -			membar_producer();
> -			sosplice_taskq = tq;
> -		}
> -		rw_exit_write(&sosplice_lock);
> -	} else {
> -		/* Ensure the taskq is fully visible on this CPU. */
> -		membar_consumer();
> -	}
> -
>  	/* Find sosp, the drain socket where data will be spliced into. */
>  	if ((error = getsock(curproc, fd, &fp)) != 0)
>  		return (error);
> @@ -1435,15 +1424,15 @@ sounsplice(struct socket *so, struct soc
>  	mtx_enter(&sosp->so_snd.sb_mtx);
>  	so->so_rcv.sb_flags &= ~SB_SPLICE;
>  	sosp->so_snd.sb_flags &= ~SB_SPLICE;
> +	timeout_del(&so->so_spliceidleto);
> +	if (so->so_splicequeue != NULL)
> +		task_del(so->so_splicequeue, &so->so_splicetask);
>  	KASSERT(so->so_sp->ssp_socket == sosp);
>  	KASSERT(sosp->so_sp->ssp_soback == so);
>  	so->so_sp->ssp_socket = sosp->so_sp->ssp_soback = NULL;
>  	mtx_leave(&sosp->so_snd.sb_mtx);
>  	mtx_leave(&so->so_rcv.sb_mtx);
>  
> -	task_del(sosplice_taskq, &so->so_splicetask);
> -	timeout_del(&so->so_spliceidleto);
> -
>  	/* Do not wakeup a socket that is about to be freed. */
>  	if ((freeing & SOSP_FREEING_READ) == 0) {
>  		int readable;
> @@ -1453,13 +1442,13 @@ sounsplice(struct socket *so, struct soc
>  		readable = so->so_qlen || soreadable(so);
>  		mtx_leave(&so->so_rcv.sb_mtx);
>  		if (readable)
> -			sorwakeup(so);
> +			sorwakeup(so, NULL);
>  		sounlock_shared(so);
>  	}
>  	if ((freeing & SOSP_FREEING_WRITE) == 0) {
>  		solock_shared(sosp);
>  		if (sowriteable(sosp))
> -			sowwakeup(sosp);
> +			sowwakeup(sosp, NULL);
>  		sounlock_shared(sosp);
>  	}
>  
> @@ -1484,20 +1473,11 @@ void
>  sotask(void *arg)
>  {
>  	struct socket *so = arg;
> -	int doyield = 0;
>  
>  	sblock(&so->so_rcv, SBL_WAIT | SBL_NOINTR);
> -	if (so->so_rcv.sb_flags & SB_SPLICE) {
> -		if (so->so_proto->pr_flags & PR_WANTRCVD)
> -			doyield = 1;
> +	if (so->so_rcv.sb_flags & SB_SPLICE)
>  		somove(so, M_DONTWAIT);
> -	}
>  	sbunlock(&so->so_rcv);
> -
> -	if (doyield) {
> -		/* Avoid user land starvation. */
> -		yield();
> -	}
>  }
>  
>  /*
> @@ -1853,13 +1833,16 @@ somove(struct socket *so, int wait)
>  #endif /* SOCKET_SPLICE */
>  
>  void
> -sorwakeup(struct socket *so)
> +sorwakeup(struct socket *so, struct netstack *ns)
>  {
>  #ifdef SOCKET_SPLICE
>  	if (so->so_proto->pr_flags & PR_SPLICE) {
>  		mtx_enter(&so->so_rcv.sb_mtx);
> -		if (so->so_rcv.sb_flags & SB_SPLICE)
> -			task_add(sosplice_taskq, &so->so_splicetask);
> +		if (so->so_rcv.sb_flags & SB_SPLICE) {
> +			atomic_cas_ptr(&so->so_splicequeue, NULL,
> +			    ns != NULL ? ns->ns_nettaskq : net_tq(0));
> +			task_add(so->so_splicequeue, &so->so_splicetask);
> +		}
>  		if (isspliced(so)) {
>  			mtx_leave(&so->so_rcv.sb_mtx);
>  			return;
> @@ -1873,14 +1856,19 @@ sorwakeup(struct socket *so)
>  }
>  
>  void
> -sowwakeup(struct socket *so)
> +sowwakeup(struct socket *so, struct netstack *ns)
>  {
>  #ifdef SOCKET_SPLICE
>  	if (so->so_proto->pr_flags & PR_SPLICE) {
>  		mtx_enter(&so->so_snd.sb_mtx);
> -		if (so->so_snd.sb_flags & SB_SPLICE)
> -			task_add(sosplice_taskq,
> -			    &so->so_sp->ssp_soback->so_splicetask);
> +		if (so->so_snd.sb_flags & SB_SPLICE) {
> +			struct socket *soback = so->so_sp->ssp_soback;
> +
> +			atomic_cas_ptr(&soback->so_splicequeue, NULL,
> +			    ns != NULL ? ns->ns_nettaskq : net_tq(0));
> +			task_add(soback->so_splicequeue,
> +			    &soback->so_splicetask);
> +		}
>  		if (issplicedback(so)) {
>  			mtx_leave(&so->so_snd.sb_mtx);
>  			return;
> Index: kern/uipc_socket2.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/kern/uipc_socket2.c,v
> diff -u -p -r1.186 uipc_socket2.c
> --- kern/uipc_socket2.c	14 Jul 2025 21:47:26 -0000	1.186
> +++ kern/uipc_socket2.c	31 Jul 2025 10:39:10 -0000
> @@ -113,15 +113,15 @@ soisconnected(struct socket *so)
>  
>  		soqremque(so, 0);
>  		soqinsque(head, so, 1);
> -		sorwakeup(head);
> +		sorwakeup(head, NULL);
>  		wakeup_one(&head->so_timeo);
>  
>  		sounlock(head);
>  		sorele(head);
>  	} else {
>  		wakeup(&so->so_timeo);
> -		sorwakeup(so);
> -		sowwakeup(so);
> +		sorwakeup(so, NULL);
> +		sowwakeup(so, NULL);
>  	}
>  }
>  
> @@ -141,8 +141,8 @@ soisdisconnecting(struct socket *so)
>  	mtx_leave(&so->so_snd.sb_mtx);
>  
>  	wakeup(&so->so_timeo);
> -	sowwakeup(so);
> -	sorwakeup(so);
> +	sowwakeup(so, NULL);
> +	sorwakeup(so, NULL);
>  }
>  
>  void
> @@ -162,8 +162,8 @@ soisdisconnected(struct socket *so)
>  	so->so_state |= SS_ISDISCONNECTED;
>  
>  	wakeup(&so->so_timeo);
> -	sowwakeup(so);
> -	sorwakeup(so);
> +	sowwakeup(so, NULL);
> +	sorwakeup(so, NULL);
>  }
>  
>  /*
> @@ -233,7 +233,7 @@ sonewconn(struct socket *head, int conns
>  	}
>  	if (connstatus) {
>  		so->so_state |= connstatus;
> -		sorwakeup(head);
> +		sorwakeup(head, NULL);
>  		wakeup(&head->so_timeo);
>  	}
>  
> @@ -308,7 +308,7 @@ socantsendmore(struct socket *so)
>  	mtx_enter(&so->so_snd.sb_mtx);
>  	so->so_snd.sb_state |= SS_CANTSENDMORE;
>  	mtx_leave(&so->so_snd.sb_mtx);
> -	sowwakeup(so);
> +	sowwakeup(so, NULL);
>  }
>  
>  void
> @@ -317,7 +317,7 @@ socantrcvmore(struct socket *so)
>  	mtx_enter(&so->so_rcv.sb_mtx);
>  	so->so_rcv.sb_state |= SS_CANTRCVMORE;
>  	mtx_leave(&so->so_rcv.sb_mtx);
> -	sorwakeup(so);
> +	sorwakeup(so, NULL);
>  }
>  
>  void
> Index: kern/uipc_usrreq.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/kern/uipc_usrreq.c,v
> diff -u -p -r1.220 uipc_usrreq.c
> --- kern/uipc_usrreq.c	12 Jun 2025 20:37:58 -0000	1.220
> +++ kern/uipc_usrreq.c	31 Jul 2025 10:39:10 -0000
> @@ -502,7 +502,7 @@ uipc_rcvd(struct socket *so)
>  	so2->so_snd.sb_cc = so->so_rcv.sb_cc;
>  	mtx_leave(&so2->so_snd.sb_mtx);
>  	mtx_leave(&so->so_rcv.sb_mtx);
> -	sowwakeup(so2);
> +	sowwakeup(so2, NULL);
>  }
>  
>  int
> @@ -568,7 +568,7 @@ uipc_send(struct socket *so, struct mbuf
>  	mtx_leave(&so2->so_rcv.sb_mtx);
>  
>  	if (dowakeup)
> -		sorwakeup(so2);
> +		sorwakeup(so2, NULL);
>  
>  	m = NULL;
>  
> @@ -636,7 +636,7 @@ uipc_dgram_send(struct socket *so, struc
>  	mtx_leave(&so2->so_rcv.sb_mtx);
>  
>  	if (dowakeup)
> -		sorwakeup(so2);
> +		sorwakeup(so2, NULL);
>  	if (nam)
>  		unp_disconnect(unp);
>  
> Index: net/if.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/net/if.c,v
> diff -u -p -r1.740 if.c
> --- net/if.c	21 Jul 2025 20:36:41 -0000	1.740
> +++ net/if.c	31 Jul 2025 10:39:10 -0000
> @@ -270,6 +270,7 @@ ifinit(void)
>  		    TASKQ_MPSAFE);
>  		if (sn->sn_taskq == NULL)
>  			panic("unable to create network taskq %d", i);
> +		sn->sn_netstack.ns_nettaskq = sn->sn_taskq;
>  	}
>  }
>  
> @@ -973,6 +974,7 @@ if_input_process(struct ifnet *ifp, stru
>  {
>  	struct mbuf *m;
>  	struct softnet *sn;
> +	struct netstack *ns;
>  
>  	if (ml_empty(ml))
>  		return;
> @@ -988,19 +990,20 @@ if_input_process(struct ifnet *ifp, stru
>  	 */
>  
>  	sn = net_sn(idx);
> -	ml_init(&sn->sn_netstack.ns_tcp_ml);
> +	ns = &sn->sn_netstack;
> +	ml_init(&ns->ns_tcp_ml);
>  #ifdef INET6
> -	ml_init(&sn->sn_netstack.ns_tcp6_ml);
> +	ml_init(&ns->ns_tcp6_ml);
>  #endif
>  
>  	NET_LOCK_SHARED();
>  
>  	while ((m = ml_dequeue(ml)) != NULL)
> -		(*ifp->if_input)(ifp, m, &sn->sn_netstack);
> +		(*ifp->if_input)(ifp, m, ns);
>  
> -	tcp_input_mlist(&sn->sn_netstack.ns_tcp_ml, AF_INET);
> +	tcp_input_mlist(&ns->ns_tcp_ml, AF_INET, ns);
>  #ifdef INET6
> -	tcp_input_mlist(&sn->sn_netstack.ns_tcp6_ml, AF_INET6);
> +	tcp_input_mlist(&ns->ns_tcp6_ml, AF_INET6, ns);
>  #endif
>  
>  	NET_UNLOCK_SHARED();
> Index: net/if_ethersubr.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/net/if_ethersubr.c,v
> diff -u -p -r1.303 if_ethersubr.c
> --- net/if_ethersubr.c	7 Jul 2025 02:28:50 -0000	1.303
> +++ net/if_ethersubr.c	31 Jul 2025 10:39:10 -0000
> @@ -2108,7 +2108,7 @@ ether_frm_recv(struct socket *so, struct
>  		return;
>  	}
>  
> -	sorwakeup(so);
> +	sorwakeup(so, NULL);
>  }
>  
>  static struct mbuf *
> Index: net/if_var.h
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/net/if_var.h,v
> diff -u -p -r1.139 if_var.h
> --- net/if_var.h	19 Jul 2025 16:40:40 -0000	1.139
> +++ net/if_var.h	31 Jul 2025 10:39:10 -0000
> @@ -92,9 +92,10 @@ struct task;
>  struct cpumem;
>  
>  struct netstack {
> -	struct route		ns_route;
> -	struct mbuf_list	ns_tcp_ml;
> -	struct mbuf_list	ns_tcp6_ml;
> +	struct route		 ns_route;
> +	struct mbuf_list	 ns_tcp_ml;
> +	struct mbuf_list	 ns_tcp6_ml;
> +	struct taskq		*ns_nettaskq;
>  };
>  
>  /*
> Index: net/pfkeyv2.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/net/pfkeyv2.c,v
> diff -u -p -r1.270 pfkeyv2.c
> --- net/pfkeyv2.c	7 Jul 2025 02:28:50 -0000	1.270
> +++ net/pfkeyv2.c	31 Jul 2025 10:39:10 -0000
> @@ -457,7 +457,7 @@ pfkey_sendup(struct pkpcb *kp, struct mb
>  		return (ENOBUFS);
>  	}
>  
> -	sorwakeup(so);
> +	sorwakeup(so, NULL);
>  	return (0);
>  }
>  
> Index: net/rtsock.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/net/rtsock.c,v
> diff -u -p -r1.386 rtsock.c
> --- net/rtsock.c	15 Jul 2025 09:55:49 -0000	1.386
> +++ net/rtsock.c	31 Jul 2025 10:39:10 -0000
> @@ -485,7 +485,7 @@ rtm_senddesync(struct socket *so)
>  
>  		if (ret != 0) {
>  			rop->rop_flags &= ~ROUTECB_FLAG_DESYNC;
> -			sorwakeup(rop->rop_socket);
> +			sorwakeup(rop->rop_socket, NULL);
>  			return;
>  		}
>  		m_freem(desync_mbuf);
> @@ -612,7 +612,7 @@ rtm_sendup(struct socket *so, struct mbu
>  		return (ENOBUFS);
>  	}
>  
> -	sorwakeup(so);
> +	sorwakeup(so, NULL);
>  	return (0);
>  }
>  
> Index: netinet/ip_divert.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/ip_divert.c,v
> diff -u -p -r1.107 ip_divert.c
> --- netinet/ip_divert.c	8 Jul 2025 00:47:41 -0000	1.107
> +++ netinet/ip_divert.c	31 Jul 2025 10:39:11 -0000
> @@ -237,7 +237,7 @@ divert_packet(struct mbuf *m, int dir, u
>  		goto bad;
>  	}
>  	mtx_leave(&so->so_rcv.sb_mtx);
> -	sorwakeup(so);
> +	sorwakeup(so, NULL);
>  
>  	in_pcbunref(inp);
>  	return;
> Index: netinet/ip_mroute.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/ip_mroute.c,v
> diff -u -p -r1.150 ip_mroute.c
> --- netinet/ip_mroute.c	19 Jul 2025 16:40:40 -0000	1.150
> +++ netinet/ip_mroute.c	31 Jul 2025 10:39:11 -0000
> @@ -1129,7 +1129,7 @@ socket_send(struct socket *so, struct mb
>  		mtx_leave(&so->so_rcv.sb_mtx);
>  
>  		if (ret != 0) {
> -			sorwakeup(so);
> +			sorwakeup(so, NULL);
>  			return (0);
>  		}
>  	}
> Index: netinet/raw_ip.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/raw_ip.c,v
> diff -u -p -r1.167 raw_ip.c
> --- netinet/raw_ip.c	8 Jul 2025 00:47:41 -0000	1.167
> +++ netinet/raw_ip.c	31 Jul 2025 10:39:11 -0000
> @@ -115,7 +115,7 @@ const struct pr_usrreqs rip_usrreqs = {
>  };
>  
>  void    rip_sbappend(struct inpcb *, struct mbuf *, struct ip *,
> -	    struct sockaddr_in *);
> +	    struct sockaddr_in *, struct netstack *);
>  
>  /*
>   * Initialize raw connection block q.
> @@ -195,7 +195,7 @@ rip_input(struct mbuf **mp, int *offp, i
>  
>  			n = m_copym(m, 0, M_COPYALL, M_NOWAIT);
>  			if (n != NULL)
> -				rip_sbappend(last, n, ip, &ripsrc);
> +				rip_sbappend(last, n, ip, &ripsrc, ns);
>  			in_pcbunref(last);
>  
>  			mtx_enter(&rawcbtable.inpt_mtx);
> @@ -222,7 +222,7 @@ rip_input(struct mbuf **mp, int *offp, i
>  		return IPPROTO_DONE;
>  	}
>  
> -	rip_sbappend(last, m, ip, &ripsrc);
> +	rip_sbappend(last, m, ip, &ripsrc, ns);
>  	in_pcbunref(last);
>  
>  	return IPPROTO_DONE;
> @@ -230,7 +230,7 @@ rip_input(struct mbuf **mp, int *offp, i
>  
>  void
>  rip_sbappend(struct inpcb *inp, struct mbuf *m, struct ip *ip,
> -    struct sockaddr_in *ripsrc)
> +    struct sockaddr_in *ripsrc, struct netstack *ns)
>  {
>  	struct socket *so = inp->inp_socket;
>  	struct mbuf *opts = NULL;
> @@ -249,7 +249,7 @@ rip_sbappend(struct inpcb *inp, struct m
>  		m_freem(opts);
>  		ipstat_inc(ips_noproto);
>  	} else
> -		sorwakeup(so);
> +		sorwakeup(so, ns);
>  }
>  
>  /*
> Index: netinet/tcp_input.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/tcp_input.c,v
> diff -u -p -r1.457 tcp_input.c
> --- netinet/tcp_input.c	24 Jul 2025 21:34:07 -0000	1.457
> +++ netinet/tcp_input.c	31 Jul 2025 10:39:11 -0000
> @@ -174,9 +174,10 @@ do { \
>  	if_put(ifp); \
>  } while (0)
>  
> -int	 tcp_input_solocked(struct mbuf **, int *, int, int, struct socket **);
> +int	 tcp_input_solocked(struct mbuf **, int *, int, int, struct socket **,
> +	    struct netstack *);
>  int	 tcp_mss_adv(struct rtentry *, int);
> -int	 tcp_flush_queue(struct tcpcb *);
> +int	 tcp_flush_queue(struct tcpcb *, struct netstack *);
>  void	 tcp_sack_partialack(struct tcpcb *, struct tcphdr *);
>  void	 tcp_newreno_partialack(struct tcpcb *, struct tcphdr *);
>  
> @@ -208,7 +209,8 @@ struct syn_cache *syn_cache_lookup(const
>   */
>  
>  int
> -tcp_reass(struct tcpcb *tp, struct tcphdr *th, struct mbuf *m, int *tlen)
> +tcp_reass(struct tcpcb *tp, struct tcphdr *th, struct mbuf *m, int *tlen,
> +    struct netstack *ns)
>  {
>  	struct tcpqent *p, *q, *nq, *tiqe;
>  
> @@ -303,11 +305,11 @@ tcp_reass(struct tcpcb *tp, struct tcphd
>  	if (th->th_seq != tp->rcv_nxt)
>  		return (0);
>  
> -	return (tcp_flush_queue(tp));
> +	return (tcp_flush_queue(tp, ns));
>  }
>  
>  int
> -tcp_flush_queue(struct tcpcb *tp)
> +tcp_flush_queue(struct tcpcb *tp, struct netstack *ns)
>  {
>  	struct socket *so = tp->t_inpcb->inp_socket;
>  	struct tcpqent *q, *nq;
> @@ -342,7 +344,7 @@ tcp_flush_queue(struct tcpcb *tp)
>  		q = nq;
>  	} while (q != NULL && q->tcpqe_tcp->th_seq == tp->rcv_nxt);
>  	tp->t_flags |= TF_BLOCKOUTPUT;
> -	sorwakeup(so);
> +	sorwakeup(so, ns);
>  	tp->t_flags &= ~TF_BLOCKOUTPUT;
>  	return (flags);
>  }
> @@ -351,7 +353,7 @@ int
>  tcp_input(struct mbuf **mp, int *offp, int proto, int af, struct netstack *ns)
>  {
>  	if (ns == NULL)
> -		return tcp_input_solocked(mp, offp, proto, af, NULL);
> +		return tcp_input_solocked(mp, offp, proto, af, NULL, ns);
>  	(*mp)->m_pkthdr.ph_cookie = (void *)(long)(*offp);
>  	switch (af) {
>  	case AF_INET:
> @@ -370,7 +372,7 @@ tcp_input(struct mbuf **mp, int *offp, i
>  }
>  
>  void
> -tcp_input_mlist(struct mbuf_list *ml, int af)
> +tcp_input_mlist(struct mbuf_list *ml, int af, struct netstack *ns)
>  {
>  	struct socket *so = NULL;
>  	struct mbuf *m;
> @@ -380,7 +382,7 @@ tcp_input_mlist(struct mbuf_list *ml, in
>  
>  		off = (long)m->m_pkthdr.ph_cookie;
>  		m->m_pkthdr.ph_cookie = NULL;
> -		nxt = tcp_input_solocked(&m, &off, IPPROTO_TCP, af, &so);
> +		nxt = tcp_input_solocked(&m, &off, IPPROTO_TCP, af, &so, ns);
>  		KASSERT(nxt == IPPROTO_DONE);
>  	}
>  
> @@ -393,7 +395,7 @@ tcp_input_mlist(struct mbuf_list *ml, in
>   */
>  int
>  tcp_input_solocked(struct mbuf **mp, int *offp, int proto, int af,
> -    struct socket **solocked)
> +    struct socket **solocked, struct netstack *ns)
>  {
>  	struct mbuf *m = *mp;
>  	int iphlen = *offp;
> @@ -1075,7 +1077,7 @@ findpcb:
>  				tcp_update_sndspace(tp);
>  				if (sb_notify(&so->so_snd)) {
>  					tp->t_flags |= TF_BLOCKOUTPUT;
> -					sowwakeup(so);
> +					sowwakeup(so, ns);
>  					tp->t_flags &= ~TF_BLOCKOUTPUT;
>  				}
>  				if (so->so_snd.sb_cc ||
> @@ -1131,7 +1133,7 @@ findpcb:
>  				mtx_leave(&so->so_rcv.sb_mtx);
>  			}
>  			tp->t_flags |= TF_BLOCKOUTPUT;
> -			sorwakeup(so);
> +			sorwakeup(so, ns);
>  			tp->t_flags &= ~TF_BLOCKOUTPUT;
>  			if (tp->t_flags & (TF_ACKNOW|TF_NEEDOUTPUT))
>  				(void) tcp_output(tp);
> @@ -1264,7 +1266,7 @@ findpcb:
>  				tp->snd_scale = tp->requested_s_scale;
>  				tp->rcv_scale = tp->request_r_scale;
>  			}
> -			tcp_flush_queue(tp);
> +			tcp_flush_queue(tp, ns);
>  
>  			/*
>  			 * if we didn't have to retransmit the SYN,
> @@ -1553,7 +1555,7 @@ trimthenstep6:
>  			tp->rcv_scale = tp->request_r_scale;
>  			tiwin = th->th_win << tp->snd_scale;
>  		}
> -		tcp_flush_queue(tp);
> +		tcp_flush_queue(tp, ns);
>  		tp->snd_wl1 = th->th_seq - 1;
>  		/* fall into ... */
>  
> @@ -1835,7 +1837,7 @@ trimthenstep6:
>  		tcp_update_sndspace(tp);
>  		if (sb_notify(&so->so_snd)) {
>  			tp->t_flags |= TF_BLOCKOUTPUT;
> -			sowwakeup(so);
> +			sowwakeup(so, ns);
>  			tp->t_flags &= ~TF_BLOCKOUTPUT;
>  		}
>  
> @@ -2051,11 +2053,11 @@ dodata:							/* XXX */
>  				mtx_leave(&so->so_rcv.sb_mtx);
>  			}
>  			tp->t_flags |= TF_BLOCKOUTPUT;
> -			sorwakeup(so);
> +			sorwakeup(so, ns);
>  			tp->t_flags &= ~TF_BLOCKOUTPUT;
>  		} else {
>  			m_adj(m, hdroptlen);
> -			tiflags = tcp_reass(tp, th, m, &tlen);
> +			tiflags = tcp_reass(tp, th, m, &tlen, ns);
>  			tp->t_flags |= TF_ACKNOW;
>  		}
>  		if (tp->sack_enable)
> Index: netinet/tcp_subr.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/tcp_subr.c,v
> diff -u -p -r1.216 tcp_subr.c
> --- netinet/tcp_subr.c	18 Jul 2025 08:39:14 -0000	1.216
> +++ netinet/tcp_subr.c	31 Jul 2025 10:39:11 -0000
> @@ -589,8 +589,8 @@ tcp_notify(struct inpcb *inp, int error)
>  	else
>  		tp->t_softerror = error;
>  	wakeup((caddr_t) &so->so_timeo);
> -	sorwakeup(so);
> -	sowwakeup(so);
> +	sorwakeup(so, NULL);
> +	sowwakeup(so, NULL);
>  }
>  
>  #ifdef INET6
> Index: netinet/tcp_var.h
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/tcp_var.h,v
> diff -u -p -r1.195 tcp_var.h
> --- netinet/tcp_var.h	18 Jun 2025 16:15:46 -0000	1.195
> +++ netinet/tcp_var.h	31 Jul 2025 10:39:11 -0000
> @@ -725,7 +725,7 @@ int	 tcp_dooptions(struct tcpcb *, u_cha
>  		struct mbuf *, int, struct tcp_opt_info *, u_int, uint64_t);
>  void	 tcp_init(void);
>  int	 tcp_input(struct mbuf **, int *, int, int, struct netstack *);
> -void	 tcp_input_mlist(struct mbuf_list *, int);
> +void	 tcp_input_mlist(struct mbuf_list *, int, struct netstack *);
>  int	 tcp_mss(struct tcpcb *, int);
>  void	 tcp_mss_update(struct tcpcb *);
>  void	 tcp_softlro_glue(struct mbuf_list *, struct mbuf *, struct ifnet *);
> @@ -744,7 +744,8 @@ int	 tcp_softtso_chop(struct mbuf_list *
>  int	 tcp_if_output_tso(struct ifnet *, struct mbuf **, struct sockaddr *,
>  	    struct rtentry *, uint32_t, u_int);
>  void	 tcp_pulloutofband(struct socket *, u_int, struct mbuf *, int);
> -int	 tcp_reass(struct tcpcb *, struct tcphdr *, struct mbuf *, int *);
> +int	 tcp_reass(struct tcpcb *, struct tcphdr *, struct mbuf *, int *,
> +	    struct netstack *);
>  void	 tcp_rscale(struct tcpcb *, u_long);
>  void	 tcp_respond(struct tcpcb *, caddr_t, struct tcphdr *, tcp_seq,
>  		tcp_seq, int, u_int, uint64_t);
> Index: netinet/udp_usrreq.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet/udp_usrreq.c,v
> diff -u -p -r1.349 udp_usrreq.c
> --- netinet/udp_usrreq.c	18 Jul 2025 08:39:14 -0000	1.349
> +++ netinet/udp_usrreq.c	31 Jul 2025 10:39:11 -0000
> @@ -709,7 +709,7 @@ udp_sbappend(struct inpcb *inp, struct m
>  	}
>  	mtx_leave(&so->so_rcv.sb_mtx);
>  
> -	sorwakeup(so);
> +	sorwakeup(so, ns);
>  }
>  
>  /*
> @@ -720,8 +720,8 @@ void
>  udp_notify(struct inpcb *inp, int errno)
>  {
>  	inp->inp_socket->so_error = errno;
> -	sorwakeup(inp->inp_socket);
> -	sowwakeup(inp->inp_socket);
> +	sorwakeup(inp->inp_socket, NULL);
> +	sowwakeup(inp->inp_socket, NULL);
>  }
>  
>  #ifdef INET6
> Index: netinet6/ip6_divert.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/ip6_divert.c,v
> diff -u -p -r1.108 ip6_divert.c
> --- netinet6/ip6_divert.c	8 Jul 2025 00:47:41 -0000	1.108
> +++ netinet6/ip6_divert.c	31 Jul 2025 10:39:11 -0000
> @@ -235,7 +235,7 @@ divert6_packet(struct mbuf *m, int dir, 
>  		goto bad;
>  	}
>  	mtx_leave(&so->so_rcv.sb_mtx);
> -	sorwakeup(so);
> +	sorwakeup(so, NULL);
>  
>  	in_pcbunref(inp);
>  	return;
> Index: netinet6/ip6_mroute.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/ip6_mroute.c,v
> diff -u -p -r1.153 ip6_mroute.c
> --- netinet6/ip6_mroute.c	25 Jul 2025 22:24:06 -0000	1.153
> +++ netinet6/ip6_mroute.c	31 Jul 2025 10:39:11 -0000
> @@ -906,7 +906,7 @@ socket6_send(struct socket *so, struct m
>  		mtx_leave(&so->so_rcv.sb_mtx);
>  
>  		if (ret != 0) {
> -			sorwakeup(so);
> +			sorwakeup(so, NULL);
>  			return 0;
>  		}
>  	}
> Index: netinet6/raw_ip6.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/netinet6/raw_ip6.c,v
> diff -u -p -r1.194 raw_ip6.c
> --- netinet6/raw_ip6.c	8 Jul 2025 00:47:41 -0000	1.194
> +++ netinet6/raw_ip6.c	31 Jul 2025 10:39:11 -0000
> @@ -114,7 +114,7 @@ const struct pr_usrreqs rip6_usrreqs = {
>  };
>  
>  void	rip6_sbappend(struct inpcb *, struct mbuf *, struct ip6_hdr *, int,
> -	    struct sockaddr_in6 *);
> +	    struct sockaddr_in6 *, struct netstack *);
>  
>  /*
>   * Initialize raw connection block queue.
> @@ -229,7 +229,8 @@ rip6_input(struct mbuf **mp, int *offp, 
>  
>  			n = m_copym(m, 0, M_COPYALL, M_NOWAIT);
>  			if (n != NULL)
> -				rip6_sbappend(last, n, ip6, *offp, &rip6src);
> +				rip6_sbappend(last, n, ip6, *offp, &rip6src,
> +				    ns);
>  			in_pcbunref(last);
>  
>  			mtx_enter(&rawin6pcbtable.inpt_mtx);
> @@ -262,7 +263,7 @@ rip6_input(struct mbuf **mp, int *offp, 
>  		return IPPROTO_DONE;
>  	}
>  
> -	rip6_sbappend(last, m, ip6, *offp, &rip6src);
> +	rip6_sbappend(last, m, ip6, *offp, &rip6src, ns);
>  	in_pcbunref(last);
>  
>  	return IPPROTO_DONE;
> @@ -270,7 +271,7 @@ rip6_input(struct mbuf **mp, int *offp, 
>  
>  void
>  rip6_sbappend(struct inpcb *inp, struct mbuf *m, struct ip6_hdr *ip6, int hlen,
> -    struct sockaddr_in6 *rip6src)
> +    struct sockaddr_in6 *rip6src, struct netstack *ns)
>  {
>  	struct socket *so = inp->inp_socket;
>  	struct mbuf *opts = NULL;
> @@ -291,7 +292,7 @@ rip6_sbappend(struct inpcb *inp, struct 
>  		m_freem(opts);
>  		rip6stat_inc(rip6s_fullsock);
>  	} else
> -		sorwakeup(so);
> +		sorwakeup(so, ns);
>  }
>  
>  void
> Index: sys/socketvar.h
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/sys/socketvar.h,v
> diff -u -p -r1.159 socketvar.h
> --- sys/socketvar.h	25 Jul 2025 08:58:44 -0000	1.159
> +++ sys/socketvar.h	31 Jul 2025 10:55:03 -0000
> @@ -74,6 +74,7 @@ struct sosplice {
>  	struct	timeval ssp_idletv;	/* [I] idle timeout */
>  	struct	timeout ssp_idleto;
>  	struct	task ssp_task;		/* task for somove */
> +	struct	taskq *ssp_queue;	/* [a] softnet queue where we add */
>  };
>  
>  /*
> @@ -267,8 +268,8 @@ int	sosend(struct socket *, struct mbuf 
>  int	sosetopt(struct socket *, int, int, struct mbuf *);
>  int	soshutdown(struct socket *, int);
>  void	sowakeup(struct socket *, struct sockbuf *);
> -void	sorwakeup(struct socket *);
> -void	sowwakeup(struct socket *);
> +void	sorwakeup(struct socket *, struct netstack *);
> +void	sowwakeup(struct socket *, struct netstack *);
>  int	sockargs(struct mbuf **, const void *, size_t, int);
>  
>  int	sosleep_nsec(struct socket *, void *, int, const char *, uint64_t);