Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

virtio-net: correct hdr_len handling for VIRTIO_NET_F_GUEST_HDRLEN

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>
Link: https://patch.msgid.link/20260320021818.111741-2-xuanzhuo@linux.alibaba.com
Acked-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Paolo Abeni <pabeni@redhat.com>

authored by

Xuan Zhuo and committed by
Paolo Abeni
38ec410b 70b439bf

+36 -6
+1 -1
drivers/net/tun_vnet.h
··· 244 244 245 245 if (virtio_net_hdr_tnl_from_skb(skb, tnl_hdr, has_tnl_offload, 246 246 tun_vnet_is_little_endian(flags), 247 - vlan_hlen, true)) { 247 + vlan_hlen, true, false)) { 248 248 struct virtio_net_hdr_v1 *hdr = &tnl_hdr->hash_hdr.hdr; 249 249 struct skb_shared_info *sinfo = skb_shinfo(skb); 250 250
+5 -1
drivers/net/virtio_net.c
··· 3267 3267 struct virtio_net_hdr_v1_hash_tunnel *hdr; 3268 3268 int num_sg; 3269 3269 unsigned hdr_len = vi->hdr_len; 3270 + bool feature_hdrlen; 3270 3271 bool can_push; 3272 + 3273 + feature_hdrlen = virtio_has_feature(vi->vdev, 3274 + VIRTIO_NET_F_GUEST_HDRLEN); 3271 3275 3272 3276 pr_debug("%s: xmit %p %pM\n", vi->dev->name, skb, dest); 3273 3277 ··· 3292 3288 3293 3289 if (virtio_net_hdr_tnl_from_skb(skb, hdr, vi->tx_tnl, 3294 3290 virtio_is_little_endian(vi->vdev), 0, 3295 - false)) 3291 + false, feature_hdrlen)) 3296 3292 return -EPROTO; 3297 3293 3298 3294 if (vi->mergeable_rx_bufs)
+30 -4
include/linux/virtio_net.h
··· 207 207 return __virtio_net_hdr_to_skb(skb, hdr, little_endian, hdr->gso_type); 208 208 } 209 209 210 + /* This function must be called after virtio_net_hdr_from_skb(). */ 211 + static inline void __virtio_net_set_hdrlen(const struct sk_buff *skb, 212 + struct virtio_net_hdr *hdr, 213 + bool little_endian) 214 + { 215 + u16 hdr_len; 216 + 217 + hdr_len = skb_transport_offset(skb); 218 + 219 + if (hdr->gso_type == VIRTIO_NET_HDR_GSO_UDP_L4) 220 + hdr_len += sizeof(struct udphdr); 221 + else 222 + hdr_len += tcp_hdrlen(skb); 223 + 224 + hdr->hdr_len = __cpu_to_virtio16(little_endian, hdr_len); 225 + } 226 + 210 227 static inline int virtio_net_hdr_from_skb(const struct sk_buff *skb, 211 228 struct virtio_net_hdr *hdr, 212 229 bool little_endian, ··· 402 385 bool tnl_hdr_negotiated, 403 386 bool little_endian, 404 387 int vlan_hlen, 405 - bool has_data_valid) 388 + bool has_data_valid, 389 + bool feature_hdrlen) 406 390 { 407 391 struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)vhdr; 408 392 unsigned int inner_nh, outer_th; ··· 412 394 413 395 tnl_gso_type = skb_shinfo(skb)->gso_type & (SKB_GSO_UDP_TUNNEL | 414 396 SKB_GSO_UDP_TUNNEL_CSUM); 415 - if (!tnl_gso_type) 416 - return virtio_net_hdr_from_skb(skb, hdr, little_endian, 417 - has_data_valid, vlan_hlen); 397 + if (!tnl_gso_type) { 398 + ret = virtio_net_hdr_from_skb(skb, hdr, little_endian, 399 + has_data_valid, vlan_hlen); 400 + if (ret) 401 + return ret; 402 + 403 + if (feature_hdrlen && hdr->hdr_len) 404 + __virtio_net_set_hdrlen(skb, hdr, little_endian); 405 + 406 + return ret; 407 + } 418 408 419 409 /* Tunnel support not negotiated but skb ask for it. */ 420 410 if (!tnl_hdr_negotiated)