Re: [PATCH net v7 1/2] virtio-net: correct hdr_len handling for VIRTIO_NET_F_GUEST_HDRLEN
From: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
Date: 2026-03-03 09:11:31
Also in:
virtualization
On Tue, 3 Mar 2026 11:15:16 +0800, Jason Wang [off-list ref] wrote:
On Mon, Mar 2, 2026 at 5:04 PM Xuan Zhuo [off-list ref] wrote:quoted
The commit be50da3e9d4a ("net: virtio_net: implement exact header length guest feature") introduces support for the VIRTIO_NET_F_GUEST_HDRLEN feature in virtio-net. This feature requires virtio-net to set hdr_len to the actual header length of the packet when transmitting, the number of bytes from the start of the packet to the beginning of the transport-layer payload. However, in practice, hdr_len was being set using skb_headlen(skb), which is clearly incorrect. This commit fixes that issue. Fixes: be50da3e9d4a ("net: virtio_net: implement exact header length guest feature") Signed-off-by: Xuan Zhuo <xuanzhuo@linux.alibaba.com> --- drivers/net/tun_vnet.h | 2 +- drivers/net/virtio_net.c | 8 +++-- include/linux/virtio_net.h | 61 ++++++++++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 15 deletions(-)diff --git a/drivers/net/tun_vnet.h b/drivers/net/tun_vnet.h index a5f93b6c4482..fa5cab9d3e55 100644 --- a/drivers/net/tun_vnet.h +++ b/drivers/net/tun_vnet.h@@ -244,7 +244,7 @@ tun_vnet_hdr_tnl_from_skb(unsigned int flags, if (virtio_net_hdr_tnl_from_skb(skb, tnl_hdr, has_tnl_offload, tun_vnet_is_little_endian(flags), - vlan_hlen, true)) { + vlan_hlen, true, false)) { struct virtio_net_hdr_v1 *hdr = &tnl_hdr->hash_hdr.hdr; struct skb_shared_info *sinfo = skb_shinfo(skb);diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index 72d6a9c6a5a2..1800058c8072 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c@@ -3265,9 +3265,13 @@ static int xmit_skb(struct send_queue *sq, struct sk_buff *skb, bool orphan) const unsigned char *dest = ((struct ethhdr *)skb->data)->h_dest; struct virtnet_info *vi = sq->vq->vdev->priv; struct virtio_net_hdr_v1_hash_tunnel *hdr; - int num_sg; unsigned hdr_len = vi->hdr_len; + bool feature_hdrlen; bool can_push; + int num_sg;nit: the change to num_sg seems to be unnecessary.quoted
+ + feature_hdrlen = virtio_has_feature(vi->vdev, + VIRTIO_NET_F_GUEST_HDRLEN); pr_debug("%s: xmit %p %pM\n", vi->dev->name, skb, dest);@@ -3288,7 +3292,7 @@ static int xmit_skb(struct send_queue *sq, struct sk_buff *skb, bool orphan) if (virtio_net_hdr_tnl_from_skb(skb, hdr, vi->tx_tnl, virtio_is_little_endian(vi->vdev), 0, - false)) + false, feature_hdrlen)) return -EPROTO; if (vi->mergeable_rx_bufs)diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index 75dabb763c65..d31c9af1f3d6 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h@@ -207,20 +207,40 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, return __virtio_net_hdr_to_skb(skb, hdr, little_endian, hdr->gso_type); } -static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, - struct virtio_net_hdr *hdr, - bool little_endian, - bool has_data_valid, - int vlan_hlen) +static inline void virtio_net_set_hdrlen(const struct sk_buff *skb, + struct virtio_net_hdr *hdr, + bool little_endian, + bool feature_hdrlen) +{ + u16 hdr_len; + + if (feature_hdrlen) { + hdr_len = skb_transport_offset(skb); + + if (hdr->gso_type == VIRTIO_NET_HDR_GSO_UDP_L4) + hdr_len += sizeof(struct udphdr); + else + hdr_len += tcp_hdrlen(skb); + } else { + /* This is a hint as to how much should be linear. */ + hdr_len = skb_headlen(skb); + } + + hdr->hdr_len = __cpu_to_virtio16(little_endian, hdr_len); +} + +static inline int __virtio_net_hdr_from_skb(const struct sk_buff *skb, + struct virtio_net_hdr *hdr, + bool little_endian, + bool has_data_valid, + bool feature_hdrlen, + int vlan_hlen) { memset(hdr, 0, sizeof(*hdr)); /* no info leak */ if (skb_is_gso(skb)) { struct skb_shared_info *sinfo = skb_shinfo(skb); - /* This is a hint as to how much should be linear. */ - hdr->hdr_len = __cpu_to_virtio16(little_endian, - skb_headlen(skb)); hdr->gso_size = __cpu_to_virtio16(little_endian, sinfo->gso_size); if (sinfo->gso_type & SKB_GSO_TCPV4)@@ -231,6 +251,10 @@ static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, hdr->gso_type = VIRTIO_NET_HDR_GSO_UDP_L4; else return -EINVAL; + + virtio_net_set_hdrlen(skb, hdr, little_endian, + feature_hdrlen); + if (sinfo->gso_type & SKB_GSO_TCP_ECN) hdr->gso_type |= VIRTIO_NET_HDR_GSO_ECN; } else@@ -250,6 +274,16 @@ static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, return 0; } +static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, + struct virtio_net_hdr *hdr, + bool little_endian, + bool has_data_valid, + int vlan_hlen) +{ + return __virtio_net_hdr_from_skb(skb, hdr, little_endian, + has_data_valid, false, vlan_hlen); +} + static inline unsigned int virtio_l3min(bool is_ipv6) { return is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr);@@ -385,7 +419,8 @@ virtio_net_hdr_tnl_from_skb(const struct sk_buff *skb, bool tnl_hdr_negotiated, bool little_endian, int vlan_hlen, - bool has_data_valid) + bool has_data_valid, + bool feature_hdrlen) { struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)vhdr; unsigned int inner_nh, outer_th;@@ -395,8 +430,9 @@ virtio_net_hdr_tnl_from_skb(const struct sk_buff *skb, tnl_gso_type = skb_shinfo(skb)->gso_type & (SKB_GSO_UDP_TUNNEL | SKB_GSO_UDP_TUNNEL_CSUM); if (!tnl_gso_type) - return virtio_net_hdr_from_skb(skb, hdr, little_endian, - has_data_valid, vlan_hlen); + return __virtio_net_hdr_from_skb(skb, hdr, little_endian, + has_data_valid, + feature_hdrlen, vlan_hlen); /* Tunnel support not negotiated but skb ask for it. */ if (!tnl_hdr_negotiated)@@ -409,7 +445,8 @@ virtio_net_hdr_tnl_from_skb(const struct sk_buff *skb, /* Let the basic parsing deal with plain GSO features. */ skb_shinfo(skb)->gso_type &= ~tnl_gso_type; - ret = virtio_net_hdr_from_skb(skb, hdr, true, false, vlan_hlen); + ret = __virtio_net_hdr_from_skb(skb, hdr, true, false, + feature_hdrlen, vlan_hlen);This seems to be incorrect as tnl_gso_type is masked so __virtio_net_hdr_from_skb() can only see UDP packet for tunnel gso packet?
Yes, moving to next patch maybe more suitable.
(And it seems to be fixed in next patch, could we simply squash them)
As the issues fixed originate from different commits. This separation makes it easier to selectively revert to previous versions. Thanks.
Thanksquoted
skb_shinfo(skb)->gso_type |= tnl_gso_type; if (ret) return ret; -- 2.32.0.3.g01195cf9f