selftests: drv-net: gro: add test for packet ordering

Add a test to check if the NIC reorders packets if the hit GRO.

Link: https://patch.msgid.link/20260318033819.1469350-6-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski
2026-03-17 20:38:18 -07:00
parent ba5d4128fc
commit ff1cb3ad2a
2 changed files with 61 additions and 6 deletions

View File

@@ -10,7 +10,7 @@ import glob
import re
from lib.py import ksft_run, ksft_exit, ksft_pr
from lib.py import ksft_eq, ksft_ge
from lib.py import ksft_eq, ksft_ge, ksft_variants
from lib.py import NetDrvEpEnv, NetdevFamily
from lib.py import KsftSkipEx
from lib.py import bkg, cmd, defer, ethtool, ip
@@ -78,7 +78,8 @@ def _setup_isolated_queue(cfg):
return test_queue
def _run_gro_test(cfg, test_name, num_flows=None, ignore_fail=False):
def _run_gro_test(cfg, test_name, num_flows=None, ignore_fail=False,
order_check=False):
"""Run gro binary with given test and return output."""
if not hasattr(cfg, "bin_remote"):
cfg.bin_local = cfg.net_lib_dir / "gro"
@@ -98,6 +99,8 @@ def _run_gro_test(cfg, test_name, num_flows=None, ignore_fail=False):
]
if num_flows:
base_args.append(f"--num-flows {num_flows}")
if order_check:
base_args.append("--order-check")
args = " ".join(base_args)
@@ -257,13 +260,33 @@ def test_gro_stats_full(cfg):
expect_wire=gro_coalesced * 2)
@ksft_variants([4, 32, 512])
def test_gro_order(cfg, num_flows):
"""
Test that HW GRO preserves packet ordering between flows.
Packets may get delayed until the aggregate is released,
but reordering between aggregates and packet terminating
the aggregate and normal packets should not happen.
Note that this test is stricter than truly required.
Reordering packets between flows should not cause issues.
This test will also fail if traffic is run over an ECMP fabric.
"""
_setup_hw_gro(cfg)
_setup_isolated_queue(cfg)
_run_gro_test(cfg, "capacity", num_flows=num_flows, order_check=True)
def main() -> None:
""" Ksft boiler plate main """
with NetDrvEpEnv(__file__, nsim_test=False) as cfg:
cfg.netnl = NetdevFamily()
ksft_run([test_gro_stats_single,
test_gro_stats_full], args=(cfg,))
test_gro_stats_full,
test_gro_order], args=(cfg,))
ksft_exit()

View File

@@ -131,6 +131,7 @@ static int ethhdr_proto = -1;
static bool ipip;
static uint64_t txtime_ns;
static int num_flows = 4;
static bool order_check;
#define CAPACITY_PAYLOAD_LEN 200
@@ -1136,6 +1137,7 @@ static void check_capacity_pkts(int fd)
static char buffer[IP_MAXPACKET + ETH_HLEN + 1];
struct iphdr *iph = (struct iphdr *)(buffer + ETH_HLEN);
struct ipv6hdr *ip6h = (struct ipv6hdr *)(buffer + ETH_HLEN);
int num_pkt = 0, num_coal = 0, pkt_idx;
const char *fail_reason = NULL;
int flow_order[num_flows * 2];
int coalesced[num_flows];
@@ -1144,8 +1146,6 @@ static void check_capacity_pkts(int fd)
int total_data = 0;
int pkt_size = -1;
int data_len = 0;
int num_pkt = 0;
int num_coal = 0;
int flow_id;
int sport;
@@ -1203,6 +1203,34 @@ static void check_capacity_pkts(int fd)
total_data += data_len;
}
/* Check flow ordering. We expect to see all non-coalesced first segs
* then interleaved coalesced and non-coalesced second frames.
*/
pkt_idx = 0;
for (flow_id = 0; order_check && flow_id < num_flows; flow_id++) {
bool coaled = coalesced[flow_id] > CAPACITY_PAYLOAD_LEN;
if (coaled)
continue;
if (flow_order[pkt_idx] != flow_id) {
vlog("Flow order mismatch (non-coalesced) at position %d: expected flow %d, got flow %d\n",
pkt_idx, flow_id, flow_order[pkt_idx]);
fail_reason = fail_reason ?: "bad packet order (1)";
}
pkt_idx++;
}
for (flow_id = 0; order_check && flow_id < num_flows; flow_id++) {
bool coaled = coalesced[flow_id] > CAPACITY_PAYLOAD_LEN;
if (flow_order[pkt_idx] != flow_id) {
vlog("Flow order mismatch at position %d: expected flow %d, got flow %d, coalesced: %d\n",
pkt_idx, flow_id, flow_order[pkt_idx], coaled);
fail_reason = fail_reason ?: "bad packet order (2)";
}
pkt_idx++;
}
if (!fail_reason) {
vlog("All %d flows coalesced correctly\n", num_flows);
printf("Test succeeded\n\n");
@@ -1622,12 +1650,13 @@ static void parse_args(int argc, char **argv)
{ "saddr", required_argument, NULL, 's' },
{ "smac", required_argument, NULL, 'S' },
{ "test", required_argument, NULL, 't' },
{ "order-check", no_argument, NULL, 'o' },
{ "verbose", no_argument, NULL, 'v' },
{ 0, 0, 0, 0 }
};
int c;
while ((c = getopt_long(argc, argv, "46d:D:ei:n:rs:S:t:v", opts, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "46d:D:ei:n:rs:S:t:ov", opts, NULL)) != -1) {
switch (c) {
case '4':
proto = PF_INET;
@@ -1666,6 +1695,9 @@ static void parse_args(int argc, char **argv)
case 't':
testname = optarg;
break;
case 'o':
order_check = true;
break;
case 'v':
verbose = true;
break;