* [PATCH net-next v2 1/8] selftests: drv-net: gro: add data burst test case
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
@ 2026-04-02 20:59 ` Jakub Kicinski
2026-04-02 20:59 ` [PATCH net-next v2 2/8] selftests: drv-net: gro: add 1 byte payload test Jakub Kicinski
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Jakub Kicinski @ 2026-04-02 20:59 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, willemb,
petrm, anubhavsinggh, richardbgobert, linux-kselftest,
Jakub Kicinski
Add a test trying to induce a GRO context timeout followed
by another sequence of packets for the same flow. The second
burst arrives 100ms after the first one so any implementation
(SW or HW) must time out waiting at that point. We expect both
bursts to be aggregated successfully but separately.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/testing/selftests/net/lib/gro.c | 21 +++++++++++++++++++++
tools/testing/selftests/drivers/net/gro.py | 2 +-
2 files changed, 22 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index 3e611ae25f61..4d002af4a7aa 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -12,6 +12,7 @@
* - data_same: same size packets coalesce
* - data_lrg_sml: large then small coalesces
* - data_sml_lrg: small then large doesn't coalesce
+ * - data_burst: two bursts of two, separated by 100ms
*
* ack:
* Pure ACK does not coalesce.
@@ -1298,6 +1299,21 @@ static void gro_sender(void)
} else if (strcmp(testname, "data_sml_lrg") == 0) {
send_data_pkts(txfd, &daddr, PAYLOAD_LEN / 2, PAYLOAD_LEN);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+ } else if (strcmp(testname, "data_burst") == 0) {
+ static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
+
+ create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
+ write_packet(txfd, buf, total_hdr_len + PAYLOAD_LEN, &daddr);
+ create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
+ write_packet(txfd, buf, total_hdr_len + PAYLOAD_LEN, &daddr);
+
+ usleep(100 * 1000); /* 100ms */
+ create_packet(buf, PAYLOAD_LEN * 2, 0, PAYLOAD_LEN, 0);
+ write_packet(txfd, buf, total_hdr_len + PAYLOAD_LEN, &daddr);
+ create_packet(buf, PAYLOAD_LEN * 3, 0, PAYLOAD_LEN, 0);
+ write_packet(txfd, buf, total_hdr_len + PAYLOAD_LEN, &daddr);
+
+ write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
/* ack test */
} else if (strcmp(testname, "ack") == 0) {
@@ -1463,6 +1479,11 @@ static void gro_receiver(void)
correct_payload[0] = PAYLOAD_LEN / 2;
correct_payload[1] = PAYLOAD_LEN;
check_recv_pkts(rxfd, correct_payload, 2);
+ } else if (strcmp(testname, "data_burst") == 0) {
+ printf("two bursts of two data packets: ");
+ correct_payload[0] = PAYLOAD_LEN * 2;
+ correct_payload[1] = PAYLOAD_LEN * 2;
+ check_recv_pkts(rxfd, correct_payload, 2);
/* ack test */
} else if (strcmp(testname, "ack") == 0) {
diff --git a/tools/testing/selftests/drivers/net/gro.py b/tools/testing/selftests/drivers/net/gro.py
index 70709bf670c7..10da5d4bee9b 100755
--- a/tools/testing/selftests/drivers/net/gro.py
+++ b/tools/testing/selftests/drivers/net/gro.py
@@ -289,7 +289,7 @@ def _run_gro_bin(cfg, test_name, protocol=None, num_flows=None,
# Tests that work for all protocols
common_tests = [
- "data_same", "data_lrg_sml", "data_sml_lrg",
+ "data_same", "data_lrg_sml", "data_sml_lrg", "data_burst",
"ack",
"flags_psh", "flags_syn", "flags_rst", "flags_urg", "flags_cwr",
"tcp_csum", "tcp_seq", "tcp_ts", "tcp_opt",
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH net-next v2 2/8] selftests: drv-net: gro: add 1 byte payload test
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
2026-04-02 20:59 ` [PATCH net-next v2 1/8] selftests: drv-net: gro: add data burst test case Jakub Kicinski
@ 2026-04-02 20:59 ` Jakub Kicinski
2026-04-02 20:59 ` [PATCH net-next v2 3/8] selftests: drv-net: gro: always wait for FIN in the capacity test Jakub Kicinski
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Jakub Kicinski @ 2026-04-02 20:59 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, willemb,
petrm, anubhavsinggh, richardbgobert, linux-kselftest,
Jakub Kicinski
Small IPv4 packets get padded to 60B, this may break / confuse
some buggy implementations. Add a test to coalesce a 1B payload.
Keep this separate from the lrg_sml test because I suspect some
implementations may not handle this case (treat padded frames
as ineligible for coalescing).
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/testing/selftests/net/lib/gro.c | 12 ++++++++++--
tools/testing/selftests/drivers/net/gro.py | 4 +++-
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index 4d002af4a7aa..27ccd742615a 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -10,8 +10,9 @@
* packet coalesced: it can be smaller than the rest and coalesced
* as long as it is in the same flow.
* - data_same: same size packets coalesce
- * - data_lrg_sml: large then small coalesces
- * - data_sml_lrg: small then large doesn't coalesce
+ * - data_lrg_sml: large then small coalesces
+ * - data_lrg_1byte: large then 1 byte coalesces (Ethernet padding)
+ * - data_sml_lrg: small then large doesn't coalesce
* - data_burst: two bursts of two, separated by 100ms
*
* ack:
@@ -1296,6 +1297,9 @@ static void gro_sender(void)
} else if (strcmp(testname, "data_lrg_sml") == 0) {
send_data_pkts(txfd, &daddr, PAYLOAD_LEN, PAYLOAD_LEN / 2);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
+ } else if (strcmp(testname, "data_lrg_1byte") == 0) {
+ send_data_pkts(txfd, &daddr, PAYLOAD_LEN, 1);
+ write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
} else if (strcmp(testname, "data_sml_lrg") == 0) {
send_data_pkts(txfd, &daddr, PAYLOAD_LEN / 2, PAYLOAD_LEN);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
@@ -1474,6 +1478,10 @@ static void gro_receiver(void)
printf("large data packets followed by a smaller one: ");
correct_payload[0] = PAYLOAD_LEN * 1.5;
check_recv_pkts(rxfd, correct_payload, 1);
+ } else if (strcmp(testname, "data_lrg_1byte") == 0) {
+ printf("large data packet followed by a 1 byte one: ");
+ correct_payload[0] = PAYLOAD_LEN + 1;
+ check_recv_pkts(rxfd, correct_payload, 1);
} else if (strcmp(testname, "data_sml_lrg") == 0) {
printf("small data packets followed by a larger one: ");
correct_payload[0] = PAYLOAD_LEN / 2;
diff --git a/tools/testing/selftests/drivers/net/gro.py b/tools/testing/selftests/drivers/net/gro.py
index 10da5d4bee9b..73436d16b40d 100755
--- a/tools/testing/selftests/drivers/net/gro.py
+++ b/tools/testing/selftests/drivers/net/gro.py
@@ -11,6 +11,7 @@ coalescing behavior.
Test cases:
- data_same: Same size data packets coalesce
- data_lrg_sml: Large packet followed by smaller one coalesces
+ - data_lrg_1byte: Large packet followed by 1B one coalesces (Ethernet padding)
- data_sml_lrg: Small packet followed by larger one doesn't coalesce
- ack: Pure ACK packets do not coalesce
- flags_psh: Packets with PSH flag don't coalesce
@@ -289,7 +290,8 @@ def _run_gro_bin(cfg, test_name, protocol=None, num_flows=None,
# Tests that work for all protocols
common_tests = [
- "data_same", "data_lrg_sml", "data_sml_lrg", "data_burst",
+ "data_same", "data_lrg_sml", "data_sml_lrg", "data_lrg_1byte",
+ "data_burst",
"ack",
"flags_psh", "flags_syn", "flags_rst", "flags_urg", "flags_cwr",
"tcp_csum", "tcp_seq", "tcp_ts", "tcp_opt",
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH net-next v2 3/8] selftests: drv-net: gro: always wait for FIN in the capacity test
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
2026-04-02 20:59 ` [PATCH net-next v2 1/8] selftests: drv-net: gro: add data burst test case Jakub Kicinski
2026-04-02 20:59 ` [PATCH net-next v2 2/8] selftests: drv-net: gro: add 1 byte payload test Jakub Kicinski
@ 2026-04-02 20:59 ` Jakub Kicinski
2026-04-02 20:59 ` [PATCH net-next v2 4/8] selftests: drv-net: gro: prepare for ip6ip6 support Jakub Kicinski
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Jakub Kicinski @ 2026-04-02 20:59 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, willemb,
petrm, anubhavsinggh, richardbgobert, linux-kselftest,
Jakub Kicinski
The new capacity/order test exits as soon as it sees the expected
packet sequence. This may allow the "flushing" FIN packet to spill
over to the next test. Let's always wait for the FIN before exiting.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/testing/selftests/net/lib/gro.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index 27ccd742615a..f05398c18e0c 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -1154,7 +1154,7 @@ static void check_capacity_pkts(int fd)
memset(coalesced, 0, sizeof(coalesced));
memset(flow_order, -1, sizeof(flow_order));
- while (total_data < num_flows * CAPACITY_PAYLOAD_LEN * 2) {
+ while (1) {
ip_ext_len = 0;
pkt_size = recv(fd, buffer, IP_MAXPACKET + ETH_HLEN + 1, 0);
if (pkt_size < 0)
@@ -1167,7 +1167,6 @@ static void check_capacity_pkts(int fd)
tcph = (struct tcphdr *)(buffer + tcp_offset + ip_ext_len);
- /* FIN packet terminates reception */
if (tcph->fin)
break;
@@ -1189,7 +1188,13 @@ static void check_capacity_pkts(int fd)
data_len = pkt_size - total_hdr_len - ip_ext_len;
}
- flow_order[num_pkt] = flow_id;
+ if (num_pkt < num_flows * 2) {
+ flow_order[num_pkt] = flow_id;
+ } else if (num_pkt == num_flows * 2) {
+ vlog("More packets than expected (%d)\n",
+ num_flows * 2);
+ fail_reason = fail_reason ?: "too many packets";
+ }
coalesced[flow_id] = data_len;
if (data_len == CAPACITY_PAYLOAD_LEN * 2) {
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH net-next v2 4/8] selftests: drv-net: gro: prepare for ip6ip6 support
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
` (2 preceding siblings ...)
2026-04-02 20:59 ` [PATCH net-next v2 3/8] selftests: drv-net: gro: always wait for FIN in the capacity test Jakub Kicinski
@ 2026-04-02 20:59 ` Jakub Kicinski
2026-04-02 20:59 ` [PATCH net-next v2 5/8] selftests: drv-net: gro: remove TOTAL_HDR_LEN Jakub Kicinski
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Jakub Kicinski @ 2026-04-02 20:59 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, willemb,
petrm, anubhavsinggh, richardbgobert, linux-kselftest,
Jakub Kicinski
Try to use already calculated offsets and not depend on the ipip
flag as much. This patch should not change any functionality,
it's just a cleanup to make ip6ip6 support easier.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/testing/selftests/net/lib/gro.c | 73 +++++++++++++++------------
1 file changed, 41 insertions(+), 32 deletions(-)
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index f05398c18e0c..57080ecc3df8 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -139,6 +139,14 @@ static bool order_check;
#define TXTIME_DELAY_MS 5
+/* Max TCP payload that GRO will coalesce. The outer header overhead
+ * varies by encapsulation, reducing the effective max payload.
+ */
+static int max_payload(void)
+{
+ return IP_MAXPACKET - (total_hdr_len - ETH_HLEN);
+}
+
static void vlog(const char *fmt, ...)
{
va_list args;
@@ -156,15 +164,13 @@ static void setup_sock_filter(int fd)
const int ethproto_off = offsetof(struct ethhdr, h_proto);
int optlen = 0;
int ipproto_off, opt_ipproto_off;
- int next_off;
- if (ipip)
- next_off = sizeof(struct iphdr) + offsetof(struct iphdr, protocol);
- else if (proto == PF_INET)
- next_off = offsetof(struct iphdr, protocol);
+ if (proto == PF_INET)
+ ipproto_off = tcp_offset - sizeof(struct iphdr) +
+ offsetof(struct iphdr, protocol);
else
- next_off = offsetof(struct ipv6hdr, nexthdr);
- ipproto_off = ETH_HLEN + next_off;
+ ipproto_off = tcp_offset - sizeof(struct ipv6hdr) +
+ offsetof(struct ipv6hdr, nexthdr);
/* Overridden later if exthdrs are used: */
opt_ipproto_off = ipproto_off;
@@ -381,19 +387,23 @@ static void write_packet(int fd, char *buf, int len, struct sockaddr_ll *daddr)
static void create_packet(void *buf, int seq_offset, int ack_offset,
int payload_len, int fin)
{
+ int ip_hdr_len = (proto == PF_INET) ?
+ sizeof(struct iphdr) : sizeof(struct ipv6hdr);
+ int inner_ip_off = tcp_offset - ip_hdr_len;
+
memset(buf, 0, total_hdr_len);
memset(buf + total_hdr_len, 'a', payload_len);
fill_transportlayer(buf + tcp_offset, seq_offset, ack_offset,
payload_len, fin);
- if (ipip) {
- fill_networklayer(buf + ETH_HLEN, payload_len + sizeof(struct iphdr),
- IPPROTO_IPIP);
- fill_networklayer(buf + ETH_HLEN + sizeof(struct iphdr),
- payload_len, IPPROTO_TCP);
- } else {
- fill_networklayer(buf + ETH_HLEN, payload_len, IPPROTO_TCP);
+ fill_networklayer(buf + inner_ip_off, payload_len, IPPROTO_TCP);
+ if (inner_ip_off > ETH_HLEN) {
+ int encap_proto = (proto == PF_INET) ?
+ IPPROTO_IPIP : IPPROTO_IPV6;
+
+ fill_networklayer(buf + ETH_HLEN,
+ payload_len + ip_hdr_len, encap_proto);
}
fill_datalinklayer(buf);
@@ -547,8 +557,7 @@ static void send_ack(int fd, struct sockaddr_ll *daddr)
static void recompute_packet(char *buf, char *no_ext, int extlen)
{
struct tcphdr *tcphdr = (struct tcphdr *)(buf + tcp_offset);
- struct ipv6hdr *ip6h = (struct ipv6hdr *)(buf + ETH_HLEN);
- struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
+ int off;
memmove(buf, no_ext, total_hdr_len);
memmove(buf + total_hdr_len + extlen,
@@ -558,18 +567,22 @@ static void recompute_packet(char *buf, char *no_ext, int extlen)
tcphdr->check = 0;
tcphdr->check = tcp_checksum(tcphdr, PAYLOAD_LEN + extlen);
if (proto == PF_INET) {
- iph->tot_len = htons(ntohs(iph->tot_len) + extlen);
- iph->check = 0;
- iph->check = checksum_fold(iph, sizeof(struct iphdr), 0);
+ for (off = ETH_HLEN; off < tcp_offset;
+ off += sizeof(struct iphdr)) {
+ struct iphdr *iph = (struct iphdr *)(buf + off);
- if (ipip) {
- iph += 1;
iph->tot_len = htons(ntohs(iph->tot_len) + extlen);
iph->check = 0;
iph->check = checksum_fold(iph, sizeof(struct iphdr), 0);
}
} else {
- ip6h->payload_len = htons(ntohs(ip6h->payload_len) + extlen);
+ for (off = ETH_HLEN; off < tcp_offset;
+ off += sizeof(struct ipv6hdr)) {
+ struct ipv6hdr *ip6h = (struct ipv6hdr *)(buf + off);
+
+ ip6h->payload_len =
+ htons(ntohs(ip6h->payload_len) + extlen);
+ }
}
}
@@ -1425,14 +1438,12 @@ static void gro_sender(void)
/* large sub-tests */
} else if (strcmp(testname, "large_max") == 0) {
- int offset = (proto == PF_INET && !ipip) ? 20 : 0;
- int remainder = (MAX_PAYLOAD + offset) % MSS;
+ int remainder = max_payload() % MSS;
send_large(txfd, &daddr, remainder);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
} else if (strcmp(testname, "large_rem") == 0) {
- int offset = (proto == PF_INET && !ipip) ? 20 : 0;
- int remainder = (MAX_PAYLOAD + offset) % MSS;
+ int remainder = max_payload() % MSS;
send_large(txfd, &daddr, remainder + 1);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
@@ -1636,19 +1647,17 @@ static void gro_receiver(void)
/* large sub-tests */
} else if (strcmp(testname, "large_max") == 0) {
- int offset = (proto == PF_INET && !ipip) ? 20 : 0;
- int remainder = (MAX_PAYLOAD + offset) % MSS;
+ int remainder = max_payload() % MSS;
- correct_payload[0] = (MAX_PAYLOAD + offset);
+ correct_payload[0] = max_payload();
correct_payload[1] = remainder;
printf("Shouldn't coalesce if exceed IP max pkt size: ");
check_recv_pkts(rxfd, correct_payload, 2);
} else if (strcmp(testname, "large_rem") == 0) {
- int offset = (proto == PF_INET && !ipip) ? 20 : 0;
- int remainder = (MAX_PAYLOAD + offset) % MSS;
+ int remainder = max_payload() % MSS;
/* last segment sent individually, doesn't start new segment */
- correct_payload[0] = (MAX_PAYLOAD + offset) - remainder;
+ correct_payload[0] = max_payload() - remainder;
correct_payload[1] = remainder + 1;
correct_payload[2] = remainder + 1;
printf("last segment sent individually: ");
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH net-next v2 5/8] selftests: drv-net: gro: remove TOTAL_HDR_LEN
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
` (3 preceding siblings ...)
2026-04-02 20:59 ` [PATCH net-next v2 4/8] selftests: drv-net: gro: prepare for ip6ip6 support Jakub Kicinski
@ 2026-04-02 20:59 ` Jakub Kicinski
2026-04-02 20:59 ` [PATCH net-next v2 6/8] selftests: drv-net: gro: make large packet math more precise Jakub Kicinski
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Jakub Kicinski @ 2026-04-02 20:59 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, willemb,
petrm, anubhavsinggh, richardbgobert, linux-kselftest,
Jakub Kicinski
Willem points out TOTAL_HDR_LEN is identical to MAX_HDR_LEN.
This seems to have been the case ever since the test was added.
Replace the uses of TOTAL_HDR_LEN with MAX_HDR_LEN, MAX seems
more common for what this value is.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/testing/selftests/net/lib/gro.c | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index 57080ecc3df8..3616ecc42a01 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -94,7 +94,6 @@
#define START_SEQ 100
#define START_ACK 100
#define ETH_P_NONE 0
-#define TOTAL_HDR_LEN (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
#define MSS (4096 - sizeof(struct tcphdr) - sizeof(struct ipv6hdr))
#define MAX_PAYLOAD (IP_MAXPACKET - sizeof(struct tcphdr) - sizeof(struct ipv6hdr))
#define NUM_LARGE_PKT (MAX_PAYLOAD / MSS)
@@ -526,9 +525,9 @@ static void send_data_pkts(int fd, struct sockaddr_ll *daddr,
*/
static void send_large(int fd, struct sockaddr_ll *daddr, int remainder)
{
- static char pkts[NUM_LARGE_PKT][TOTAL_HDR_LEN + MSS];
- static char last[TOTAL_HDR_LEN + MSS];
- static char new_seg[TOTAL_HDR_LEN + MSS];
+ static char pkts[NUM_LARGE_PKT][MAX_HDR_LEN + MSS];
+ static char last[MAX_HDR_LEN + MSS];
+ static char new_seg[MAX_HDR_LEN + MSS];
int i;
for (i = 0; i < NUM_LARGE_PKT; i++)
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH net-next v2 6/8] selftests: drv-net: gro: make large packet math more precise
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
` (4 preceding siblings ...)
2026-04-02 20:59 ` [PATCH net-next v2 5/8] selftests: drv-net: gro: remove TOTAL_HDR_LEN Jakub Kicinski
@ 2026-04-02 20:59 ` Jakub Kicinski
2026-04-02 20:59 ` [PATCH net-next v2 7/8] selftests: drv-net: gro: test ip6ip6 Jakub Kicinski
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Jakub Kicinski @ 2026-04-02 20:59 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, willemb,
petrm, anubhavsinggh, richardbgobert, linux-kselftest,
Jakub Kicinski
When constructing the packets for large_* test cases we use
a static value for packet count and MSS. It works okay for
ipv4 vs ipv6 but the gap between ipv4 and ip6ip6 is going to
be quite significant.
Make the defines calculate the worst case values, those
are only used for sizing stack arrays. Create helpers for
calculating precise values based on the exact test case.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/testing/selftests/net/lib/gro.c | 45 +++++++++++++++++----------
1 file changed, 29 insertions(+), 16 deletions(-)
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index 3616ecc42a01..dc8638d5d74d 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -94,10 +94,11 @@
#define START_SEQ 100
#define START_ACK 100
#define ETH_P_NONE 0
-#define MSS (4096 - sizeof(struct tcphdr) - sizeof(struct ipv6hdr))
-#define MAX_PAYLOAD (IP_MAXPACKET - sizeof(struct tcphdr) - sizeof(struct ipv6hdr))
-#define NUM_LARGE_PKT (MAX_PAYLOAD / MSS)
+#define ASSUMED_MTU 4096
+#define MAX_MSS (ASSUMED_MTU - sizeof(struct iphdr) - sizeof(struct tcphdr))
#define MAX_HDR_LEN (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
+#define MAX_LARGE_PKT_CNT ((IP_MAXPACKET - (MAX_HDR_LEN - ETH_HLEN)) / \
+ (ASSUMED_MTU - (MAX_HDR_LEN - ETH_HLEN)))
#define MIN_EXTHDR_SIZE 8
#define EXT_PAYLOAD_1 "\x00\x00\x00\x00\x00\x00"
#define EXT_PAYLOAD_2 "\x11\x11\x11\x11\x11\x11"
@@ -146,6 +147,16 @@ static int max_payload(void)
return IP_MAXPACKET - (total_hdr_len - ETH_HLEN);
}
+static int calc_mss(void)
+{
+ return ASSUMED_MTU - (total_hdr_len - ETH_HLEN);
+}
+
+static int num_large_pkt(void)
+{
+ return max_payload() / calc_mss();
+}
+
static void vlog(const char *fmt, ...)
{
va_list args;
@@ -525,18 +536,20 @@ static void send_data_pkts(int fd, struct sockaddr_ll *daddr,
*/
static void send_large(int fd, struct sockaddr_ll *daddr, int remainder)
{
- static char pkts[NUM_LARGE_PKT][MAX_HDR_LEN + MSS];
- static char last[MAX_HDR_LEN + MSS];
- static char new_seg[MAX_HDR_LEN + MSS];
+ static char pkts[MAX_LARGE_PKT_CNT][MAX_HDR_LEN + MAX_MSS];
+ static char new_seg[MAX_HDR_LEN + MAX_MSS];
+ static char last[MAX_HDR_LEN + MAX_MSS];
+ const int num_pkt = num_large_pkt();
+ const int mss = calc_mss();
int i;
- for (i = 0; i < NUM_LARGE_PKT; i++)
- create_packet(pkts[i], i * MSS, 0, MSS, 0);
- create_packet(last, NUM_LARGE_PKT * MSS, 0, remainder, 0);
- create_packet(new_seg, (NUM_LARGE_PKT + 1) * MSS, 0, remainder, 0);
+ for (i = 0; i < num_pkt; i++)
+ create_packet(pkts[i], i * mss, 0, mss, 0);
+ create_packet(last, num_pkt * mss, 0, remainder, 0);
+ create_packet(new_seg, (num_pkt + 1) * mss, 0, remainder, 0);
- for (i = 0; i < NUM_LARGE_PKT; i++)
- write_packet(fd, pkts[i], total_hdr_len + MSS, daddr);
+ for (i = 0; i < num_pkt; i++)
+ write_packet(fd, pkts[i], total_hdr_len + mss, daddr);
write_packet(fd, last, total_hdr_len + remainder, daddr);
write_packet(fd, new_seg, total_hdr_len + remainder, daddr);
}
@@ -1437,12 +1450,12 @@ static void gro_sender(void)
/* large sub-tests */
} else if (strcmp(testname, "large_max") == 0) {
- int remainder = max_payload() % MSS;
+ int remainder = max_payload() % calc_mss();
send_large(txfd, &daddr, remainder);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
} else if (strcmp(testname, "large_rem") == 0) {
- int remainder = max_payload() % MSS;
+ int remainder = max_payload() % calc_mss();
send_large(txfd, &daddr, remainder + 1);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
@@ -1646,14 +1659,14 @@ static void gro_receiver(void)
/* large sub-tests */
} else if (strcmp(testname, "large_max") == 0) {
- int remainder = max_payload() % MSS;
+ int remainder = max_payload() % calc_mss();
correct_payload[0] = max_payload();
correct_payload[1] = remainder;
printf("Shouldn't coalesce if exceed IP max pkt size: ");
check_recv_pkts(rxfd, correct_payload, 2);
} else if (strcmp(testname, "large_rem") == 0) {
- int remainder = max_payload() % MSS;
+ int remainder = max_payload() % calc_mss();
/* last segment sent individually, doesn't start new segment */
correct_payload[0] = max_payload() - remainder;
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH net-next v2 7/8] selftests: drv-net: gro: test ip6ip6
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
` (5 preceding siblings ...)
2026-04-02 20:59 ` [PATCH net-next v2 6/8] selftests: drv-net: gro: make large packet math more precise Jakub Kicinski
@ 2026-04-02 20:59 ` Jakub Kicinski
2026-04-02 21:00 ` [PATCH net-next v2 8/8] selftests: drv-net: gro: add a test for bad IPv4 csum Jakub Kicinski
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Jakub Kicinski @ 2026-04-02 20:59 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, willemb,
petrm, anubhavsinggh, richardbgobert, linux-kselftest,
Jakub Kicinski
We explicitly test ipip encap. Let's add ip6ip6, too. Having
just ipip seems like favoring IPv4 which we should not do :)
Testing all combinations is left for future work, not sure
it's actually worth it.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
tools/testing/selftests/net/lib/gro.c | 23 +++++++++++++++++-----
tools/testing/selftests/drivers/net/gro.py | 2 +-
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index dc8638d5d74d..b99c0f00b8fe 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -96,7 +96,8 @@
#define ETH_P_NONE 0
#define ASSUMED_MTU 4096
#define MAX_MSS (ASSUMED_MTU - sizeof(struct iphdr) - sizeof(struct tcphdr))
-#define MAX_HDR_LEN (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
+#define MAX_HDR_LEN \
+ (ETH_HLEN + sizeof(struct ipv6hdr) * 2 + sizeof(struct tcphdr))
#define MAX_LARGE_PKT_CNT ((IP_MAXPACKET - (MAX_HDR_LEN - ETH_HLEN)) / \
(ASSUMED_MTU - (MAX_HDR_LEN - ETH_HLEN)))
#define MIN_EXTHDR_SIZE 8
@@ -131,6 +132,7 @@ static int tcp_offset = -1;
static int total_hdr_len = -1;
static int ethhdr_proto = -1;
static bool ipip;
+static bool ip6ip6;
static uint64_t txtime_ns;
static int num_flows = 4;
static bool order_check;
@@ -1125,7 +1127,8 @@ static void check_recv_pkts(int fd, int *correct_payload,
if (iph->version == 4)
ip_ext_len = (iph->ihl - 5) * 4;
- else if (ip6h->version == 6 && ip6h->nexthdr != IPPROTO_TCP)
+ else if (ip6h->version == 6 && !ip6ip6 &&
+ ip6h->nexthdr != IPPROTO_TCP)
ip_ext_len = MIN_EXTHDR_SIZE;
tcph = (struct tcphdr *)(buffer + tcp_offset + ip_ext_len);
@@ -1187,7 +1190,8 @@ static void check_capacity_pkts(int fd)
if (iph->version == 4)
ip_ext_len = (iph->ihl - 5) * 4;
- else if (ip6h->version == 6 && ip6h->nexthdr != IPPROTO_TCP)
+ else if (ip6h->version == 6 && !ip6ip6 &&
+ ip6h->nexthdr != IPPROTO_TCP)
ip_ext_len = MIN_EXTHDR_SIZE;
tcph = (struct tcphdr *)(buffer + tcp_offset + ip_ext_len);
@@ -1700,6 +1704,7 @@ static void parse_args(int argc, char **argv)
{ "ipv4", no_argument, NULL, '4' },
{ "ipv6", no_argument, NULL, '6' },
{ "ipip", no_argument, NULL, 'e' },
+ { "ip6ip6", no_argument, NULL, 'E' },
{ "num-flows", required_argument, NULL, 'n' },
{ "rx", no_argument, NULL, 'r' },
{ "saddr", required_argument, NULL, 's' },
@@ -1711,7 +1716,7 @@ static void parse_args(int argc, char **argv)
};
int c;
- while ((c = getopt_long(argc, argv, "46d:D:ei:n:rs:S:t:ov", opts, NULL)) != -1) {
+ while ((c = getopt_long(argc, argv, "46d:D:eEi:n:rs:S:t:ov", opts, NULL)) != -1) {
switch (c) {
case '4':
proto = PF_INET;
@@ -1726,6 +1731,11 @@ static void parse_args(int argc, char **argv)
proto = PF_INET;
ethhdr_proto = htons(ETH_P_IP);
break;
+ case 'E':
+ ip6ip6 = true;
+ proto = PF_INET6;
+ ethhdr_proto = htons(ETH_P_IPV6);
+ break;
case 'd':
addr4_dst = addr6_dst = optarg;
break;
@@ -1770,12 +1780,15 @@ int main(int argc, char **argv)
if (ipip) {
tcp_offset = ETH_HLEN + sizeof(struct iphdr) * 2;
total_hdr_len = tcp_offset + sizeof(struct tcphdr);
+ } else if (ip6ip6) {
+ tcp_offset = ETH_HLEN + sizeof(struct ipv6hdr) * 2;
+ total_hdr_len = tcp_offset + sizeof(struct tcphdr);
} else if (proto == PF_INET) {
tcp_offset = ETH_HLEN + sizeof(struct iphdr);
total_hdr_len = tcp_offset + sizeof(struct tcphdr);
} else if (proto == PF_INET6) {
tcp_offset = ETH_HLEN + sizeof(struct ipv6hdr);
- total_hdr_len = MAX_HDR_LEN;
+ total_hdr_len = tcp_offset + sizeof(struct tcphdr);
} else {
error(1, 0, "Protocol family is not ipv4 or ipv6");
}
diff --git a/tools/testing/selftests/drivers/net/gro.py b/tools/testing/selftests/drivers/net/gro.py
index 73436d16b40d..ee95144b73ac 100755
--- a/tools/testing/selftests/drivers/net/gro.py
+++ b/tools/testing/selftests/drivers/net/gro.py
@@ -313,7 +313,7 @@ def _run_gro_bin(cfg, test_name, protocol=None, num_flows=None,
]
for mode in ["sw", "hw", "lro"]:
- for protocol in ["ipv4", "ipv6", "ipip"]:
+ for protocol in ["ipv4", "ipv6", "ipip", "ip6ip6"]:
for test_name in common_tests:
yield mode, protocol, test_name
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH net-next v2 8/8] selftests: drv-net: gro: add a test for bad IPv4 csum
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
` (6 preceding siblings ...)
2026-04-02 20:59 ` [PATCH net-next v2 7/8] selftests: drv-net: gro: test ip6ip6 Jakub Kicinski
@ 2026-04-02 21:00 ` Jakub Kicinski
2026-04-02 21:37 ` [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Willem de Bruijn
2026-04-03 23:10 ` patchwork-bot+netdevbpf
9 siblings, 0 replies; 11+ messages in thread
From: Jakub Kicinski @ 2026-04-02 21:00 UTC (permalink / raw)
To: davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, willemb,
petrm, anubhavsinggh, richardbgobert, linux-kselftest,
Jakub Kicinski
We have a test for coalescing with bad TCP checksum, let's also
test bad IPv4 header checksum.
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
---
v2:
- add a third packet
v1: https://lore.kernel.org/all/20260401182625.372605-7-kuba@kernel.org/
---
tools/testing/selftests/net/lib/gro.c | 29 ++++++++++++++++++++++
tools/testing/selftests/drivers/net/gro.py | 1 +
2 files changed, 30 insertions(+)
diff --git a/tools/testing/selftests/net/lib/gro.c b/tools/testing/selftests/net/lib/gro.c
index b99c0f00b8fe..11b16ae5f0e8 100644
--- a/tools/testing/selftests/net/lib/gro.c
+++ b/tools/testing/selftests/net/lib/gro.c
@@ -36,6 +36,7 @@
* Packets with different (ECN, TTL, TOS) header, IP options or
* IP fragments shouldn't coalesce.
* - ip_ecn, ip_tos: shared between IPv4/IPv6
+ * - ip_csum: IPv4 only, bad IP header checksum
* - ip_ttl, ip_opt, ip_frag4: IPv4 only
* - ip_id_df*: IPv4 IP ID field coalescing tests
* - ip_frag6, ip_v6ext_*: IPv6 only
@@ -685,6 +686,24 @@ static void send_changed_checksum(int fd, struct sockaddr_ll *daddr)
write_packet(fd, buf, pkt_size, daddr);
}
+/* Packets with incorrect IPv4 header checksum don't coalesce. */
+static void send_changed_ip_checksum(int fd, struct sockaddr_ll *daddr)
+{
+ static char buf[MAX_HDR_LEN + PAYLOAD_LEN];
+ struct iphdr *iph = (struct iphdr *)(buf + ETH_HLEN);
+ int pkt_size = total_hdr_len + PAYLOAD_LEN;
+
+ create_packet(buf, 0, 0, PAYLOAD_LEN, 0);
+ write_packet(fd, buf, pkt_size, daddr);
+
+ create_packet(buf, PAYLOAD_LEN, 0, PAYLOAD_LEN, 0);
+ iph->check = iph->check - 1;
+ write_packet(fd, buf, pkt_size, daddr);
+
+ create_packet(buf, PAYLOAD_LEN * 2, 0, PAYLOAD_LEN, 0);
+ write_packet(fd, buf, pkt_size, daddr);
+}
+
/* Packets with non-consecutive sequence number don't coalesce.*/
static void send_changed_seq(int fd, struct sockaddr_ll *daddr)
{
@@ -1402,6 +1421,10 @@ static void gro_sender(void)
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
/* ip sub-tests - IPv4 only */
+ } else if (strcmp(testname, "ip_csum") == 0) {
+ send_changed_ip_checksum(txfd, &daddr);
+ usleep(fin_delay_us);
+ write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
} else if (strcmp(testname, "ip_ttl") == 0) {
send_changed_ttl(txfd, &daddr);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
@@ -1598,6 +1621,12 @@ static void gro_receiver(void)
check_recv_pkts(rxfd, correct_payload, 2);
/* ip sub-tests - IPv4 only */
+ } else if (strcmp(testname, "ip_csum") == 0) {
+ correct_payload[0] = PAYLOAD_LEN;
+ correct_payload[1] = PAYLOAD_LEN;
+ correct_payload[2] = PAYLOAD_LEN;
+ printf("bad ip checksum doesn't coalesce: ");
+ check_recv_pkts(rxfd, correct_payload, 3);
} else if (strcmp(testname, "ip_ttl") == 0) {
correct_payload[0] = PAYLOAD_LEN;
correct_payload[1] = PAYLOAD_LEN;
diff --git a/tools/testing/selftests/drivers/net/gro.py b/tools/testing/selftests/drivers/net/gro.py
index ee95144b73ac..221f27e57147 100755
--- a/tools/testing/selftests/drivers/net/gro.py
+++ b/tools/testing/selftests/drivers/net/gro.py
@@ -301,6 +301,7 @@ def _run_gro_bin(cfg, test_name, protocol=None, num_flows=None,
# Tests specific to IPv4
ipv4_tests = [
+ "ip_csum",
"ip_ttl", "ip_opt", "ip_frag4",
"ip_id_df1_inc", "ip_id_df1_fixed",
"ip_id_df0_inc", "ip_id_df0_fixed",
--
2.53.0
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
` (7 preceding siblings ...)
2026-04-02 21:00 ` [PATCH net-next v2 8/8] selftests: drv-net: gro: add a test for bad IPv4 csum Jakub Kicinski
@ 2026-04-02 21:37 ` Willem de Bruijn
2026-04-03 23:10 ` patchwork-bot+netdevbpf
9 siblings, 0 replies; 11+ messages in thread
From: Willem de Bruijn @ 2026-04-02 21:37 UTC (permalink / raw)
To: Jakub Kicinski, davem
Cc: netdev, edumazet, pabeni, andrew+netdev, horms, shuah, willemb,
petrm, anubhavsinggh, richardbgobert, linux-kselftest,
Jakub Kicinski
Jakub Kicinski wrote:
> Add a few more test cases for GRO.
>
> First 4 patches are unchanged from v1.
>
> Patches 5 and 6 are new. Willem pointed out that the defines are
> duplicated and all these imprecise defines have been annoying me
> for a while so I decided to clean them up.
>
> With the defines cleaned up and now more precise patch 7 (was 5)
> no longer has to play any games with the MTU for ip6ip6.
>
> The last patch now sends 3 segments as requested.
>
> v1: https://lore.kernel.org/20260401182625.372605-1-kuba@kernel.org
>
> Jakub Kicinski (8):
> selftests: drv-net: gro: add data burst test case
> selftests: drv-net: gro: add 1 byte payload test
> selftests: drv-net: gro: always wait for FIN in the capacity test
> selftests: drv-net: gro: prepare for ip6ip6 support
> selftests: drv-net: gro: remove TOTAL_HDR_LEN
> selftests: drv-net: gro: make large packet math more precise
> selftests: drv-net: gro: test ip6ip6
> selftests: drv-net: gro: add a test for bad IPv4 csum
>
> tools/testing/selftests/net/lib/gro.c | 207 +++++++++++++++------
> tools/testing/selftests/drivers/net/gro.py | 7 +-
> 2 files changed, 157 insertions(+), 57 deletions(-)
For the series:
Reviewed-by: Willem de Bruijn <willemb@google.com>
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases
2026-04-02 20:59 [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Jakub Kicinski
` (8 preceding siblings ...)
2026-04-02 21:37 ` [PATCH net-next v2 0/8] selftests: drv-net: gro: more test cases Willem de Bruijn
@ 2026-04-03 23:10 ` patchwork-bot+netdevbpf
9 siblings, 0 replies; 11+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-04-03 23:10 UTC (permalink / raw)
To: Jakub Kicinski
Cc: davem, netdev, edumazet, pabeni, andrew+netdev, horms, shuah,
willemb, petrm, anubhavsinggh, richardbgobert, linux-kselftest
Hello:
This series was applied to netdev/net-next.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Thu, 2 Apr 2026 13:59:52 -0700 you wrote:
> Add a few more test cases for GRO.
>
> First 4 patches are unchanged from v1.
>
> Patches 5 and 6 are new. Willem pointed out that the defines are
> duplicated and all these imprecise defines have been annoying me
> for a while so I decided to clean them up.
>
> [...]
Here is the summary with links:
- [net-next,v2,1/8] selftests: drv-net: gro: add data burst test case
https://git.kernel.org/netdev/net-next/c/30f831b44a98
- [net-next,v2,2/8] selftests: drv-net: gro: add 1 byte payload test
https://git.kernel.org/netdev/net-next/c/436ea8a1b7ed
- [net-next,v2,3/8] selftests: drv-net: gro: always wait for FIN in the capacity test
https://git.kernel.org/netdev/net-next/c/d97348474708
- [net-next,v2,4/8] selftests: drv-net: gro: prepare for ip6ip6 support
https://git.kernel.org/netdev/net-next/c/5469b695f236
- [net-next,v2,5/8] selftests: drv-net: gro: remove TOTAL_HDR_LEN
https://git.kernel.org/netdev/net-next/c/166b0cc6df8c
- [net-next,v2,6/8] selftests: drv-net: gro: make large packet math more precise
https://git.kernel.org/netdev/net-next/c/024597cc2077
- [net-next,v2,7/8] selftests: drv-net: gro: test ip6ip6
https://git.kernel.org/netdev/net-next/c/9a84a4047df7
- [net-next,v2,8/8] selftests: drv-net: gro: add a test for bad IPv4 csum
https://git.kernel.org/netdev/net-next/c/764d0833e795
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 11+ messages in thread