Index | Thread | Search

From:
David Gwynne <david@gwynne.id.au>
Subject:
Re: use softnet for socket splicing
To:
Alexander Bluhm <bluhm@openbsd.org>
Cc:
tech@openbsd.org
Date:
Sat, 26 Jul 2025 14:02:11 +1000

Download raw body.

Thread
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.

> Depending on the test, it improves TCP thoughput between 3% and
> 20%.  For UDP I see 30% to 75% increase.
> 
> ok?
> 
> bluhm
> 
> Index: kern/uipc_socket.c
> ===================================================================
> RCS file: /data/mirror/openbsd/cvs/src/sys/kern/uipc_socket.c,v
> diff -u -p -r1.384 uipc_socket.c
> --- kern/uipc_socket.c	24 Jul 2025 23:30:04 -0000	1.384
> +++ kern/uipc_socket.c	24 Jul 2025 23:43: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
> @@ -475,8 +477,9 @@ notsplicedback:
>  		sbunlock(&so->so_rcv);
>  
>  		timeout_del_barrier(&so->so_spliceidleto);
> -		task_del(sosplice_taskq, &so->so_splicetask);
> -		taskq_barrier(sosplice_taskq);
> +		if (so->so_splicequeue != NULL &&
> +		    task_del(so->so_splicequeue, &so->so_splicetask))
> +			sorele(so);
>  
>  		solock_shared(so);
>  	}
> @@ -1290,7 +1293,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 +1314,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);
> @@ -1441,8 +1424,10 @@ sounsplice(struct socket *so, struct soc
>  	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);
> +	if (so->so_splicequeue != NULL &&
> +	    task_del(so->so_splicequeue, &so->so_splicetask))
> +		sorele(so);
>  
>  	/* Do not wakeup a socket that is about to be freed. */
>  	if ((freeing & SOSP_FREEING_READ) == 0) {
> @@ -1453,13 +1438,13 @@ sounsplice(struct socket *so, struct soc
>  		readable = 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 +1469,12 @@ 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();
> -	}
> +	sorele(so);
>  }
>  
>  /*
> @@ -1853,13 +1830,19 @@ 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) {
> +			if (so->so_splicequeue == NULL) {
> +				so->so_splicequeue = ns != NULL ?
> +				    ns->ns_nettaskq : net_tq(0);
> +			}
> +			if (task_add(so->so_splicequeue, &so->so_splicetask))
> +				soref(so);
> +		}
>  		if (isspliced(so)) {
>  			mtx_leave(&so->so_rcv.sb_mtx);
>  			return;
> @@ -1873,14 +1856,22 @@ 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;
> +
> +			if (soback->so_splicequeue == NULL) {
> +				soback->so_splicequeue = ns != NULL ?
> +				    ns->ns_nettaskq : net_tq(0);
> +			}
> +			if (task_add(soback->so_splicequeue,
> +			    &soback->so_splicetask))
> +				soref(soback);
> +		}
>  		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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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.152 ip6_mroute.c
> --- netinet6/ip6_mroute.c	24 Jul 2025 21:35:53 -0000	1.152
> +++ netinet6/ip6_mroute.c	24 Jul 2025 23:43:04 -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	24 Jul 2025 23:43:04 -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.158 socketvar.h
> --- sys/socketvar.h	8 Apr 2025 15:31:22 -0000	1.158
> +++ sys/socketvar.h	24 Jul 2025 23:43:04 -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;	/* softnet queue where we were added */
>  };
>  
>  /*
> @@ -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);
>