Netdev List
 help / color / mirror / Atom feed
* [RFC PATCH net-next] selftests: net: exercise virtio checksum ingress outcomes
@ 2026-10-17  6:00 Megane Hebo
  2026-10-17  6:02 ` Megane Hebo
  0 siblings, 1 reply; 4+ messages in thread
From: Megane Hebo @ 2026-10-17  6:00 UTC (permalink / raw)
  To: netdev
  Cc: linux-kselftest, davem, edumazet, kuba, pabeni, horms, shuah, mst,
	virtualization, willemdebruijn.kernel, habte.yibelo, jasowangio,
	eperezma, xuanzhuo

The virtio checksum hardening series gives virtio_net_hdr_to_skb()
three outcomes for VIRTIO_NET_HDR_F_NEEDS_CSUM: known-good partial
checksum metadata is retained, uncommon non-GSO metadata is resolved
in software at ingress, and GSO metadata that cannot be resolved safely
is rejected.

Add focused TUN tests for these three outcomes. Keep the packet shape
otherwise identical (IPv4/UDP, no encapsulation), so each test changes
only the metadata needed to select a different branch.

For the software-resolution case, verify the rx_csum_sw ethtool counter
added by the hardening series. A recognized partial checksum must not
increment it. An uncommon non-GSO checksum must increment it once. A
GSO/protocol mismatch must be rejected without incrementing it.

These are contract tests for ingress normalization: packet semantics
must be made consistent before later protocol processing observes the
packet.

This is intended as a follow-up to [PATCH net-next v2 0/7]
"more virtio-net csum hardening".

Signed-off-by: Kiyotaka Tonegawa <hebomegane@gmail.com>
---
Testing:
- applied on current upstream master after Michael's v2 0/7 series
- built tools/testing/selftests/net/tun with the normal YNL dependencies
- runtime against a kernel containing v2: not run

 tools/testing/selftests/net/tun.c | 160 ++++++++++++++++++++++++++++++
 1 file changed, 160 insertions(+)

diff --git a/tools/testing/selftests/net/tun.c
b/tools/testing/selftests/net/tun.c
index afaa81c..8775315 100644
--- a/tools/testing/selftests/net/tun.c
+++ b/tools/testing/selftests/net/tun.c
@@ -8,7 +8,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
+#include <linux/ethtool.h>
 #include <linux/if_tun.h>
+#include <linux/sockios.h>
 #include <netinet/tcp.h>
 #include <sys/ioctl.h>
 #include <sys/socket.h>
@@ -543,6 +545,76 @@ TEST_F(tun, reattach_close_delete)
        EXPECT_EQ(tun_delete(self->ifname), 0);
 }

+static int tun_get_stat(const char *ifname, const char *name, uint64_t *value)
+{
+       struct ethtool_drvinfo drvinfo = { .cmd = ETHTOOL_GDRVINFO };
+       struct ethtool_gstrings *strings = NULL;
+       struct ethtool_stats *stats = NULL;
+       struct ifreq ifr = {};
+       unsigned int i;
+       int fd, ret;
+
+       fd = socket(AF_INET, SOCK_DGRAM, 0);
+       if (fd < 0)
+               return -errno;
+
+       strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
+       ifr.ifr_data = (void *)&drvinfo;
+       if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) {
+               ret = -errno;
+               goto out;
+       }
+
+       if (!drvinfo.n_stats) {
+               ret = -ENOENT;
+               goto out;
+       }
+
+       strings = calloc(1, sizeof(*strings) +
+                        drvinfo.n_stats * ETH_GSTRING_LEN);
+       stats = calloc(1, sizeof(*stats) +
+                      drvinfo.n_stats * sizeof(stats->data[0]));
+       if (!strings || !stats) {
+               ret = -ENOMEM;
+               goto out;
+       }
+
+       strings->cmd = ETHTOOL_GSTRINGS;
+       strings->string_set = ETH_SS_STATS;
+       strings->len = drvinfo.n_stats;
+       ifr.ifr_data = (void *)strings;
+       if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) {
+               ret = -errno;
+               goto out;
+       }
+
+       stats->cmd = ETHTOOL_GSTATS;
+       stats->n_stats = drvinfo.n_stats;
+       ifr.ifr_data = (void *)stats;
+       if (ioctl(fd, SIOCETHTOOL, &ifr) < 0) {
+               ret = -errno;
+               goto out;
+       }
+
+       ret = -ENOENT;
+       for (i = 0; i < drvinfo.n_stats; i++) {
+               const char *stat_name =
+                       (const char *)&strings->data[i * ETH_GSTRING_LEN];
+
+               if (!strncmp(stat_name, name, ETH_GSTRING_LEN)) {
+                       *value = stats->data[i];
+                       ret = 0;
+                       break;
+               }
+       }
+
+out:
+       free(stats);
+       free(strings);
+       close(fd);
+       return ret;
+}
+
 FIXTURE(tun_vnet_gso)
 {
        char ifname[IFNAMSIZ];
@@ -611,6 +683,35 @@ static int build_vlan_tcpv4_gso_packet(uint8_t
*buf, int payload_len)
        return cur - buf;
 }

+static int build_ipv4_udp_partial_packet(uint8_t *buf, int payload_len,
+                                        uint8_t gso_type,
+                                        uint16_t csum_offset)
+{
+       uint8_t *cur = buf + sizeof(struct virtio_net_hdr);
+       struct virtio_net_hdr vh = { 0 };
+
+       cur += build_eth(cur, ETH_P_IP, param_hwaddr_outer_src,
+                        param_hwaddr_outer_dst);
+       cur += build_ipv4_header(cur, IPPROTO_UDP,
+                                sizeof(struct udphdr) + payload_len,
+                                &param_ipaddr4_outer_src,
+                                &param_ipaddr4_outer_dst);
+       cur += build_udp_packet(cur, UDP_SRC_PORT, UDP_DST_PORT,
+                               payload_len, AF_INET, true);
+
+       vh.flags = VIRTIO_NET_HDR_F_NEEDS_CSUM;
+       vh.hdr_len = ETH_HLEN + sizeof(struct iphdr) + sizeof(struct udphdr);
+       vh.csum_start = ETH_HLEN + sizeof(struct iphdr);
+       vh.csum_offset = csum_offset;
+       vh.gso_type = gso_type;
+       if (gso_type != VIRTIO_NET_HDR_GSO_NONE)
+               vh.gso_size = 64;
+
+       memcpy(buf, &vh, sizeof(vh));
+
+       return cur - buf;
+}
+
 TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum)
 {
        struct virtio_net_hdr vh;
@@ -678,6 +779,65 @@ TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum_64k)
        ASSERT_EQ(ret, len);
 }

+TEST_F(tun_vnet_gso, csum_partial_known_good)
+{
+       uint8_t pkt[512] = { 0 };
+       uint64_t before, after;
+       int len, ret;
+
+       ASSERT_EQ(tun_get_stat(self->ifname, "rx_csum_sw", &before), 0);
+
+       len = build_ipv4_udp_partial_packet(pkt, 128,
+                                           VIRTIO_NET_HDR_GSO_NONE,
+                                           __builtin_offsetof(struct
udphdr, check));
+       ret = write(self->fd, pkt, len);
+       ASSERT_EQ(ret, len);
+
+       ASSERT_EQ(tun_get_stat(self->ifname, "rx_csum_sw", &after), 0);
+       EXPECT_EQ(after, before);
+}
+
+TEST_F(tun_vnet_gso, csum_partial_uncommon_resolved)
+{
+       uint8_t pkt[512] = { 0 };
+       uint64_t before, after;
+       int len, ret;
+
+       ASSERT_EQ(tun_get_stat(self->ifname, "rx_csum_sw", &before), 0);
+
+       len = build_ipv4_udp_partial_packet(pkt, 128,
+                                           VIRTIO_NET_HDR_GSO_NONE,
+                                           __builtin_offsetof(struct
udphdr, source));
+       ret = write(self->fd, pkt, len);
+       ASSERT_EQ(ret, len);
+
+       ASSERT_EQ(tun_get_stat(self->ifname, "rx_csum_sw", &after), 0);
+       EXPECT_EQ(after, before + 1);
+}
+
+TEST_F(tun_vnet_gso, csum_partial_gso_protocol_mismatch_rejected)
+{
+       uint8_t pkt[512] = { 0 };
+       uint64_t before, after;
+       int len, ret;
+
+       ASSERT_EQ(tun_get_stat(self->ifname, "rx_csum_sw", &before), 0);
+
+       /* The checksum field is a valid UDP checksum field, but the
+        * virtio header claims TCPv4 GSO. GSO cannot be resolved as an
+        * arbitrary checksum write, so the ingress contract rejects it.
+        */
+       len = build_ipv4_udp_partial_packet(pkt, 256,
+                                           VIRTIO_NET_HDR_GSO_TCPV4,
+                                           __builtin_offsetof(struct
udphdr, check));
+       ret = write(self->fd, pkt, len);
+       ASSERT_EQ(ret, -1);
+       ASSERT_EQ(errno, EINVAL);
+
+       ASSERT_EQ(tun_get_stat(self->ifname, "rx_csum_sw", &after), 0);
+       EXPECT_EQ(after, before);
+}
+
 FIXTURE(tun_vnet_udptnl)
 {
        char ifname[IFNAMSIZ];
--
2.53.0

^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-10-17 16:35 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-17  6:00 [RFC PATCH net-next] selftests: net: exercise virtio checksum ingress outcomes Megane Hebo
2026-10-17  6:02 ` Megane Hebo
2026-10-17 15:55   ` Willem de Bruijn
2026-10-17 16:35     ` Megane Hebo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox