Thread (4 messages) flat view 4 messages, 3 authors, 6d ago

Re: [PATCH net-next v3] selftests: drv-net: add BIG TCP test cases

From: netdev-bot+sashiko@kernel.org
Date: 2026-09-14 05:26:39
Also in: linux-kselftest, lkml

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider:

Critical: 0 · High: 0 · Medium: 1 (1) · Low: 1

- [Medium] The four new `big_tcp_*` variants are yielded by
  `_gro_variants()` in tools/testing/selftests/drivers/net/gro_lib.py…
- [Low] The patch changes the failure-tolerance policy in `run_test()`
  from `test_name.startswith("large_")` to…

Pre-existing issues:
- [Medium] `check_recv_pkts()` (tools/testing/selftests/net/lib/gro.c)
  reads `correct_payload[num_pkt]` once per received non-FIN frame with…

--

selftests: drv-net: add BIG TCP test cases

This adds four new GRO selftest cases (big_tcp_data_same,
big_tcp_data_lrg_sml, big_tcp_tcp_seq, big_tcp_large_max) that exercise
coalescing past the legacy IP_MAXPACKET limit by raising the device
gro_max_size/gro_ipv4_max_size to 128000.  It also doubles
gro_flush_timeout for those cases and extends the KSFT_MACHINE_SLOW
failure tolerance to them.

A few questions below.
quoted hunk ↗ jump to hunk
diff --git a/tools/testing/selftests/drivers/net/gro_lib.py b/tools/testing/selftests/drivers/net/gro_lib.py
index b7ac0660adc01..abecf60baa537 100644
--- a/tools/testing/selftests/drivers/net/gro_lib.py
+++ b/tools/testing/selftests/drivers/net/gro_lib.py
[ ... ]
quoted hunk ↗ jump to hunk
@@ -91,6 +97,34 @@ def _set_mtu_restore(dev, mtu, host):
         defer(ip, f"link set dev {dev['ifname']} mtu {dev['mtu']}", host=host)
 
 
+def _set_gro_size_restore(cfg, size):
+    """
+    Set the local device's GRO size limits, then confirm they stuck.
+    """
+
+    _set_mtu_restore(cfg.dev, 4096, None)
+    _set_mtu_restore(cfg.remote_dev, 4096, cfg.remote)
+
+    if "gro_max_size" not in cfg.dev or "gro_ipv4_max_size" not in cfg.dev:
+        raise KsftSkipEx("iproute2 does not report the GRO size limits")
+
+    if (cfg.dev["gro_max_size"] == size and
+            cfg.dev["gro_ipv4_max_size"] == size):
+        return
+
+    old = (f"gro_max_size {cfg.dev['gro_max_size']} "
+           f"gro_ipv4_max_size {cfg.dev['gro_ipv4_max_size']}")
+    new = f"gro_max_size {size} gro_ipv4_max_size {size}"
+
+    ip(f"link set dev {cfg.ifname} {new}")
+    defer(ip, f"link set dev {cfg.ifname} {old}")
[ ... ]
quoted hunk ↗ jump to hunk
@@ -239,7 +273,11 @@ def _setup(cfg, mode, test_name):
         flush_path = f"/sys/class/net/{cfg.ifname}/gro_flush_timeout"
         irq_path = f"/sys/class/net/{cfg.ifname}/napi_defer_hard_irqs"
 
-        ctl_file_write(flush_path, "200000")
+        # "big_tcp_*" tests need a longer timeout, use 2x the regular timeout
+        if test_name.startswith("big_tcp_"):
+            ctl_file_write(flush_path, "400000")
+        else:
+            ctl_file_write(flush_path, "200000")
         ctl_file_write(irq_path, "10")
[ ... ]
quoted hunk ↗ jump to hunk
@@ -322,6 +368,10 @@ def _gro_variants():
         for test_name in common_tests:
             yield protocol, test_name
 
+        if protocol in ["ipv4", "ipv6"]:
+            for test_name in big_tcp_tests:
+                yield protocol, test_name
+
[Severity: Medium]
Should the new variants be restricted to the software GRO mode here?

_gro_variants() has no notion of the coalescing mode, so the four
big_tcp_* cases are also instantiated by gro_hw.py (gro_main(__file__,
"hw")) and gro_lro.py (gro_main(__file__, "lro")).

The expectations for those cases come only from the netdev software GRO
tunable.  _set_gro_size_restore() writes gro_max_size /
gro_ipv4_max_size, and the only consumer of that value in the stack is
skb_gro_receive() in net/core/gro.c:

	if (unlikely(p->len + len >= netif_get_gro_max_size(p->dev, p) ||
		     NAPI_GRO_CB(skb)->flush))
		return -E2BIG;

git grep gro_max_size drivers/net/ returns nothing, and
netif_set_gro_max_size() / netif_set_gro_ipv4_max_size() in net/core/dev.h
only WRITE_ONCE the netdev fields without notifying the driver.

So with rx-gro-hw or LRO enabled the hardware coalescer keeps to its own
(commonly 64 KB) limit and cannot produce the >65535 byte aggregate that
big_tcp_data_same and friends expect, and big_tcp_large_max cannot stop
at big_tcp_large_cnt() * calc_mss().  In that case check_recv_pkts()
fails with "incorrect number of packets" or "incorrect packet geometry".

Note also that the compensating gro_flush_timeout bump added above only
happens inside the "if mode == \"sw\":" branch of _setup(), which suggests
these cases were designed for software GRO only.
quoted hunk ↗ jump to hunk
         if protocol in ["ipv4", "ipip"]:
             for test_name in ipv4_tests:
                 yield protocol, test_name
@@ -358,7 +408,8 @@ def run_test(cfg, mode, protocol, test_name):
         if rx_proc.ret == 42:
             raise KsftFailEx(f"GRO over-coalesced in {protocol}/{test_name}")
 
-        if test_name.startswith("large_") and os.environ.get("KSFT_MACHINE_SLOW"):
+        if (test_name.startswith(("large_", "big_tcp_")) and
+                os.environ.get("KSFT_MACHINE_SLOW")):
             ksft_pr(f"Ignoring {protocol}/{test_name} failure due to slow environment")
             return
[Severity: Low]
Could the commit message mention this policy change too?

The changelog only says:

  "Use a gro_flush_timeout value 2x higher for the BIG TCP test cases
   to prevent under-coalescing."

but run_test() now also returns success for any non-over-coalescing
failure of the four new cases whenever KSFT_MACHINE_SLOW is set.  That is
described only in the v3 notes below the "---" marker, which are dropped
when the patch is applied, so git history would not record that the new
cases are advisory on slow machines.
quoted hunk ↗ jump to hunk
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index 7a333155de1ab..70b0deb3c11fa 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
[ ... ]
quoted hunk ↗ jump to hunk
@@ -166,6 +182,27 @@ static int num_large_pkt(void)
 	return max_payload() / calc_mss();
 }
 
+/* How many maximum sized segments fit under the configured limit. */
+static int big_tcp_large_cnt(void)
+{
+	return (BIG_TCP_GRO_MAX_SIZE - 1 - (total_hdr_len - ETH_HLEN)) /
+	       calc_mss();
+}
+
+/* How many calc_mss() sized segments are needed to satisfy the
+ * following condition:
+ * pkt_count * calc_mss() < IP_MAXPACKET < (pkt_count + 1) * calc_mss()
+ */
+static int big_tcp_fill_cnt(void)
+{
+	return IP_MAXPACKET / calc_mss();
+}
[ ... ]
quoted hunk ↗ jump to hunk
@@ -1161,7 +1253,7 @@ static void recv_error(int fd, int rcv_errno)
 static void check_recv_pkts(int fd, int *correct_payload,
 			    int correct_num_pkts)
 {
-	static char buffer[IP_MAXPACKET + L2_HLEN_MAX + 1];
+	static char buffer[BIG_TCP_RECV_BUF_LEN];
[Severity: Medium]
This isn't a bug introduced by this patch, but do the new cases make it
easy to read past the end of correct_payload[]?

check_recv_pkts() indexes the expectation array once per received
non-FIN frame with no bound on num_pkt:

tools/testing/selftests/net/lib/gro.c:check_recv_pkts() {
	while (1) {
		...
		vlog("%d ", data_len);
		if (data_len != correct_payload[num_pkt]) {
			vlog("[!=%d]", correct_payload[num_pkt]);
			bad_packet = true;
		}
		bytes_received += data_len;
		num_pkt++;
	}
}

while gro_receiver() supplies:

	static int correct_payload[NUM_PACKETS];

with NUM_PACKETS defined as 4.

For IPv4 at the MTU 4096 that _set_gro_size_restore() configures,
calc_mss() is 4056 and big_tcp_fill_cnt() is 16, so big_tcp_data_same,
big_tcp_data_lrg_sml and big_tcp_tcp_seq put 17 data segments on the wire
while passing correct_num_pkts of 1 or 2, and big_tcp_large_max sends
big_tcp_large_cnt() + 1 = 32 segments while passing 2.

Whenever GRO under-coalesces - which the 6 retries, the doubled
gro_flush_timeout and the KSFT_MACHINE_SLOW tolerance in gro_lib.py all
anticipate, and which is unavoidable in the hw/lro modes where
gro_max_size has no effect - the loop runs 17 or 32 times and reads
correct_payload[4] through correct_payload[31].

Would it make sense to bound the loop with something like

	if (num_pkt >= correct_num_pkts)
		break;

or to size correct_payload[] for the worst case (BIG_TCP_MAX_FILL_CNT + 1)?
 	int nhoff = ETH_HLEN + (pppoe ? PPPOE_SES_HLEN : 0);
 	struct iphdr *iph = (struct iphdr *)(buffer + nhoff);
 	struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + nhoff);
[ ... ]
quoted hunk ↗ jump to hunk
@@ -1541,6 +1633,25 @@ static void gro_sender(void)
 		send_large(txfd, &daddr, remainder + 1);
 		write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
 
+	/* big tcp sub-tests */
+	} else if (strcmp(testname, "big_tcp_data_same") == 0) {
+		send_big_tcp(txfd, &daddr, calc_mss(), big_tcp_fill_cnt(),
+			     calc_mss());
+		write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
[ ... ]
+	} else if (strcmp(testname, "big_tcp_large_max") == 0) {
+		send_big_tcp(txfd, &daddr, calc_mss(), big_tcp_large_cnt(),
+			     calc_mss());
+		write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+
[ ... ]
quoted hunk ↗ jump to hunk
@@ -1768,6 +1879,26 @@ static void gro_receiver(void)
 		printf("last segment sent individually: ");
 		check_recv_pkts(rxfd, correct_payload, 3);
 
+	/* big tcp sub-tests */
+	} else if (strcmp(testname, "big_tcp_data_same") == 0) {
+		correct_payload[0] = big_tcp_fill_len() + calc_mss();
+		printf("data packets of same size past IP_MAXPACKET: ");
+		check_recv_pkts(rxfd, correct_payload, 1);
+	} else if (strcmp(testname, "big_tcp_data_lrg_sml") == 0) {
+		correct_payload[0] = big_tcp_fill_len() + calc_mss() / 2;
+		printf("smaller last packet past IP_MAXPACKET: ");
+		check_recv_pkts(rxfd, correct_payload, 1);
+	} else if (strcmp(testname, "big_tcp_tcp_seq") == 0) {
+		correct_payload[0] = (big_tcp_fill_cnt() + 1) * calc_mss();
+		correct_payload[1] = calc_mss();
+		printf("aliased seq past IP_MAXPACKET doesn't coalesce: ");
+		check_recv_pkts(rxfd, correct_payload, 2);
+	} else if (strcmp(testname, "big_tcp_large_max") == 0) {
+		correct_payload[0] = big_tcp_large_cnt() * calc_mss();
+		correct_payload[1] = calc_mss();
+		printf("shouldn't coalesce past gro_max_size: ");
+		check_recv_pkts(rxfd, correct_payload, 2);
+
-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260913052419.77910-1-narcisav.kernel%40gmail.com
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help