selftests: add functionals test for UDP GRO
authorPaolo Abeni <pabeni@redhat.com>
Wed, 7 Nov 2018 11:38:37 +0000 (12:38 +0100)
committerDavid S. Miller <davem@davemloft.net>
Thu, 8 Nov 2018 00:23:05 +0000 (16:23 -0800)
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>
tools/testing/selftests/net/Makefile
tools/testing/selftests/net/udpgro.sh [new file with mode: 0755]
tools/testing/selftests/net/udpgro_bench.sh
tools/testing/selftests/net/udpgso_bench.sh
tools/testing/selftests/net/udpgso_bench_rx.c
tools/testing/selftests/net/udpgso_bench_tx.c

index b1bba2f60467f67580d5697dd6d9554ad237124b..eec359895feb73b36ed1782dd86f94f6a951b54d 100644 (file)
@@ -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
diff --git a/tools/testing/selftests/net/udpgro.sh b/tools/testing/selftests/net/udpgro.sh
new file mode 100755 (executable)
index 0000000..e94ef80
--- /dev/null
@@ -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
index 9ef4b1ee0f762a8f806ee06cf42b9b7aa78d35f2..820bc50f6b6871fdf99a46774e2dbb6ac1e2c1f0 100755 (executable)
@@ -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 rx
 
        echo "udp gso and gro - over veth touching data"
-       run_in_netns ${args} -S rx -G
+       run_in_netns ${args} -S rx -G
 }
 
 run_tcp() {
index 99e537ab5ad9a3418b0350d7e193e71205608b04..0f0628613f818d8bad788683e5ab5e4fb0b7277a 100755 (executable)
@@ -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() {
index 8f48d7fb32cf8da8b32576cebe73f23789abe7a5..0c960f673324072430a5258da15b3eefa03b1d46 100644 (file)
@@ -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");
 }
index e821564053cfb7d0482bb85800c7d8f0d977232f..4074538b5df594346ab2f88a427472bbaf29659b 100644 (file)
@@ -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,