* [PATCH 1/7] tap: Move the tap_hdr file to a separate file
2026-09-04 21:28 [PATCH 0/7] Add vhost-net kernel support to pasta aerosouund
@ 2026-09-04 21:28 ` aerosouund
2026-09-05 1:52 ` David Gibson
2026-09-04 21:28 ` [PATCH 2/7] conf: Add context fields, epoll types and --vhost-kernel flag to pasta aerosouund
` (6 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: aerosouund @ 2026-09-04 21:28 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
It was defined in tap.h, put it on its own so that future callers won't
need to depend on all definitions in tap.h.
Also turn it into a union of a vnet_len and virtio_net_mrg_rxbuf
because for vhost acceleration the frames will have this virtio net
header prepended to them.
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
tap.h | 9 +--------
tap_hdr.h | 23 +++++++++++++++++++++++
2 files changed, 24 insertions(+), 8 deletions(-)
create mode 100644 tap_hdr.h
diff --git a/tap.h b/tap.h
index b335933..1625975 100644
--- a/tap.h
+++ b/tap.h
@@ -10,6 +10,7 @@
#include <stdint.h>
#include "passt.h"
+#include "tap_hdr.h"
/** L2_MAX_LEN_PASTA - Maximum frame length for pasta mode (with L2 header)
*
@@ -38,14 +39,6 @@
struct udphdr;
-/**
- * struct tap_hdr - tap backend specific headers
- * @vnet_len: Frame length (for qemu socket transport)
- */
-struct tap_hdr {
- uint32_t vnet_len;
-} __attribute__((packed));
-
/**
* tap_hdr_iov() - struct iovec for a tap header
* @c: Execution context
diff --git a/tap_hdr.h b/tap_hdr.h
new file mode 100644
index 0000000..aa270b7
--- /dev/null
+++ b/tap_hdr.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright (c) 2021 Red Hat GmbH
+ * Author: Stefano Brivio <sbrivio@redhat.com>
+ */
+
+#ifndef TAP_HDR_H
+#define TAP_HDR_H
+
+#include <stdint.h>
+#include <linux/virtio_net.h>
+
+/**
+ * struct tap_hdr - tap backend specific headers
+ * @vnet_len: Frame length (for qemu socket transport)
+ */
+struct tap_hdr {
+ union {
+ uint32_t vnet_len;
+ struct virtio_net_hdr_mrg_rxbuf hdr;
+ };
+};
+
+#endif /* TAP_HDR_H */
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH 1/7] tap: Move the tap_hdr file to a separate file
2026-09-04 21:28 ` [PATCH 1/7] tap: Move the tap_hdr file to a separate file aerosouund
@ 2026-09-05 1:52 ` David Gibson
0 siblings, 0 replies; 13+ messages in thread
From: David Gibson @ 2026-09-05 1:52 UTC (permalink / raw)
To: aerosouund; +Cc: passt-dev, eperezma
[-- Attachment #1: Type: text/plain, Size: 2615 bytes --]
On Sat, Sep 05, 2026 at 12:28:20AM +0300, aerosouund wrote:
> From: Ammar Yasser <aerosound161@gmail.com>
>
> It was defined in tap.h, put it on its own so that future callers won't
> need to depend on all definitions in tap.h.
> Also turn it into a union of a vnet_len and virtio_net_mrg_rxbuf
> because for vhost acceleration the frames will have this virtio net
> header prepended to them.
>
> Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
> Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
The code motion itself looks fine.
> ---
> tap.h | 9 +--------
> tap_hdr.h | 23 +++++++++++++++++++++++
> 2 files changed, 24 insertions(+), 8 deletions(-)
> create mode 100644 tap_hdr.h
>
> diff --git a/tap.h b/tap.h
> index b335933..1625975 100644
> --- a/tap.h
> +++ b/tap.h
> @@ -10,6 +10,7 @@
> #include <stdint.h>
>
> #include "passt.h"
> +#include "tap_hdr.h"
>
> /** L2_MAX_LEN_PASTA - Maximum frame length for pasta mode (with L2 header)
> *
> @@ -38,14 +39,6 @@
>
> struct udphdr;
>
> -/**
> - * struct tap_hdr - tap backend specific headers
> - * @vnet_len: Frame length (for qemu socket transport)
> - */
> -struct tap_hdr {
> - uint32_t vnet_len;
> -} __attribute__((packed));
> -
> /**
> * tap_hdr_iov() - struct iovec for a tap header
> * @c: Execution context
> diff --git a/tap_hdr.h b/tap_hdr.h
> new file mode 100644
> index 0000000..aa270b7
> --- /dev/null
> +++ b/tap_hdr.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later
> + * Copyright (c) 2021 Red Hat GmbH
> + * Author: Stefano Brivio <sbrivio@redhat.com>
This might be a bit pedantic since this may be a smaller block of code
than is copyrightable, but..
Fwiw, Red Hat legal's advice, last I knew was to prefer simply
"Copyright Red Hat" - the rest is apparently not really useful. Also,
these specific lines were added by me in 2023, not Stefano in 2021
(commit 4b3d38a06).
> + */
> +
> +#ifndef TAP_HDR_H
> +#define TAP_HDR_H
> +
> +#include <stdint.h>
> +#include <linux/virtio_net.h>
> +
> +/**
> + * struct tap_hdr - tap backend specific headers
> + * @vnet_len: Frame length (for qemu socket transport)
> + */
> +struct tap_hdr {
> + union {
> + uint32_t vnet_len;
> + struct virtio_net_hdr_mrg_rxbuf hdr;
> + };
> +};
> +
> +#endif /* TAP_HDR_H */
> --
> 2.39.5 (Apple Git-154)
>
--
David Gibson (he or they) | I'll have my music baroque, and my code
david AT gibson.dropbear.id.au | minimalist, thank you, not the other way
| around.
http://www.ozlabs.org/~dgibson
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 2/7] conf: Add context fields, epoll types and --vhost-kernel flag to pasta
2026-09-04 21:28 [PATCH 0/7] Add vhost-net kernel support to pasta aerosouund
2026-09-04 21:28 ` [PATCH 1/7] tap: Move the tap_hdr file to a separate file aerosouund
@ 2026-09-04 21:28 ` aerosouund
2026-09-04 21:28 ` [PATCH 3/7] virtio: Add the pasta vhost-net interface and implementation aerosouund
` (5 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: aerosouund @ 2026-09-04 21:28 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
--vhost-kernel controls whether to use vhost-net acceleration for pasta.
It takes one of three modes: "on" requires acceleration and fails if it
isn't available, "off" never uses it, and "auto" uses it if it's
available and falls back to plain tap operation otherwise. "auto" is the
default, so an unmodified invocation picks up acceleration wherever the
kernel offers it, and behaves as before where it doesn't.
EPOLL_TYPE_VHOST_CALL means the kernel wants to notify us about data it
has written. EPOLL_TYPE_VHOST_ERROR means the kernel encountered an
internal error on vhost and wants to tell us something went wrong.
Collect the vhost state in a single struct vhost_ctx with the following
fields:
- mode: which of the three modes above was requested
- fd: file descriptor for /dev/vhost-net, which doubles as our
indication that setup succeeded
- features: virtio feature bits negotiated with vhost-net
- vq[]: kick, call and err eventfds for both queues
Modify tap_hdr_iov to create an iov with the size of a
virtio_net_mrg_rxbuf if vhost initialization has been successful (we are
in mode 'on' or 'auto' and we didn't need the tap fallback)
Move the L2_MAX_LEN_XX constants from tap.h to passt.h to avoid the
circular dependency between both files. VHOST_DESC_BYTES is defined as
the maximum l2 frame size + the size of the virtio net header, if the
length constant remains in tap.h passt.h would need to include it, but
tap.h needs to include passt.h to get the definition of `struct ctx`.
Moving the length constants to passt.h makes it free of dependency on
tap.h
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
conf.c | 19 +++++++++++++
epoll_type.h | 4 +++
passt.c | 3 ++
passt.h | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++--
tap.h | 36 ++++++------------------
5 files changed, 112 insertions(+), 29 deletions(-)
diff --git a/conf.c b/conf.c
index faf2681..a7b934a 100644
--- a/conf.c
+++ b/conf.c
@@ -736,6 +736,9 @@ pasta_opts:
" default: auto\n"
" --host-lo-to-ns-lo Translate host-loopback forwards to\n"
" namespace loopback\n"
+ " --vhost-kernel MODE Use vhost-kernel acceleration\n"
+ " MODE is \"on\", \"off\" or \"auto\"\n"
+ " default: auto\n"
" --userns NSPATH Target user namespace to join\n"
" --netns PATH|NAME Target network namespace to join\n"
" --netns-only Don't join existing user namespace\n"
@@ -766,6 +769,7 @@ enum passt_modes conf_mode(int argc, char *argv[])
int vhost_user = 0;
const struct option optvu[] = {
{"vhost-user", no_argument, &vhost_user, 1 },
+ {"vhost-kernel", required_argument, NULL, 0 },
{ 0 },
};
char argv0[PATH_MAX], *basearg0;
@@ -1348,6 +1352,7 @@ void conf(struct ctx *c, int argc, char **argv)
{"stats", required_argument, NULL, 31 },
{"conf-path", required_argument, NULL, 'c' },
{"chroot-fallback", no_argument, NULL, 32 },
+ {"vhost-kernel", required_argument, NULL, 33 },
{ 0 },
};
const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:";
@@ -1588,6 +1593,20 @@ void conf(struct ctx *c, int argc, char **argv)
break;
case 32:
c->chroot_fallback = true;
+ break;
+ case 33:
+ if (c->mode != MODE_PASTA)
+ die("--vhost-kernel is for pasta mode only");
+
+ if (!strcmp(optarg, "auto"))
+ c->vhost.mode = VHOST_MODE_AUTO;
+ else if (!strcmp(optarg, "on"))
+ c->vhost.mode = VHOST_MODE_ON;
+ else if (!strcmp(optarg, "off"))
+ c->vhost.mode = VHOST_MODE_OFF;
+ else
+ die("Invalid vhost-kernel mode: %s", optarg);
+
break;
case 'd':
c->debug = 1;
diff --git a/epoll_type.h b/epoll_type.h
index 061325a..e3206d1 100644
--- a/epoll_type.h
+++ b/epoll_type.h
@@ -50,6 +50,10 @@ enum epoll_type {
EPOLL_TYPE_CONF_LISTEN,
/* Configuration socket */
EPOLL_TYPE_CONF,
+ /* vhost-kernel call socket */
+ EPOLL_TYPE_VHOST_CALL,
+ /* vhost-kernel error socket */
+ EPOLL_TYPE_VHOST_ERROR,
EPOLL_NUM_TYPES,
};
diff --git a/passt.c b/passt.c
index 5054551..6002231 100644
--- a/passt.c
+++ b/passt.c
@@ -65,6 +65,7 @@ char pkt_buf[PKT_BUF_BYTES] __attribute__ ((aligned(PAGE_SIZE)));
struct ctx passt_ctx = {
.pidfile_fd = -1,
.fd_tap = -1,
+ .vhost = { .fd = -1 },
.fd_tap_listen = -1,
.fd_control_listen = -1,
.fd_repair_listen = -1,
@@ -92,6 +93,8 @@ char *epoll_type_str[] = {
[EPOLL_TYPE_NL_NEIGH] = "netlink neighbour notifier socket",
[EPOLL_TYPE_CONF_LISTEN] = "configuration listening socket",
[EPOLL_TYPE_CONF] = "configuration socket",
+ [EPOLL_TYPE_VHOST_CALL] = "vhost-kernel call socket",
+ [EPOLL_TYPE_VHOST_ERROR] = "vhost-kernel error socket",
};
static_assert(ARRAY_SIZE(epoll_type_str) == EPOLL_NUM_TYPES,
"epoll_type_str[] doesn't match enum epoll_type");
diff --git a/passt.h b/passt.h
index 51ccd4f..5e0de3e 100644
--- a/passt.h
+++ b/passt.h
@@ -13,8 +13,11 @@ union epoll_ref;
#include <stdbool.h>
#include <assert.h>
+#include <limits.h>
#include <sys/epoll.h>
+#include <linux/virtio_net.h>
+
#include "pif.h"
#include "packet.h"
#include "siphash.h"
@@ -35,8 +38,43 @@ union epoll_ref;
#define MAC_OUR_LAA \
((uint8_t [ETH_ALEN]){0x9a, 0x55, 0x9a, 0x55, 0x9a, 0x55})
-/* Large enough for ~128 maximum size frames */
-#define PKT_BUF_BYTES (8UL << 20)
+
+/** L2_MAX_LEN_PASTA - Maximum frame length for pasta mode (with L2 header)
+ *
+ * The kernel tuntap device imposes a maximum frame size of 65535 including
+ * 'hard_header_len' (14 bytes for L2 Ethernet in the case of "tap" mode).
+ */
+#define L2_MAX_LEN_PASTA USHRT_MAX
+
+/** L2_MAX_LEN_PASST - Maximum frame length for passt mode (with L2 header)
+ *
+ * The only structural limit the QEMU socket protocol imposes on frames is
+ * (2^32-1) bytes, but that would be ludicrously long in practice. For now,
+ * limit it somewhat arbitrarily to 65535 bytes. FIXME: Work out an appropriate
+ * limit with more precision.
+ */
+#define L2_MAX_LEN_PASST USHRT_MAX
+
+/** L2_MAX_LEN_VU - Maximum frame length for vhost-user mode (with L2 header)
+ *
+ * vhost-user allows multiple buffers per frame, each of which can be quite
+ * large, so the inherent frame size limit is rather large. Much larger than is
+ * actually useful for IP. For now limit arbitrarily to 65535 bytes. FIXME:
+ * Work out an appropriate limit with more precision.
+ */
+#define L2_MAX_LEN_VU USHRT_MAX
+
+/* Number of descriptors in each vhost-net virtqueue */
+#define VHOST_NDESCS 128
+
+/* Bytes of pkt_buf backing one from-guest descriptor: a maximum size frame
+ * plus the virtio-net header the kernel writes in front of it
+ */
+#define VHOST_DESC_BYTES (L2_MAX_LEN_PASTA + \
+ sizeof(struct virtio_net_hdr_mrg_rxbuf))
+
+/* One maximum size frame per vhost-net descriptor */
+#define PKT_BUF_BYTES (VHOST_NDESCS * VHOST_DESC_BYTES)
extern char pkt_buf [PKT_BUF_BYTES];
@@ -156,6 +194,40 @@ struct ip6_ctx {
#include <netinet/if_ether.h>
+/**
+ * enum vhost_mode - Whether to use vhost-kernel acceleration
+ * @VHOST_MODE_AUTO: Use it if it's available, fall back to plain tap if not
+ * @VHOST_MODE_ON: Require it, fail if it's not available
+ * @VHOST_MODE_OFF: Never use it
+ */
+enum vhost_mode {
+ VHOST_MODE_AUTO = 0,
+ VHOST_MODE_ON,
+ VHOST_MODE_OFF,
+};
+
+/**
+ * struct vhost_ctx - Execution context for vhost-kernel acceleration
+ * @mode: Whether to use acceleration at all, see enum vhost_mode
+ * @fd: File descriptor for /dev/vhost-net, -1 if not set up
+ * @features: virtio feature bits negotiated with vhost-net
+ * @vq: Per-virtqueue eventfds ([0] is from-guest, [1] is to-guest)
+ * @vq.kick_fd: Written by us, to tell the kernel we queued something
+ * @vq.call_fd: Written by the kernel, to tell us it queued something
+ * @vq.err_fd: Written by the kernel on an internal vhost error
+ */
+struct vhost_ctx {
+ enum vhost_mode mode;
+ int fd;
+ uint64_t features;
+
+ struct {
+ int kick_fd;
+ int call_fd;
+ int err_fd;
+ } vq[2];
+};
+
/**
* struct ctx - Execution context
* @mode: Operation mode, qemu/UNIX domain socket or namespace/tap
@@ -185,6 +257,7 @@ struct ip6_ctx {
* @our_tap_mac: Pasta/passt's MAC on the tap link
* @guest_mac: MAC address of guest or namespace, seen or configured
* @hash_secret: 128-bit secret for siphash functions
+ * @vhost: vhost-kernel acceleration context, pasta mode only
* @ifi4: Template interface for IPv4, -1: none, 0: IPv4 disabled
* @ip4: IPv4 configuration
* @dns_search: DNS search list
@@ -264,6 +337,8 @@ struct ctx {
uint64_t hash_secret[2];
+ struct vhost_ctx vhost;
+
int ifi4;
struct ip4_ctx ip4;
diff --git a/tap.h b/tap.h
index 1625975..ce1efa8 100644
--- a/tap.h
+++ b/tap.h
@@ -12,31 +12,6 @@
#include "passt.h"
#include "tap_hdr.h"
-/** L2_MAX_LEN_PASTA - Maximum frame length for pasta mode (with L2 header)
- *
- * The kernel tuntap device imposes a maximum frame size of 65535 including
- * 'hard_header_len' (14 bytes for L2 Ethernet in the case of "tap" mode).
- */
-#define L2_MAX_LEN_PASTA USHRT_MAX
-
-/** L2_MAX_LEN_PASST - Maximum frame length for passt mode (with L2 header)
- *
- * The only structural limit the QEMU socket protocol imposes on frames is
- * (2^32-1) bytes, but that would be ludicrously long in practice. For now,
- * limit it somewhat arbitrarily to 65535 bytes. FIXME: Work out an appropriate
- * limit with more precision.
- */
-#define L2_MAX_LEN_PASST USHRT_MAX
-
-/** L2_MAX_LEN_VU - Maximum frame length for vhost-user mode (with L2 header)
- *
- * vhost-user allows multiple buffers per frame, each of which can be quite
- * large, so the inherent frame size limit is rather large. Much larger than is
- * actually useful for IP. For now limit arbitrarily to 65535 bytes. FIXME:
- * Work out an appropriate limit with more precision.
- */
-#define L2_MAX_LEN_VU USHRT_MAX
-
struct udphdr;
/**
@@ -50,10 +25,17 @@ struct udphdr;
static inline struct iovec tap_hdr_iov(const struct ctx *c,
struct tap_hdr *thdr)
{
- return (struct iovec){
+ struct iovec ret = {
.iov_base = thdr,
- .iov_len = c->mode == MODE_PASST ? sizeof(*thdr) : 0,
};
+
+ if ((c->vhost.fd != -1)) {
+ ret.iov_len = sizeof(struct virtio_net_hdr_mrg_rxbuf);
+ } else {
+ ret.iov_len = c->mode == MODE_PASST ? sizeof(thdr->vnet_len) : 0;
+ };
+
+ return ret;
}
/**
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 3/7] virtio: Add the pasta vhost-net interface and implementation
2026-09-04 21:28 [PATCH 0/7] Add vhost-net kernel support to pasta aerosouund
2026-09-04 21:28 ` [PATCH 1/7] tap: Move the tap_hdr file to a separate file aerosouund
2026-09-04 21:28 ` [PATCH 2/7] conf: Add context fields, epoll types and --vhost-kernel flag to pasta aerosouund
@ 2026-09-04 21:28 ` aerosouund
2026-09-04 21:28 ` [PATCH 4/7] tap: Implement the pasta vhost-net from-guest path aerosouund
` (4 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: aerosouund @ 2026-09-04 21:28 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
Add vhost.c and vhost.h which define the api and implementation for
pasta vhost acceleration
- vq_state/vhost_vq_state[]: our own view of each virtqueue, which the
kernel doesn't track for us: descriptors ready to hand back, how far
we've read the used ring, and the next descriptor to allocate
- vring_desc, vring_avail_all, vring_used_all, vhost_memory: the rings
and the region table shared with the kernel
- vhost_setup_net: open /dev/vhost-net and negotiate VIRTIO_F_VERSION_1
and VHOST_NET_F_VIRTIO_NET_HDR. Failure is fatal only if
--vhost-kernel is set to 'on'.
- vhost_setup_eventfds: create, register and watch the call, kick and
error eventfds for one queue
- vhost_setup_memory_table: register the buffers the kernel may touch.
pasta has no guest, so each region maps guest_phys_addr onto the
identical userspace_addr, making GPA translation an identity mapping.
This function uses the *_register_memory_regions functions created per
protocol and for general purpose buffers.
- vhost_set_vring: publish a vring's addresses, bind the tap fd as its
backend and lay out its descriptors
- vhost_rx_descriptor_handoff, vhost_kick: hand consumed descriptors back
and notify the kernel unless it says it's already polling
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
Makefile | 2 +-
tcp_buf.c | 16 ++-
tcp_buf.h | 4 +
udp.c | 14 +++
udp.h | 3 +
util.c | 12 +++
util.h | 2 +
vhost.c | 295 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
vhost.h | 79 +++++++++++++++
9 files changed, 425 insertions(+), 2 deletions(-)
create mode 100644 vhost.c
create mode 100644 vhost.h
diff --git a/Makefile b/Makefile
index b315242..74d0f96 100644
--- a/Makefile
+++ b/Makefile
@@ -38,7 +38,7 @@ PASST_SRCS = arch.c arp.c bitmap.c checksum.c conf.c dhcp.c dhcpv6.c \
isolation.c lineread.c log.c mld.c ndp.c netlink.c migrate.c packet.c \
parse.c passt.c pasta.c pcap.c pif.c repair.c serialise.c tap.c tcp.c \
tcp_buf.c tcp_splice.c tcp_vu.c udp.c udp_flow.c udp_vu.c util.c \
- vhost_user.c virtio.c vu_common.c
+ vhost.c vhost_user.c virtio.c vu_common.c
PASST_REPAIR_SRCS = passt-repair.c
PESTO_SRCS = pesto.c bitmap.c fwd_rule.c inany.c ip.c lineread.c parse.c \
serialise.c
diff --git a/tcp_buf.c b/tcp_buf.c
index 72c4541..1f28728 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -31,7 +31,7 @@
#include "inany.h"
#include "tcp_conn.h"
#include "tcp_internal.h"
-#include "tcp_buf.h"
+#include "vhost.h"
#define TCP_FRAMES_MEM 128
#define TCP_FRAMES \
@@ -75,6 +75,20 @@ void tcp_update_l2_buf(const unsigned char *eth_d)
eth_update_mac(&tcp_eth_hdr[i], eth_d, NULL);
}
+/**
+ * tcp_register_memory_region() - Register the TCP specific buffers into a memory
+ * struct so it can be shared with the kernel.
+ * @vhost_mem: The memory struct to register regions into
+ * @last_idx: The last index at which memory region has been placed
+ */
+void tcp_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx) {
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp_payload_tap_hdr);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp4_payload_ip);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp6_payload_ip);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp_payload);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp_eth_hdr);
+}
+
/**
* tcp_sock_iov_init() - Initialise scatter-gather L2 buffers for IPv4 sockets
* @c: Execution context
diff --git a/tcp_buf.h b/tcp_buf.h
index 5d31cea..5e341ed 100644
--- a/tcp_buf.h
+++ b/tcp_buf.h
@@ -6,11 +6,15 @@
#ifndef TCP_BUF_H
#define TCP_BUF_H
+union vhost_memory_u;
+struct tcp_tap_conn;
+
void tcp_sock_iov_init(const struct ctx *c);
void tcp_payload_flush(const struct ctx *c, const struct timespec *now);
int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn,
uint32_t already_sent, const struct timespec *now);
int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
const struct timespec *now);
+void tcp_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx);
#endif /*TCP_BUF_H */
diff --git a/udp.c b/udp.c
index 505e554..5e3bd86 100644
--- a/udp.c
+++ b/udp.c
@@ -118,6 +118,7 @@
#include "udp_internal.h"
#include "udp_vu.h"
#include "epoll_ctl.h"
+#include "vhost.h"
#define UDP_MAX_FRAMES 32 /* max # of frames to receive at once */
@@ -448,6 +449,19 @@ static void udp_send_tap_icmp4(const struct ctx *c,
tap_icmp4_send(c, saddr, eaddr, &msg, tap_omac, msglen);
}
+/**
+ * udp_register_memory_region() - Register the UDP specific buffers into a memory
+ * struct so it can be shared with the kernel.
+ * @vhost_mem: The memory struct to register regions into
+ * @last_idx: The last index at which memory region has been placed
+ */
+void udp_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx) {
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_payload);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_eth_hdr);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_iov_recv);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_mh_recv);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_meta);
+}
/**
* udp_send_tap_icmp6() - Construct and send ICMPv6 to local peer
diff --git a/udp.h b/udp.h
index b50283e..ebf1a4e 100644
--- a/udp.h
+++ b/udp.h
@@ -11,6 +11,8 @@
#include "fwd.h"
+union vhost_memory_u;
+
void udp_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
uint32_t events, const struct timespec *now);
void udp_sock_handler(const struct ctx *c, union epoll_ref ref,
@@ -21,6 +23,7 @@ int udp_tap_handler(const struct ctx *c, uint8_t pif,
const struct timespec *now);
int udp_init(struct ctx *c);
void udp_update_l2_buf(const unsigned char *eth_d);
+void udp_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx);
/**
* struct udp_ctx - Execution context for UDP
diff --git a/util.c b/util.c
index 28c32e4..a994d88 100644
--- a/util.c
+++ b/util.c
@@ -36,6 +36,7 @@
#include "epoll_ctl.h"
#include "pasta.h"
#include "serialise.h"
+#include "vhost.h"
#ifdef HAS_GETRANDOM
#include <sys/random.h>
#endif
@@ -456,6 +457,17 @@ int open_in_ns(const struct ctx *c, const char *path, int flags)
return arg.fd;
}
+/**
+ * general_register_memory_region() - Register the general (non protocol specific) buffers
+ * into a memory struct so it can be shared with the kernel.
+ * @vhost_mem: The memory struct to register regions into
+ * @last_idx: The last index at which memory region has been placed
+ */
+void general_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx) {
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(pkt_buf);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(eth_pad);
+}
+
/**
* pidfile_write() - Write PID to file, if requested to do so, and close it
* @fd: Open PID file descriptor, closed on exit, -1 to skip writing it
diff --git a/util.h b/util.h
index 2435f53..27eac22 100644
--- a/util.h
+++ b/util.h
@@ -145,6 +145,7 @@ int do_clone(int (*fn)(void *), char *stack_area, size_t stack_size, int flags,
struct ctx;
union sockaddr_inany;
+union vhost_memory_u;
int sock_l4(const struct ctx *c, enum epoll_type type,
const union sockaddr_inany *sa, const char *ifname);
@@ -170,6 +171,7 @@ int write_remainder(int fd, const struct iovec *iov, size_t iovcnt,
int read_remainder(int fd, const struct iovec *iov, size_t cnt, size_t skip);
bool snprintf_check(char *str, size_t size, const char *format, ...);
long clamped_scale(long x, long y, long lo, long hi, long f);
+void general_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx);
/**
* af_name() - Return name of an address family
diff --git a/vhost.c b/vhost.c
new file mode 100644
index 0000000..0c60c21
--- /dev/null
+++ b/vhost.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright Red Hat
+ * Author: Ammar Yasser <aerosound161@gmail.com>
+ *
+ * vhost.c - vhost-net (vhost-kernel) acceleration for pasta mode
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+
+#include "util.h"
+#include "passt.h"
+#include "vhost.h"
+#include "epoll_ctl.h"
+#include "udp.h"
+#include "tcp_buf.h"
+
+struct vq_state vhost_vq_state[2];
+
+struct vring_desc vring_desc[2][VHOST_NDESCS]
+ __attribute__((aligned(PAGE_SIZE)));
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+union vring_avail_u vring_avail_all[2] __attribute__((aligned(PAGE_SIZE)));
+union vring_used_u vring_used_all[2] __attribute__((aligned(PAGE_SIZE)));
+#pragma GCC diagnostic pop
+
+union vhost_memory_u vhost_memory = {
+ .mem = {
+ .nregions = N_VHOST_REGIONS,
+ },
+};
+
+/**
+ * vhost_setup_net() - Open and negotiate features on /dev/vhost-net
+ * @c: Execution context; c->vhost.fd and c->vhost.features are set
+ * on success, and left untouched on failure
+ *
+ * Failure here means vhost-net isn't usable, not that pasta can't run: the
+ * caller falls back to plain tap operation unless acceleration was required.
+ * That's why nothing in here is fatal.
+ *
+ * Return: 0 on success, -1 if vhost-net is unavailable or unusable
+ */
+int vhost_setup_net(struct ctx *c)
+{
+ const uint64_t req_features = (1ULL << VIRTIO_F_VERSION_1) |
+ (1ULL << VHOST_NET_F_VIRTIO_NET_HDR);
+ uint64_t features;
+ int vhost_fd;
+
+ vhost_fd = open("/dev/vhost-net", O_RDWR | O_NONBLOCK | O_CLOEXEC);
+ if (vhost_fd < 0) {
+ debug_perror("Couldn't open /dev/vhost-net");
+ return -1;
+ }
+
+ if (ioctl(vhost_fd, VHOST_SET_OWNER, NULL) < 0) {
+ debug_perror("VHOST_SET_OWNER ioctl failed");
+ goto close_fd;
+ }
+
+ if (ioctl(vhost_fd, VHOST_GET_FEATURES, &features) < 0) {
+ debug_perror("VHOST_GET_FEATURES ioctl failed");
+ goto close_fd;
+ }
+
+ if ((features & req_features) != req_features) {
+ debug("vhost-net is missing features: 0x%016" PRIx64,
+ req_features & ~features);
+ goto close_fd;
+ }
+
+ features = req_features;
+ if (ioctl(vhost_fd, VHOST_SET_FEATURES, &features) < 0) {
+ debug_perror("VHOST_SET_FEATURES ioctl failed");
+ goto close_fd;
+ }
+
+ c->vhost.features = features;
+ c->vhost.fd = vhost_fd;
+
+ return 0;
+
+close_fd:
+ close(vhost_fd);
+
+ return -1;
+}
+
+/**
+ * vhost_setup_eventfds() - Set up one queue's eventfds and ring size
+ * @c: Execution context; c->vhost.fd must already be set
+ * @queue_idx: Index of the queue (vring) to configure
+ *
+ */
+void vhost_setup_eventfds(struct ctx *c, int queue_idx)
+{
+ struct vhost_vring_file call_file = { .index = queue_idx };
+ struct vhost_vring_file kick_file = { .index = queue_idx };
+ struct vhost_vring_file err_file = { .index = queue_idx };
+ struct vhost_vring_state state = { .index = queue_idx };
+ union epoll_ref ref = { .type = EPOLL_TYPE_VHOST_CALL };
+ int vhost_fd = c->vhost.fd;
+ struct epoll_event ev;
+ int rc;
+
+ state.num = VHOST_NDESCS;
+ ref.queue = queue_idx;
+
+ call_file.fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (call_file.fd < 0)
+ die_perror("Failed to create vhost call eventfd, queue %d",
+ queue_idx);
+ ref.fd = call_file.fd;
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_CALL, &call_file);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_CALL ioctl failed, queue %d",
+ queue_idx);
+
+ ev = (struct epoll_event){ .data.u64 = ref.u64, .events = EPOLLIN };
+ rc = epoll_ctl(c->epollfd, EPOLL_CTL_ADD, ref.fd, &ev);
+ if (rc < 0)
+ die_perror("Failed to watch vhost call eventfd, queue %d",
+ queue_idx);
+ c->vhost.vq[queue_idx].call_fd = call_file.fd;
+
+ err_file.fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (err_file.fd < 0)
+ die_perror("Failed to create vhost error eventfd, queue %d",
+ queue_idx);
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_ERR, &err_file);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_ERR ioctl failed, queue %d",
+ queue_idx);
+
+ ref.type = EPOLL_TYPE_VHOST_ERROR;
+ ref.fd = err_file.fd;
+ ev.data.u64 = ref.u64;
+ rc = epoll_ctl(c->epollfd, EPOLL_CTL_ADD, ref.fd, &ev);
+ if (rc < 0)
+ die_perror("Failed to watch vhost error eventfd, queue %d",
+ queue_idx);
+ c->vhost.vq[queue_idx].err_fd = err_file.fd;
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_NUM, &state);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_NUM ioctl failed, queue %d",
+ queue_idx);
+
+ kick_file.fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (kick_file.fd < 0)
+ die_perror("Failed to create vhost kick eventfd, queue %d",
+ queue_idx);
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_KICK, &kick_file);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_KICK ioctl failed, queue %d",
+ queue_idx);
+
+ c->vhost.vq[queue_idx].kick_fd = kick_file.fd;
+
+ vhost_vq_state[queue_idx].num_free = VHOST_NDESCS;
+}
+
+/**
+ * vhost_setup_memory_table() - Register the GPA/HVA translation table
+ * @c: Execution context; c->vhost.fd must already be set
+ *
+ * vhost-net reads the addresses we put in descriptors as guest physical
+ * addresses, and translates them through this table before touching the
+ * memory they refer to. pasta has no guest and no second address space:
+ * the addresses we put there are our own virtual addresses. Every region
+ * below therefore sets guest_phys_addr equal to userspace_addr, so that
+ * GPA == HVA and the translation is an identity mapping.
+ *
+ * Return: 0 on success, -1 on error with errno set
+ */
+int vhost_setup_memory_table(struct ctx *c) {
+ size_t region_idx = 0;
+
+ /* general purpose buffers */
+ general_register_memory_regions(&vhost_memory, ®ion_idx);
+
+ /* tcp specific buffers */
+ tcp_register_memory_regions(&vhost_memory, ®ion_idx);
+
+ /* udp specific buffers */
+ udp_register_memory_regions(&vhost_memory, ®ion_idx);
+
+ vhost_memory.mem.nregions = region_idx;
+
+ return ioctl(c->vhost.fd, VHOST_SET_MEM_TABLE, &vhost_memory.mem);
+}
+
+/**
+ * vhost_set_vring() - Register a vring's addresses and bind its backend
+ * @c: Execution context; c->vhost.fd must already be set
+ * @queue_idx: Index of the queue (vring) to configure
+ * @tap_fd: Tap fd to bind as this queue's backend
+ */
+void vhost_set_vring(struct ctx *c, int queue_idx, int tap_fd)
+{
+ int vhost_fd = c->vhost.fd;
+ struct vhost_vring_addr addr = {
+ .index = queue_idx,
+ .desc_user_addr = (unsigned long)vring_desc[queue_idx],
+ .avail_user_addr = (unsigned long)&vring_avail_all[queue_idx],
+ .used_user_addr = (unsigned long)&vring_used_all[queue_idx],
+ .log_guest_addr = (unsigned long)&vring_used_all[queue_idx],
+ };
+ struct vhost_vring_file file = {
+ .index = queue_idx,
+ .fd = tap_fd,
+ };
+ unsigned int i;
+ int rc;
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_ADDR, &addr);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_ADDR ioctl failed, queue %d",
+ queue_idx);
+
+ if (queue_idx == 0) {
+ for (i = 0; i < VHOST_NDESCS; ++i) {
+ vring_desc[0][i].addr = (uintptr_t)pkt_buf +
+ i * VHOST_DESC_BYTES;
+ vring_desc[0][i].len = VHOST_DESC_BYTES;
+ vring_desc[0][i].flags = VRING_DESC_F_WRITE;
+ }
+
+ for (i = 0; i < VHOST_NDESCS; ++i)
+ vring_avail_all[0].avail.ring[i] = htole16(i);
+
+ vhost_rx_descriptor_handoff(c);
+ }
+
+ if (queue_idx == 1) {
+ for (i = 0; i < (VHOST_NDESCS - 1); ++i)
+ vring_desc[1][i].next = i + 1;
+ }
+
+ rc = ioctl(vhost_fd, VHOST_NET_SET_BACKEND, &file);
+ if (rc < 0)
+ die_perror("VHOST_NET_SET_BACKEND ioctl failed, queue %d",
+ queue_idx);
+}
+
+/**
+ * vhost_rx_descriptor_handoff() - Announce freed from-guest descriptors
+ * @c: Execution context
+ *
+ * Bumps avail.idx by the number of descriptors accumulated in
+ * vhost_vq_state[0].num_free (from prior consume_one_rx_descriptor() calls),
+ * then resets the counter to zero. The kernel will see the new
+ * avail.idx and consume the freshly-available descriptors.
+ */
+void vhost_rx_descriptor_handoff(struct ctx *c)
+{
+ smp_wmb();
+
+ if (!vhost_vq_state[0].num_free)
+ return;
+
+ vring_avail_all[0].avail.idx += vhost_vq_state[0].num_free;
+ vhost_vq_state[0].num_free = 0;
+ vhost_kick(&vring_used_all[0].used, c->vhost.vq[0].kick_fd);
+}
+
+/**
+ * vhost_kick() - Notify the kernel that new descriptors are available
+ * @used: Used ring of the queue we're announcing on, checked to see
+ * whether the kernel's virtio thread is already reading
+ * descriptors and doesn't want to be notified
+ * @kick_fd: Kick eventfd of that same queue
+ */
+void vhost_kick(struct vring_used *used, int kick_fd)
+{
+ /* Ensure that the read of used->flags doesn't get reordered to be
+ * above the avail.idx update
+ */
+ smp_mb();
+
+ if (!(used->flags & VRING_USED_F_NO_NOTIFY))
+ eventfd_write(kick_fd, 1);
+}
diff --git a/vhost.h b/vhost.h
new file mode 100644
index 0000000..aaa937d
--- /dev/null
+++ b/vhost.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright Red Hat
+ * Author: Ammar Yasser <aerosound161@gmail.com>
+ *
+ * vhost.h - vhost-net (vhost-kernel) acceleration for pasta mode
+ */
+
+#ifndef VHOST_H
+#define VHOST_H
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <linux/vhost.h>
+#include <linux/vhost_types.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_net.h>
+#include <linux/virtio_ring.h>
+
+#include "passt.h"
+
+/**
+ * struct vq_state - Per-virtqueue local descriptor tracking
+ * @num_free: Number of descriptors ready to be announced to the
+ * kernel via vhost_rx_descriptor_handoff()
+ * @last_used_idx: Number of used-ring entries consumed so far;
+ * lagging read cursor vs. vring_used->idx (the
+ * kernel's write cursor)
+ */
+extern struct vq_state {
+ uint16_t num_free;
+ uint16_t last_used_idx;
+ uint16_t next_free;
+} vhost_vq_state[2];
+
+
+extern struct vring_desc vring_desc[2][VHOST_NDESCS];
+union vring_avail_u {
+ struct vring_avail avail;
+ char buf[offsetof(struct vring_avail, ring[VHOST_NDESCS])];
+};
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+extern union vring_avail_u vring_avail_all[2];
+#pragma GCC diagnostic pop
+
+union vring_used_u {
+ struct vring_used used;
+ char buf[offsetof(struct vring_used, ring[VHOST_NDESCS])];
+};
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+extern union vring_used_u vring_used_all[2];
+#pragma GCC diagnostic pop
+
+#define N_VHOST_REGIONS 2
+union vhost_memory_u {
+ struct vhost_memory mem;
+ char buf[offsetof(struct vhost_memory, regions[N_VHOST_REGIONS])];
+};
+extern union vhost_memory_u vhost_memory;
+
+#define VHOST_MEMORY_REGION_PTR(addr, size) \
+ (struct vhost_memory_region) { \
+ .guest_phys_addr = (uintptr_t)addr, \
+ .memory_size = size, \
+ .userspace_addr = (uintptr_t)addr, \
+ }
+#define VHOST_MEMORY_REGION(buf) VHOST_MEMORY_REGION_PTR(&buf, sizeof(buf))
+
+void vhost_set_vring(struct ctx *c, int queue_idx, int tap_fd);
+int vhost_setup_memory_table(struct ctx *c);
+int vhost_setup_net(struct ctx *c);
+void vhost_setup_eventfds(struct ctx *c, int queue_idx);
+void vhost_rx_descriptor_handoff(struct ctx *c);
+void vhost_kick(struct vring_used *used, int kick_fd);
+
+#endif /* VHOST_H */
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 4/7] tap: Implement the pasta vhost-net from-guest path
2026-09-04 21:28 [PATCH 0/7] Add vhost-net kernel support to pasta aerosouund
` (2 preceding siblings ...)
2026-09-04 21:28 ` [PATCH 3/7] virtio: Add the pasta vhost-net interface and implementation aerosouund
@ 2026-09-04 21:28 ` aerosouund
2026-09-04 21:28 ` [PATCH 5/7] tap, tcp, udp: Prepare the to-guest path for vhost-net aerosouund
` (3 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: aerosouund @ 2026-09-04 21:28 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
Add tap_vhost_input(), called when the kernel signals on a call eventfd
that it has written guest frames into the from-guest queue. It consumes
descriptors with consume_one_rx_descriptor(), skips the virtio_net header
on each, builds an iov_tail from what's left and queues it for
processing, then tells the kernel the descriptors are free to reuse.
consume_one_rx_descriptor() pops one entry from the used ring, advances
our read cursor and the count of descriptors awaiting handoff, and
returns a pointer to that descriptor's data.
Bootstrap vhost in tap_sock_tun_init(). If setup fails we leave
c->vhost.fd at -1 and carry on unaccelerated.
With vhost-net running the kernel reads the tap fd itself, so don't add
that fd to our epoll set at all: tap_handler_pasta() would otherwise be
woken for data it must not read.
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
passt.c | 6 +++
tap.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
tap.h | 2 +
3 files changed, 119 insertions(+), 1 deletion(-)
diff --git a/passt.c b/passt.c
index 6002231..372b743 100644
--- a/passt.c
+++ b/passt.c
@@ -305,6 +305,12 @@ static void passt_worker(void *opaque, int nfds, struct epoll_event *events)
case EPOLL_TYPE_CONF:
conf_handler(c, eventmask);
break;
+ case EPOLL_TYPE_VHOST_CALL:
+ tap_vhost_input(c, ref, &now);
+ break;
+ case EPOLL_TYPE_VHOST_ERROR:
+ die("Error on vhost-kernel socket");
+ break;
default:
/* Can't happen */
assert(0);
diff --git a/tap.c b/tap.c
index dfa66c7..f84cb0a 100644
--- a/tap.c
+++ b/tap.c
@@ -38,6 +38,7 @@
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <netinet/if_ether.h>
+#include <sys/eventfd.h>
#include <linux/if_tun.h>
#include <linux/icmpv6.h>
@@ -61,6 +62,7 @@
#include "vhost_user.h"
#include "vu_common.h"
#include "epoll_ctl.h"
+#include "vhost.h"
/* Maximum allowed frame lengths (including L2 header) */
@@ -1446,7 +1448,11 @@ static void tap_start_connection(const struct ctx *c)
break;
}
- epoll_add(c->epollfd, EPOLLIN | EPOLLRDHUP, ref);
+ /* With vhost-net running, the kernel reads the tap fd for us and
+ * notifies us on the call eventfd, so we mustn't watch it ourselves
+ */
+ if (c->vhost.fd == -1)
+ epoll_add(c->epollfd, EPOLLIN | EPOLLRDHUP, ref);
if (!tap_is_ready(c))
return;
@@ -1514,6 +1520,89 @@ void tap_listen_handler(struct ctx *c, uint32_t events)
tap_start_connection(c);
}
+/**
+ * consume_one_rx_descriptor() - Consume one used from-guest descriptor
+ * @len: Set to the length of data written by the kernel
+ *
+ * Pops a single entry from the used ring, advancing our read cursor and the
+ * count of descriptors awaiting refill announcement.
+ *
+ * NOTE: This function assumes the kernel is going to post single descriptors
+ * always, No chains. If that changes, we would need to increment num_free
+ * as we advertise back to the kernel the free descriptors by the length of the chain.
+ *
+ * Return: pointer to the packet buffer, or NULL if no data is available
+ */
+static void *consume_one_rx_descriptor(size_t *len)
+{
+ struct vring_used *used = &vring_used_all[0].used;
+ uint16_t used_idx, last_used;
+ uint32_t i;
+
+ used_idx = le16toh(used->idx);
+
+ smp_rmb();
+
+ /* If the kernel's used index matches ours, we've consumed everything
+ * it has posted
+ */
+ if (used_idx == vhost_vq_state[0].last_used_idx) {
+ *len = 0;
+ return NULL;
+ }
+
+ last_used = vhost_vq_state[0].last_used_idx % VHOST_NDESCS;
+ i = le32toh(used->ring[last_used].id);
+ *len = le32toh(used->ring[last_used].len);
+
+ if (i != last_used)
+ die("vhost: id %u at used position %u", i, last_used);
+
+ /* The kernel has queued for us something we cannot receive */
+ if (*len > VHOST_DESC_BYTES)
+ die("vhost: id %u len %zu > %zu", i, *len,
+ (size_t)VHOST_DESC_BYTES);
+
+ vhost_vq_state[0].last_used_idx++;
+ vhost_vq_state[0].num_free++;
+
+ return pkt_buf + i * VHOST_DESC_BYTES;
+}
+
+/**
+ * tap_vhost_input() - Handle frames the kernel wrote to the from-guest queue
+ * @c: Execution context
+ * @ref: epoll reference
+ * @now: Current timestamp
+ */
+void tap_vhost_input(struct ctx *c, union epoll_ref ref,
+ const struct timespec *now)
+{
+ struct virtio_net_hdr_mrg_rxbuf *hdr;
+ struct iov_tail data;
+ size_t len;
+
+ eventfd_read(ref.fd, (eventfd_t[]){ 0 });
+
+ tap_flush_pools();
+
+ while ((hdr = consume_one_rx_descriptor(&len))) {
+ /* a valid receive is one where there is a virtio net header followed by atleast sizeof(struct ethhdr) bytes */
+ if (len < (sizeof(*hdr) + sizeof(struct ethhdr))) {
+ warn("vhost: invalid len %lu", len);
+ continue;
+ }
+
+ /* Skip over the vnet header, add the packet without it */
+ data = IOV_TAIL_FROM_BUF((void *)(hdr + 1),
+ len - sizeof(*hdr), 0);
+ tap_add_packet(c, &data, now);
+ }
+
+ tap_handler(c, now);
+ vhost_rx_descriptor_handoff(c);
+}
+
/**
* tap_ns_tun() - Get tuntap fd in namespace
* @c: Execution context
@@ -1555,12 +1644,33 @@ static int tap_ns_tun(void *arg)
*/
static void tap_sock_tun_init(struct ctx *c)
{
+ int i;
+
if (!c->splice_only) {
NS_CALL(tap_ns_tun, c);
if (c->fd_tap == -1)
die("Failed to set up tap device in namespace");
}
+ /* Nothing else here runs if this fails: c->vhost.fd stays -1 and we
+ * carry on reading the tap fd ourselves, unless acceleration was
+ * explicitly asked for
+ */
+ if (c->vhost.mode != VHOST_MODE_OFF && vhost_setup_net(c) < 0 &&
+ c->vhost.mode == VHOST_MODE_ON)
+ die("vhost-net is not available");
+
+ if (c->vhost.fd != -1) {
+ for (i = 0; i < ARRAY_SIZE(c->vhost.vq); i++)
+ vhost_setup_eventfds(c, i);
+
+ if (vhost_setup_memory_table(c) < 0)
+ die_perror("VHOST_SET_MEM_TABLE ioctl failed");
+
+ for (i = 0; i < ARRAY_SIZE(c->vhost.vq); i++)
+ vhost_set_vring(c, i, c->fd_tap);
+ }
+
pasta_ns_conf(c);
if (!c->splice_only)
diff --git a/tap.h b/tap.h
index ce1efa8..b0d70f8 100644
--- a/tap.h
+++ b/tap.h
@@ -48,6 +48,8 @@ static inline void tap_hdr_update(struct tap_hdr *thdr, size_t l2len)
thdr->vnet_len = htonl(l2len);
}
+void tap_vhost_input(struct ctx *c, union epoll_ref ref,
+ const struct timespec *now);
unsigned long tap_l2_max_len(const struct ctx *c);
void *tap_push_l2h(const struct ctx *c, void *buf,
const void *src_mac, uint16_t proto);
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 5/7] tap, tcp, udp: Prepare the to-guest path for vhost-net
2026-09-04 21:28 [PATCH 0/7] Add vhost-net kernel support to pasta aerosouund
` (3 preceding siblings ...)
2026-09-04 21:28 ` [PATCH 4/7] tap: Implement the pasta vhost-net from-guest path aerosouund
@ 2026-09-04 21:28 ` aerosouund
2026-09-04 21:28 ` [PATCH 6/7] tap: Implement the pasta vhost-net to-guest path aerosouund
` (2 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: aerosouund @ 2026-09-04 21:28 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
Frames sent through vhost-net carry a virtio-net header where pasta's
tap header would otherwise go, this necessitates the following two
changes:
- udp_tap_prepare() has been modified to take a context parameter so it
can check if vhost is set up. Only if it isn't should tap_hdr_update
be called because this means that the active union variant is vnet_len
no the virtio net header
- tap_l2_offset() a new helper, has been introduced which tells
pcap_multiple() how many bytes precede the L2 frame, which now varies
with vhost as well as with the mode
tap_send_single() calls tap_send_frames_passt() and
tap_send_frames_pasta() directly rather than going through
tap_send_frames(). Its callers - ARP, TCP resets, and DHCP, DHCPv6, NDP
and ICMP via tap_udp*_send()/tap_icmp*_send() - build their frame in a
buffer on the stack, not in the buffers registered with vhost-net. The
kernel can only read from registered memory, so these protocols can
never take the vhost path and shouldn't be tested for it on every frame.
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
tap.c | 112 ++++++++++++++++++++++++++++++++++--------------------
tcp_buf.c | 12 ++++--
udp.c | 27 +++++++++----
3 files changed, 99 insertions(+), 52 deletions(-)
diff --git a/tap.c b/tap.c
index f84cb0a..c730535 100644
--- a/tap.c
+++ b/tap.c
@@ -123,45 +123,6 @@ unsigned long tap_l2_max_len(const struct ctx *c)
return 0; /* Unreachable, for cppcheck's sake */
}
-/**
- * tap_send_single() - Send a single frame
- * @c: Execution context
- * @data: Packet buffer
- * @l2len: Total L2 packet length
- */
-void tap_send_single(const struct ctx *c, const void *data, size_t l2len)
-{
- uint8_t padded[ETH_ZLEN] = { 0 };
- struct iovec iov[2];
- size_t iovcnt = 0;
- uint32_t vnet_len;
-
- if (l2len < ETH_ZLEN) {
- memcpy(padded, data, l2len);
- data = padded;
- l2len = ETH_ZLEN;
- }
-
- vnet_len = htonl(l2len);
-
- switch (c->mode) {
- case MODE_PASST:
- iov[iovcnt] = IOV_OF_LVALUE(vnet_len);
- iovcnt++;
- /* fall through */
- case MODE_PASTA:
- iov[iovcnt].iov_base = (void *)data;
- iov[iovcnt].iov_len = l2len;
- iovcnt++;
-
- tap_send_frames(c, iov, iovcnt, 1);
- break;
- case MODE_VU:
- vu_send_single(c, data, l2len);
- break;
- }
-}
-
/**
* tap_push_l2h() - Build an L2 header for an inbound packet
* @c: Execution context
@@ -497,6 +458,76 @@ static size_t tap_send_frames_passt(const struct ctx *c,
return i / bufs_per_frame;
}
+/**
+ * tap_l2_offset() - Backend specific header size preceding each L2 frame
+ * @c: Execution context
+ *
+ * Return: offset of the L2 frame within each frame's first buffer
+ */
+static size_t tap_l2_offset(const struct ctx *c)
+{
+ if (c->mode == MODE_PASST)
+ return sizeof(uint32_t); /* vnet_len */
+
+ if (c->vhost.fd != -1)
+ return VNET_HLEN;
+
+ return 0;
+}
+
+/**
+ * tap_send_single() - Send a single frame
+ * @c: Execution context
+ * @data: Packet buffer
+ * @l2len: Total L2 packet length
+ */
+void tap_send_single(const struct ctx *c, const void *data, size_t l2len)
+{
+ uint8_t padded[ETH_ZLEN] = { 0 };
+ struct iovec iov[2];
+ uint32_t vnet_len;
+ size_t iovcnt = 0;
+ size_t m = 0;
+
+ if (l2len < ETH_ZLEN) {
+ memcpy(padded, data, l2len);
+ data = padded;
+ l2len = ETH_ZLEN;
+ }
+
+ vnet_len = htonl(l2len);
+
+ switch (c->mode) {
+ case MODE_PASST:
+ /* create an iov for the length */
+ iov[iovcnt] = IOV_OF_LVALUE(vnet_len);
+ iovcnt++;
+ /* create the data iov */
+ iov[iovcnt].iov_base = (void *)data;
+ iov[iovcnt].iov_len = l2len;
+ iovcnt++;
+
+ m = tap_send_frames_passt(c, iov, iovcnt, 1);
+ break;
+ case MODE_PASTA:
+ /* don't create a length iov in the case of pasta */
+ iov[iovcnt].iov_base = (void *)data;
+ iov[iovcnt].iov_len = l2len;
+ iovcnt++;
+
+ m = tap_send_frames_pasta(c, iov, iovcnt, 1);
+ break;
+ case MODE_VU:
+ vu_send_single(c, data, l2len);
+ return;
+ }
+
+ if (!m)
+ debug("tap: failed to send a single frame");
+
+ pcap_multiple(iov, iovcnt, m, tap_l2_offset(c));
+}
+
/**
* tap_send_frames() - Send out multiple prepared frames
* @c: Execution context
@@ -537,8 +568,7 @@ size_t tap_send_frames(const struct ctx *c, const struct iovec *iov,
debug("tap: failed to send %zu frames of %zu",
nframes - m, nframes);
- pcap_multiple(iov, bufs_per_frame, m,
- c->mode == MODE_PASST ? sizeof(uint32_t) : 0);
+ pcap_multiple(iov, bufs_per_frame, m, tap_l2_offset(c));
return m;
}
diff --git a/tcp_buf.c b/tcp_buf.c
index 1f28728..9cc541a 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -21,6 +21,7 @@
#include <netinet/ip.h>
#include <netinet/tcp.h>
+#include <linux/virtio_net.h>
#include "util.h"
#include "ip.h"
@@ -42,7 +43,7 @@
/* Ethernet header for IPv4 and IPv6 frames */
static struct ethhdr tcp_eth_hdr[TCP_FRAMES_MEM];
-static struct tap_hdr tcp_payload_tap_hdr[TCP_FRAMES_MEM];
+static struct virtio_net_hdr_mrg_rxbuf tcp_payload_tap_hdr[TCP_FRAMES_MEM];
/* IP headers for IPv4 and IPv6 */
static struct iphdr tcp4_payload_ip[TCP_FRAMES_MEM];
@@ -107,7 +108,7 @@ void tcp_sock_iov_init(const struct ctx *c)
for (i = 0; i < TCP_FRAMES_MEM; i++) {
struct iovec *iov = tcp_l2_iov[i];
- iov[TCP_IOV_TAP] = tap_hdr_iov(c, &tcp_payload_tap_hdr[i]);
+ iov[TCP_IOV_TAP] = tap_hdr_iov(c, (struct tap_hdr *)&tcp_payload_tap_hdr[i]);
iov[TCP_IOV_ETH].iov_len = sizeof(struct ethhdr);
iov[TCP_IOV_PAYLOAD].iov_base = &tcp_payload[i];
iov[TCP_IOV_ETH_PAD].iov_base = eth_pad;
@@ -210,7 +211,12 @@ static void tcp_l2_buf_fill_headers(const struct ctx *c,
l2len = tcp_fill_headers(c, conn, eh, ip4h, ip6h, th, &tail,
iov_tail_size(&tail), csum_flags, seq);
- tap_hdr_update(taph, l2len);
+
+ /* With vhost-net this buffer holds a virtio-net header, which a tap
+ * one must not be written over
+ */
+ if (c->vhost.fd == -1)
+ tap_hdr_update(taph, l2len);
}
/**
diff --git a/udp.c b/udp.c
index 5e3bd86..e360ccf 100644
--- a/udp.c
+++ b/udp.c
@@ -103,6 +103,7 @@
#include <time.h>
#include <arpa/inet.h>
#include <linux/errqueue.h>
+#include <linux/virtio_net.h>
#include "checksum.h"
#include "util.h"
@@ -147,12 +148,16 @@ static struct ethhdr udp_eth_hdr[UDP_MAX_FRAMES];
* struct udp_meta_t - Pre-cooked headers for UDP packets
* @ip6h: Pre-filled IPv6 header (except for payload_len and addresses)
* @ip4h: Pre-filled IPv4 header (except for tot_len and saddr)
- * @taph: Tap backend specific header
+ * @vnet_hdr: virtio-net header, used when sending through vhost-net
+ * @taph: Tap backend specific header, used otherwise
*/
static struct udp_meta_t {
struct ipv6hdr ip6h;
struct iphdr ip4h;
- struct tap_hdr taph;
+ union {
+ struct virtio_net_hdr_mrg_rxbuf vnet_hdr;
+ struct tap_hdr taph;
+ };
}
#ifdef __AVX2__
__attribute__ ((aligned(32)))
@@ -354,13 +359,15 @@ static void udp_tap_pad(struct iovec *iov)
/**
* udp_tap_prepare() - Convert one datagram into a tap frame
+ * @c: Execution context
* @mmh: Receiving mmsghdr array
* @idx: Index of the datagram to prepare
* @tap_omac: MAC address of remote endpoint as seen from the guest
* @toside: Flowside for destination side
* @no_udp_csum: Do not set UDP checksum
*/
-static void udp_tap_prepare(const struct mmsghdr *mmh,
+static void udp_tap_prepare(const struct ctx *c,
+ const struct mmsghdr *mmh,
unsigned int idx,
const uint8_t *tap_omac,
const struct flowside *toside,
@@ -382,8 +389,10 @@ static void udp_tap_prepare(const struct mmsghdr *mmh,
udp_update_hdr6(&bm->ip6h, uh, &payload, toside,
mmh[idx].msg_len, no_udp_csum);
- l2len = MAX(l4len + sizeof(bm->ip6h) + ETH_HLEN, ETH_ZLEN);
- tap_hdr_update(&bm->taph, l2len);
+ if (c->mode == MODE_PASST) {
+ l2len = MAX(l4len + sizeof(bm->ip6h) + ETH_HLEN, ETH_ZLEN);
+ tap_hdr_update(&bm->taph, l2len);
+ }
eh->h_proto = htons_constant(ETH_P_IPV6);
(*tap_iov)[UDP_IOV_IP] = IOV_OF_LVALUE(bm->ip6h);
@@ -391,8 +400,10 @@ static void udp_tap_prepare(const struct mmsghdr *mmh,
udp_update_hdr4(&bm->ip4h, uh, &payload, toside,
mmh[idx].msg_len, no_udp_csum);
- l2len = MAX(l4len + sizeof(bm->ip4h) + ETH_HLEN, ETH_ZLEN);
- tap_hdr_update(&bm->taph, l2len);
+ if (c->mode == MODE_PASST) {
+ l2len = MAX(l4len + sizeof(bm->ip4h) + ETH_HLEN, ETH_ZLEN);
+ tap_hdr_update(&bm->taph, l2len);
+ }
eh->h_proto = htons_constant(ETH_P_IP);
(*tap_iov)[UDP_IOV_IP] = IOV_OF_LVALUE(bm->ip4h);
@@ -861,7 +872,7 @@ static void udp_buf_sock_to_tap(const struct ctx *c, int s, int n,
fwd_neigh_mac_get(c, &toside->oaddr, omac);
for (i = 0; i < n; i++)
- udp_tap_prepare(udp_mh_recv, i, omac, toside, false);
+ udp_tap_prepare(c, udp_mh_recv, i, omac, toside, false);
tap_send_frames(c, &udp_l2_iov[0][0], UDP_NUM_IOVS, n);
}
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 6/7] tap: Implement the pasta vhost-net to-guest path
2026-09-04 21:28 [PATCH 0/7] Add vhost-net kernel support to pasta aerosouund
` (4 preceding siblings ...)
2026-09-04 21:28 ` [PATCH 5/7] tap, tcp, udp: Prepare the to-guest path for vhost-net aerosouund
@ 2026-09-04 21:28 ` aerosouund
2026-09-04 21:28 ` [PATCH 7/7] tap/tcp: Replace tcp_payload_used with a ring buffer style index aerosouund
2026-09-05 16:25 ` [PATCH 2/7] conf: Add context fields, epoll types and --vhost-kernel flag to pasta aerosouund
7 siblings, 0 replies; 13+ messages in thread
From: aerosouund @ 2026-09-04 21:28 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
Add a vhost argument to tap_send_frames_pasta(). Callers pass it to say
whether this send should go through vhost-net, and when it does sending
goes through tap_send_frames_vhost(), which takes a descriptor from the
queue shared with the kernel for each iov and points it at that iov's
base address, chaining the bufs_per_frame descriptors of a frame
together so the guest receives it in one go.
tx_reap() reclaims the descriptors the kernel has finished with, walking
each used chain to its end so that every descriptor in it is counted as
free again.
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
tap.c | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 142 insertions(+), 3 deletions(-)
diff --git a/tap.c b/tap.c
index c730535..0f51e62 100644
--- a/tap.c
+++ b/tap.c
@@ -359,12 +359,146 @@ void tap_icmp6_send(const struct ctx *c,
tap_send_single(c, buf, l4len + ((char *)icmp6h - buf));
}
+/**
+ * tx_reap() - Reclaim the descriptors the kernel has already processed
+ */
+static void tx_reap(void)
+{
+ struct vring_used *used = &vring_used_all[1].used;
+ uint16_t used_idx = le16toh(used->idx);
+
+ smp_rmb();
+
+ /* increment last_used_idx until it reaches the kernel's used index */
+ while (vhost_vq_state[1].last_used_idx != used_idx) {
+ uint16_t last_used, desc_id;
+
+ last_used = vhost_vq_state[1].last_used_idx % VHOST_NDESCS;
+ desc_id = le32toh(used->ring[last_used].id);
+
+ for (;;) {
+ /* keep going until we find a descriptor without the
+ * next flag
+ */
+ vhost_vq_state[1].num_free++;
+ if (!(le16toh(vring_desc[1][desc_id].flags) &
+ VRING_DESC_F_NEXT))
+ break;
+
+ /* this descriptor wasn't the last, set desc_id to the
+ * next one and keep going
+ */
+ desc_id = le16toh(vring_desc[1][desc_id].next);
+ }
+
+ vhost_vq_state[1].last_used_idx++;
+ }
+}
+
+/**
+ * tap_send_frames_vhost() - Send multiple frames to the pasta tap
+ * @c: Execution context
+ * @iov: Array of buffers
+ * @bufs_per_frame: Number of buffers (iovec entries) per frame
+ * @nframes: Number of frames to send
+ *
+ * @iov must have total length @bufs_per_frame * @nframes, with each set of
+ * @bufs_per_frame contiguous buffers representing a single frame.
+ *
+ * Return: number of frames successfully sent
+ */
+static size_t tap_send_frames_vhost(const struct ctx *c,
+ const struct iovec *iov,
+ size_t bufs_per_frame, size_t nframes)
+{
+ struct vring_avail *avail = &vring_avail_all[1].avail;
+ size_t processed_frames = 0;
+ size_t i;
+
+ /* reclaim descriptors if we don't have enough available buffers to
+ * perform this send
+ */
+ if (vhost_vq_state[1].num_free < bufs_per_frame * nframes)
+ tx_reap();
+
+ for (i = 0; i < nframes; i++) {
+ uint16_t head;
+ size_t j;
+
+ /* it's likely that tx_reap returned to us less than
+ * bufs_per_frame descs
+ */
+ if (vhost_vq_state[1].num_free < bufs_per_frame)
+ break;
+
+ /* set the index of the avail ring in the tx queue to be our
+ * last_used_idx
+ */
+ head = vhost_vq_state[1].next_free % VHOST_NDESCS;
+ avail->ring[(avail->idx + i) % VHOST_NDESCS] = htole16(head);
+
+ /* we will be consuming bufs_per_frame descriptors for every
+ * frame, decrement the local num_free
+ */
+ vhost_vq_state[1].num_free -= bufs_per_frame;
+
+ for (j = 0; j < bufs_per_frame; ++j) {
+ uint16_t next = vhost_vq_state[1].next_free %
+ VHOST_NDESCS;
+ /* get the last_used_idx descriptor */
+ struct vring_desc *desc = &vring_desc[1][next];
+ const struct iovec *iov_i;
+
+ /* the iov variable contains the iovecs for all frames
+ * we will send. access a single fragment of a frame
+ * (each fragment is one of tcp_iov_parts) denoted by
+ * iov at index i (index of the frame being processed)
+ * * bufs_per_frame plus j (the index of the fragment
+ * being processed)
+ */
+ iov_i = &iov[i * bufs_per_frame + j];
+
+ /* set that descriptor's address to the base of the iov
+ * and set the VRING_DESC_F_NEXT flag on the descriptor
+ * if its not the last frame fragment, so that the
+ * guest would recieve the entire frame in one go.
+ */
+ desc->addr = (uint64_t)iov_i->iov_base;
+ desc->len = iov_i->iov_len;
+ desc->flags = (j == bufs_per_frame - 1) ?
+ 0 : htole16(VRING_DESC_F_NEXT);
+
+ vhost_vq_state[1].next_free++;
+ }
+
+ processed_frames++;
+ }
+
+ /* we didn't process any frames, no need to notify the kernel */
+ if (!processed_frames)
+ return 0;
+
+ smp_wmb();
+
+ /* we will have used nframes descriptor chains */
+ avail->idx = htole16(le16toh(avail->idx) + processed_frames);
+
+ vhost_kick(&vring_used_all[1].used, c->vhost.vq[1].kick_fd);
+
+ /* wait until the kernel finishes processing this send */
+ while (avail->idx != vring_used_all[1].used.idx)
+ ;
+
+ return processed_frames;
+}
+
/**
* tap_send_frames_pasta() - Send multiple frames to the pasta tap
* @c: Execution context
* @iov: Array of buffers
* @bufs_per_frame: Number of buffers (iovec entries) per frame
* @nframes: Number of frames to send
+ * @vhost: Send through vhost-net rather than writing to the tap
*
* @iov must have total length @bufs_per_frame * @nframes, with each set of
* @bufs_per_frame contiguous buffers representing a single frame.
@@ -375,11 +509,15 @@ void tap_icmp6_send(const struct ctx *c,
*/
static size_t tap_send_frames_pasta(const struct ctx *c,
const struct iovec *iov,
- size_t bufs_per_frame, size_t nframes)
+ size_t bufs_per_frame, size_t nframes,
+ bool vhost)
{
size_t nbufs = bufs_per_frame * nframes;
size_t i;
+ if (vhost)
+ return tap_send_frames_vhost(c, iov, bufs_per_frame, nframes);
+
for (i = 0; i < nbufs; i += bufs_per_frame) {
ssize_t rc = writev(c->fd_tap, iov + i, bufs_per_frame);
size_t framelen = iov_size(iov + i, bufs_per_frame);
@@ -515,7 +653,7 @@ void tap_send_single(const struct ctx *c, const void *data, size_t l2len)
iov[iovcnt].iov_len = l2len;
iovcnt++;
- m = tap_send_frames_pasta(c, iov, iovcnt, 1);
+ m = tap_send_frames_pasta(c, iov, iovcnt, 1, false);
break;
case MODE_VU:
vu_send_single(c, data, l2len);
@@ -553,7 +691,8 @@ size_t tap_send_frames(const struct ctx *c, const struct iovec *iov,
switch (c->mode) {
case MODE_PASTA:
- m = tap_send_frames_pasta(c, iov, bufs_per_frame, nframes);
+ m = tap_send_frames_pasta(c, iov, bufs_per_frame, nframes,
+ c->vhost.fd != -1);
break;
case MODE_PASST:
m = tap_send_frames_passt(c, iov, bufs_per_frame, nframes);
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 7/7] tap/tcp: Replace tcp_payload_used with a ring buffer style index
2026-09-04 21:28 [PATCH 0/7] Add vhost-net kernel support to pasta aerosouund
` (5 preceding siblings ...)
2026-09-04 21:28 ` [PATCH 6/7] tap: Implement the pasta vhost-net to-guest path aerosouund
@ 2026-09-04 21:28 ` aerosouund
2026-09-05 16:25 ` [PATCH 2/7] conf: Add context fields, epoll types and --vhost-kernel flag to pasta aerosouund
7 siblings, 0 replies; 13+ messages in thread
From: aerosouund @ 2026-09-04 21:28 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
Because pasta resets tcp_payload_used to 0 after every tcp flush this
means that pasta will very quickly override data in the tcp buffers
after sending. This is not a problem when using writev because the data
will have been copied elsewhere by the kernel, but this is a problem for
vhost where the kernel thread may not have finished processing this
data. This is why a synchronous send was required in vhost, which
obviously affects performance.
Replace tcp_payload_used with a struct tcp_payload_idx holding the last
index we wrote to and a counter that gets updated when we flush packets,
from where we last flushed up to that index. Both are grow only and
access gets modded by TCP_FRAMES_MEM.
Rewrite functions in tcp_buf.c to use that index and handle the
wraparound case (sending a batch that started near the end of the
buffers and continued to be at the start) in tcp_payload_flush().
With the buffers no longer reused from under the kernel, drop the
synchronous wait from tap_send_frames_vhost() and reclaim descriptors
until there are enough for the send instead.
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
tap.c | 9 ++---
tcp_buf.c | 115 ++++++++++++++++++++++++++++++++++++++++--------------
2 files changed, 88 insertions(+), 36 deletions(-)
diff --git a/tap.c b/tap.c
index 0f51e62..670605a 100644
--- a/tap.c
+++ b/tap.c
@@ -418,7 +418,8 @@ static size_t tap_send_frames_vhost(const struct ctx *c,
/* reclaim descriptors if we don't have enough available buffers to
* perform this send
*/
- if (vhost_vq_state[1].num_free < bufs_per_frame * nframes)
+ while (vhost_vq_state[1].num_free < bufs_per_frame * nframes &&
+ vhost_vq_state[1].last_used_idx != le16toh(avail->idx))
tx_reap();
for (i = 0; i < nframes; i++) {
@@ -485,10 +486,6 @@ static size_t tap_send_frames_vhost(const struct ctx *c,
vhost_kick(&vring_used_all[1].used, c->vhost.vq[1].kick_fd);
- /* wait until the kernel finishes processing this send */
- while (avail->idx != vring_used_all[1].used.idx)
- ;
-
return processed_frames;
}
@@ -516,7 +513,7 @@ static size_t tap_send_frames_pasta(const struct ctx *c,
size_t i;
if (vhost)
- return tap_send_frames_vhost(c, iov, bufs_per_frame, nframes);
+ return tap_send_frames_vhost(c, iov, bufs_per_frame, nframes);
for (i = 0; i < nbufs; i += bufs_per_frame) {
ssize_t rc = writev(c->fd_tap, iov + i, bufs_per_frame);
diff --git a/tcp_buf.c b/tcp_buf.c
index 9cc541a..1b57ebd 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -57,7 +57,22 @@ static_assert(MSS6 <= sizeof(tcp_payload[0].data), "MSS6 is greater than 65516")
/* References tracking the owner connection of frames in the tap outqueue */
static struct tcp_tap_conn *tcp_frame_conns[TCP_FRAMES_MEM];
-static unsigned int tcp_payload_used;
+
+/**
+ * struct tcp_payload_idx - Grow-only cursors into the TCP frame buffers
+ * @tcp_buf_idx: The last index we wrote data to in the tcp buffers
+ * @processed: Last index we pushed to the underlying connection
+ */
+static struct tcp_payload_idx {
+ unsigned int tcp_buf_idx;
+ unsigned int processed;
+} tcp_idx;
+
+/* latest index we wrote to into the tcp buffers */
+#define TCP_CURR_IDX (tcp_idx.tcp_buf_idx % TCP_FRAMES_MEM)
+
+/* current amount of frames queued in tcp buffers but not sent */
+#define TCP_FRAME_COUNT (tcp_idx.tcp_buf_idx - tcp_idx.processed)
/* recvmsg()/sendmsg() data for tap */
static struct iovec iov_sock [TCP_FRAMES_MEM + DISCARD_IOV_NUM];
@@ -152,15 +167,46 @@ static void tcp_revert_seq(const struct ctx *c, struct tcp_tap_conn **conns,
*/
void tcp_payload_flush(const struct ctx *c, const struct timespec *now)
{
- size_t m;
+ unsigned int total = TCP_FRAME_COUNT;
+ unsigned int start = tcp_idx.processed % TCP_FRAMES_MEM;
+ unsigned int first_batch_size, sent;
- m = tap_send_frames(c, &tcp_l2_iov[0][0], TCP_NUM_IOVS,
- tcp_payload_used);
- if (m != tcp_payload_used) {
- tcp_revert_seq(c, &tcp_frame_conns[m], &tcp_l2_iov[m],
- tcp_payload_used - m, now);
+ if (!total)
+ return;
+
+ /* What is smaller ? all we what we want to send ? or 128 - the index
+ * indicating the start of where we wrote this batch ?
+ */
+ first_batch_size = MIN(total, TCP_FRAMES_MEM - start);
+
+ sent = tap_send_frames(c, &tcp_l2_iov[start][0], TCP_NUM_IOVS,
+ first_batch_size);
+ if (sent < first_batch_size) {
+ tcp_revert_seq(c, &tcp_frame_conns[start + sent],
+ &tcp_l2_iov[start + sent],
+ first_batch_size - sent, now);
+ goto out;
}
- tcp_payload_used = 0;
+
+ /* There was more data to send than from tcp_idx.processed up to 128.
+ * the rest of the batch is going to be at index 0 up total -
+ * first_batch_size.
+ */
+ if (total > first_batch_size) {
+ unsigned int second_batch_size = total - first_batch_size;
+ size_t m2;
+
+ m2 = tap_send_frames(c, &tcp_l2_iov[0][0], TCP_NUM_IOVS,
+ second_batch_size);
+ sent += m2;
+
+ if (m2 < second_batch_size)
+ tcp_revert_seq(c, &tcp_frame_conns[m2], &tcp_l2_iov[m2],
+ second_batch_size - m2, now);
+ }
+
+out:
+ tcp_idx.processed += sent;
}
/**
@@ -238,13 +284,13 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
uint32_t seq;
int ret;
- iov = tcp_l2_iov[tcp_payload_used];
+ iov = tcp_l2_iov[TCP_CURR_IDX];
if (CONN_V4(conn))
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[TCP_CURR_IDX]);
else
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[TCP_CURR_IDX]);
- iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp_eth_hdr[tcp_payload_used]);
+ iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp_eth_hdr[TCP_CURR_IDX]);
payload = iov[TCP_IOV_PAYLOAD].iov_base;
seq = conn->seq_to_tap;
ret = tcp_prepare_flags(c, conn, flags, &payload->th,
@@ -253,7 +299,8 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
if (ret <= 0)
return ret;
- tcp_frame_conns[tcp_payload_used++] = conn;
+ tcp_idx.tcp_buf_idx++;
+ tcp_frame_conns[TCP_CURR_IDX] = conn;
l4len = optlen + sizeof(struct tcphdr);
iov[TCP_IOV_PAYLOAD].iov_len = l4len;
@@ -265,8 +312,9 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
tcp_l2_buf_pad(iov);
if (flags & DUP_ACK) {
- struct iovec *dup_iov = tcp_l2_iov[tcp_payload_used];
- tcp_frame_conns[tcp_payload_used++] = conn;
+ struct iovec *dup_iov = tcp_l2_iov[TCP_CURR_IDX];
+ tcp_frame_conns[TCP_CURR_IDX] = conn;
+ tcp_idx.tcp_buf_idx++;
memcpy(dup_iov[TCP_IOV_TAP].iov_base, iov[TCP_IOV_TAP].iov_base,
iov[TCP_IOV_TAP].iov_len);
@@ -278,7 +326,7 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
dup_iov[TCP_IOV_ETH_PAD].iov_len = iov[TCP_IOV_ETH_PAD].iov_len;
}
- if (tcp_payload_used > TCP_FRAMES_MEM - 2)
+ if (TCP_FRAME_COUNT > TCP_FRAMES_MEM - 2)
tcp_payload_flush(c, now);
return 0;
@@ -303,21 +351,29 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn,
struct iovec *iov;
conn->seq_to_tap = seq + dlen;
- tcp_frame_conns[tcp_payload_used] = conn;
- iov = tcp_l2_iov[tcp_payload_used];
+ tcp_frame_conns[TCP_CURR_IDX] = conn;
+ iov = tcp_l2_iov[TCP_CURR_IDX];
if (CONN_V4(conn)) {
if (no_csum) {
- struct iovec *iov_prev = tcp_l2_iov[tcp_payload_used - 1];
+ /* TCP_CURR_IDX may be zero if the underlying
+ * tcp_idx.tcp_buf_idx is a multiple of 128, minus one
+ * will yield an invalid index. The previous index to 0
+ * is 127.
+ */
+ unsigned int prev_idx = (TCP_CURR_IDX +
+ TCP_FRAMES_MEM - 1) %
+ TCP_FRAMES_MEM;
+ struct iovec *iov_prev = tcp_l2_iov[prev_idx];
const struct iphdr *iph = iov_prev[TCP_IOV_IP].iov_base;
/* overwrite IP4_CSUM flag as we set the checksum */
check = iph->check;
}
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[TCP_CURR_IDX]);
} else if (CONN_V6(conn)) {
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[TCP_CURR_IDX]);
}
- iov[TCP_IOV_ETH].iov_base = &tcp_eth_hdr[tcp_payload_used];
+ iov[TCP_IOV_ETH].iov_base = &tcp_eth_hdr[TCP_CURR_IDX];
payload = iov[TCP_IOV_PAYLOAD].iov_base;
payload->th.th_off = sizeof(struct tcphdr) / 4;
payload->th.th_x2 = 0;
@@ -329,7 +385,8 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn,
tcp_l2_buf_pad(iov);
- if (++tcp_payload_used > TCP_FRAMES_MEM - 1)
+ tcp_idx.tcp_buf_idx++;
+ if (TCP_FRAME_COUNT > TCP_FRAMES_MEM - 1)
tcp_payload_flush(c, now);
}
@@ -369,15 +426,13 @@ int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn,
return -1;
}
- if (tcp_payload_used + fill_bufs > TCP_FRAMES_MEM) {
+ if (TCP_FRAME_COUNT + (unsigned int)fill_bufs > TCP_FRAMES_MEM)
tcp_payload_flush(c, now);
- /* Silence Coverity CWE-125 false positive */
- tcp_payload_used = 0;
- }
-
for (i = 0, iov = iov_sock + DISCARD_IOV_NUM; i < fill_bufs; i++, iov++) {
- iov->iov_base = &tcp_payload[tcp_payload_used + i].data;
+ unsigned int idx = (TCP_CURR_IDX + i) % TCP_FRAMES_MEM;
+
+ iov->iov_base = &tcp_payload[idx].data;
iov->iov_len = mss;
}
if (iov_rem)
@@ -451,7 +506,7 @@ int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn,
dlen = mss;
seq = conn->seq_to_tap;
for (i = 0; i < send_bufs; i++) {
- int no_csum = i && i != send_bufs - 1 && tcp_payload_used;
+ int no_csum = i && i != send_bufs - 1 && TCP_CURR_IDX;
bool push = false;
if (i == send_bufs - 1) {
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 2/7] conf: Add context fields, epoll types and --vhost-kernel flag to pasta
2026-09-04 21:28 [PATCH 0/7] Add vhost-net kernel support to pasta aerosouund
` (6 preceding siblings ...)
2026-09-04 21:28 ` [PATCH 7/7] tap/tcp: Replace tcp_payload_used with a ring buffer style index aerosouund
@ 2026-09-05 16:25 ` aerosouund
2026-09-05 16:25 ` [PATCH 3/7] virtio: Add the pasta vhost-net interface and implementation aerosouund
` (2 more replies)
7 siblings, 3 replies; 13+ messages in thread
From: aerosouund @ 2026-09-05 16:25 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
--vhost-kernel controls whether to use vhost-net acceleration for pasta.
It takes one of three modes: "on" requires acceleration and fails if it
isn't available, "off" never uses it, and "auto" uses it if it's
available and falls back to plain tap operation otherwise. "auto" is the
default, so an unmodified invocation picks up acceleration wherever the
kernel offers it, and behaves as before where it doesn't.
EPOLL_TYPE_VHOST_CALL means the kernel wants to notify us about data it
has written. EPOLL_TYPE_VHOST_ERROR means the kernel encountered an
internal error on vhost and wants to tell us something went wrong.
Collect the vhost state in a single struct vhost_ctx with the following
fields:
- mode: which of the three modes above was requested
- fd: file descriptor for /dev/vhost-net, which doubles as our
indication that setup succeeded
- features: virtio feature bits negotiated with vhost-net
- vq[]: kick, call and err eventfds for both queues
Modify tap_hdr_iov to create an iov with the size of a
virtio_net_mrg_rxbuf if vhost initialization has been successful (we are
in mode 'on' or 'auto' and we didn't need the tap fallback)
Move the L2_MAX_LEN_XX constants from tap.h to passt.h to avoid the
circular dependency between both files. VHOST_DESC_BYTES is defined as
the maximum l2 frame size + the size of the virtio net header, if the
length constant remains in tap.h passt.h would need to include it, but
tap.h needs to include passt.h to get the definition of `struct ctx`.
Moving the length constants to passt.h makes it free of dependency on
tap.h
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
conf.c | 19 +++++++++++++
epoll_type.h | 4 +++
passt.c | 3 ++
passt.h | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++--
tap.h | 36 ++++++------------------
5 files changed, 112 insertions(+), 29 deletions(-)
diff --git a/conf.c b/conf.c
index faf2681..a7b934a 100644
--- a/conf.c
+++ b/conf.c
@@ -736,6 +736,9 @@ pasta_opts:
" default: auto\n"
" --host-lo-to-ns-lo Translate host-loopback forwards to\n"
" namespace loopback\n"
+ " --vhost-kernel MODE Use vhost-kernel acceleration\n"
+ " MODE is \"on\", \"off\" or \"auto\"\n"
+ " default: auto\n"
" --userns NSPATH Target user namespace to join\n"
" --netns PATH|NAME Target network namespace to join\n"
" --netns-only Don't join existing user namespace\n"
@@ -766,6 +769,7 @@ enum passt_modes conf_mode(int argc, char *argv[])
int vhost_user = 0;
const struct option optvu[] = {
{"vhost-user", no_argument, &vhost_user, 1 },
+ {"vhost-kernel", required_argument, NULL, 0 },
{ 0 },
};
char argv0[PATH_MAX], *basearg0;
@@ -1348,6 +1352,7 @@ void conf(struct ctx *c, int argc, char **argv)
{"stats", required_argument, NULL, 31 },
{"conf-path", required_argument, NULL, 'c' },
{"chroot-fallback", no_argument, NULL, 32 },
+ {"vhost-kernel", required_argument, NULL, 33 },
{ 0 },
};
const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:";
@@ -1588,6 +1593,20 @@ void conf(struct ctx *c, int argc, char **argv)
break;
case 32:
c->chroot_fallback = true;
+ break;
+ case 33:
+ if (c->mode != MODE_PASTA)
+ die("--vhost-kernel is for pasta mode only");
+
+ if (!strcmp(optarg, "auto"))
+ c->vhost.mode = VHOST_MODE_AUTO;
+ else if (!strcmp(optarg, "on"))
+ c->vhost.mode = VHOST_MODE_ON;
+ else if (!strcmp(optarg, "off"))
+ c->vhost.mode = VHOST_MODE_OFF;
+ else
+ die("Invalid vhost-kernel mode: %s", optarg);
+
break;
case 'd':
c->debug = 1;
diff --git a/epoll_type.h b/epoll_type.h
index 061325a..e3206d1 100644
--- a/epoll_type.h
+++ b/epoll_type.h
@@ -50,6 +50,10 @@ enum epoll_type {
EPOLL_TYPE_CONF_LISTEN,
/* Configuration socket */
EPOLL_TYPE_CONF,
+ /* vhost-kernel call socket */
+ EPOLL_TYPE_VHOST_CALL,
+ /* vhost-kernel error socket */
+ EPOLL_TYPE_VHOST_ERROR,
EPOLL_NUM_TYPES,
};
diff --git a/passt.c b/passt.c
index 5054551..6002231 100644
--- a/passt.c
+++ b/passt.c
@@ -65,6 +65,7 @@ char pkt_buf[PKT_BUF_BYTES] __attribute__ ((aligned(PAGE_SIZE)));
struct ctx passt_ctx = {
.pidfile_fd = -1,
.fd_tap = -1,
+ .vhost = { .fd = -1 },
.fd_tap_listen = -1,
.fd_control_listen = -1,
.fd_repair_listen = -1,
@@ -92,6 +93,8 @@ char *epoll_type_str[] = {
[EPOLL_TYPE_NL_NEIGH] = "netlink neighbour notifier socket",
[EPOLL_TYPE_CONF_LISTEN] = "configuration listening socket",
[EPOLL_TYPE_CONF] = "configuration socket",
+ [EPOLL_TYPE_VHOST_CALL] = "vhost-kernel call socket",
+ [EPOLL_TYPE_VHOST_ERROR] = "vhost-kernel error socket",
};
static_assert(ARRAY_SIZE(epoll_type_str) == EPOLL_NUM_TYPES,
"epoll_type_str[] doesn't match enum epoll_type");
diff --git a/passt.h b/passt.h
index 51ccd4f..5e0de3e 100644
--- a/passt.h
+++ b/passt.h
@@ -13,8 +13,11 @@ union epoll_ref;
#include <stdbool.h>
#include <assert.h>
+#include <limits.h>
#include <sys/epoll.h>
+#include <linux/virtio_net.h>
+
#include "pif.h"
#include "packet.h"
#include "siphash.h"
@@ -35,8 +38,43 @@ union epoll_ref;
#define MAC_OUR_LAA \
((uint8_t [ETH_ALEN]){0x9a, 0x55, 0x9a, 0x55, 0x9a, 0x55})
-/* Large enough for ~128 maximum size frames */
-#define PKT_BUF_BYTES (8UL << 20)
+
+/** L2_MAX_LEN_PASTA - Maximum frame length for pasta mode (with L2 header)
+ *
+ * The kernel tuntap device imposes a maximum frame size of 65535 including
+ * 'hard_header_len' (14 bytes for L2 Ethernet in the case of "tap" mode).
+ */
+#define L2_MAX_LEN_PASTA USHRT_MAX
+
+/** L2_MAX_LEN_PASST - Maximum frame length for passt mode (with L2 header)
+ *
+ * The only structural limit the QEMU socket protocol imposes on frames is
+ * (2^32-1) bytes, but that would be ludicrously long in practice. For now,
+ * limit it somewhat arbitrarily to 65535 bytes. FIXME: Work out an appropriate
+ * limit with more precision.
+ */
+#define L2_MAX_LEN_PASST USHRT_MAX
+
+/** L2_MAX_LEN_VU - Maximum frame length for vhost-user mode (with L2 header)
+ *
+ * vhost-user allows multiple buffers per frame, each of which can be quite
+ * large, so the inherent frame size limit is rather large. Much larger than is
+ * actually useful for IP. For now limit arbitrarily to 65535 bytes. FIXME:
+ * Work out an appropriate limit with more precision.
+ */
+#define L2_MAX_LEN_VU USHRT_MAX
+
+/* Number of descriptors in each vhost-net virtqueue */
+#define VHOST_NDESCS 128
+
+/* Bytes of pkt_buf backing one from-guest descriptor: a maximum size frame
+ * plus the virtio-net header the kernel writes in front of it
+ */
+#define VHOST_DESC_BYTES (L2_MAX_LEN_PASTA + \
+ sizeof(struct virtio_net_hdr_mrg_rxbuf))
+
+/* One maximum size frame per vhost-net descriptor */
+#define PKT_BUF_BYTES (VHOST_NDESCS * VHOST_DESC_BYTES)
extern char pkt_buf [PKT_BUF_BYTES];
@@ -156,6 +194,40 @@ struct ip6_ctx {
#include <netinet/if_ether.h>
+/**
+ * enum vhost_mode - Whether to use vhost-kernel acceleration
+ * @VHOST_MODE_AUTO: Use it if it's available, fall back to plain tap if not
+ * @VHOST_MODE_ON: Require it, fail if it's not available
+ * @VHOST_MODE_OFF: Never use it
+ */
+enum vhost_mode {
+ VHOST_MODE_AUTO = 0,
+ VHOST_MODE_ON,
+ VHOST_MODE_OFF,
+};
+
+/**
+ * struct vhost_ctx - Execution context for vhost-kernel acceleration
+ * @mode: Whether to use acceleration at all, see enum vhost_mode
+ * @fd: File descriptor for /dev/vhost-net, -1 if not set up
+ * @features: virtio feature bits negotiated with vhost-net
+ * @vq: Per-virtqueue eventfds ([0] is from-guest, [1] is to-guest)
+ * @vq.kick_fd: Written by us, to tell the kernel we queued something
+ * @vq.call_fd: Written by the kernel, to tell us it queued something
+ * @vq.err_fd: Written by the kernel on an internal vhost error
+ */
+struct vhost_ctx {
+ enum vhost_mode mode;
+ int fd;
+ uint64_t features;
+
+ struct {
+ int kick_fd;
+ int call_fd;
+ int err_fd;
+ } vq[2];
+};
+
/**
* struct ctx - Execution context
* @mode: Operation mode, qemu/UNIX domain socket or namespace/tap
@@ -185,6 +257,7 @@ struct ip6_ctx {
* @our_tap_mac: Pasta/passt's MAC on the tap link
* @guest_mac: MAC address of guest or namespace, seen or configured
* @hash_secret: 128-bit secret for siphash functions
+ * @vhost: vhost-kernel acceleration context, pasta mode only
* @ifi4: Template interface for IPv4, -1: none, 0: IPv4 disabled
* @ip4: IPv4 configuration
* @dns_search: DNS search list
@@ -264,6 +337,8 @@ struct ctx {
uint64_t hash_secret[2];
+ struct vhost_ctx vhost;
+
int ifi4;
struct ip4_ctx ip4;
diff --git a/tap.h b/tap.h
index 1625975..ce1efa8 100644
--- a/tap.h
+++ b/tap.h
@@ -12,31 +12,6 @@
#include "passt.h"
#include "tap_hdr.h"
-/** L2_MAX_LEN_PASTA - Maximum frame length for pasta mode (with L2 header)
- *
- * The kernel tuntap device imposes a maximum frame size of 65535 including
- * 'hard_header_len' (14 bytes for L2 Ethernet in the case of "tap" mode).
- */
-#define L2_MAX_LEN_PASTA USHRT_MAX
-
-/** L2_MAX_LEN_PASST - Maximum frame length for passt mode (with L2 header)
- *
- * The only structural limit the QEMU socket protocol imposes on frames is
- * (2^32-1) bytes, but that would be ludicrously long in practice. For now,
- * limit it somewhat arbitrarily to 65535 bytes. FIXME: Work out an appropriate
- * limit with more precision.
- */
-#define L2_MAX_LEN_PASST USHRT_MAX
-
-/** L2_MAX_LEN_VU - Maximum frame length for vhost-user mode (with L2 header)
- *
- * vhost-user allows multiple buffers per frame, each of which can be quite
- * large, so the inherent frame size limit is rather large. Much larger than is
- * actually useful for IP. For now limit arbitrarily to 65535 bytes. FIXME:
- * Work out an appropriate limit with more precision.
- */
-#define L2_MAX_LEN_VU USHRT_MAX
-
struct udphdr;
/**
@@ -50,10 +25,17 @@ struct udphdr;
static inline struct iovec tap_hdr_iov(const struct ctx *c,
struct tap_hdr *thdr)
{
- return (struct iovec){
+ struct iovec ret = {
.iov_base = thdr,
- .iov_len = c->mode == MODE_PASST ? sizeof(*thdr) : 0,
};
+
+ if ((c->vhost.fd != -1)) {
+ ret.iov_len = sizeof(struct virtio_net_hdr_mrg_rxbuf);
+ } else {
+ ret.iov_len = c->mode == MODE_PASST ? sizeof(thdr->vnet_len) : 0;
+ };
+
+ return ret;
}
/**
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 3/7] virtio: Add the pasta vhost-net interface and implementation
2026-09-05 16:25 ` [PATCH 2/7] conf: Add context fields, epoll types and --vhost-kernel flag to pasta aerosouund
@ 2026-09-05 16:25 ` aerosouund
2026-09-05 16:25 ` [PATCH 4/7] tap: Implement the pasta vhost-net from-guest path aerosouund
2026-09-05 16:25 ` [PATCH 7/7] tap/tcp: Replace tcp_payload_used with a ring buffer style index aerosouund
2 siblings, 0 replies; 13+ messages in thread
From: aerosouund @ 2026-09-05 16:25 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
Add vhost.c and vhost.h which define the api and implementation for
pasta vhost acceleration
- vq_state/vhost_vq_state[]: our own view of each virtqueue, which the
kernel doesn't track for us: descriptors ready to hand back, how far
we've read the used ring, and the next descriptor to allocate
- vring_desc, vring_avail_all, vring_used_all, vhost_memory: the rings
and the region table shared with the kernel
- vhost_setup_net: open /dev/vhost-net and negotiate VIRTIO_F_VERSION_1
and VHOST_NET_F_VIRTIO_NET_HDR. Failure is fatal only if
--vhost-kernel is set to 'on'.
- vhost_setup_eventfds: create, register and watch the call, kick and
error eventfds for one queue
- vhost_setup_memory_table: register the buffers the kernel may touch.
pasta has no guest, so each region maps guest_phys_addr onto the
identical userspace_addr, making GPA translation an identity mapping.
This function uses the *_register_memory_regions functions created per
protocol and for general purpose buffers.
- vhost_set_vring: publish a vring's addresses, bind the tap fd as its
backend and lay out its descriptors
- vhost_rx_descriptor_handoff, vhost_kick: hand consumed descriptors back
and notify the kernel unless it says it's already polling
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
Makefile | 2 +-
tcp_buf.c | 16 ++-
tcp_buf.h | 4 +
udp.c | 14 +++
udp.h | 3 +
util.c | 12 +++
util.h | 2 +
vhost.c | 295 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
vhost.h | 79 +++++++++++++++
9 files changed, 425 insertions(+), 2 deletions(-)
create mode 100644 vhost.c
create mode 100644 vhost.h
diff --git a/Makefile b/Makefile
index b315242..74d0f96 100644
--- a/Makefile
+++ b/Makefile
@@ -38,7 +38,7 @@ PASST_SRCS = arch.c arp.c bitmap.c checksum.c conf.c dhcp.c dhcpv6.c \
isolation.c lineread.c log.c mld.c ndp.c netlink.c migrate.c packet.c \
parse.c passt.c pasta.c pcap.c pif.c repair.c serialise.c tap.c tcp.c \
tcp_buf.c tcp_splice.c tcp_vu.c udp.c udp_flow.c udp_vu.c util.c \
- vhost_user.c virtio.c vu_common.c
+ vhost.c vhost_user.c virtio.c vu_common.c
PASST_REPAIR_SRCS = passt-repair.c
PESTO_SRCS = pesto.c bitmap.c fwd_rule.c inany.c ip.c lineread.c parse.c \
serialise.c
diff --git a/tcp_buf.c b/tcp_buf.c
index 72c4541..1f28728 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -31,7 +31,7 @@
#include "inany.h"
#include "tcp_conn.h"
#include "tcp_internal.h"
-#include "tcp_buf.h"
+#include "vhost.h"
#define TCP_FRAMES_MEM 128
#define TCP_FRAMES \
@@ -75,6 +75,20 @@ void tcp_update_l2_buf(const unsigned char *eth_d)
eth_update_mac(&tcp_eth_hdr[i], eth_d, NULL);
}
+/**
+ * tcp_register_memory_region() - Register the TCP specific buffers into a memory
+ * struct so it can be shared with the kernel.
+ * @vhost_mem: The memory struct to register regions into
+ * @last_idx: The last index at which memory region has been placed
+ */
+void tcp_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx) {
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp_payload_tap_hdr);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp4_payload_ip);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp6_payload_ip);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp_payload);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(tcp_eth_hdr);
+}
+
/**
* tcp_sock_iov_init() - Initialise scatter-gather L2 buffers for IPv4 sockets
* @c: Execution context
diff --git a/tcp_buf.h b/tcp_buf.h
index 5d31cea..5e341ed 100644
--- a/tcp_buf.h
+++ b/tcp_buf.h
@@ -6,11 +6,15 @@
#ifndef TCP_BUF_H
#define TCP_BUF_H
+union vhost_memory_u;
+struct tcp_tap_conn;
+
void tcp_sock_iov_init(const struct ctx *c);
void tcp_payload_flush(const struct ctx *c, const struct timespec *now);
int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn,
uint32_t already_sent, const struct timespec *now);
int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
const struct timespec *now);
+void tcp_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx);
#endif /*TCP_BUF_H */
diff --git a/udp.c b/udp.c
index 505e554..5e3bd86 100644
--- a/udp.c
+++ b/udp.c
@@ -118,6 +118,7 @@
#include "udp_internal.h"
#include "udp_vu.h"
#include "epoll_ctl.h"
+#include "vhost.h"
#define UDP_MAX_FRAMES 32 /* max # of frames to receive at once */
@@ -448,6 +449,19 @@ static void udp_send_tap_icmp4(const struct ctx *c,
tap_icmp4_send(c, saddr, eaddr, &msg, tap_omac, msglen);
}
+/**
+ * udp_register_memory_region() - Register the UDP specific buffers into a memory
+ * struct so it can be shared with the kernel.
+ * @vhost_mem: The memory struct to register regions into
+ * @last_idx: The last index at which memory region has been placed
+ */
+void udp_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx) {
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_payload);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_eth_hdr);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_iov_recv);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_mh_recv);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(udp_meta);
+}
/**
* udp_send_tap_icmp6() - Construct and send ICMPv6 to local peer
diff --git a/udp.h b/udp.h
index b50283e..ebf1a4e 100644
--- a/udp.h
+++ b/udp.h
@@ -11,6 +11,8 @@
#include "fwd.h"
+union vhost_memory_u;
+
void udp_listen_sock_handler(const struct ctx *c, union epoll_ref ref,
uint32_t events, const struct timespec *now);
void udp_sock_handler(const struct ctx *c, union epoll_ref ref,
@@ -21,6 +23,7 @@ int udp_tap_handler(const struct ctx *c, uint8_t pif,
const struct timespec *now);
int udp_init(struct ctx *c);
void udp_update_l2_buf(const unsigned char *eth_d);
+void udp_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx);
/**
* struct udp_ctx - Execution context for UDP
diff --git a/util.c b/util.c
index 28c32e4..a994d88 100644
--- a/util.c
+++ b/util.c
@@ -36,6 +36,7 @@
#include "epoll_ctl.h"
#include "pasta.h"
#include "serialise.h"
+#include "vhost.h"
#ifdef HAS_GETRANDOM
#include <sys/random.h>
#endif
@@ -456,6 +457,17 @@ int open_in_ns(const struct ctx *c, const char *path, int flags)
return arg.fd;
}
+/**
+ * general_register_memory_region() - Register the general (non protocol specific) buffers
+ * into a memory struct so it can be shared with the kernel.
+ * @vhost_mem: The memory struct to register regions into
+ * @last_idx: The last index at which memory region has been placed
+ */
+void general_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx) {
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(pkt_buf);
+ vhost_mem->mem.regions[(*last_idx)++] = VHOST_MEMORY_REGION(eth_pad);
+}
+
/**
* pidfile_write() - Write PID to file, if requested to do so, and close it
* @fd: Open PID file descriptor, closed on exit, -1 to skip writing it
diff --git a/util.h b/util.h
index 2435f53..27eac22 100644
--- a/util.h
+++ b/util.h
@@ -145,6 +145,7 @@ int do_clone(int (*fn)(void *), char *stack_area, size_t stack_size, int flags,
struct ctx;
union sockaddr_inany;
+union vhost_memory_u;
int sock_l4(const struct ctx *c, enum epoll_type type,
const union sockaddr_inany *sa, const char *ifname);
@@ -170,6 +171,7 @@ int write_remainder(int fd, const struct iovec *iov, size_t iovcnt,
int read_remainder(int fd, const struct iovec *iov, size_t cnt, size_t skip);
bool snprintf_check(char *str, size_t size, const char *format, ...);
long clamped_scale(long x, long y, long lo, long hi, long f);
+void general_register_memory_regions(union vhost_memory_u *vhost_mem, size_t *last_idx);
/**
* af_name() - Return name of an address family
diff --git a/vhost.c b/vhost.c
new file mode 100644
index 0000000..0c60c21
--- /dev/null
+++ b/vhost.c
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Copyright Red Hat
+ * Author: Ammar Yasser <aerosound161@gmail.com>
+ *
+ * vhost.c - vhost-net (vhost-kernel) acceleration for pasta mode
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/eventfd.h>
+#include <sys/ioctl.h>
+
+#include "util.h"
+#include "passt.h"
+#include "vhost.h"
+#include "epoll_ctl.h"
+#include "udp.h"
+#include "tcp_buf.h"
+
+struct vq_state vhost_vq_state[2];
+
+struct vring_desc vring_desc[2][VHOST_NDESCS]
+ __attribute__((aligned(PAGE_SIZE)));
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+union vring_avail_u vring_avail_all[2] __attribute__((aligned(PAGE_SIZE)));
+union vring_used_u vring_used_all[2] __attribute__((aligned(PAGE_SIZE)));
+#pragma GCC diagnostic pop
+
+union vhost_memory_u vhost_memory = {
+ .mem = {
+ .nregions = N_VHOST_REGIONS,
+ },
+};
+
+/**
+ * vhost_setup_net() - Open and negotiate features on /dev/vhost-net
+ * @c: Execution context; c->vhost.fd and c->vhost.features are set
+ * on success, and left untouched on failure
+ *
+ * Failure here means vhost-net isn't usable, not that pasta can't run: the
+ * caller falls back to plain tap operation unless acceleration was required.
+ * That's why nothing in here is fatal.
+ *
+ * Return: 0 on success, -1 if vhost-net is unavailable or unusable
+ */
+int vhost_setup_net(struct ctx *c)
+{
+ const uint64_t req_features = (1ULL << VIRTIO_F_VERSION_1) |
+ (1ULL << VHOST_NET_F_VIRTIO_NET_HDR);
+ uint64_t features;
+ int vhost_fd;
+
+ vhost_fd = open("/dev/vhost-net", O_RDWR | O_NONBLOCK | O_CLOEXEC);
+ if (vhost_fd < 0) {
+ debug_perror("Couldn't open /dev/vhost-net");
+ return -1;
+ }
+
+ if (ioctl(vhost_fd, VHOST_SET_OWNER, NULL) < 0) {
+ debug_perror("VHOST_SET_OWNER ioctl failed");
+ goto close_fd;
+ }
+
+ if (ioctl(vhost_fd, VHOST_GET_FEATURES, &features) < 0) {
+ debug_perror("VHOST_GET_FEATURES ioctl failed");
+ goto close_fd;
+ }
+
+ if ((features & req_features) != req_features) {
+ debug("vhost-net is missing features: 0x%016" PRIx64,
+ req_features & ~features);
+ goto close_fd;
+ }
+
+ features = req_features;
+ if (ioctl(vhost_fd, VHOST_SET_FEATURES, &features) < 0) {
+ debug_perror("VHOST_SET_FEATURES ioctl failed");
+ goto close_fd;
+ }
+
+ c->vhost.features = features;
+ c->vhost.fd = vhost_fd;
+
+ return 0;
+
+close_fd:
+ close(vhost_fd);
+
+ return -1;
+}
+
+/**
+ * vhost_setup_eventfds() - Set up one queue's eventfds and ring size
+ * @c: Execution context; c->vhost.fd must already be set
+ * @queue_idx: Index of the queue (vring) to configure
+ *
+ */
+void vhost_setup_eventfds(struct ctx *c, int queue_idx)
+{
+ struct vhost_vring_file call_file = { .index = queue_idx };
+ struct vhost_vring_file kick_file = { .index = queue_idx };
+ struct vhost_vring_file err_file = { .index = queue_idx };
+ struct vhost_vring_state state = { .index = queue_idx };
+ union epoll_ref ref = { .type = EPOLL_TYPE_VHOST_CALL };
+ int vhost_fd = c->vhost.fd;
+ struct epoll_event ev;
+ int rc;
+
+ state.num = VHOST_NDESCS;
+ ref.queue = queue_idx;
+
+ call_file.fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (call_file.fd < 0)
+ die_perror("Failed to create vhost call eventfd, queue %d",
+ queue_idx);
+ ref.fd = call_file.fd;
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_CALL, &call_file);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_CALL ioctl failed, queue %d",
+ queue_idx);
+
+ ev = (struct epoll_event){ .data.u64 = ref.u64, .events = EPOLLIN };
+ rc = epoll_ctl(c->epollfd, EPOLL_CTL_ADD, ref.fd, &ev);
+ if (rc < 0)
+ die_perror("Failed to watch vhost call eventfd, queue %d",
+ queue_idx);
+ c->vhost.vq[queue_idx].call_fd = call_file.fd;
+
+ err_file.fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (err_file.fd < 0)
+ die_perror("Failed to create vhost error eventfd, queue %d",
+ queue_idx);
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_ERR, &err_file);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_ERR ioctl failed, queue %d",
+ queue_idx);
+
+ ref.type = EPOLL_TYPE_VHOST_ERROR;
+ ref.fd = err_file.fd;
+ ev.data.u64 = ref.u64;
+ rc = epoll_ctl(c->epollfd, EPOLL_CTL_ADD, ref.fd, &ev);
+ if (rc < 0)
+ die_perror("Failed to watch vhost error eventfd, queue %d",
+ queue_idx);
+ c->vhost.vq[queue_idx].err_fd = err_file.fd;
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_NUM, &state);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_NUM ioctl failed, queue %d",
+ queue_idx);
+
+ kick_file.fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (kick_file.fd < 0)
+ die_perror("Failed to create vhost kick eventfd, queue %d",
+ queue_idx);
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_KICK, &kick_file);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_KICK ioctl failed, queue %d",
+ queue_idx);
+
+ c->vhost.vq[queue_idx].kick_fd = kick_file.fd;
+
+ vhost_vq_state[queue_idx].num_free = VHOST_NDESCS;
+}
+
+/**
+ * vhost_setup_memory_table() - Register the GPA/HVA translation table
+ * @c: Execution context; c->vhost.fd must already be set
+ *
+ * vhost-net reads the addresses we put in descriptors as guest physical
+ * addresses, and translates them through this table before touching the
+ * memory they refer to. pasta has no guest and no second address space:
+ * the addresses we put there are our own virtual addresses. Every region
+ * below therefore sets guest_phys_addr equal to userspace_addr, so that
+ * GPA == HVA and the translation is an identity mapping.
+ *
+ * Return: 0 on success, -1 on error with errno set
+ */
+int vhost_setup_memory_table(struct ctx *c) {
+ size_t region_idx = 0;
+
+ /* general purpose buffers */
+ general_register_memory_regions(&vhost_memory, ®ion_idx);
+
+ /* tcp specific buffers */
+ tcp_register_memory_regions(&vhost_memory, ®ion_idx);
+
+ /* udp specific buffers */
+ udp_register_memory_regions(&vhost_memory, ®ion_idx);
+
+ vhost_memory.mem.nregions = region_idx;
+
+ return ioctl(c->vhost.fd, VHOST_SET_MEM_TABLE, &vhost_memory.mem);
+}
+
+/**
+ * vhost_set_vring() - Register a vring's addresses and bind its backend
+ * @c: Execution context; c->vhost.fd must already be set
+ * @queue_idx: Index of the queue (vring) to configure
+ * @tap_fd: Tap fd to bind as this queue's backend
+ */
+void vhost_set_vring(struct ctx *c, int queue_idx, int tap_fd)
+{
+ int vhost_fd = c->vhost.fd;
+ struct vhost_vring_addr addr = {
+ .index = queue_idx,
+ .desc_user_addr = (unsigned long)vring_desc[queue_idx],
+ .avail_user_addr = (unsigned long)&vring_avail_all[queue_idx],
+ .used_user_addr = (unsigned long)&vring_used_all[queue_idx],
+ .log_guest_addr = (unsigned long)&vring_used_all[queue_idx],
+ };
+ struct vhost_vring_file file = {
+ .index = queue_idx,
+ .fd = tap_fd,
+ };
+ unsigned int i;
+ int rc;
+
+ rc = ioctl(vhost_fd, VHOST_SET_VRING_ADDR, &addr);
+ if (rc < 0)
+ die_perror("VHOST_SET_VRING_ADDR ioctl failed, queue %d",
+ queue_idx);
+
+ if (queue_idx == 0) {
+ for (i = 0; i < VHOST_NDESCS; ++i) {
+ vring_desc[0][i].addr = (uintptr_t)pkt_buf +
+ i * VHOST_DESC_BYTES;
+ vring_desc[0][i].len = VHOST_DESC_BYTES;
+ vring_desc[0][i].flags = VRING_DESC_F_WRITE;
+ }
+
+ for (i = 0; i < VHOST_NDESCS; ++i)
+ vring_avail_all[0].avail.ring[i] = htole16(i);
+
+ vhost_rx_descriptor_handoff(c);
+ }
+
+ if (queue_idx == 1) {
+ for (i = 0; i < (VHOST_NDESCS - 1); ++i)
+ vring_desc[1][i].next = i + 1;
+ }
+
+ rc = ioctl(vhost_fd, VHOST_NET_SET_BACKEND, &file);
+ if (rc < 0)
+ die_perror("VHOST_NET_SET_BACKEND ioctl failed, queue %d",
+ queue_idx);
+}
+
+/**
+ * vhost_rx_descriptor_handoff() - Announce freed from-guest descriptors
+ * @c: Execution context
+ *
+ * Bumps avail.idx by the number of descriptors accumulated in
+ * vhost_vq_state[0].num_free (from prior consume_one_rx_descriptor() calls),
+ * then resets the counter to zero. The kernel will see the new
+ * avail.idx and consume the freshly-available descriptors.
+ */
+void vhost_rx_descriptor_handoff(struct ctx *c)
+{
+ smp_wmb();
+
+ if (!vhost_vq_state[0].num_free)
+ return;
+
+ vring_avail_all[0].avail.idx += vhost_vq_state[0].num_free;
+ vhost_vq_state[0].num_free = 0;
+ vhost_kick(&vring_used_all[0].used, c->vhost.vq[0].kick_fd);
+}
+
+/**
+ * vhost_kick() - Notify the kernel that new descriptors are available
+ * @used: Used ring of the queue we're announcing on, checked to see
+ * whether the kernel's virtio thread is already reading
+ * descriptors and doesn't want to be notified
+ * @kick_fd: Kick eventfd of that same queue
+ */
+void vhost_kick(struct vring_used *used, int kick_fd)
+{
+ /* Ensure that the read of used->flags doesn't get reordered to be
+ * above the avail.idx update
+ */
+ smp_mb();
+
+ if (!(used->flags & VRING_USED_F_NO_NOTIFY))
+ eventfd_write(kick_fd, 1);
+}
diff --git a/vhost.h b/vhost.h
new file mode 100644
index 0000000..aaa937d
--- /dev/null
+++ b/vhost.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later
+ * Copyright Red Hat
+ * Author: Ammar Yasser <aerosound161@gmail.com>
+ *
+ * vhost.h - vhost-net (vhost-kernel) acceleration for pasta mode
+ */
+
+#ifndef VHOST_H
+#define VHOST_H
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <linux/vhost.h>
+#include <linux/vhost_types.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_net.h>
+#include <linux/virtio_ring.h>
+
+#include "passt.h"
+
+/**
+ * struct vq_state - Per-virtqueue local descriptor tracking
+ * @num_free: Number of descriptors ready to be announced to the
+ * kernel via vhost_rx_descriptor_handoff()
+ * @last_used_idx: Number of used-ring entries consumed so far;
+ * lagging read cursor vs. vring_used->idx (the
+ * kernel's write cursor)
+ */
+extern struct vq_state {
+ uint16_t num_free;
+ uint16_t last_used_idx;
+ uint16_t next_free;
+} vhost_vq_state[2];
+
+
+extern struct vring_desc vring_desc[2][VHOST_NDESCS];
+union vring_avail_u {
+ struct vring_avail avail;
+ char buf[offsetof(struct vring_avail, ring[VHOST_NDESCS])];
+};
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+extern union vring_avail_u vring_avail_all[2];
+#pragma GCC diagnostic pop
+
+union vring_used_u {
+ struct vring_used used;
+ char buf[offsetof(struct vring_used, ring[VHOST_NDESCS])];
+};
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpedantic"
+extern union vring_used_u vring_used_all[2];
+#pragma GCC diagnostic pop
+
+#define N_VHOST_REGIONS 2
+union vhost_memory_u {
+ struct vhost_memory mem;
+ char buf[offsetof(struct vhost_memory, regions[N_VHOST_REGIONS])];
+};
+extern union vhost_memory_u vhost_memory;
+
+#define VHOST_MEMORY_REGION_PTR(addr, size) \
+ (struct vhost_memory_region) { \
+ .guest_phys_addr = (uintptr_t)addr, \
+ .memory_size = size, \
+ .userspace_addr = (uintptr_t)addr, \
+ }
+#define VHOST_MEMORY_REGION(buf) VHOST_MEMORY_REGION_PTR(&buf, sizeof(buf))
+
+void vhost_set_vring(struct ctx *c, int queue_idx, int tap_fd);
+int vhost_setup_memory_table(struct ctx *c);
+int vhost_setup_net(struct ctx *c);
+void vhost_setup_eventfds(struct ctx *c, int queue_idx);
+void vhost_rx_descriptor_handoff(struct ctx *c);
+void vhost_kick(struct vring_used *used, int kick_fd);
+
+#endif /* VHOST_H */
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 4/7] tap: Implement the pasta vhost-net from-guest path
2026-09-05 16:25 ` [PATCH 2/7] conf: Add context fields, epoll types and --vhost-kernel flag to pasta aerosouund
2026-09-05 16:25 ` [PATCH 3/7] virtio: Add the pasta vhost-net interface and implementation aerosouund
@ 2026-09-05 16:25 ` aerosouund
2026-09-05 16:25 ` [PATCH 7/7] tap/tcp: Replace tcp_payload_used with a ring buffer style index aerosouund
2 siblings, 0 replies; 13+ messages in thread
From: aerosouund @ 2026-09-05 16:25 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
Add tap_vhost_input(), called when the kernel signals on a call eventfd
that it has written guest frames into the from-guest queue. It consumes
descriptors with consume_one_rx_descriptor(), skips the virtio_net header
on each, builds an iov_tail from what's left and queues it for
processing, then tells the kernel the descriptors are free to reuse.
consume_one_rx_descriptor() pops one entry from the used ring, advances
our read cursor and the count of descriptors awaiting handoff, and
returns a pointer to that descriptor's data.
Bootstrap vhost in tap_sock_tun_init(). If setup fails we leave
c->vhost.fd at -1 and carry on unaccelerated.
With vhost-net running the kernel reads the tap fd itself, so don't add
that fd to our epoll set at all: tap_handler_pasta() would otherwise be
woken for data it must not read.
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
passt.c | 6 +++
tap.c | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
tap.h | 2 +
3 files changed, 119 insertions(+), 1 deletion(-)
diff --git a/passt.c b/passt.c
index 6002231..372b743 100644
--- a/passt.c
+++ b/passt.c
@@ -305,6 +305,12 @@ static void passt_worker(void *opaque, int nfds, struct epoll_event *events)
case EPOLL_TYPE_CONF:
conf_handler(c, eventmask);
break;
+ case EPOLL_TYPE_VHOST_CALL:
+ tap_vhost_input(c, ref, &now);
+ break;
+ case EPOLL_TYPE_VHOST_ERROR:
+ die("Error on vhost-kernel socket");
+ break;
default:
/* Can't happen */
assert(0);
diff --git a/tap.c b/tap.c
index dfa66c7..f84cb0a 100644
--- a/tap.c
+++ b/tap.c
@@ -38,6 +38,7 @@
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <netinet/if_ether.h>
+#include <sys/eventfd.h>
#include <linux/if_tun.h>
#include <linux/icmpv6.h>
@@ -61,6 +62,7 @@
#include "vhost_user.h"
#include "vu_common.h"
#include "epoll_ctl.h"
+#include "vhost.h"
/* Maximum allowed frame lengths (including L2 header) */
@@ -1446,7 +1448,11 @@ static void tap_start_connection(const struct ctx *c)
break;
}
- epoll_add(c->epollfd, EPOLLIN | EPOLLRDHUP, ref);
+ /* With vhost-net running, the kernel reads the tap fd for us and
+ * notifies us on the call eventfd, so we mustn't watch it ourselves
+ */
+ if (c->vhost.fd == -1)
+ epoll_add(c->epollfd, EPOLLIN | EPOLLRDHUP, ref);
if (!tap_is_ready(c))
return;
@@ -1514,6 +1520,89 @@ void tap_listen_handler(struct ctx *c, uint32_t events)
tap_start_connection(c);
}
+/**
+ * consume_one_rx_descriptor() - Consume one used from-guest descriptor
+ * @len: Set to the length of data written by the kernel
+ *
+ * Pops a single entry from the used ring, advancing our read cursor and the
+ * count of descriptors awaiting refill announcement.
+ *
+ * NOTE: This function assumes the kernel is going to post single descriptors
+ * always, No chains. If that changes, we would need to increment num_free
+ * as we advertise back to the kernel the free descriptors by the length of the chain.
+ *
+ * Return: pointer to the packet buffer, or NULL if no data is available
+ */
+static void *consume_one_rx_descriptor(size_t *len)
+{
+ struct vring_used *used = &vring_used_all[0].used;
+ uint16_t used_idx, last_used;
+ uint32_t i;
+
+ used_idx = le16toh(used->idx);
+
+ smp_rmb();
+
+ /* If the kernel's used index matches ours, we've consumed everything
+ * it has posted
+ */
+ if (used_idx == vhost_vq_state[0].last_used_idx) {
+ *len = 0;
+ return NULL;
+ }
+
+ last_used = vhost_vq_state[0].last_used_idx % VHOST_NDESCS;
+ i = le32toh(used->ring[last_used].id);
+ *len = le32toh(used->ring[last_used].len);
+
+ if (i != last_used)
+ die("vhost: id %u at used position %u", i, last_used);
+
+ /* The kernel has queued for us something we cannot receive */
+ if (*len > VHOST_DESC_BYTES)
+ die("vhost: id %u len %zu > %zu", i, *len,
+ (size_t)VHOST_DESC_BYTES);
+
+ vhost_vq_state[0].last_used_idx++;
+ vhost_vq_state[0].num_free++;
+
+ return pkt_buf + i * VHOST_DESC_BYTES;
+}
+
+/**
+ * tap_vhost_input() - Handle frames the kernel wrote to the from-guest queue
+ * @c: Execution context
+ * @ref: epoll reference
+ * @now: Current timestamp
+ */
+void tap_vhost_input(struct ctx *c, union epoll_ref ref,
+ const struct timespec *now)
+{
+ struct virtio_net_hdr_mrg_rxbuf *hdr;
+ struct iov_tail data;
+ size_t len;
+
+ eventfd_read(ref.fd, (eventfd_t[]){ 0 });
+
+ tap_flush_pools();
+
+ while ((hdr = consume_one_rx_descriptor(&len))) {
+ /* a valid receive is one where there is a virtio net header followed by atleast sizeof(struct ethhdr) bytes */
+ if (len < (sizeof(*hdr) + sizeof(struct ethhdr))) {
+ warn("vhost: invalid len %lu", len);
+ continue;
+ }
+
+ /* Skip over the vnet header, add the packet without it */
+ data = IOV_TAIL_FROM_BUF((void *)(hdr + 1),
+ len - sizeof(*hdr), 0);
+ tap_add_packet(c, &data, now);
+ }
+
+ tap_handler(c, now);
+ vhost_rx_descriptor_handoff(c);
+}
+
/**
* tap_ns_tun() - Get tuntap fd in namespace
* @c: Execution context
@@ -1555,12 +1644,33 @@ static int tap_ns_tun(void *arg)
*/
static void tap_sock_tun_init(struct ctx *c)
{
+ int i;
+
if (!c->splice_only) {
NS_CALL(tap_ns_tun, c);
if (c->fd_tap == -1)
die("Failed to set up tap device in namespace");
}
+ /* Nothing else here runs if this fails: c->vhost.fd stays -1 and we
+ * carry on reading the tap fd ourselves, unless acceleration was
+ * explicitly asked for
+ */
+ if (c->vhost.mode != VHOST_MODE_OFF && vhost_setup_net(c) < 0 &&
+ c->vhost.mode == VHOST_MODE_ON)
+ die("vhost-net is not available");
+
+ if (c->vhost.fd != -1) {
+ for (i = 0; i < ARRAY_SIZE(c->vhost.vq); i++)
+ vhost_setup_eventfds(c, i);
+
+ if (vhost_setup_memory_table(c) < 0)
+ die_perror("VHOST_SET_MEM_TABLE ioctl failed");
+
+ for (i = 0; i < ARRAY_SIZE(c->vhost.vq); i++)
+ vhost_set_vring(c, i, c->fd_tap);
+ }
+
pasta_ns_conf(c);
if (!c->splice_only)
diff --git a/tap.h b/tap.h
index ce1efa8..b0d70f8 100644
--- a/tap.h
+++ b/tap.h
@@ -48,6 +48,8 @@ static inline void tap_hdr_update(struct tap_hdr *thdr, size_t l2len)
thdr->vnet_len = htonl(l2len);
}
+void tap_vhost_input(struct ctx *c, union epoll_ref ref,
+ const struct timespec *now);
unsigned long tap_l2_max_len(const struct ctx *c);
void *tap_push_l2h(const struct ctx *c, void *buf,
const void *src_mac, uint16_t proto);
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH 7/7] tap/tcp: Replace tcp_payload_used with a ring buffer style index
2026-09-05 16:25 ` [PATCH 2/7] conf: Add context fields, epoll types and --vhost-kernel flag to pasta aerosouund
2026-09-05 16:25 ` [PATCH 3/7] virtio: Add the pasta vhost-net interface and implementation aerosouund
2026-09-05 16:25 ` [PATCH 4/7] tap: Implement the pasta vhost-net from-guest path aerosouund
@ 2026-09-05 16:25 ` aerosouund
2 siblings, 0 replies; 13+ messages in thread
From: aerosouund @ 2026-09-05 16:25 UTC (permalink / raw)
To: passt-dev; +Cc: eperezma, Ammar Yasser
From: Ammar Yasser <aerosound161@gmail.com>
Because pasta resets tcp_payload_used to 0 after every tcp flush this
means that pasta will very quickly override data in the tcp buffers
after sending. This is not a problem when using writev because the data
will have been copied elsewhere by the kernel, but this is a problem for
vhost where the kernel thread may not have finished processing this
data. This is why a synchronous send was required in vhost, which
obviously affects performance.
Replace tcp_payload_used with a struct tcp_payload_idx holding the last
index we wrote to and a counter that gets updated when we flush packets,
from where we last flushed up to that index. Both are grow only and
access gets modded by TCP_FRAMES_MEM.
Rewrite functions in tcp_buf.c to use that index and handle the
wraparound case (sending a batch that started near the end of the
buffers and continued to be at the start) in tcp_payload_flush().
With the buffers no longer reused from under the kernel, drop the
synchronous wait from tap_send_frames_vhost() and reclaim descriptors
until there are enough for the send instead.
Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Ammar Yasser <aerosound161@gmail.com>
---
tap.c | 9 ++---
tcp_buf.c | 115 ++++++++++++++++++++++++++++++++++++++++--------------
2 files changed, 88 insertions(+), 36 deletions(-)
diff --git a/tap.c b/tap.c
index 0f51e62..670605a 100644
--- a/tap.c
+++ b/tap.c
@@ -418,7 +418,8 @@ static size_t tap_send_frames_vhost(const struct ctx *c,
/* reclaim descriptors if we don't have enough available buffers to
* perform this send
*/
- if (vhost_vq_state[1].num_free < bufs_per_frame * nframes)
+ while (vhost_vq_state[1].num_free < bufs_per_frame * nframes &&
+ vhost_vq_state[1].last_used_idx != le16toh(avail->idx))
tx_reap();
for (i = 0; i < nframes; i++) {
@@ -485,10 +486,6 @@ static size_t tap_send_frames_vhost(const struct ctx *c,
vhost_kick(&vring_used_all[1].used, c->vhost.vq[1].kick_fd);
- /* wait until the kernel finishes processing this send */
- while (avail->idx != vring_used_all[1].used.idx)
- ;
-
return processed_frames;
}
@@ -516,7 +513,7 @@ static size_t tap_send_frames_pasta(const struct ctx *c,
size_t i;
if (vhost)
- return tap_send_frames_vhost(c, iov, bufs_per_frame, nframes);
+ return tap_send_frames_vhost(c, iov, bufs_per_frame, nframes);
for (i = 0; i < nbufs; i += bufs_per_frame) {
ssize_t rc = writev(c->fd_tap, iov + i, bufs_per_frame);
diff --git a/tcp_buf.c b/tcp_buf.c
index 9cc541a..1b57ebd 100644
--- a/tcp_buf.c
+++ b/tcp_buf.c
@@ -57,7 +57,22 @@ static_assert(MSS6 <= sizeof(tcp_payload[0].data), "MSS6 is greater than 65516")
/* References tracking the owner connection of frames in the tap outqueue */
static struct tcp_tap_conn *tcp_frame_conns[TCP_FRAMES_MEM];
-static unsigned int tcp_payload_used;
+
+/**
+ * struct tcp_payload_idx - Grow-only cursors into the TCP frame buffers
+ * @tcp_buf_idx: The last index we wrote data to in the tcp buffers
+ * @processed: Last index we pushed to the underlying connection
+ */
+static struct tcp_payload_idx {
+ unsigned int tcp_buf_idx;
+ unsigned int processed;
+} tcp_idx;
+
+/* latest index we wrote to into the tcp buffers */
+#define TCP_CURR_IDX (tcp_idx.tcp_buf_idx % TCP_FRAMES_MEM)
+
+/* current amount of frames queued in tcp buffers but not sent */
+#define TCP_FRAME_COUNT (tcp_idx.tcp_buf_idx - tcp_idx.processed)
/* recvmsg()/sendmsg() data for tap */
static struct iovec iov_sock [TCP_FRAMES_MEM + DISCARD_IOV_NUM];
@@ -152,15 +167,46 @@ static void tcp_revert_seq(const struct ctx *c, struct tcp_tap_conn **conns,
*/
void tcp_payload_flush(const struct ctx *c, const struct timespec *now)
{
- size_t m;
+ unsigned int total = TCP_FRAME_COUNT;
+ unsigned int start = tcp_idx.processed % TCP_FRAMES_MEM;
+ unsigned int first_batch_size, sent;
- m = tap_send_frames(c, &tcp_l2_iov[0][0], TCP_NUM_IOVS,
- tcp_payload_used);
- if (m != tcp_payload_used) {
- tcp_revert_seq(c, &tcp_frame_conns[m], &tcp_l2_iov[m],
- tcp_payload_used - m, now);
+ if (!total)
+ return;
+
+ /* What is smaller ? all we what we want to send ? or 128 - the index
+ * indicating the start of where we wrote this batch ?
+ */
+ first_batch_size = MIN(total, TCP_FRAMES_MEM - start);
+
+ sent = tap_send_frames(c, &tcp_l2_iov[start][0], TCP_NUM_IOVS,
+ first_batch_size);
+ if (sent < first_batch_size) {
+ tcp_revert_seq(c, &tcp_frame_conns[start + sent],
+ &tcp_l2_iov[start + sent],
+ first_batch_size - sent, now);
+ goto out;
}
- tcp_payload_used = 0;
+
+ /* There was more data to send than from tcp_idx.processed up to 128.
+ * the rest of the batch is going to be at index 0 up total -
+ * first_batch_size.
+ */
+ if (total > first_batch_size) {
+ unsigned int second_batch_size = total - first_batch_size;
+ size_t m2;
+
+ m2 = tap_send_frames(c, &tcp_l2_iov[0][0], TCP_NUM_IOVS,
+ second_batch_size);
+ sent += m2;
+
+ if (m2 < second_batch_size)
+ tcp_revert_seq(c, &tcp_frame_conns[m2], &tcp_l2_iov[m2],
+ second_batch_size - m2, now);
+ }
+
+out:
+ tcp_idx.processed += sent;
}
/**
@@ -238,13 +284,13 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
uint32_t seq;
int ret;
- iov = tcp_l2_iov[tcp_payload_used];
+ iov = tcp_l2_iov[TCP_CURR_IDX];
if (CONN_V4(conn))
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[TCP_CURR_IDX]);
else
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[TCP_CURR_IDX]);
- iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp_eth_hdr[tcp_payload_used]);
+ iov[TCP_IOV_ETH] = IOV_OF_LVALUE(tcp_eth_hdr[TCP_CURR_IDX]);
payload = iov[TCP_IOV_PAYLOAD].iov_base;
seq = conn->seq_to_tap;
ret = tcp_prepare_flags(c, conn, flags, &payload->th,
@@ -253,7 +299,8 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
if (ret <= 0)
return ret;
- tcp_frame_conns[tcp_payload_used++] = conn;
+ tcp_idx.tcp_buf_idx++;
+ tcp_frame_conns[TCP_CURR_IDX] = conn;
l4len = optlen + sizeof(struct tcphdr);
iov[TCP_IOV_PAYLOAD].iov_len = l4len;
@@ -265,8 +312,9 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
tcp_l2_buf_pad(iov);
if (flags & DUP_ACK) {
- struct iovec *dup_iov = tcp_l2_iov[tcp_payload_used];
- tcp_frame_conns[tcp_payload_used++] = conn;
+ struct iovec *dup_iov = tcp_l2_iov[TCP_CURR_IDX];
+ tcp_frame_conns[TCP_CURR_IDX] = conn;
+ tcp_idx.tcp_buf_idx++;
memcpy(dup_iov[TCP_IOV_TAP].iov_base, iov[TCP_IOV_TAP].iov_base,
iov[TCP_IOV_TAP].iov_len);
@@ -278,7 +326,7 @@ int tcp_buf_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags,
dup_iov[TCP_IOV_ETH_PAD].iov_len = iov[TCP_IOV_ETH_PAD].iov_len;
}
- if (tcp_payload_used > TCP_FRAMES_MEM - 2)
+ if (TCP_FRAME_COUNT > TCP_FRAMES_MEM - 2)
tcp_payload_flush(c, now);
return 0;
@@ -303,21 +351,29 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn,
struct iovec *iov;
conn->seq_to_tap = seq + dlen;
- tcp_frame_conns[tcp_payload_used] = conn;
- iov = tcp_l2_iov[tcp_payload_used];
+ tcp_frame_conns[TCP_CURR_IDX] = conn;
+ iov = tcp_l2_iov[TCP_CURR_IDX];
if (CONN_V4(conn)) {
if (no_csum) {
- struct iovec *iov_prev = tcp_l2_iov[tcp_payload_used - 1];
+ /* TCP_CURR_IDX may be zero if the underlying
+ * tcp_idx.tcp_buf_idx is a multiple of 128, minus one
+ * will yield an invalid index. The previous index to 0
+ * is 127.
+ */
+ unsigned int prev_idx = (TCP_CURR_IDX +
+ TCP_FRAMES_MEM - 1) %
+ TCP_FRAMES_MEM;
+ struct iovec *iov_prev = tcp_l2_iov[prev_idx];
const struct iphdr *iph = iov_prev[TCP_IOV_IP].iov_base;
/* overwrite IP4_CSUM flag as we set the checksum */
check = iph->check;
}
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp4_payload_ip[TCP_CURR_IDX]);
} else if (CONN_V6(conn)) {
- iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[tcp_payload_used]);
+ iov[TCP_IOV_IP] = IOV_OF_LVALUE(tcp6_payload_ip[TCP_CURR_IDX]);
}
- iov[TCP_IOV_ETH].iov_base = &tcp_eth_hdr[tcp_payload_used];
+ iov[TCP_IOV_ETH].iov_base = &tcp_eth_hdr[TCP_CURR_IDX];
payload = iov[TCP_IOV_PAYLOAD].iov_base;
payload->th.th_off = sizeof(struct tcphdr) / 4;
payload->th.th_x2 = 0;
@@ -329,7 +385,8 @@ static void tcp_data_to_tap(const struct ctx *c, struct tcp_tap_conn *conn,
tcp_l2_buf_pad(iov);
- if (++tcp_payload_used > TCP_FRAMES_MEM - 1)
+ tcp_idx.tcp_buf_idx++;
+ if (TCP_FRAME_COUNT > TCP_FRAMES_MEM - 1)
tcp_payload_flush(c, now);
}
@@ -369,15 +426,13 @@ int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn,
return -1;
}
- if (tcp_payload_used + fill_bufs > TCP_FRAMES_MEM) {
+ if (TCP_FRAME_COUNT + (unsigned int)fill_bufs > TCP_FRAMES_MEM)
tcp_payload_flush(c, now);
- /* Silence Coverity CWE-125 false positive */
- tcp_payload_used = 0;
- }
-
for (i = 0, iov = iov_sock + DISCARD_IOV_NUM; i < fill_bufs; i++, iov++) {
- iov->iov_base = &tcp_payload[tcp_payload_used + i].data;
+ unsigned int idx = (TCP_CURR_IDX + i) % TCP_FRAMES_MEM;
+
+ iov->iov_base = &tcp_payload[idx].data;
iov->iov_len = mss;
}
if (iov_rem)
@@ -451,7 +506,7 @@ int tcp_buf_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn,
dlen = mss;
seq = conn->seq_to_tap;
for (i = 0; i < send_bufs; i++) {
- int no_csum = i && i != send_bufs - 1 && tcp_payload_used;
+ int no_csum = i && i != send_bufs - 1 && TCP_CURR_IDX;
bool push = false;
if (i == send_bufs - 1) {
--
2.39.5 (Apple Git-154)
^ permalink raw reply [flat|nested] 13+ messages in thread