| From 1cd1c472343b06d6d32038636ce51bfa2251e3cf Mon Sep 17 00:00:00 2001 |
| From: Jon Kohler <jon@nutanix.com> |
| Date: Tue, 25 Nov 2025 15:27:53 -0700 |
| Subject: virtio-net: avoid unnecessary checksum calculation on guest RX |
| |
| From: Jon Kohler <jon@nutanix.com> |
| |
| commit 1cd1c472343b06d6d32038636ce51bfa2251e3cf upstream. |
| |
| Commit a2fb4bc4e2a6 ("net: implement virtio helpers to handle UDP |
| GSO tunneling.") inadvertently altered checksum offload behavior |
| for guests not using UDP GSO tunneling. |
| |
| Before, tun_put_user called tun_vnet_hdr_from_skb, which passed |
| has_data_valid = true to virtio_net_hdr_from_skb. |
| |
| After, tun_put_user began calling tun_vnet_hdr_tnl_from_skb instead, |
| which passes has_data_valid = false into both call sites. |
| |
| This caused virtio hdr flags to not include VIRTIO_NET_HDR_F_DATA_VALID |
| for SKBs where skb->ip_summed == CHECKSUM_UNNECESSARY. As a result, |
| guests are forced to recalculate checksums unnecessarily. |
| |
| Restore the previous behavior by ensuring has_data_valid = true is |
| passed in the !tnl_gso_type case, but only from tun side, as |
| virtio_net_hdr_tnl_from_skb() is used also by the virtio_net driver, |
| which in turn must not use VIRTIO_NET_HDR_F_DATA_VALID on tx. |
| |
| cc: stable@vger.kernel.org |
| Fixes: a2fb4bc4e2a6 ("net: implement virtio helpers to handle UDP GSO tunneling.") |
| Signed-off-by: Jon Kohler <jon@nutanix.com> |
| Acked-by: Michael S. Tsirkin <mst@redhat.com> |
| Acked-by: Jason Wang <jasowang@redhat.com> |
| Link: https://patch.msgid.link/20251125222754.1737443-1-jon@nutanix.com |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/net/tun_vnet.h | 2 +- |
| drivers/net/virtio_net.c | 3 ++- |
| include/linux/virtio_net.h | 7 ++++--- |
| 3 files changed, 7 insertions(+), 5 deletions(-) |
| |
| --- a/drivers/net/tun_vnet.h |
| +++ b/drivers/net/tun_vnet.h |
| @@ -244,7 +244,7 @@ tun_vnet_hdr_tnl_from_skb(unsigned int f |
| |
| if (virtio_net_hdr_tnl_from_skb(skb, tnl_hdr, has_tnl_offload, |
| tun_vnet_is_little_endian(flags), |
| - vlan_hlen)) { |
| + vlan_hlen, true)) { |
| struct virtio_net_hdr_v1 *hdr = &tnl_hdr->hash_hdr.hdr; |
| struct skb_shared_info *sinfo = skb_shinfo(skb); |
| |
| --- a/drivers/net/virtio_net.c |
| +++ b/drivers/net/virtio_net.c |
| @@ -3340,7 +3340,8 @@ static int xmit_skb(struct send_queue *s |
| hdr = &skb_vnet_common_hdr(skb)->tnl_hdr; |
| |
| if (virtio_net_hdr_tnl_from_skb(skb, hdr, vi->tx_tnl, |
| - virtio_is_little_endian(vi->vdev), 0)) |
| + virtio_is_little_endian(vi->vdev), 0, |
| + false)) |
| return -EPROTO; |
| |
| if (vi->mergeable_rx_bufs) |
| --- a/include/linux/virtio_net.h |
| +++ b/include/linux/virtio_net.h |
| @@ -384,7 +384,8 @@ virtio_net_hdr_tnl_from_skb(const struct |
| struct virtio_net_hdr_v1_hash_tunnel *vhdr, |
| bool tnl_hdr_negotiated, |
| bool little_endian, |
| - int vlan_hlen) |
| + int vlan_hlen, |
| + bool has_data_valid) |
| { |
| struct virtio_net_hdr *hdr = (struct virtio_net_hdr *)vhdr; |
| unsigned int inner_nh, outer_th; |
| @@ -394,8 +395,8 @@ virtio_net_hdr_tnl_from_skb(const struct |
| 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, false, |
| - vlan_hlen); |
| + return virtio_net_hdr_from_skb(skb, hdr, little_endian, |
| + has_data_valid, vlan_hlen); |
| |
| /* Tunnel support not negotiated but skb ask for it. */ |
| if (!tnl_hdr_negotiated) |