Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3327a9c4 authored by Paolo Abeni's avatar Paolo Abeni Committed by David S. Miller
Browse files

selftests: add functionals test for UDP GRO



Extends the existing udp programs to allow checking for proper
GRO aggregation/GSO size, and run the tests via a shell script, using
a veth pair with XDP program attached to trigger the GRO code path.

rfc v3 -> v1:
 - use ip route to attach the xdp helper to the veth

rfc v2 -> rfc v3:
 - add missing test program options documentation
 - fix sporatic test failures (receiver faster than sender)

Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent e87f53b4
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -7,7 +7,7 @@ CFLAGS += -I../../../../usr/include/
TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh rtnetlink.sh
TEST_PROGS := run_netsocktests run_afpackettests test_bpf.sh netdevice.sh rtnetlink.sh
TEST_PROGS += fib_tests.sh fib-onlink-tests.sh pmtu.sh udpgso.sh ip_defrag.sh
TEST_PROGS += fib_tests.sh fib-onlink-tests.sh pmtu.sh udpgso.sh ip_defrag.sh
TEST_PROGS += udpgso_bench.sh fib_rule_tests.sh msg_zerocopy.sh psock_snd.sh
TEST_PROGS += udpgso_bench.sh fib_rule_tests.sh msg_zerocopy.sh psock_snd.sh
TEST_PROGS += udpgro_bench.sh
TEST_PROGS += udpgro_bench.sh udpgro.sh
TEST_PROGS_EXTENDED := in_netns.sh
TEST_PROGS_EXTENDED := in_netns.sh
TEST_GEN_FILES =  socket
TEST_GEN_FILES =  socket
TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy
TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy
+148 −0
Original line number Original line Diff line number Diff line
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# Run a series of udpgro functional tests.

readonly PEER_NS="ns-peer-$(mktemp -u XXXXXX)"

cleanup() {
	local -r jobs="$(jobs -p)"
	local -r ns="$(ip netns list|grep $PEER_NS)"

	[ -n "${jobs}" ] && kill -1 ${jobs} 2>/dev/null
	[ -n "$ns" ] && ip netns del $ns 2>/dev/null
}
trap cleanup EXIT

cfg_veth() {
	ip netns add "${PEER_NS}"
	ip -netns "${PEER_NS}" link set lo up
	ip link add type veth
	ip link set dev veth0 up
	ip addr add dev veth0 192.168.1.2/24
	ip addr add dev veth0 2001:db8::2/64 nodad

	ip link set dev veth1 netns "${PEER_NS}"
	ip -netns "${PEER_NS}" addr add dev veth1 192.168.1.1/24
	ip -netns "${PEER_NS}" addr add dev veth1 2001:db8::1/64 nodad
	ip -netns "${PEER_NS}" link set dev veth1 up
	ip -n "${PEER_NS}" link set veth1 xdp object ../bpf/xdp_dummy.o section xdp_dummy
}

run_one() {
	# use 'rx' as separator between sender args and receiver args
	local -r all="$@"
	local -r tx_args=${all%rx*}
	local -r rx_args=${all#*rx}

	cfg_veth

	ip netns exec "${PEER_NS}" ./udpgso_bench_rx ${rx_args} && \
		echo "ok" || \
		echo "failed" &

	# Hack: let bg programs complete the startup
	sleep 0.1
	./udpgso_bench_tx ${tx_args}
	wait $(jobs -p)
}

run_test() {
	local -r args=$@

	printf " %-40s" "$1"
	./in_netns.sh $0 __subprocess $2 rx -G -r $3
}

run_one_nat() {
	# use 'rx' as separator between sender args and receiver args
	local addr1 addr2 pid family="" ipt_cmd=ip6tables
	local -r all="$@"
	local -r tx_args=${all%rx*}
	local -r rx_args=${all#*rx}

	if [[ ${tx_args} = *-4* ]]; then
		ipt_cmd=iptables
		family=-4
		addr1=192.168.1.1
		addr2=192.168.1.3/24
	else
		addr1=2001:db8::1
		addr2="2001:db8::3/64 nodad"
	fi

	cfg_veth
	ip -netns "${PEER_NS}" addr add dev veth1 ${addr2}

	# fool the GRO engine changing the destination address ...
	ip netns exec "${PEER_NS}" $ipt_cmd -t nat -I PREROUTING -d ${addr1} -j DNAT --to-destination ${addr2%/*}

	# ... so that GRO will match the UDP_GRO enabled socket, but packets
	# will land on the 'plain' one
	ip netns exec "${PEER_NS}" ./udpgso_bench_rx -G ${family} -b ${addr1} -n 0 &
	pid=$!
	ip netns exec "${PEER_NS}" ./udpgso_bench_rx ${family} -b ${addr2%/*} ${rx_args} && \
		echo "ok" || \
		echo "failed"&

	sleep 0.1
	./udpgso_bench_tx ${tx_args}
	kill -INT $pid
	wait $(jobs -p)
}

run_nat_test() {
	local -r args=$@

	printf " %-40s" "$1"
	./in_netns.sh $0 __subprocess_nat $2 rx -r $3
}

run_all() {
	local -r core_args="-l 4"
	local -r ipv4_args="${core_args} -4 -D 192.168.1.1"
	local -r ipv6_args="${core_args} -6 -D 2001:db8::1"

	echo "ipv4"
	run_test "no GRO" "${ipv4_args} -M 10 -s 1400" "-4 -n 10 -l 1400"

	# explicitly check we are not receiving UDP_SEGMENT cmsg (-S -1)
	# when GRO does not take place
	run_test "no GRO chk cmsg" "${ipv4_args} -M 10 -s 1400" "-4 -n 10 -l 1400 -S -1"

	# the GSO packets are aggregated because:
	# * veth schedule napi after each xmit
	# * segmentation happens in BH context, veth napi poll is delayed after
	#   the transmission of the last segment
	run_test "GRO" "${ipv4_args} -M 1 -s 14720 -S 0 " "-4 -n 1 -l 14720"
	run_test "GRO chk cmsg" "${ipv4_args} -M 1 -s 14720 -S 0 " "-4 -n 1 -l 14720 -S 1472"
	run_test "GRO with custom segment size" "${ipv4_args} -M 1 -s 14720 -S 500 " "-4 -n 1 -l 14720"
	run_test "GRO with custom segment size cmsg" "${ipv4_args} -M 1 -s 14720 -S 500 " "-4 -n 1 -l 14720 -S 500"

	run_nat_test "bad GRO lookup" "${ipv4_args} -M 1 -s 14720 -S 0" "-n 10 -l 1472"

	echo "ipv6"
	run_test "no GRO" "${ipv6_args} -M 10 -s 1400" "-n 10 -l 1400"
	run_test "no GRO chk cmsg" "${ipv6_args} -M 10 -s 1400" "-n 10 -l 1400 -S -1"
	run_test "GRO" "${ipv6_args} -M 1 -s 14520 -S 0" "-n 1 -l 14520"
	run_test "GRO chk cmsg" "${ipv6_args} -M 1 -s 14520 -S 0" "-n 1 -l 14520 -S 1452"
	run_test "GRO with custom segment size" "${ipv6_args} -M 1 -s 14520 -S 500" "-n 1 -l 14520"
	run_test "GRO with custom segment size cmsg" "${ipv6_args} -M 1 -s 14520 -S 500" "-n 1 -l 14520 -S 500"

	run_nat_test "bad GRO lookup" "${ipv6_args} -M 1 -s 14520 -S 0" "-n 10 -l 1452"
}

if [ ! -f ../bpf/xdp_dummy.o ]; then
	echo "Missing xdp_dummy helper. Build bpf selftest first"
	exit -1
fi

if [[ $# -eq 0 ]]; then
	run_all
elif [[ $1 == "__subprocess" ]]; then
	shift
	run_one $@
elif [[ $1 == "__subprocess_nat" ]]; then
	shift
	run_one_nat $@
fi
+5 −3
Original line number Original line Diff line number Diff line
@@ -18,7 +18,9 @@ run_one() {
	# use 'rx' as separator between sender args and receiver args
	# use 'rx' as separator between sender args and receiver args
	local -r all="$@"
	local -r all="$@"
	local -r tx_args=${all%rx*}
	local -r tx_args=${all%rx*}
	local -r rx_args=${all#*rx}
	local rx_args=${all#*rx}

	[[ "${tx_args}" == *"-4"* ]] && rx_args="${rx_args} -4"


	ip netns add "${PEER_NS}"
	ip netns add "${PEER_NS}"
	ip -netns "${PEER_NS}" link set lo up
	ip -netns "${PEER_NS}" link set lo up
@@ -51,10 +53,10 @@ run_udp() {
	local -r args=$@
	local -r args=$@


	echo "udp gso - over veth touching data"
	echo "udp gso - over veth touching data"
	run_in_netns ${args} -S rx
	run_in_netns ${args} -S 0 rx


	echo "udp gso and gro - over veth touching data"
	echo "udp gso and gro - over veth touching data"
	run_in_netns ${args} -S rx -G
	run_in_netns ${args} -S 0 rx -G
}
}


run_tcp() {
run_tcp() {
+1 −1
Original line number Original line Diff line number Diff line
@@ -34,7 +34,7 @@ run_udp() {
	run_in_netns ${args}
	run_in_netns ${args}


	echo "udp gso"
	echo "udp gso"
	run_in_netns ${args} -S
	run_in_netns ${args} -S 0
}
}


run_tcp() {
run_tcp() {
+111 −12
Original line number Original line Diff line number Diff line
@@ -40,6 +40,12 @@ static bool cfg_tcp;
static bool cfg_verify;
static bool cfg_verify;
static bool cfg_read_all;
static bool cfg_read_all;
static bool cfg_gro_segment;
static bool cfg_gro_segment;
static int  cfg_family		= PF_INET6;
static int  cfg_alen 		= sizeof(struct sockaddr_in6);
static int  cfg_expected_pkt_nr;
static int  cfg_expected_pkt_len;
static int  cfg_expected_gso_size;
static struct sockaddr_storage cfg_bind_addr;


static bool interrupted;
static bool interrupted;
static unsigned long packets, bytes;
static unsigned long packets, bytes;
@@ -50,6 +56,29 @@ static void sigint_handler(int signum)
		interrupted = true;
		interrupted = true;
}
}


static void setup_sockaddr(int domain, const char *str_addr, void *sockaddr)
{
	struct sockaddr_in6 *addr6 = (void *) sockaddr;
	struct sockaddr_in *addr4 = (void *) sockaddr;

	switch (domain) {
	case PF_INET:
		addr4->sin_family = AF_INET;
		addr4->sin_port = htons(cfg_port);
		if (inet_pton(AF_INET, str_addr, &(addr4->sin_addr)) != 1)
			error(1, 0, "ipv4 parse error: %s", str_addr);
		break;
	case PF_INET6:
		addr6->sin6_family = AF_INET6;
		addr6->sin6_port = htons(cfg_port);
		if (inet_pton(AF_INET6, str_addr, &(addr6->sin6_addr)) != 1)
			error(1, 0, "ipv6 parse error: %s", str_addr);
		break;
	default:
		error(1, 0, "illegal domain");
	}
}

static unsigned long gettimeofday_ms(void)
static unsigned long gettimeofday_ms(void)
{
{
	struct timeval tv;
	struct timeval tv;
@@ -83,10 +112,9 @@ static void do_poll(int fd)


static int do_socket(bool do_tcp)
static int do_socket(bool do_tcp)
{
{
	struct sockaddr_in6 addr = {0};
	int fd, val;
	int fd, val;


	fd = socket(PF_INET6, cfg_tcp ? SOCK_STREAM : SOCK_DGRAM, 0);
	fd = socket(cfg_family, cfg_tcp ? SOCK_STREAM : SOCK_DGRAM, 0);
	if (fd == -1)
	if (fd == -1)
		error(1, errno, "socket");
		error(1, errno, "socket");


@@ -97,10 +125,7 @@ static int do_socket(bool do_tcp)
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)))
	if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)))
		error(1, errno, "setsockopt reuseport");
		error(1, errno, "setsockopt reuseport");


	addr.sin6_family =	PF_INET6;
	if (bind(fd, (void *)&cfg_bind_addr, cfg_alen))
	addr.sin6_port =	htons(cfg_port);
	addr.sin6_addr =	in6addr_any;
	if (bind(fd, (void *) &addr, sizeof(addr)))
		error(1, errno, "bind");
		error(1, errno, "bind");


	if (do_tcp) {
	if (do_tcp) {
@@ -174,52 +199,117 @@ static void do_verify_udp(const char *data, int len)
	}
	}
}
}


static int recv_msg(int fd, char *buf, int len, int *gso_size)
{
	char control[CMSG_SPACE(sizeof(uint16_t))] = {0};
	struct msghdr msg = {0};
	struct iovec iov = {0};
	struct cmsghdr *cmsg;
	uint16_t *gsosizeptr;
	int ret;

	iov.iov_base = buf;
	iov.iov_len = len;

	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;

	msg.msg_control = control;
	msg.msg_controllen = sizeof(control);

	*gso_size = -1;
	ret = recvmsg(fd, &msg, MSG_TRUNC | MSG_DONTWAIT);
	if (ret != -1) {
		for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
		     cmsg = CMSG_NXTHDR(&msg, cmsg)) {
			if (cmsg->cmsg_level == SOL_UDP
			    && cmsg->cmsg_type == UDP_GRO) {
				gsosizeptr = (uint16_t *) CMSG_DATA(cmsg);
				*gso_size = *gsosizeptr;
				break;
			}
		}
	}
	return ret;
}

/* Flush all outstanding datagrams. Verify first few bytes of each. */
/* Flush all outstanding datagrams. Verify first few bytes of each. */
static void do_flush_udp(int fd)
static void do_flush_udp(int fd)
{
{
	static char rbuf[ETH_MAX_MTU];
	static char rbuf[ETH_MAX_MTU];
	int ret, len, budget = 256;
	int ret, len, gso_size, budget = 256;


	len = cfg_read_all ? sizeof(rbuf) : 0;
	len = cfg_read_all ? sizeof(rbuf) : 0;
	while (budget--) {
	while (budget--) {
		/* MSG_TRUNC will make return value full datagram length */
		/* MSG_TRUNC will make return value full datagram length */
		if (!cfg_expected_gso_size)
			ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT);
			ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT);
		else
			ret = recv_msg(fd, rbuf, len, &gso_size);
		if (ret == -1 && errno == EAGAIN)
		if (ret == -1 && errno == EAGAIN)
			return;
			break;
		if (ret == -1)
		if (ret == -1)
			error(1, errno, "recv");
			error(1, errno, "recv");
		if (cfg_expected_pkt_len && ret != cfg_expected_pkt_len)
			error(1, 0, "recv: bad packet len, got %d,"
			      " expected %d\n", ret, cfg_expected_pkt_len);
		if (len && cfg_verify) {
		if (len && cfg_verify) {
			if (ret == 0)
			if (ret == 0)
				error(1, errno, "recv: 0 byte datagram\n");
				error(1, errno, "recv: 0 byte datagram\n");


			do_verify_udp(rbuf, ret);
			do_verify_udp(rbuf, ret);
		}
		}
		if (cfg_expected_gso_size && cfg_expected_gso_size != gso_size)
			error(1, 0, "recv: bad gso size, got %d, expected %d "
			      "(-1 == no gso cmsg))\n", gso_size,
			      cfg_expected_gso_size);


		packets++;
		packets++;
		bytes += ret;
		bytes += ret;
		if (cfg_expected_pkt_nr && packets >= cfg_expected_pkt_nr)
			break;
	}
	}
}
}


static void usage(const char *filepath)
static void usage(const char *filepath)
{
{
	error(1, 0, "Usage: %s [-Grtv] [-p port]", filepath);
	error(1, 0, "Usage: %s [-Grtv] [-b addr] [-p port] [-l pktlen] [-n packetnr] [-S gsosize]", filepath);
}
}


static void parse_opts(int argc, char **argv)
static void parse_opts(int argc, char **argv)
{
{
	int c;
	int c;


	while ((c = getopt(argc, argv, "Gp:rtv")) != -1) {
	/* bind to any by default */
	setup_sockaddr(PF_INET6, "::", &cfg_bind_addr);
	while ((c = getopt(argc, argv, "4b:Gl:n:p:rS:tv")) != -1) {
		switch (c) {
		switch (c) {
		case '4':
			cfg_family = PF_INET;
			cfg_alen = sizeof(struct sockaddr_in);
			setup_sockaddr(PF_INET, "0.0.0.0", &cfg_bind_addr);
			break;
		case 'b':
			setup_sockaddr(cfg_family, optarg, &cfg_bind_addr);
			break;
		case 'G':
		case 'G':
			cfg_gro_segment = true;
			cfg_gro_segment = true;
			break;
			break;
		case 'l':
			cfg_expected_pkt_len = strtoul(optarg, NULL, 0);
			break;
		case 'n':
			cfg_expected_pkt_nr = strtoul(optarg, NULL, 0);
			break;
		case 'p':
		case 'p':
			cfg_port = strtoul(optarg, NULL, 0);
			cfg_port = strtoul(optarg, NULL, 0);
			break;
			break;
		case 'r':
		case 'r':
			cfg_read_all = true;
			cfg_read_all = true;
			break;
			break;
		case 'S':
			cfg_expected_gso_size = strtol(optarg, NULL, 0);
			break;
		case 't':
		case 't':
			cfg_tcp = true;
			cfg_tcp = true;
			break;
			break;
@@ -240,7 +330,7 @@ static void parse_opts(int argc, char **argv)
static void do_recv(void)
static void do_recv(void)
{
{
	unsigned long tnow, treport;
	unsigned long tnow, treport;
	int fd;
	int fd, loop = 0;


	fd = do_socket(cfg_tcp);
	fd = do_socket(cfg_tcp);


@@ -252,6 +342,11 @@ static void do_recv(void)


	treport = gettimeofday_ms() + 1000;
	treport = gettimeofday_ms() + 1000;
	do {
	do {
		/* force termination after the second poll(); this cope both
		 * with sender slower than receiver and missing packet errors
		 */
		if (cfg_expected_pkt_nr && loop++)
			interrupted = true;
		do_poll(fd);
		do_poll(fd);


		if (cfg_tcp)
		if (cfg_tcp)
@@ -272,6 +367,10 @@ static void do_recv(void)


	} while (!interrupted);
	} while (!interrupted);


	if (cfg_expected_pkt_nr && (packets != cfg_expected_pkt_nr))
		error(1, 0, "wrong packet number! got %ld, expected %d\n",
		      packets, cfg_expected_pkt_nr);

	if (close(fd))
	if (close(fd))
		error(1, errno, "close");
		error(1, errno, "close");
}
}
Loading