* [PATCH 0/2] Fix minimum frame size checks in vhost-user paths @ 2026-02-12 11:39 Laurent Vivier 2026-02-12 11:39 ` [PATCH 1/2] virtio: Introduce VNET_HLEN macro for virtio net header length Laurent Vivier 2026-02-12 11:39 ` [PATCH 2/2] tcp_vu, udp_vu: Account for virtio net header in minimum frame size Laurent Vivier 0 siblings, 2 replies; 6+ messages in thread From: Laurent Vivier @ 2026-02-12 11:39 UTC (permalink / raw) To: passt-dev; +Cc: Laurent Vivier In the vhost-user code paths, buffers from the virtio queue include a virtio net header prepended to the Ethernet frame. The minimum frame size checks (ETH_ZLEN, i.e. 60 bytes per IEEE 802.3) must account for this extra header, otherwise the size requested from vu_collect() and validated in ASSERT() is too small. This two-patch series: 1. Introduce a VNET_HLEN macro in virtio.h to replace all open-coded sizeof(struct virtio_net_hdr_mrg_rxbuf) throughout the vhost-user code, improving readability and consistency. 2. Fix the minimum frame size calculations in tcp_vu.c and udp_vu.c to use ETH_ZLEN + VNET_HLEN instead of bare ETH_ZLEN, correctly accounting for the virtio net header in the buffer size. Laurent Vivier (2): virtio: Introduce VNET_HLEN macro for virtio net header length tcp_vu, udp_vu: Account for virtio net header in minimum frame size tcp_vu.c | 29 +++++++++++------------------ udp_vu.c | 14 +++++--------- virtio.h | 2 ++ vu_common.c | 13 +++++-------- vu_common.h | 2 +- 5 files changed, 24 insertions(+), 36 deletions(-) -- 2.52.0 ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 1/2] virtio: Introduce VNET_HLEN macro for virtio net header length 2026-02-12 11:39 [PATCH 0/2] Fix minimum frame size checks in vhost-user paths Laurent Vivier @ 2026-02-12 11:39 ` Laurent Vivier 2026-02-13 11:47 ` Stefano Brivio 2026-02-12 11:39 ` [PATCH 2/2] tcp_vu, udp_vu: Account for virtio net header in minimum frame size Laurent Vivier 1 sibling, 1 reply; 6+ messages in thread From: Laurent Vivier @ 2026-02-12 11:39 UTC (permalink / raw) To: passt-dev; +Cc: Laurent Vivier Replace all open-coded sizeof(struct virtio_net_hdr_mrg_rxbuf) with a VNET_HLEN macro. Signed-off-by: Laurent Vivier <lvivier@redhat.com> --- tcp_vu.c | 21 +++++++-------------- udp_vu.c | 12 ++++-------- virtio.h | 2 ++ vu_common.c | 13 +++++-------- vu_common.h | 2 +- 5 files changed, 19 insertions(+), 31 deletions(-) diff --git a/tcp_vu.c b/tcp_vu.c index b9e9b55ed3d3..f7bda4943e43 100644 --- a/tcp_vu.c +++ b/tcp_vu.c @@ -49,8 +49,7 @@ static size_t tcp_vu_hdrlen(bool v6) { size_t hdrlen; - hdrlen = sizeof(struct virtio_net_hdr_mrg_rxbuf) + - sizeof(struct ethhdr) + sizeof(struct tcphdr); + hdrlen = VNET_HLEN + sizeof(struct ethhdr) + sizeof(struct tcphdr); if (v6) hdrlen += sizeof(struct ipv6hdr); @@ -140,10 +139,8 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags) vu_pad(&flags_elem[0].in_sg[0], hdrlen + optlen); - if (*c->pcap) { - pcap_iov(&flags_elem[0].in_sg[0], 1, - sizeof(struct virtio_net_hdr_mrg_rxbuf)); - } + if (*c->pcap) + pcap_iov(&flags_elem[0].in_sg[0], 1, VNET_HLEN); nb_ack = 1; if (flags & DUP_ACK) { @@ -159,10 +156,8 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags) flags_elem[0].in_sg[0].iov_len); nb_ack++; - if (*c->pcap) { - pcap_iov(&flags_elem[1].in_sg[0], 1, - sizeof(struct virtio_net_hdr_mrg_rxbuf)); - } + if (*c->pcap) + pcap_iov(&flags_elem[1].in_sg[0], 1, VNET_HLEN); } } @@ -464,10 +459,8 @@ int tcp_vu_data_from_sock(const struct ctx *c, struct tcp_tap_conn *conn) /* Pad first/single buffer only, it's at least ETH_ZLEN long */ vu_pad(iov, dlen + hdrlen); - if (*c->pcap) { - pcap_iov(iov, buf_cnt, - sizeof(struct virtio_net_hdr_mrg_rxbuf)); - } + if (*c->pcap) + pcap_iov(iov, buf_cnt, VNET_HLEN); conn->seq_to_tap += dlen; } diff --git a/udp_vu.c b/udp_vu.c index 3774d538a2d0..4f1eac48d162 100644 --- a/udp_vu.c +++ b/udp_vu.c @@ -46,8 +46,7 @@ static size_t udp_vu_hdrlen(bool v6) { size_t hdrlen; - hdrlen = sizeof(struct virtio_net_hdr_mrg_rxbuf) + - sizeof(struct ethhdr) + sizeof(struct udphdr); + hdrlen = VNET_HLEN + sizeof(struct ethhdr) + sizeof(struct udphdr); if (v6) hdrlen += sizeof(struct ipv6hdr); @@ -93,9 +92,7 @@ static int udp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, int s, vu_init_elem(elem, iov_vu, VIRTQUEUE_MAX_SIZE); iov_cnt = vu_collect(vdev, vq, elem, VIRTQUEUE_MAX_SIZE, - IP_MAX_MTU + ETH_HLEN + - sizeof(struct virtio_net_hdr_mrg_rxbuf), - NULL); + IP_MAX_MTU + ETH_HLEN + VNET_HLEN, NULL); if (iov_cnt == 0) return -1; @@ -127,7 +124,7 @@ static int udp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, int s, iov_used = idx + !!off; /* pad frame to 60 bytes: first buffer is at least ETH_ZLEN long */ - l2len = *dlen + hdrlen - sizeof(struct virtio_net_hdr_mrg_rxbuf); + l2len = *dlen + hdrlen - VNET_HLEN; vu_pad(&iov_vu[0], l2len); vu_set_vnethdr(vdev, iov_vu[0].iov_base, iov_used); @@ -233,8 +230,7 @@ void udp_vu_sock_to_tap(const struct ctx *c, int s, int n, flow_sidx_t tosidx) udp_vu_prepare(c, toside, dlen); if (*c->pcap) { udp_vu_csum(toside, iov_used); - pcap_iov(iov_vu, iov_used, - sizeof(struct virtio_net_hdr_mrg_rxbuf)); + pcap_iov(iov_vu, iov_used, VNET_HLEN); } vu_flush(vdev, vq, elem, iov_used); } diff --git a/virtio.h b/virtio.h index 12caaa0b6def..d04bbe84e5c4 100644 --- a/virtio.h +++ b/virtio.h @@ -15,6 +15,8 @@ /* Maximum size of a virtqueue */ #define VIRTQUEUE_MAX_SIZE 1024 +#define VNET_HLEN (sizeof(struct virtio_net_hdr_mrg_rxbuf)) + /** * struct vu_ring - Virtqueue rings * @num: Size of the queue diff --git a/vu_common.c b/vu_common.c index c682498fb555..aa14598ea028 100644 --- a/vu_common.c +++ b/vu_common.c @@ -261,7 +261,7 @@ int vu_send_single(const struct ctx *c, const void *buf, size_t size) vu_init_elem(elem, in_sg, VIRTQUEUE_MAX_SIZE); - size += sizeof(struct virtio_net_hdr_mrg_rxbuf); + size += VNET_HLEN; elem_cnt = vu_collect(vdev, vq, elem, VIRTQUEUE_MAX_SIZE, size, &total); if (total < size) { debug("vu_send_single: no space to send the data " @@ -271,16 +271,13 @@ int vu_send_single(const struct ctx *c, const void *buf, size_t size) vu_set_vnethdr(vdev, in_sg[0].iov_base, elem_cnt); - total -= sizeof(struct virtio_net_hdr_mrg_rxbuf); + total -= VNET_HLEN; /* copy data from the buffer to the iovec */ - iov_from_buf(in_sg, elem_cnt, sizeof(struct virtio_net_hdr_mrg_rxbuf), - buf, total); + iov_from_buf(in_sg, elem_cnt, VNET_HLEN, buf, total); - if (*c->pcap) { - pcap_iov(in_sg, elem_cnt, - sizeof(struct virtio_net_hdr_mrg_rxbuf)); - } + if (*c->pcap) + pcap_iov(in_sg, elem_cnt, VNET_HLEN); vu_flush(vdev, vq, elem, elem_cnt); diff --git a/vu_common.h b/vu_common.h index b5112c1e059d..052aff710502 100644 --- a/vu_common.h +++ b/vu_common.h @@ -11,7 +11,7 @@ static inline void *vu_eth(void *base) { - return ((char *)base + sizeof(struct virtio_net_hdr_mrg_rxbuf)); + return ((char *)base + VNET_HLEN); } static inline void *vu_ip(void *base) -- 2.52.0 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 1/2] virtio: Introduce VNET_HLEN macro for virtio net header length 2026-02-12 11:39 ` [PATCH 1/2] virtio: Introduce VNET_HLEN macro for virtio net header length Laurent Vivier @ 2026-02-13 11:47 ` Stefano Brivio 0 siblings, 0 replies; 6+ messages in thread From: Stefano Brivio @ 2026-02-13 11:47 UTC (permalink / raw) To: Laurent Vivier; +Cc: passt-dev On Thu, 12 Feb 2026 12:39:31 +0100 Laurent Vivier <lvivier@redhat.com> wrote: > Replace all open-coded sizeof(struct virtio_net_hdr_mrg_rxbuf) with a > VNET_HLEN macro. > > Signed-off-by: Laurent Vivier <lvivier@redhat.com> > --- > tcp_vu.c | 21 +++++++-------------- > udp_vu.c | 12 ++++-------- > virtio.h | 2 ++ > vu_common.c | 13 +++++-------- > vu_common.h | 2 +- > 5 files changed, 19 insertions(+), 31 deletions(-) > > diff --git a/tcp_vu.c b/tcp_vu.c > index b9e9b55ed3d3..f7bda4943e43 100644 > --- a/tcp_vu.c > +++ b/tcp_vu.c > @@ -49,8 +49,7 @@ static size_t tcp_vu_hdrlen(bool v6) > { > size_t hdrlen; > > - hdrlen = sizeof(struct virtio_net_hdr_mrg_rxbuf) + > - sizeof(struct ethhdr) + sizeof(struct tcphdr); > + hdrlen = VNET_HLEN + sizeof(struct ethhdr) + sizeof(struct tcphdr); My eyes thank you dearly. -- Stefano ^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/2] tcp_vu, udp_vu: Account for virtio net header in minimum frame size 2026-02-12 11:39 [PATCH 0/2] Fix minimum frame size checks in vhost-user paths Laurent Vivier 2026-02-12 11:39 ` [PATCH 1/2] virtio: Introduce VNET_HLEN macro for virtio net header length Laurent Vivier @ 2026-02-12 11:39 ` Laurent Vivier 2026-02-13 11:48 ` Stefano Brivio 2026-02-15 10:56 ` Stefano Brivio 1 sibling, 2 replies; 6+ messages in thread From: Laurent Vivier @ 2026-02-12 11:39 UTC (permalink / raw) To: passt-dev; +Cc: Laurent Vivier In the vhost-user paths, the buffers provided by the virtio queue include the virtio net header (VNET_HLEN) prepended to the Ethernet frame. The minimum size checks using ETH_ZLEN must therefore account for this additional header length, otherwise we underestimate the minimum buffer size needed. Use ETH_ZLEN + VNET_HLEN instead of bare ETH_ZLEN in vu_collect() calls and the corresponding ASSERT() checks. Fixes: 0cb8f9003654 ("tcp, udp: Pad batched frames for vhost-user modes to 60 bytes (802.3 minimum)") Signed-off-by: Laurent Vivier <lvivier@redhat.com> --- tcp_vu.c | 8 ++++---- udp_vu.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tcp_vu.c b/tcp_vu.c index f7bda4943e43..2d593d534d68 100644 --- a/tcp_vu.c +++ b/tcp_vu.c @@ -90,12 +90,12 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags) vu_set_element(&flags_elem[0], NULL, &flags_iov[0]); elem_cnt = vu_collect(vdev, vq, &flags_elem[0], 1, - MAX(hdrlen + sizeof(*opts), ETH_ZLEN), NULL); + MAX(hdrlen + sizeof(*opts), ETH_ZLEN + VNET_HLEN), NULL); if (elem_cnt != 1) return -1; ASSERT(flags_elem[0].in_sg[0].iov_len >= - MAX(hdrlen + sizeof(*opts), ETH_ZLEN)); + MAX(hdrlen + sizeof(*opts), ETH_ZLEN + VNET_HLEN)); vu_set_vnethdr(vdev, flags_elem[0].in_sg[0].iov_base, 1); @@ -208,7 +208,7 @@ static ssize_t tcp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, cnt = vu_collect(vdev, vq, &elem[elem_cnt], VIRTQUEUE_MAX_SIZE - elem_cnt, - MAX(MIN(mss, fillsize) + hdrlen, ETH_ZLEN), + MAX(MIN(mss, fillsize) + hdrlen, ETH_ZLEN + VNET_HLEN), &frame_size); if (cnt == 0) break; @@ -302,7 +302,7 @@ static void tcp_vu_prepare(const struct ctx *c, struct tcp_tap_conn *conn, /* we guess the first iovec provided by the guest can embed * all the headers needed by L2 frame, including any padding */ - ASSERT(iov[0].iov_len >= MAX(hdrlen, ETH_ZLEN)); + ASSERT(iov[0].iov_len >= MAX(hdrlen, ETH_ZLEN + VNET_HLEN)); eh = vu_eth(base); diff --git a/udp_vu.c b/udp_vu.c index 4f1eac48d162..d190fef82d49 100644 --- a/udp_vu.c +++ b/udp_vu.c @@ -97,7 +97,7 @@ static int udp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, int s, return -1; /* reserve space for the headers */ - ASSERT(iov_vu[0].iov_len >= MAX(hdrlen, ETH_ZLEN)); + ASSERT(iov_vu[0].iov_len >= MAX(hdrlen, ETH_ZLEN + VNET_HLEN)); iov_vu[0].iov_base = (char *)iov_vu[0].iov_base + hdrlen; iov_vu[0].iov_len -= hdrlen; -- 2.52.0 ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] tcp_vu, udp_vu: Account for virtio net header in minimum frame size 2026-02-12 11:39 ` [PATCH 2/2] tcp_vu, udp_vu: Account for virtio net header in minimum frame size Laurent Vivier @ 2026-02-13 11:48 ` Stefano Brivio 2026-02-15 10:56 ` Stefano Brivio 1 sibling, 0 replies; 6+ messages in thread From: Stefano Brivio @ 2026-02-13 11:48 UTC (permalink / raw) To: Laurent Vivier; +Cc: passt-dev On Thu, 12 Feb 2026 12:39:32 +0100 Laurent Vivier <lvivier@redhat.com> wrote: > In the vhost-user paths, the buffers provided by the virtio queue > include the virtio net header (VNET_HLEN) prepended to the Ethernet > frame. The minimum size checks using ETH_ZLEN must therefore account > for this additional header length, otherwise we underestimate the > minimum buffer size needed. Oops, thanks for fixing this. > Use ETH_ZLEN + VNET_HLEN instead of bare ETH_ZLEN in vu_collect() > calls and the corresponding ASSERT() checks. > > Fixes: 0cb8f9003654 ("tcp, udp: Pad batched frames for vhost-user modes to 60 bytes (802.3 minimum)") > Signed-off-by: Laurent Vivier <lvivier@redhat.com> > --- > tcp_vu.c | 8 ++++---- > udp_vu.c | 2 +- > 2 files changed, 5 insertions(+), 5 deletions(-) > > diff --git a/tcp_vu.c b/tcp_vu.c > index f7bda4943e43..2d593d534d68 100644 > --- a/tcp_vu.c > +++ b/tcp_vu.c > @@ -90,12 +90,12 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags) > vu_set_element(&flags_elem[0], NULL, &flags_iov[0]); > > elem_cnt = vu_collect(vdev, vq, &flags_elem[0], 1, > - MAX(hdrlen + sizeof(*opts), ETH_ZLEN), NULL); > + MAX(hdrlen + sizeof(*opts), ETH_ZLEN + VNET_HLEN), NULL); I'm applying this now but, perhaps as a follow-up, should we consider to clarify further the arguments to vu_collect(), and perhaps related sizes? I'm not the most indicated person to do this I guess, as I keep getting them wrong. :( In this case, I really thought VNET_HLEN would be added internally, because of: * @size: Maximum size of the data in the frame -- Stefano ^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 2/2] tcp_vu, udp_vu: Account for virtio net header in minimum frame size 2026-02-12 11:39 ` [PATCH 2/2] tcp_vu, udp_vu: Account for virtio net header in minimum frame size Laurent Vivier 2026-02-13 11:48 ` Stefano Brivio @ 2026-02-15 10:56 ` Stefano Brivio 1 sibling, 0 replies; 6+ messages in thread From: Stefano Brivio @ 2026-02-15 10:56 UTC (permalink / raw) To: Laurent Vivier; +Cc: passt-dev On Thu, 12 Feb 2026 12:39:32 +0100 Laurent Vivier <lvivier@redhat.com> wrote: > In the vhost-user paths, the buffers provided by the virtio queue > include the virtio net header (VNET_HLEN) prepended to the Ethernet > frame. The minimum size checks using ETH_ZLEN must therefore account > for this additional header length, otherwise we underestimate the > minimum buffer size needed. > > Use ETH_ZLEN + VNET_HLEN instead of bare ETH_ZLEN in vu_collect() > calls and the corresponding ASSERT() checks. > > Fixes: 0cb8f9003654 ("tcp, udp: Pad batched frames for vhost-user modes to 60 bytes (802.3 minimum)") > Signed-off-by: Laurent Vivier <lvivier@redhat.com> > --- > tcp_vu.c | 8 ++++---- > udp_vu.c | 2 +- > 2 files changed, 5 insertions(+), 5 deletions(-) > > diff --git a/tcp_vu.c b/tcp_vu.c > index f7bda4943e43..2d593d534d68 100644 > --- a/tcp_vu.c > +++ b/tcp_vu.c > @@ -90,12 +90,12 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags) > vu_set_element(&flags_elem[0], NULL, &flags_iov[0]); > > elem_cnt = vu_collect(vdev, vq, &flags_elem[0], 1, > - MAX(hdrlen + sizeof(*opts), ETH_ZLEN), NULL); > + MAX(hdrlen + sizeof(*opts), ETH_ZLEN + VNET_HLEN), NULL); > if (elem_cnt != 1) > return -1; > > ASSERT(flags_elem[0].in_sg[0].iov_len >= > - MAX(hdrlen + sizeof(*opts), ETH_ZLEN)); > + MAX(hdrlen + sizeof(*opts), ETH_ZLEN + VNET_HLEN)); > > vu_set_vnethdr(vdev, flags_elem[0].in_sg[0].iov_base, 1); > > @@ -208,7 +208,7 @@ static ssize_t tcp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, > > cnt = vu_collect(vdev, vq, &elem[elem_cnt], > VIRTQUEUE_MAX_SIZE - elem_cnt, > - MAX(MIN(mss, fillsize) + hdrlen, ETH_ZLEN), > + MAX(MIN(mss, fillsize) + hdrlen, ETH_ZLEN + VNET_HLEN), > &frame_size); > if (cnt == 0) > break; > @@ -302,7 +302,7 @@ static void tcp_vu_prepare(const struct ctx *c, struct tcp_tap_conn *conn, > /* we guess the first iovec provided by the guest can embed > * all the headers needed by L2 frame, including any padding > */ > - ASSERT(iov[0].iov_len >= MAX(hdrlen, ETH_ZLEN)); > + ASSERT(iov[0].iov_len >= MAX(hdrlen, ETH_ZLEN + VNET_HLEN)); This triggers in the passt_vu_in_ns/tcp, "TCP/IPv4: host to guest: big transfer" test case, that is, the first time we connect to the guest (probably on the initial SYN segment). I didn't check why. I applied 1/2, but not this one. -- Stefano ^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-02-15 10:56 UTC | newest] Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-02-12 11:39 [PATCH 0/2] Fix minimum frame size checks in vhost-user paths Laurent Vivier 2026-02-12 11:39 ` [PATCH 1/2] virtio: Introduce VNET_HLEN macro for virtio net header length Laurent Vivier 2026-02-13 11:47 ` Stefano Brivio 2026-02-12 11:39 ` [PATCH 2/2] tcp_vu, udp_vu: Account for virtio net header in minimum frame size Laurent Vivier 2026-02-13 11:48 ` Stefano Brivio 2026-02-15 10:56 ` Stefano Brivio
Code repositories for project(s) associated with this public inbox https://passt.top/passt This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for IMAP folder(s).