| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright(c) 2020 Intel Corporation. */ |
| |
| /* |
| * Some functions in this program are taken from |
| * Linux kernel samples/bpf/xdpsock* and modified |
| * for use. |
| * |
| * See test_xsk.sh for detailed information on test topology |
| * and prerequisite network setup. |
| * |
| * This test program contains two threads, each thread is single socket with |
| * a unique UMEM. It validates in-order packet delivery and packet content |
| * by sending packets to each other. |
| * |
| * Tests Information: |
| * ------------------ |
| * These selftests test AF_XDP SKB and Native/DRV modes using veth |
| * Virtual Ethernet interfaces. |
| * |
| * For each mode, the following tests are run: |
| * a. nopoll - soft-irq processing in run-to-completion mode |
| * b. poll - using poll() syscall |
| * c. Socket Teardown |
| * Create a Tx and a Rx socket, Tx from one socket, Rx on another. Destroy |
| * both sockets, then repeat multiple times. Only nopoll mode is used |
| * d. Bi-directional sockets |
| * Configure sockets as bi-directional tx/rx sockets, sets up fill and |
| * completion rings on each socket, tx/rx in both directions. Only nopoll |
| * mode is used |
| * e. Statistics |
| * Trigger some error conditions and ensure that the appropriate statistics |
| * are incremented. Within this test, the following statistics are tested: |
| * i. rx dropped |
| * Increase the UMEM frame headroom to a value which results in |
| * insufficient space in the rx buffer for both the packet and the headroom. |
| * ii. tx invalid |
| * Set the 'len' field of tx descriptors to an invalid value (umem frame |
| * size + 1). |
| * iii. rx ring full |
| * Reduce the size of the RX ring to a fraction of the fill ring size. |
| * iv. fill queue empty |
| * Do not populate the fill queue and then try to receive pkts. |
| * f. bpf_link resource persistence |
| * Configure sockets at indexes 0 and 1, run a traffic on queue ids 0, |
| * then remove xsk sockets from queue 0 on both veth interfaces and |
| * finally run a traffic on queues ids 1 |
| * g. unaligned mode |
| * h. tests for invalid and corner case Tx descriptors so that the correct ones |
| * are discarded and let through, respectively. |
| * i. 2K frame size tests |
| * j. If multi-buffer is supported, send 9k packets divided into 3 frames |
| * k. If multi-buffer and huge pages are supported, send 9k packets in a single frame |
| * using unaligned mode |
| * l. If multi-buffer is supported, try various nasty combinations of descriptors to |
| * check if they pass the validation or not |
| * |
| * Flow: |
| * ----- |
| * - Single process spawns two threads: Tx and Rx |
| * - Each of these two threads attach to a veth interface |
| * - Each thread creates one AF_XDP socket connected to a unique umem for each |
| * veth interface |
| * - Tx thread Transmits a number of packets from veth<xxxx> to veth<yyyy> |
| * - Rx thread verifies if all packets were received and delivered in-order, |
| * and have the right content |
| * |
| * Enable/disable packet dump mode: |
| * -------------------------- |
| * To enable L2 - L4 headers and payload dump of each packet on STDOUT, add |
| * parameter -D to params array in test_xsk.sh, i.e. params=("-S" "-D") |
| */ |
| |
| #define _GNU_SOURCE |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <linux/if_link.h> |
| #include <linux/if_ether.h> |
| #include <linux/mman.h> |
| #include <linux/netdev.h> |
| #include <linux/ethtool.h> |
| #include <arpa/inet.h> |
| #include <net/if.h> |
| #include <locale.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <libgen.h> |
| #include <stddef.h> |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| |
| #include "prog_tests/test_xsk.h" |
| #include "xsk_xdp_progs.skel.h" |
| #include "xsk.h" |
| #include "xskxceiver.h" |
| #include <bpf/bpf.h> |
| #include <linux/filter.h> |
| #include "kselftest.h" |
| #include "xsk_xdp_common.h" |
| |
| #include <network_helpers.h> |
| |
| static bool opt_print_tests; |
| static enum test_mode opt_mode = TEST_MODE_ALL; |
| static u32 opt_run_test = RUN_ALL_TESTS; |
| |
| void test__fail(void) { /* for network_helpers.c */ } |
| |
| static void __exit_with_error(int error, const char *file, const char *func, int line) |
| { |
| ksft_test_result_fail("[%s:%s:%i]: ERROR: %d/\"%s\"\n", file, func, line, |
| error, strerror(error)); |
| ksft_exit_xfail(); |
| } |
| |
| #define exit_with_error(error) __exit_with_error(error, __FILE__, __func__, __LINE__) |
| |
| static bool ifobj_zc_avail(struct ifobject *ifobject) |
| { |
| size_t umem_sz = DEFAULT_UMEM_BUFFERS * XSK_UMEM__DEFAULT_FRAME_SIZE; |
| int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE; |
| struct xsk_socket_info *xsk; |
| struct xsk_umem_info *umem; |
| bool zc_avail = false; |
| void *bufs; |
| int ret; |
| |
| bufs = mmap(NULL, umem_sz, PROT_READ | PROT_WRITE, mmap_flags, -1, 0); |
| if (bufs == MAP_FAILED) |
| exit_with_error(errno); |
| |
| umem = calloc(1, sizeof(struct xsk_umem_info)); |
| if (!umem) { |
| munmap(bufs, umem_sz); |
| exit_with_error(ENOMEM); |
| } |
| umem->frame_size = XSK_UMEM__DEFAULT_FRAME_SIZE; |
| ret = xsk_configure_umem(ifobject, umem, bufs, umem_sz); |
| if (ret) |
| exit_with_error(-ret); |
| |
| xsk = calloc(1, sizeof(struct xsk_socket_info)); |
| if (!xsk) |
| goto out; |
| ifobject->bind_flags = XDP_USE_NEED_WAKEUP | XDP_ZEROCOPY; |
| ifobject->rx_on = true; |
| xsk->rxqsize = XSK_RING_CONS__DEFAULT_NUM_DESCS; |
| ret = xsk_configure_socket(xsk, umem, ifobject, false); |
| if (!ret) |
| zc_avail = true; |
| |
| xsk_socket__delete(xsk->xsk); |
| free(xsk); |
| out: |
| munmap(umem->buffer, umem_sz); |
| xsk_umem__delete(umem->umem); |
| free(umem); |
| return zc_avail; |
| } |
| |
| static struct option long_options[] = { |
| {"interface", required_argument, 0, 'i'}, |
| {"busy-poll", no_argument, 0, 'b'}, |
| {"verbose", no_argument, 0, 'v'}, |
| {"mode", required_argument, 0, 'm'}, |
| {"list", no_argument, 0, 'l'}, |
| {"test", required_argument, 0, 't'}, |
| {"help", no_argument, 0, 'h'}, |
| {0, 0, 0, 0} |
| }; |
| |
| static void print_usage(char **argv) |
| { |
| const char *str = |
| " Usage: xskxceiver [OPTIONS]\n" |
| " Options:\n" |
| " -i, --interface Use interface\n" |
| " -v, --verbose Verbose output\n" |
| " -b, --busy-poll Enable busy poll\n" |
| " -m, --mode Run only mode skb, drv, or zc\n" |
| " -l, --list List all available tests\n" |
| " -t, --test Run a specific test. Enter number from -l option.\n" |
| " -h, --help Display this help and exit\n"; |
| |
| ksft_print_msg(str, basename(argv[0])); |
| ksft_exit_xfail(); |
| } |
| |
| static bool validate_interface(struct ifobject *ifobj) |
| { |
| if (!strcmp(ifobj->ifname, "")) |
| return false; |
| return true; |
| } |
| |
| static void parse_command_line(struct ifobject *ifobj_tx, struct ifobject *ifobj_rx, int argc, |
| char **argv) |
| { |
| struct ifobject *ifobj; |
| u32 interface_nb = 0; |
| int option_index, c; |
| |
| opterr = 0; |
| |
| for (;;) { |
| c = getopt_long(argc, argv, "i:vbm:lt:", long_options, &option_index); |
| if (c == -1) |
| break; |
| |
| switch (c) { |
| case 'i': |
| if (interface_nb == 0) |
| ifobj = ifobj_tx; |
| else if (interface_nb == 1) |
| ifobj = ifobj_rx; |
| else |
| break; |
| |
| memcpy(ifobj->ifname, optarg, |
| min_t(size_t, MAX_INTERFACE_NAME_CHARS, strlen(optarg))); |
| |
| ifobj->ifindex = if_nametoindex(ifobj->ifname); |
| if (!ifobj->ifindex) |
| exit_with_error(errno); |
| |
| interface_nb++; |
| break; |
| case 'v': |
| opt_verbose = true; |
| break; |
| case 'b': |
| ifobj_tx->busy_poll = true; |
| ifobj_rx->busy_poll = true; |
| break; |
| case 'm': |
| if (!strncmp("skb", optarg, strlen(optarg))) |
| opt_mode = TEST_MODE_SKB; |
| else if (!strncmp("drv", optarg, strlen(optarg))) |
| opt_mode = TEST_MODE_DRV; |
| else if (!strncmp("zc", optarg, strlen(optarg))) |
| opt_mode = TEST_MODE_ZC; |
| else |
| print_usage(argv); |
| break; |
| case 'l': |
| opt_print_tests = true; |
| break; |
| case 't': |
| errno = 0; |
| opt_run_test = strtol(optarg, NULL, 0); |
| if (errno) |
| print_usage(argv); |
| break; |
| case 'h': |
| default: |
| print_usage(argv); |
| } |
| } |
| } |
| |
| static void xsk_unload_xdp_programs(struct ifobject *ifobj) |
| { |
| xsk_xdp_progs__destroy(ifobj->xdp_progs); |
| } |
| |
| static void run_pkt_test(struct test_spec *test) |
| { |
| int ret; |
| |
| ret = test->test_func(test); |
| |
| switch (ret) { |
| case TEST_PASS: |
| ksft_test_result_pass("PASS: %s %s%s\n", mode_string(test), busy_poll_string(test), |
| test->name); |
| break; |
| case TEST_SKIP: |
| ksft_test_result_skip("SKIP: %s %s%s\n", mode_string(test), busy_poll_string(test), |
| test->name); |
| break; |
| case TEST_FAILURE: |
| ksft_test_result_fail("FAIL: %s %s%s\n", mode_string(test), busy_poll_string(test), |
| test->name); |
| break; |
| default: |
| ksft_test_result_fail("FAIL: %s %s%s -- Unexpected returned value (%d)\n", |
| mode_string(test), busy_poll_string(test), test->name, ret); |
| } |
| |
| pkt_stream_restore_default(test); |
| } |
| |
| static bool is_xdp_supported(int ifindex) |
| { |
| int flags = XDP_FLAGS_DRV_MODE; |
| |
| LIBBPF_OPTS(bpf_link_create_opts, opts, .flags = flags); |
| struct bpf_insn insns[2] = { |
| BPF_MOV64_IMM(BPF_REG_0, XDP_PASS), |
| BPF_EXIT_INSN() |
| }; |
| int prog_fd, insn_cnt = ARRAY_SIZE(insns); |
| int err; |
| |
| prog_fd = bpf_prog_load(BPF_PROG_TYPE_XDP, NULL, "GPL", insns, insn_cnt, NULL); |
| if (prog_fd < 0) |
| return false; |
| |
| err = bpf_xdp_attach(ifindex, prog_fd, flags, NULL); |
| if (err) { |
| close(prog_fd); |
| return false; |
| } |
| |
| bpf_xdp_detach(ifindex, flags, NULL); |
| close(prog_fd); |
| |
| return true; |
| } |
| |
| static void print_tests(void) |
| { |
| u32 i; |
| |
| printf("Tests:\n"); |
| for (i = 0; i < ARRAY_SIZE(tests); i++) |
| printf("%u: %s\n", i, tests[i].name); |
| for (i = ARRAY_SIZE(tests); i < ARRAY_SIZE(tests) + ARRAY_SIZE(ci_skip_tests); i++) |
| printf("%u: %s\n", i, ci_skip_tests[i - ARRAY_SIZE(tests)].name); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| const size_t total_tests = ARRAY_SIZE(tests) + ARRAY_SIZE(ci_skip_tests); |
| struct pkt_stream *rx_pkt_stream_default; |
| struct pkt_stream *tx_pkt_stream_default; |
| struct ifobject *ifobj_tx, *ifobj_rx; |
| u32 i, j, failed_tests = 0, nb_tests; |
| int modes = TEST_MODE_SKB + 1; |
| struct test_spec test; |
| bool shared_netdev; |
| int ret; |
| |
| /* Use libbpf 1.0 API mode */ |
| libbpf_set_strict_mode(LIBBPF_STRICT_ALL); |
| |
| ifobj_tx = ifobject_create(); |
| if (!ifobj_tx) |
| exit_with_error(ENOMEM); |
| ifobj_rx = ifobject_create(); |
| if (!ifobj_rx) |
| exit_with_error(ENOMEM); |
| |
| setlocale(LC_ALL, ""); |
| |
| parse_command_line(ifobj_tx, ifobj_rx, argc, argv); |
| |
| if (opt_print_tests) { |
| print_tests(); |
| ksft_exit_xpass(); |
| } |
| if (opt_run_test != RUN_ALL_TESTS && opt_run_test >= total_tests) { |
| ksft_print_msg("Error: test %u does not exist.\n", opt_run_test); |
| ksft_exit_xfail(); |
| } |
| |
| shared_netdev = (ifobj_tx->ifindex == ifobj_rx->ifindex); |
| ifobj_tx->shared_umem = shared_netdev; |
| ifobj_rx->shared_umem = shared_netdev; |
| |
| if (!validate_interface(ifobj_tx) || !validate_interface(ifobj_rx)) |
| print_usage(argv); |
| |
| if (is_xdp_supported(ifobj_tx->ifindex)) { |
| modes++; |
| if (ifobj_zc_avail(ifobj_tx)) |
| modes++; |
| } |
| |
| ret = get_hw_ring_size(ifobj_tx->ifname, &ifobj_tx->ring); |
| if (!ret) { |
| ifobj_tx->hw_ring_size_supp = true; |
| ifobj_tx->set_ring.default_tx = ifobj_tx->ring.tx_pending; |
| ifobj_tx->set_ring.default_rx = ifobj_tx->ring.rx_pending; |
| } |
| |
| if (init_iface(ifobj_rx, worker_testapp_validate_rx) || |
| init_iface(ifobj_tx, worker_testapp_validate_tx)) { |
| ksft_print_msg("Error : can't initialize interfaces\n"); |
| ksft_exit_xfail(); |
| } |
| |
| test_init(&test, ifobj_tx, ifobj_rx, 0, &tests[0]); |
| tx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE); |
| rx_pkt_stream_default = pkt_stream_generate(DEFAULT_PKT_CNT, MIN_PKT_SIZE); |
| if (!tx_pkt_stream_default || !rx_pkt_stream_default) |
| exit_with_error(ENOMEM); |
| test.tx_pkt_stream_default = tx_pkt_stream_default; |
| test.rx_pkt_stream_default = rx_pkt_stream_default; |
| |
| if (opt_run_test == RUN_ALL_TESTS) |
| nb_tests = total_tests; |
| else |
| nb_tests = 1; |
| if (opt_mode == TEST_MODE_ALL) { |
| ksft_set_plan(modes * nb_tests); |
| } else { |
| if (opt_mode == TEST_MODE_DRV && modes <= TEST_MODE_DRV) { |
| ksft_print_msg("Error: XDP_DRV mode not supported.\n"); |
| ksft_exit_xfail(); |
| } |
| if (opt_mode == TEST_MODE_ZC && modes <= TEST_MODE_ZC) { |
| ksft_print_msg("Error: zero-copy mode not supported.\n"); |
| ksft_exit_xfail(); |
| } |
| |
| ksft_set_plan(nb_tests); |
| } |
| |
| for (i = 0; i < modes; i++) { |
| if (opt_mode != TEST_MODE_ALL && i != opt_mode) |
| continue; |
| |
| for (j = 0; j < total_tests; j++) { |
| if (opt_run_test != RUN_ALL_TESTS && j != opt_run_test) |
| continue; |
| |
| if (j < ARRAY_SIZE(tests)) |
| test_init(&test, ifobj_tx, ifobj_rx, i, &tests[j]); |
| else |
| test_init(&test, ifobj_tx, ifobj_rx, i, |
| &ci_skip_tests[j - ARRAY_SIZE(tests)]); |
| run_pkt_test(&test); |
| usleep(USLEEP_MAX); |
| |
| if (test.fail) |
| failed_tests++; |
| } |
| } |
| |
| if (ifobj_tx->hw_ring_size_supp) |
| hw_ring_size_reset(ifobj_tx); |
| |
| pkt_stream_delete(tx_pkt_stream_default); |
| pkt_stream_delete(rx_pkt_stream_default); |
| xsk_unload_xdp_programs(ifobj_tx); |
| xsk_unload_xdp_programs(ifobj_rx); |
| ifobject_delete(ifobj_tx); |
| ifobject_delete(ifobj_rx); |
| |
| if (failed_tests) |
| ksft_exit_fail(); |
| else |
| ksft_exit_pass(); |
| } |