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

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
...@@ -7,7 +7,7 @@ CFLAGS += -I../../../../usr/include/ ...@@ -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
......
#!/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() { ...@@ -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() { ...@@ -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() {
......
...@@ -34,7 +34,7 @@ run_udp() { ...@@ -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() {
......
...@@ -40,6 +40,12 @@ static bool cfg_tcp; ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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");
} }
......
...@@ -52,6 +52,8 @@ static bool cfg_segment; ...@@ -52,6 +52,8 @@ static bool cfg_segment;
static bool cfg_sendmmsg; static bool cfg_sendmmsg;
static bool cfg_tcp; static bool cfg_tcp;
static bool cfg_zerocopy; static bool cfg_zerocopy;
static int cfg_msg_nr;
static uint16_t cfg_gso_size;
static socklen_t cfg_alen; static socklen_t cfg_alen;
static struct sockaddr_storage cfg_dst_addr; static struct sockaddr_storage cfg_dst_addr;
...@@ -205,14 +207,14 @@ static void send_udp_segment_cmsg(struct cmsghdr *cm) ...@@ -205,14 +207,14 @@ static void send_udp_segment_cmsg(struct cmsghdr *cm)
cm->cmsg_level = SOL_UDP; cm->cmsg_level = SOL_UDP;
cm->cmsg_type = UDP_SEGMENT; 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 = (void *)CMSG_DATA(cm);
*valp = cfg_mss; *valp = cfg_gso_size;
} }
static int send_udp_segment(int fd, char *data) 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 msghdr msg = {0};
struct iovec iov = {0}; struct iovec iov = {0};
int ret; int ret;
...@@ -241,7 +243,7 @@ static int send_udp_segment(int fd, char *data) ...@@ -241,7 +243,7 @@ static int send_udp_segment(int fd, char *data)
static void usage(const char *filepath) 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); filepath);
} }
...@@ -250,7 +252,7 @@ static void parse_opts(int argc, char **argv) ...@@ -250,7 +252,7 @@ static void parse_opts(int argc, char **argv)
int max_len, hdrlen; int max_len, hdrlen;
int c; 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) { switch (c) {
case '4': case '4':
if (cfg_family != PF_UNSPEC) if (cfg_family != PF_UNSPEC)
...@@ -279,6 +281,9 @@ static void parse_opts(int argc, char **argv) ...@@ -279,6 +281,9 @@ static void parse_opts(int argc, char **argv)
case 'm': case 'm':
cfg_sendmmsg = true; cfg_sendmmsg = true;
break; break;
case 'M':
cfg_msg_nr = strtoul(optarg, NULL, 10);
break;
case 'p': case 'p':
cfg_port = strtoul(optarg, NULL, 0); cfg_port = strtoul(optarg, NULL, 0);
break; break;
...@@ -286,6 +291,7 @@ static void parse_opts(int argc, char **argv) ...@@ -286,6 +291,7 @@ static void parse_opts(int argc, char **argv)
cfg_payload_len = strtoul(optarg, NULL, 0); cfg_payload_len = strtoul(optarg, NULL, 0);
break; break;
case 'S': case 'S':
cfg_gso_size = strtoul(optarg, NULL, 0);
cfg_segment = true; cfg_segment = true;
break; break;
case 't': case 't':
...@@ -317,6 +323,8 @@ static void parse_opts(int argc, char **argv) ...@@ -317,6 +323,8 @@ static void parse_opts(int argc, char **argv)
cfg_mss = ETH_DATA_LEN - hdrlen; cfg_mss = ETH_DATA_LEN - hdrlen;
max_len = ETH_MAX_MTU - hdrlen; max_len = ETH_MAX_MTU - hdrlen;
if (!cfg_gso_size)
cfg_gso_size = cfg_mss;
if (cfg_payload_len > max_len) if (cfg_payload_len > max_len)
error(1, 0, "payload length %u exceeds max %u", error(1, 0, "payload length %u exceeds max %u",
...@@ -392,10 +400,12 @@ int main(int argc, char **argv) ...@@ -392,10 +400,12 @@ int main(int argc, char **argv)
else else
num_sends += send_udp(fd, buf[i]); num_sends += send_udp(fd, buf[i]);
num_msgs++; num_msgs++;
if (cfg_zerocopy && ((num_msgs & 0xF) == 0)) if (cfg_zerocopy && ((num_msgs & 0xF) == 0))
flush_zerocopy(fd); flush_zerocopy(fd);
if (cfg_msg_nr && num_msgs >= cfg_msg_nr)
break;
tnow = gettimeofday_ms(); tnow = gettimeofday_ms();
if (tnow > treport) { if (tnow > treport) {
fprintf(stderr, fprintf(stderr,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment