Commit 071fe8b5 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

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: default avatarJakub Kicinski <kuba@kernel.org>
parents 789ec16e 764d0833
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -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

+152 −55
Original line number Diff line number Diff line
@@ -11,7 +11,9 @@
 *  as long as it is in the same flow.
 *   - data_same:    same size packets 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;
		}

		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");
	}