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: Paolo Abeni <pabeni@redhat.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
							parent
							
								
									e87f53b4fa
								
							
						
					
					
						commit
						3327a9c463
					
				| @ -7,7 +7,7 @@ CFLAGS += -I../../../../usr/include/ | ||||
| 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 += 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_GEN_FILES =  socket | ||||
| TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy | ||||
|  | ||||
							
								
								
									
										148
									
								
								tools/testing/selftests/net/udpgro.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										148
									
								
								tools/testing/selftests/net/udpgro.sh
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,148 @@ | ||||
| #!/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 | ||||
| @ -18,7 +18,9 @@ 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} | ||||
| 	local rx_args=${all#*rx} | ||||
| 
 | ||||
| 	[[ "${tx_args}" == *"-4"* ]] && rx_args="${rx_args} -4" | ||||
| 
 | ||||
| 	ip netns add "${PEER_NS}" | ||||
| 	ip -netns "${PEER_NS}" link set lo up | ||||
| @ -51,10 +53,10 @@ run_udp() { | ||||
| 	local -r args=$@ | ||||
| 
 | ||||
| 	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" | ||||
| 	run_in_netns ${args} -S rx -G | ||||
| 	run_in_netns ${args} -S 0 rx -G | ||||
| } | ||||
| 
 | ||||
| run_tcp() { | ||||
|  | ||||
| @ -34,7 +34,7 @@ run_udp() { | ||||
| 	run_in_netns ${args} | ||||
| 
 | ||||
| 	echo "udp gso" | ||||
| 	run_in_netns ${args} -S | ||||
| 	run_in_netns ${args} -S 0 | ||||
| } | ||||
| 
 | ||||
| run_tcp() { | ||||
|  | ||||
| @ -40,6 +40,12 @@ static bool cfg_tcp; | ||||
| static bool cfg_verify; | ||||
| static bool cfg_read_all; | ||||
| 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 unsigned long packets, bytes; | ||||
| @ -50,6 +56,29 @@ static void sigint_handler(int signum) | ||||
| 		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) | ||||
| { | ||||
| 	struct timeval tv; | ||||
| @ -83,10 +112,9 @@ static void do_poll(int fd) | ||||
| 
 | ||||
| static int do_socket(bool do_tcp) | ||||
| { | ||||
| 	struct sockaddr_in6 addr = {0}; | ||||
| 	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) | ||||
| 		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))) | ||||
| 		error(1, errno, "setsockopt reuseport"); | ||||
| 
 | ||||
| 	addr.sin6_family =	PF_INET6; | ||||
| 	addr.sin6_port =	htons(cfg_port); | ||||
| 	addr.sin6_addr =	in6addr_any; | ||||
| 	if (bind(fd, (void *) &addr, sizeof(addr))) | ||||
| 	if (bind(fd, (void *)&cfg_bind_addr, cfg_alen)) | ||||
| 		error(1, errno, "bind"); | ||||
| 
 | ||||
| 	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. */ | ||||
| static void do_flush_udp(int fd) | ||||
| { | ||||
| 	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; | ||||
| 	while (budget--) { | ||||
| 		/* MSG_TRUNC will make return value full datagram length */ | ||||
| 		ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT); | ||||
| 		if (!cfg_expected_gso_size) | ||||
| 			ret = recv(fd, rbuf, len, MSG_TRUNC | MSG_DONTWAIT); | ||||
| 		else | ||||
| 			ret = recv_msg(fd, rbuf, len, &gso_size); | ||||
| 		if (ret == -1 && errno == EAGAIN) | ||||
| 			return; | ||||
| 			break; | ||||
| 		if (ret == -1) | ||||
| 			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 (ret == 0) | ||||
| 				error(1, errno, "recv: 0 byte datagram\n"); | ||||
| 
 | ||||
| 			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++; | ||||
| 		bytes += ret; | ||||
| 		if (cfg_expected_pkt_nr && packets >= cfg_expected_pkt_nr) | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
| { | ||||
| 	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) { | ||||
| 		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': | ||||
| 			cfg_gro_segment = true; | ||||
| 			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': | ||||
| 			cfg_port = strtoul(optarg, NULL, 0); | ||||
| 			break; | ||||
| 		case 'r': | ||||
| 			cfg_read_all = true; | ||||
| 			break; | ||||
| 		case 'S': | ||||
| 			cfg_expected_gso_size = strtol(optarg, NULL, 0); | ||||
| 			break; | ||||
| 		case 't': | ||||
| 			cfg_tcp = true; | ||||
| 			break; | ||||
| @ -240,7 +330,7 @@ static void parse_opts(int argc, char **argv) | ||||
| static void do_recv(void) | ||||
| { | ||||
| 	unsigned long tnow, treport; | ||||
| 	int fd; | ||||
| 	int fd, loop = 0; | ||||
| 
 | ||||
| 	fd = do_socket(cfg_tcp); | ||||
| 
 | ||||
| @ -252,6 +342,11 @@ static void do_recv(void) | ||||
| 
 | ||||
| 	treport = gettimeofday_ms() + 1000; | ||||
| 	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); | ||||
| 
 | ||||
| 		if (cfg_tcp) | ||||
| @ -272,6 +367,10 @@ static void do_recv(void) | ||||
| 
 | ||||
| 	} 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)) | ||||
| 		error(1, errno, "close"); | ||||
| } | ||||
|  | ||||
| @ -52,6 +52,8 @@ static bool	cfg_segment; | ||||
| static bool	cfg_sendmmsg; | ||||
| static bool	cfg_tcp; | ||||
| static bool	cfg_zerocopy; | ||||
| static int	cfg_msg_nr; | ||||
| static uint16_t	cfg_gso_size; | ||||
| 
 | ||||
| static socklen_t cfg_alen; | ||||
| static struct sockaddr_storage cfg_dst_addr; | ||||
| @ -205,14 +207,14 @@ static void send_udp_segment_cmsg(struct cmsghdr *cm) | ||||
| 
 | ||||
| 	cm->cmsg_level = SOL_UDP; | ||||
| 	cm->cmsg_type = UDP_SEGMENT; | ||||
| 	cm->cmsg_len = CMSG_LEN(sizeof(cfg_mss)); | ||||
| 	cm->cmsg_len = CMSG_LEN(sizeof(cfg_gso_size)); | ||||
| 	valp = (void *)CMSG_DATA(cm); | ||||
| 	*valp = cfg_mss; | ||||
| 	*valp = cfg_gso_size; | ||||
| } | ||||
| 
 | ||||
| static int send_udp_segment(int fd, char *data) | ||||
| { | ||||
| 	char control[CMSG_SPACE(sizeof(cfg_mss))] = {0}; | ||||
| 	char control[CMSG_SPACE(sizeof(cfg_gso_size))] = {0}; | ||||
| 	struct msghdr msg = {0}; | ||||
| 	struct iovec iov = {0}; | ||||
| 	int ret; | ||||
| @ -241,7 +243,7 @@ static int send_udp_segment(int fd, char *data) | ||||
| 
 | ||||
| static void usage(const char *filepath) | ||||
| { | ||||
| 	error(1, 0, "Usage: %s [-46cmStuz] [-C cpu] [-D dst ip] [-l secs] [-p port] [-s sendsize]", | ||||
| 	error(1, 0, "Usage: %s [-46cmtuz] [-C cpu] [-D dst ip] [-l secs] [-m messagenr] [-p port] [-s sendsize] [-S gsosize]", | ||||
| 		    filepath); | ||||
| } | ||||
| 
 | ||||
| @ -250,7 +252,7 @@ static void parse_opts(int argc, char **argv) | ||||
| 	int max_len, hdrlen; | ||||
| 	int c; | ||||
| 
 | ||||
| 	while ((c = getopt(argc, argv, "46cC:D:l:mp:s:Stuz")) != -1) { | ||||
| 	while ((c = getopt(argc, argv, "46cC:D:l:mM:p:s:S:tuz")) != -1) { | ||||
| 		switch (c) { | ||||
| 		case '4': | ||||
| 			if (cfg_family != PF_UNSPEC) | ||||
| @ -279,6 +281,9 @@ static void parse_opts(int argc, char **argv) | ||||
| 		case 'm': | ||||
| 			cfg_sendmmsg = true; | ||||
| 			break; | ||||
| 		case 'M': | ||||
| 			cfg_msg_nr = strtoul(optarg, NULL, 10); | ||||
| 			break; | ||||
| 		case 'p': | ||||
| 			cfg_port = strtoul(optarg, NULL, 0); | ||||
| 			break; | ||||
| @ -286,6 +291,7 @@ static void parse_opts(int argc, char **argv) | ||||
| 			cfg_payload_len = strtoul(optarg, NULL, 0); | ||||
| 			break; | ||||
| 		case 'S': | ||||
| 			cfg_gso_size = strtoul(optarg, NULL, 0); | ||||
| 			cfg_segment = true; | ||||
| 			break; | ||||
| 		case 't': | ||||
| @ -317,6 +323,8 @@ static void parse_opts(int argc, char **argv) | ||||
| 
 | ||||
| 	cfg_mss = ETH_DATA_LEN - hdrlen; | ||||
| 	max_len = ETH_MAX_MTU - hdrlen; | ||||
| 	if (!cfg_gso_size) | ||||
| 		cfg_gso_size = cfg_mss; | ||||
| 
 | ||||
| 	if (cfg_payload_len > max_len) | ||||
| 		error(1, 0, "payload length %u exceeds max %u", | ||||
| @ -392,10 +400,12 @@ int main(int argc, char **argv) | ||||
| 		else | ||||
| 			num_sends += send_udp(fd, buf[i]); | ||||
| 		num_msgs++; | ||||
| 
 | ||||
| 		if (cfg_zerocopy && ((num_msgs & 0xF) == 0)) | ||||
| 			flush_zerocopy(fd); | ||||
| 
 | ||||
| 		if (cfg_msg_nr && num_msgs >= cfg_msg_nr) | ||||
| 			break; | ||||
| 
 | ||||
| 		tnow = gettimeofday_ms(); | ||||
| 		if (tnow > treport) { | ||||
| 			fprintf(stderr, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user