Merge branch 'selftests-drv-net-gro-more-test-cases'

Jakub Kicinski says:

====================
selftests: drv-net: gro: more test cases

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.
====================

Link: https://patch.msgid.link/20260402210000.1512696-1-kuba@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-04-03 15:05:47 -07:00
commit 071fe8b5d5
2 changed files with 157 additions and 57 deletions

View File

@ -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 _gro_variants():
# 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_lrg_1byte",
"data_burst",
"ack",
"flags_psh", "flags_syn", "flags_rst", "flags_urg", "flags_cwr",
"tcp_csum", "tcp_seq", "tcp_ts", "tcp_opt",
@ -299,6 +301,7 @@ def _gro_variants():
# 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",
@ -311,7 +314,7 @@ def _gro_variants():
]
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

View File

@ -10,8 +10,10 @@
* 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:
* Pure ACK does not coalesce.
@ -34,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
@ -92,11 +95,12 @@
#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)
#define MAX_HDR_LEN (ETH_HLEN + sizeof(struct ipv6hdr) + sizeof(struct tcphdr))
#define ASSUMED_MTU 4096
#define MAX_MSS (ASSUMED_MTU - sizeof(struct iphdr) - 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
#define EXT_PAYLOAD_1 "\x00\x00\x00\x00\x00\x00"
#define EXT_PAYLOAD_2 "\x11\x11\x11\x11\x11\x11"
@ -129,6 +133,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;
@ -137,6 +142,24 @@ 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 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;
@ -154,15 +177,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;
@ -379,19 +400,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);
@ -514,18 +539,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][TOTAL_HDR_LEN + MSS];
static char last[TOTAL_HDR_LEN + MSS];
static char new_seg[TOTAL_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);
}
@ -545,8 +572,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,
@ -556,18 +582,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);
}
}
}
@ -656,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)
{
@ -1098,7 +1146,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);
@ -1152,7 +1201,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)
@ -1160,12 +1209,12 @@ 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);
/* FIN packet terminates reception */
if (tcph->fin)
break;
@ -1187,7 +1236,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) {
@ -1295,9 +1350,27 @@ 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);
} 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) {
@ -1348,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);
@ -1400,14 +1477,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() % calc_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() % calc_mss();
send_large(txfd, &daddr, remainder + 1);
write_packet(txfd, fin_pkt, total_hdr_len, &daddr);
@ -1458,11 +1533,20 @@ 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;
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) {
@ -1537,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;
@ -1602,19 +1692,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() % calc_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() % calc_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: ");
@ -1645,6 +1733,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' },
@ -1656,7 +1745,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;
@ -1671,6 +1760,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;
@ -1715,12 +1809,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");
}