diff options
Diffstat (limited to 'lib/libxdp/tests')
-rw-r--r-- | lib/libxdp/tests/.gitignore | 4 | ||||
-rw-r--r-- | lib/libxdp/tests/Makefile | 80 | ||||
-rw-r--r-- | lib/libxdp/tests/check_kern_compat.c | 10 | ||||
-rw-r--r-- | lib/libxdp/tests/test-libxdp.sh | 99 | ||||
-rw-r--r-- | lib/libxdp/tests/test_dispatcher_versions.c | 300 | ||||
-rwxr-xr-x | lib/libxdp/tests/test_runner.sh | 118 | ||||
-rw-r--r-- | lib/libxdp/tests/test_utils.h | 49 | ||||
-rw-r--r-- | lib/libxdp/tests/test_xdp_frags.c | 339 | ||||
-rw-r--r-- | lib/libxdp/tests/test_xsk_refcnt.c | 304 | ||||
-rw-r--r-- | lib/libxdp/tests/xdp_dispatcher_v1.c | 43 | ||||
-rw-r--r-- | lib/libxdp/tests/xdp_dispatcher_v1.h | 16 | ||||
-rw-r--r-- | lib/libxdp/tests/xdp_pass.c | 11 |
12 files changed, 1373 insertions, 0 deletions
diff --git a/lib/libxdp/tests/.gitignore b/lib/libxdp/tests/.gitignore new file mode 100644 index 0000000..cc3a114 --- /dev/null +++ b/lib/libxdp/tests/.gitignore @@ -0,0 +1,4 @@ +test_xsk_refcnt +check_kern_compat +test_xdp_frags +test_dispatcher_versions diff --git a/lib/libxdp/tests/Makefile b/lib/libxdp/tests/Makefile new file mode 100644 index 0000000..3c22901 --- /dev/null +++ b/lib/libxdp/tests/Makefile @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) + +USER_TARGETS := test_xsk_refcnt check_kern_compat test_xdp_frags test_dispatcher_versions +BPF_TARGETS := xdp_dispatcher_v1 xdp_pass +USER_LIBS := -lpthread + +EXTRA_DEPS += xdp_dispatcher_v1.h +EXTRA_USER_DEPS += test_utils.h + +TEST_FILE := ./test-libxdp.sh +TEST_RUNNER := ./test_runner.sh + +USER_C := ${USER_TARGETS:=.c} +USER_OBJ := ${USER_C:.c=.o} +BPF_OBJS := $(BPF_TARGETS:=.o) + +LIB_DIR := ../.. +LDLIBS += $(USER_LIBS) + +include $(LIB_DIR)/defines.mk + +LDFLAGS+=-L$(LIBXDP_DIR) +ifeq ($(DYNAMIC_LIBXDP),1) + LDLIBS:=-lxdp $(LDLIBS) + OBJECT_LIBXDP:=$(LIBXDP_DIR)/libxdp.so.$(LIBXDP_VERSION) +else + LDLIBS:=-l:libxdp.a $(LDLIBS) + OBJECT_LIBXDP:=$(LIBXDP_DIR)/libxdp.a +endif + +# Detect submodule libbpf source file changes +ifeq ($(SYSTEM_LIBBPF),n) + LIBBPF_SOURCES := $(wildcard $(LIBBPF_DIR)/src/*.[ch]) +endif + +LIBXDP_SOURCES := $(wildcard $(LIBXDP_DIR)/*.[ch] $(LIBXDP_DIR)/*.in) + +CFLAGS += -I$(HEADER_DIR) + +BPF_HEADERS := $(wildcard $(HEADER_DIR)/bpf/*.h) $(wildcard $(HEADER_DIR)/xdp/*.h) + +all: $(USER_TARGETS) $(BPF_OBJS) + +.PHONY: clean +clean:: + $(Q)rm -f $(USER_TARGETS) $(USER_OBJ) + +$(OBJECT_LIBBPF): $(LIBBPF_SOURCES) + $(Q)$(MAKE) -C $(LIB_DIR) libbpf + +$(OBJECT_LIBXDP): $(LIBXDP_SOURCES) + $(Q)$(MAKE) -C $(LIBXDP_DIR) + +# Create expansions for dependencies +LIB_H := ${LIB_OBJS:.o=.h} + +# Detect if any of common obj changed and create dependency on .h-files +$(LIB_OBJS): %.o: %.c %.h $(LIB_H) + $(Q)$(MAKE) -C $(dir $@) $(notdir $@) + +ALL_EXEC_TARGETS=$(USER_TARGETS) +$(ALL_EXEC_TARGETS): %: %.c $(OBJECT_LIBBPF) $(OBJECT_LIBXDP) $(LIBMK) $(LIB_OBJS) $(EXTRA_DEPS) $(EXTRA_USER_DEPS) + $(QUIET_CC)$(CC) -Wall $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ $(LIB_OBJS) \ + $< $(LDLIBS) + +$(BPF_OBJS): %.o: %.c $(BPF_HEADERS) $(LIBMK) $(EXTRA_DEPS) + $(QUIET_CLANG)$(CLANG) -S \ + -target $(BPF_TARGET) \ + -D __BPF_TRACING__ \ + $(BPF_CFLAGS) \ + -Wall \ + -Wno-unused-value \ + -Wno-pointer-sign \ + -Wno-compare-distinct-pointer-types \ + -Werror \ + -O2 -emit-llvm -c -g -o ${@:.o=.ll} $< + $(QUIET_LLC)$(LLC) -march=$(BPF_TARGET) -filetype=obj -o $@ ${@:.o=.ll} + +run: all + $(Q)env CC="$(CC)" CFLAGS="$(CFLAGS) $(LDFLAGS)" CPPFLAGS="$(CPPFLAGS)" LDLIBS="$(LDLIBS)" V=$(V) $(TEST_RUNNER) $(TEST_FILE) $(RUN_TESTS) diff --git a/lib/libxdp/tests/check_kern_compat.c b/lib/libxdp/tests/check_kern_compat.c new file mode 100644 index 0000000..8fb8991 --- /dev/null +++ b/lib/libxdp/tests/check_kern_compat.c @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include "test_utils.h" +#include "../libxdp_internal.h" + +int main(__unused int argc, __unused char** argv) +{ + silence_libbpf_logging(); + return libxdp_check_kern_compat(); +} diff --git a/lib/libxdp/tests/test-libxdp.sh b/lib/libxdp/tests/test-libxdp.sh new file mode 100644 index 0000000..90fc44c --- /dev/null +++ b/lib/libxdp/tests/test-libxdp.sh @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) + +ALL_TESTS="test_link_so test_link_a test_old_dispatcher test_xdp_frags test_xsk_prog_refcnt_bpffs test_xsk_prog_refcnt_legacy" + +TESTS_DIR=$(dirname "${BASH_SOURCE[0]}") + +test_link_so() +{ + TMPDIR=$(mktemp --tmpdir -d libxdp-test.XXXXXX) + cat >$TMPDIR/libxdptest.c <<EOF +#include <xdp/libxdp.h> +int main(int argc, char **argv) { + (void) argc; (void) argv; + (void) xdp_program__open_file("filename", "section_name", NULL); + return 0; +} +EOF + $CC -o $TMPDIR/libxdptest $TMPDIR/libxdptest.c $CFLAGS $CPPFLAGS -lxdp $LDLIBS 2>&1 + retval=$? + rm -rf "$TMPDIR" + return $retval +} + +test_link_a() +{ + TMPDIR=$(mktemp --tmpdir -d libxdp-test.XXXXXX) + cat >$TMPDIR/libxdptest.c <<EOF +#include <xdp/libxdp.h> +int main(int argc, char **argv) { + (void) argc; (void) argv; + (void) xdp_program__open_file("filename", "section_name", NULL); + return 0; +} +EOF + $CC -o $TMPDIR/libxdptest $TMPDIR/libxdptest.c $CFLAGS $CPPFLAGS -l:libxdp.a $LDLIBS 2>&1 + retval=$? + rm -rf "$TMPDIR" + return $retval +} + +test_refcnt_once() +{ + # We need multiple queues for this test + NUM_QUEUES_REQUIRED=3 + ip link add xsk_veth0 numrxqueues $NUM_QUEUES_REQUIRED type veth peer name xsk_veth1 + check_run $TESTS_DIR/test_xsk_refcnt xsk_veth0 2>&1 + ip link delete xsk_veth0 +} + +check_mount_bpffs() +{ + mount | grep -q /sys/fs/bpf || mount -t bpf bpf /sys/fs/bpf/ || echo "Unable to mount /sys/fs/bpf" + mount | grep -q /sys/fs/bpf +} + +check_unmount_bpffs() +{ + mount | grep -q /sys/fs/bpf && umount /sys/fs/bpf/ || echo "Unable to unmount /sys/fs/bpf" + ! mount | grep -q /sys/fs/bpf +} + +test_xsk_prog_refcnt_bpffs() +{ + check_mount_bpffs && test_refcnt_once "$@" +} + +test_xsk_prog_refcnt_legacy() +{ + check_unmount_bpffs && test_refcnt_once "$@" +} + +test_xdp_frags() +{ + skip_if_missing_libxdp_compat + + check_mount_bpffs || return 1 + ip link add xdp_veth_big0 mtu 5000 type veth peer name xdp_veth_big1 mtu 5000 + ip link add xdp_veth_small0 type veth peer name xdp_veth_small1 + check_run $TESTS_DIR/test_xdp_frags xdp_veth_big0 xdp_veth_small0 2>&1 + ip link delete xdp_veth_big0 + ip link delete xdp_veth_small0 +} + +test_old_dispatcher() +{ + skip_if_missing_libxdp_compat + + check_mount_bpffs || return 1 + ip link add xdp_veth0 type veth peer name xdp_veth1 + check_run $TESTS_DIR/test_dispatcher_versions xdp_veth0 + ip link delete xdp_veth0 +} + +cleanup_tests() +{ + ip link del dev xdp_veth_big0 >/dev/null 2>&1 + ip link del dev xdp_veth_small0 >/dev/null 2>&1 + ip link del dev xsk_veth0 >/dev/null 2>&1 +} diff --git a/lib/libxdp/tests/test_dispatcher_versions.c b/lib/libxdp/tests/test_dispatcher_versions.c new file mode 100644 index 0000000..14a8ba8 --- /dev/null +++ b/lib/libxdp/tests/test_dispatcher_versions.c @@ -0,0 +1,300 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#define _GNU_SOURCE + +#include <errno.h> +#include <linux/err.h> +#include <net/if.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "test_utils.h" +#include "../libxdp_internal.h" +#include "xdp_dispatcher_v1.h" + +#include <xdp/libxdp.h> +#include <bpf/libbpf.h> +#include <bpf/btf.h> + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#define BPFFS_DIR "/sys/fs/bpf/xdp" + +#define PROG_RUN_PRIO 42 +#define PROG_CHAIN_CALL_ACTIONS (1 << XDP_DROP) + +int get_prog_id(int prog_fd) +{ + struct bpf_prog_info info = {}; + __u32 len = sizeof(info); + int err; + + err = bpf_obj_get_info_by_fd(prog_fd, &info, &len); + if (err) + return -errno; + + return info.id; +} + +int load_dispatcher_v1(int ifindex) +{ + struct xdp_dispatcher_config_v1 dispatcher_config = {}; + struct bpf_object *obj_dispatcher, *obj_prog = NULL; + DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts); + struct bpf_program *dispatcher_prog, *xdp_prog; + int ret, btf_id, lfd = -1, dispatcher_id; + char pin_path[PATH_MAX], buf[PATH_MAX]; + const char *attach_func = "prog0"; + struct bpf_map *map; + + if (!ifindex) + return -ENOENT; + + obj_dispatcher = bpf_object__open("xdp_dispatcher_v1.o"); + if (!obj_dispatcher) + return -errno; + + btf_id = btf__find_by_name_kind(bpf_object__btf(obj_dispatcher), + attach_func, BTF_KIND_FUNC); + if (btf_id <= 0) { + ret = -ENOENT; + goto out; + } + opts.target_btf_id = btf_id; + + map = bpf_object__next_map(obj_dispatcher, NULL); + if (!map) { + ret = -ENOENT; + goto out; + } + + dispatcher_prog = bpf_object__find_program_by_name(obj_dispatcher, + "xdp_dispatcher"); + if (!dispatcher_prog) { + ret = -errno; + goto out; + } + + dispatcher_config.num_progs_enabled = 1; + dispatcher_config.chain_call_actions[0] = PROG_CHAIN_CALL_ACTIONS; + dispatcher_config.run_prios[0] = PROG_RUN_PRIO; + + ret = bpf_map__set_initial_value(map, &dispatcher_config, + sizeof(dispatcher_config)); + if (ret) + goto out; + + + ret = bpf_object__load(obj_dispatcher); + if (ret) + goto out; + + dispatcher_id = get_prog_id(bpf_program__fd(dispatcher_prog)); + if (dispatcher_id < 0) { + ret = dispatcher_id; + goto out; + } + + obj_prog = bpf_object__open("xdp_pass.o"); + if (!obj_prog) { + ret = -errno; + goto out; + } + + xdp_prog = bpf_object__find_program_by_name(obj_prog, "xdp_pass"); + if (!xdp_prog) { + ret = -errno; + goto out; + } + + ret = bpf_program__set_attach_target(xdp_prog, + bpf_program__fd(dispatcher_prog), + attach_func); + if (ret) + goto out; + + bpf_program__set_type(xdp_prog, BPF_PROG_TYPE_EXT); + bpf_program__set_expected_attach_type(xdp_prog, 0); + + ret = bpf_object__load(obj_prog); + if (ret) + goto out; + + lfd = bpf_link_create(bpf_program__fd(xdp_prog), + bpf_program__fd(dispatcher_prog), 0, &opts); + if (lfd < 0) { + ret = -errno; + goto out; + } + + ret = try_snprintf(pin_path, sizeof(pin_path), "%s/dispatch-%d-%d", + BPFFS_DIR, ifindex, dispatcher_id); + if (ret) + goto out; + + ret = mkdir(BPFFS_DIR, S_IRWXU); + if (ret && errno != EEXIST) { + ret = -errno; + printf("mkdir err (%s): %s\n", BPFFS_DIR, strerror(-ret)); + goto out; + } + + ret = mkdir(pin_path, S_IRWXU); + if (ret) { + ret = -errno; + printf("mkdir err (%s): %s\n", pin_path, strerror(-ret)); + goto out; + } + + ret = try_snprintf(buf, sizeof(buf), "%s/prog0-link", pin_path); + if (ret) + goto err_unpin; + + ret = bpf_obj_pin(lfd, buf); + if (ret) + goto err_unpin; + + ret = try_snprintf(buf, sizeof(buf), "%s/prog0-prog", pin_path); + if (ret) + goto err_unpin; + + ret = bpf_obj_pin(bpf_program__fd(xdp_prog), buf); + if (ret) + goto err_unpin; + + ret = xdp_attach_fd(bpf_program__fd(dispatcher_prog), -1, ifindex, + XDP_MODE_NATIVE); + if (ret) + goto err_unpin; + +out: + if (lfd >= 0) + close(lfd); + bpf_object__close(obj_dispatcher); + bpf_object__close(obj_prog); + return ret; + +err_unpin: + if (!try_snprintf(buf, sizeof(buf), "%s/prog0-link", pin_path)) + unlink(buf); + if (!try_snprintf(buf, sizeof(buf), "%s/prog0-prog", pin_path)) + unlink(buf); + rmdir(pin_path); + goto out; +} + +int check_old_dispatcher(int ifindex) +{ + struct xdp_multiprog *mp = NULL; + struct xdp_program *xdp_prog; + char buf[100]; + int ret; + + ret = load_dispatcher_v1(ifindex); + if (ret) + goto out; + + mp = xdp_multiprog__get_from_ifindex(ifindex); + ret = libxdp_get_error(mp); + if (ret) + goto out; + + if (xdp_multiprog__is_legacy(mp)) { + printf("Got unexpected legacy multiprog\n"); + ret = -EINVAL; + goto out; + } + + if (xdp_multiprog__program_count(mp) != 1) { + printf("Expected 1 attached program, got %d\n", + xdp_multiprog__program_count(mp)); + ret = -EINVAL; + goto out; + } + + xdp_prog = xdp_multiprog__next_prog(NULL, mp); + if (!xdp_prog) { + ret = -errno; + goto out; + } + + if (strcmp(xdp_program__name(xdp_prog), "xdp_pass")) { + printf("Expected xdp_pass program, got %s\n", + xdp_program__name(xdp_prog)); + ret = -EINVAL; + goto out; + } + + if (xdp_program__run_prio(xdp_prog) != PROG_RUN_PRIO) { + printf("Expected run prio %d got %d\n", PROG_RUN_PRIO, + xdp_program__run_prio(xdp_prog)); + ret = -EINVAL; + goto out; + } + + ret = xdp_program__print_chain_call_actions(xdp_prog, buf, sizeof(buf)); + if (ret) + goto out; + + if (strcmp(buf, "XDP_DROP")) { + printf("Expected actions XDP_PASS, got %s\n", buf); + ret = -EINVAL; + goto out; + } + + xdp_prog = xdp_program__open_file("xdp_pass.o", "xdp", NULL); + ret = libxdp_get_error(xdp_prog); + if (ret) + goto out; + + ret = xdp_program__attach(xdp_prog, ifindex, XDP_MODE_NATIVE, 0); + xdp_program__close(xdp_prog); + if (!ret) { + printf("Shouldn't have been able to attach a new program to ifindex!\n"); + ret = -EINVAL; + goto out; + } + ret = 0; + +out: + if (mp) + xdp_multiprog__detach(mp); + xdp_multiprog__close(mp); + return ret; +} + +static void usage(char *progname) +{ + fprintf(stderr, "Usage: %s <ifname>\n", progname); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + int ifindex, ret; + char *envval; + + envval = secure_getenv("VERBOSE_TESTS"); + + silence_libbpf_logging(); + if (envval && envval[0] == '1') + verbose_libxdp_logging(); + else + silence_libxdp_logging(); + + if (argc != 2) + usage(argv[0]); + + ifindex = if_nametoindex(argv[1]); + + ret = check_old_dispatcher(ifindex); + + return ret; +} diff --git a/lib/libxdp/tests/test_runner.sh b/lib/libxdp/tests/test_runner.sh new file mode 100755 index 0000000..eb043a1 --- /dev/null +++ b/lib/libxdp/tests/test_runner.sh @@ -0,0 +1,118 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Script to setup and manage tests for xdp-tools. +# Based on the test-env script from xdp-tutorial. +# +# Author: Toke Høiland-Jørgensen (toke@redhat.com) +# Date: 26 May 2020 +# Copyright (c) 2020 Red Hat + +set -o errexit +set -o nounset +umask 077 + +TEST_PROG_DIR="${TEST_PROG_DIR:-$(dirname "${BASH_SOURCE[0]}")}" +ALL_TESTS="" +VERBOSE_TESTS=${V:-0} + +export VERBOSE_TESTS + +# Odd return value for skipping, as only 0-255 is valid. +SKIPPED_TEST=249 + +skip_if_missing_libxdp_compat() +{ + if ! $TEST_PROG_DIR/check_kern_compat; then + exit "$SKIPPED_TEST" + fi +} + +is_func() +{ + type "$1" 2>/dev/null | grep -q 'is a function' +} + +check_run() +{ + local ret + + [ "$VERBOSE_TESTS" -eq "1" ] && echo "$@" + "$@" + ret=$? + if [ "$ret" -ne "0" ]; then + exit $ret + fi +} + +exec_test() +{ + local testn="$1" + local output + local ret + + printf " %-30s" "[$testn]" + if ! is_func "$testn"; then + echo "INVALID" + return 1 + fi + + output=$($testn 2>&1) + ret=$? + if [ "$ret" -eq "0" ]; then + echo "PASS" + elif [ "$ret" -eq "$SKIPPED_TEST" ]; then + echo "SKIPPED" + ret=0 + else + echo "FAIL" + fi + if [ "$ret" -ne "0" ] || [ "$VERBOSE_TESTS" -eq "1" ]; then + echo "$output" | sed 's/^/\t/' + fi + return $ret +} + +run_tests() +{ + local TESTS="$*" + local ret=0 + [ -z "$TESTS" ] && TESTS="$ALL_TESTS" + + echo " Running tests from $TEST_DEFINITIONS" + + for testn in $TESTS; do + exec_test $testn || ret=1 + if is_func cleanup_tests; then + cleanup_tests || true + fi + done + + return $ret +} + +usage() +{ + echo "Usage: $0 <test_definition_file> [test names]" >&2 + exit 1 +} + +if [ "$EUID" -ne "0" ]; then + if command -v sudo >/dev/null 2>&1; then + exec sudo env CC="$CC" CFLAGS="$CFLAGS" CPPFLAGS="$CPPFLAGS" LDLIBS="$LDLIBS" V=${VERBOSE_TESTS} "$0" "$@" + else + die "Tests must be run as root" + fi +else + if [ "${DID_UNSHARE:-0}" -ne "1" ]; then + echo "Executing tests in separate net- and mount namespaces" >&2 + exec env DID_UNSHARE=1 unshare -n -m "$0" "$@" + fi +fi + +TEST_DEFINITIONS="${1:-}" +[ -f "$TEST_DEFINITIONS" ] || usage +source "$TEST_DEFINITIONS" + +shift +run_tests "$@" diff --git a/lib/libxdp/tests/test_utils.h b/lib/libxdp/tests/test_utils.h new file mode 100644 index 0000000..1642c12 --- /dev/null +++ b/lib/libxdp/tests/test_utils.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __TEST_UTILS_H +#define __TEST_UTILS_H + +#include <bpf/libbpf.h> +#include <xdp/libxdp.h> + +#define __unused __attribute__((unused)) + +static int libbpf_silent_func(__unused enum libbpf_print_level level, + __unused const char *format, + __unused va_list args) +{ + return 0; +} + +static inline void silence_libbpf_logging(void) +{ + libbpf_set_print(libbpf_silent_func); +} + +static int libxdp_silent_func(__unused enum libxdp_print_level level, + __unused const char *format, + __unused va_list args) +{ + return 0; +} + +static int libxdp_verbose_func(__unused enum libxdp_print_level level, + __unused const char *format, + __unused va_list args) +{ + fprintf(stderr, " "); + vfprintf(stderr, format, args); + return 0; +} + +static inline void silence_libxdp_logging(void) +{ + libxdp_set_print(libxdp_silent_func); +} + +static inline void verbose_libxdp_logging(void) +{ + libxdp_set_print(libxdp_verbose_func); +} + +#endif diff --git a/lib/libxdp/tests/test_xdp_frags.c b/lib/libxdp/tests/test_xdp_frags.c new file mode 100644 index 0000000..d70e802 --- /dev/null +++ b/lib/libxdp/tests/test_xdp_frags.c @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#define _GNU_SOURCE + +#include <errno.h> +#include <linux/err.h> +#include <net/if.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> +#include <unistd.h> + +#include "test_utils.h" + +#include <xdp/libxdp.h> +#include <bpf/libbpf.h> + +# define ARRAY_SIZE(_x) (sizeof(_x) / sizeof((_x)[0])) + +static bool kern_compat; + + +static struct xdp_program *load_prog(void) +{ + DECLARE_LIBXDP_OPTS(xdp_program_opts, opts, + .prog_name = "xdp_pass", + .find_filename = "xdp-dispatcher.o", + ); + return xdp_program__create(&opts); +} + +static int check_attached_progs(int ifindex, int count, bool frags) +{ + struct xdp_multiprog *mp; + int ret; + + /* If the kernel does not support frags, we always expect + * frags support to be disabled on a returned dispatcher + */ + if (!kern_compat) + frags = false; + + mp = xdp_multiprog__get_from_ifindex(ifindex); + ret = libxdp_get_error(mp); + if (ret) { + fprintf(stderr, "Couldn't get multiprog on ifindex %d: %s\n", + ifindex, strerror(-ret)); + return ret; + } + + ret = -EINVAL; + + if (xdp_multiprog__is_legacy(mp)) { + fprintf(stderr, "Found legacy prog on ifindex %d\n", ifindex); + goto out; + } + + if (xdp_multiprog__program_count(mp) != count) { + fprintf(stderr, "Expected %d programs loaded on ifindex %d, found %d\n", + count, ifindex, xdp_multiprog__program_count(mp)); + goto out; + } + + if (xdp_multiprog__xdp_frags_support(mp) != frags) { + fprintf(stderr, + "Multiprog on ifindex %d %s frags, expected %s\n", + ifindex, + xdp_multiprog__xdp_frags_support(mp) ? + "supports" : + "does not support", + frags ? "support" : "no support"); + goto out; + } + + ret = 0; + +out: + xdp_multiprog__close(mp); + return ret; +} + +static void print_test_result(const char *func, int ret) +{ + fflush(stderr); + fprintf(stderr, "%s:\t%s\n", func, ret ? "FAILED" : "PASSED"); + fflush(stdout); +} + +static int load_attach_prog(struct xdp_program **prog, int ifindex, bool frags) +{ + int ret; + + *prog = load_prog(); + if (!*prog) { + ret = -errno; + fprintf(stderr, "Couldn't load program: %s\n", strerror(-ret)); + return ret; + } + + ret = xdp_program__set_xdp_frags_support(*prog, frags); + if (ret) + return ret; + + return xdp_program__attach(*prog, ifindex, XDP_MODE_NATIVE, 0); +} + +static int _check_load(int ifindex, bool frags, bool should_succeed) +{ + struct xdp_program *prog = NULL; + bool attached; + int ret; + + ret = load_attach_prog(&prog, ifindex, frags); + attached = !ret; + + if (attached != should_succeed) { + ret = -EINVAL; + goto out; + } + + if (should_succeed) + ret = check_attached_progs(ifindex, 1, frags); + else + ret = 0; + +out: + if (attached) + xdp_program__detach(prog, ifindex, XDP_MODE_NATIVE, 0); + xdp_program__close(prog); + return ret; +} + +static int check_load_frags(int ifindex_bigmtu, int ifindex_smallmtu) +{ + int ret = _check_load(ifindex_smallmtu, true, true); + if (!ret && ifindex_bigmtu) + _check_load(ifindex_bigmtu, true, true); + print_test_result(__func__, ret); + return ret; +} + +static int check_load_nofrags_success(int ifindex) +{ + int ret = _check_load(ifindex, false, true); + print_test_result(__func__, ret); + return ret; +} + +static int check_load_nofrags_fail(int ifindex) +{ + int ret = _check_load(ifindex, false, false); + print_test_result(__func__, ret); + return ret; +} +static int check_load_frags_multi(int ifindex) +{ + struct xdp_program *prog1 = NULL, *prog2 = NULL; + int ret; + + ret = load_attach_prog(&prog1, ifindex, true); + if (ret) + goto out; + + ret = load_attach_prog(&prog2, ifindex, true); + if (ret) + goto out_prog1; + + ret = check_attached_progs(ifindex, 2, true); + + xdp_program__detach(prog2, ifindex, XDP_MODE_NATIVE, 0); +out_prog1: + xdp_program__detach(prog1, ifindex, XDP_MODE_NATIVE, 0); +out: + xdp_program__close(prog2); + xdp_program__close(prog1); + print_test_result(__func__, ret); + return ret; +} + +static int check_load_mix_small(int ifindex) +{ + struct xdp_program *prog1 = NULL, *prog2 = NULL; + int ret; + + ret = load_attach_prog(&prog1, ifindex, true); + if (ret) + goto out; + + /* First program attached, dispatcher supports frags */ + ret = check_attached_progs(ifindex, 1, true); + if (ret) + goto out; + + ret = load_attach_prog(&prog2, ifindex, false); + if (ret) + goto out_prog1; + + /* Mixed program attachment, dispatcher should not support frags */ + ret = check_attached_progs(ifindex, 2, false); + + ret = xdp_program__detach(prog2, ifindex, XDP_MODE_NATIVE, 0) || ret; + if (ret) + goto out_prog1; + + /* Second program removed, back to frags-only */ + ret = check_attached_progs(ifindex, 1, true) || ret; + +out_prog1: + xdp_program__detach(prog1, ifindex, XDP_MODE_NATIVE, 0); + +out: + xdp_program__close(prog2); + xdp_program__close(prog1); + print_test_result(__func__, ret); + return ret; +} + +static int check_load_mix_big(int ifindex) +{ + struct xdp_program *prog1 = NULL, *prog2 = NULL; + int ret; + + ret = load_attach_prog(&prog1, ifindex, true); + if (ret) + goto out; + + /* First program attached, dispatcher supports frags */ + ret = check_attached_progs(ifindex, 1, true); + if (ret) + goto out; + + /* Second non-frags program should fail on big-MTU device */ + ret = load_attach_prog(&prog2, ifindex, false); + if (!ret) { + xdp_program__detach(prog2, ifindex, XDP_MODE_NATIVE, 0); + ret = -EINVAL; + goto out_prog1; + } + + /* Still only a single program loaded, with frags support */ + ret = check_attached_progs(ifindex, 1, true); + +out_prog1: + xdp_program__detach(prog1, ifindex, XDP_MODE_NATIVE, 0); + +out: + xdp_program__close(prog2); + xdp_program__close(prog1); + print_test_result(__func__, ret); + return ret; +} + + +static bool check_frags_compat(void) +{ + struct xdp_program *test_prog; + struct bpf_program *prog; + struct bpf_object *obj; + bool ret = false; + int err; + + test_prog = load_prog(); + if (!test_prog) + return false; + + obj = xdp_program__bpf_obj(test_prog); + if (!obj) + goto out; + + prog = bpf_object__find_program_by_name(obj, "xdp_pass"); + if (!prog) + goto out; + + bpf_program__set_flags(prog, BPF_F_XDP_HAS_FRAGS); + err = bpf_object__load(obj); + if (!err) { + printf("Kernel supports XDP programs with frags\n"); + ret = true; + } else { + printf("Kernel DOES NOT support XDP programs with frags\n"); + } + fflush(stdout); + +out: + xdp_program__close(test_prog); + return ret; +} + +static void usage(char *progname) +{ + fprintf(stderr, "Usage: %s <ifname_bigmtu> <ifname_smallmtu>\n", progname); + exit(EXIT_FAILURE); +} + +int main(int argc, char **argv) +{ + struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; + int ifindex_bigmtu, ifindex_smallmtu, ret; + char *envval; + + envval = secure_getenv("VERBOSE_TESTS"); + + silence_libbpf_logging(); + if (envval && envval[0] == '1') + verbose_libxdp_logging(); + else + silence_libxdp_logging(); + + kern_compat = check_frags_compat(); + + if (argc != 3) + usage(argv[0]); + + ifindex_bigmtu = if_nametoindex(argv[1]); + ifindex_smallmtu = if_nametoindex(argv[2]); + if (!ifindex_bigmtu || !ifindex_smallmtu) { + fprintf(stderr, "Interface '%s' or '%s' not found.\n", argv[1], argv[2]); + usage(argv[0]); + } + + if (setrlimit(RLIMIT_MEMLOCK, &r)) { + fprintf(stderr, "ERROR: setrlimit(RLIMIT_MEMLOCK) \"%s\"\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + ret = check_load_frags(kern_compat ? ifindex_bigmtu : 0, ifindex_smallmtu); + ret = check_load_nofrags_success(ifindex_smallmtu) || ret; + if (kern_compat) { + ret = check_load_nofrags_fail(ifindex_bigmtu) || ret; + ret = check_load_frags_multi(ifindex_bigmtu) || ret; + ret = check_load_mix_big(ifindex_bigmtu) || ret; + } + ret = check_load_mix_small(ifindex_smallmtu) || ret; + + return ret; +} diff --git a/lib/libxdp/tests/test_xsk_refcnt.c b/lib/libxdp/tests/test_xsk_refcnt.c new file mode 100644 index 0000000..bdd22da --- /dev/null +++ b/lib/libxdp/tests/test_xsk_refcnt.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) + +#include <errno.h> +#include <linux/err.h> +#include <net/if.h> +#include <pthread.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <sys/resource.h> +#include <unistd.h> + +#include "test_utils.h" + +#include <xdp/libxdp.h> +#include <xdp/xsk.h> + +typedef __u64 u64; +typedef __u32 u32; +typedef __u16 u16; +typedef __u8 u8; + +#define MAX_EVENTS 10 +#define MAX_NUM_QUEUES 4 +#define TEST_NAME_LENGTH 128 + +struct xsk_umem_info { + struct xsk_ring_prod fq; + struct xsk_ring_cons cq; + struct xsk_umem *umem; + void *buffer; +}; + +struct xsk_socket_info { + struct xsk_ring_cons rx; + struct xsk_umem_info *umem; + struct xsk_socket *xsk; +}; + +/* Event holds socket operations that are run concurrently + * and in theory can produce a race condition + */ +struct xsk_test_event { + u32 num_create; + u32 num_delete; + u32 create_qids[MAX_NUM_QUEUES]; /* QIDs for sockets being created in this event */ + u32 delete_qids[MAX_NUM_QUEUES]; /* QIDs for sockets being deleted in this event */ +}; + +struct xsk_test { + char name[TEST_NAME_LENGTH]; + u32 num_events; + struct xsk_test_event events[MAX_EVENTS]; +}; + +/* Tests that use less queues must come first, + * so we can run all possible tests on VMs with + * small number of CPUs + */ +static struct xsk_test all_tests[] = { + { "Single socket created and deleted", + .num_events = 2, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_delete = 1, .delete_qids = {0} } + }}, + { "2 sockets, created and deleted sequentially", + .num_events = 4, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_create = 1, .create_qids = {1} }, + { .num_delete = 1, .delete_qids = {0} }, + { .num_delete = 1, .delete_qids = {1} } + }}, + { "2 sockets, created sequentially and deleted asynchronously", + .num_events = 3, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_create = 1, .create_qids = {1} }, + { .num_delete = 2, .delete_qids = {0, 1} } + }}, + { "2 sockets, asynchronously delete and create", + .num_events = 3, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_create = 1, .create_qids = {1}, + .num_delete = 1, .delete_qids = {0} }, + { .num_delete = 1, .delete_qids = {1} } + }}, + { "3 sockets, created and deleted sequentially", + .num_events = 6, + .events = {{ .num_create = 1, .create_qids = {0} }, + { .num_create = 1, .create_qids = {1} }, + { .num_create = 1, .create_qids = {2} }, + { .num_delete = 1, .delete_qids = {1} }, + { .num_delete = 1, .delete_qids = {2} }, + { .num_delete = 1, .delete_qids = {0} } + }}, +}; + +# define ARRAY_SIZE(_x) (sizeof(_x) / sizeof((_x)[0])) + +static const char *opt_if; +static const u8 num_tests = ARRAY_SIZE(all_tests); + +static struct xsk_socket_info *xsks[MAX_NUM_QUEUES]; + +#define FRAME_SIZE 64 +#define NUM_FRAMES (XSK_RING_CONS__DEFAULT_NUM_DESCS * 2) + +static struct xsk_umem_info *xsk_configure_umem(void *buffer, u64 size) +{ + struct xsk_umem_info *umem; + int ret; + + umem = calloc(1, sizeof(*umem)); + if (!umem) + exit(EXIT_FAILURE); + + ret = xsk_umem__create(&umem->umem, buffer, size, &umem->fq, &umem->cq, + NULL); + if (ret) + exit(ret); + + umem->buffer = buffer; + return umem; +} + +static struct xsk_socket_info *xsk_configure_socket(struct xsk_umem_info *umem, + unsigned int qid) +{ + struct xsk_socket_config cfg = {}; + struct xsk_socket_info *xsk; + struct xsk_ring_cons *rxr; + + xsk = calloc(1, sizeof(*xsk)); + if (!xsk) + exit(EXIT_FAILURE); + + xsk->umem = umem; + cfg.rx_size = XSK_RING_CONS__DEFAULT_NUM_DESCS; + + rxr = &xsk->rx; + xsk_socket__create(&xsk->xsk, opt_if, qid, umem->umem, + rxr, NULL, &cfg); + + return xsk; +} + +static void *create_socket(void *args) +{ + struct xsk_umem_info *umem; + u32 qid = *(u32 *)args; + void *buffs; + + if (posix_memalign(&buffs, + getpagesize(), /* PAGE_SIZE aligned */ + NUM_FRAMES * FRAME_SIZE)) { + fprintf(stderr, "ERROR: Can't allocate buffer memory \"%s\"\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + umem = xsk_configure_umem(buffs, NUM_FRAMES * FRAME_SIZE); + xsks[qid] = xsk_configure_socket(umem, qid); + + return NULL; +} + +static void *delete_socket(void *args) +{ + u32 qid = *(u32 *)args; + struct xsk_umem *umem; + void *buff; + + buff = xsks[qid]->umem->buffer; + umem = xsks[qid]->umem->umem; + xsk_socket__delete(xsks[qid]->xsk); + free(buff); + (void)xsk_umem__delete(umem); + + return NULL; +} + +static bool xsk_prog_attached(void) +{ + char xsk_prog_name[] = "xsk_def_prog"; + int ifindex = if_nametoindex(opt_if); + struct xdp_program *xsk_prog; + struct xdp_multiprog *mp; + bool answer = false; + + mp = xdp_multiprog__get_from_ifindex(ifindex); + if (IS_ERR_OR_NULL(mp)) + return false; + + xsk_prog = xdp_multiprog__is_legacy(mp) ? xdp_multiprog__main_prog(mp) : + xdp_multiprog__next_prog(NULL, mp); + + if (IS_ERR_OR_NULL(xsk_prog)) + goto free_mp; + + answer = !strncmp(xsk_prog_name, xdp_program__name(xsk_prog), + sizeof(xsk_prog_name)); +free_mp: + xdp_multiprog__close(mp); + return answer; +} + +static void update_reference_refcnt(struct xsk_test_event *event, int *refcnt) +{ + *refcnt += event->num_create; + *refcnt -= event->num_delete; +} + +static bool check_run_event(struct xsk_test_event *event, int *refcnt) +{ + pthread_t threads[MAX_NUM_QUEUES]; + bool prog_attached, prog_needed; + u8 thread_num = 0, i; + int ret; + + update_reference_refcnt(event, refcnt); + + for (i = 0; i < event->num_create; i++) { + ret = pthread_create(&threads[thread_num++], NULL, + &create_socket, &event->create_qids[i]); + if (ret) + exit(ret); + } + + for (i = 0; i < event->num_delete; i++) { + ret = pthread_create(&threads[thread_num++], NULL, + &delete_socket, &event->delete_qids[i]); + if (ret) + exit(ret); + } + + for (i = 0; i < thread_num; i++) + pthread_join(threads[i], NULL); + + prog_attached = xsk_prog_attached(); + prog_needed = *refcnt > 0; + + if (prog_needed != prog_attached) { + printf("Program is referenced by %d sockets, but is %s attached\n", + *refcnt, prog_attached ? "still" : "not"); + return false; + } + + return true; +} + +static bool check_run_test(struct xsk_test *test) +{ + bool test_ok = false; + int refcnt = 0; + u8 i = 0; + + for (i = 0; i < test->num_events; i++) { + if (!check_run_event(&test->events[i], &refcnt)) { + printf("Event %u failed\n", i); + goto print_result; + } + } + + /* Do not let tests interfere with each other */ + sleep(1); + + test_ok = true; + +print_result: + printf("%s: %s\n", test->name, test_ok ? "PASSED" : "FAILED"); + return test_ok; +} + +static int read_args(int argc, char **argv) +{ + if (argc != 2) + return -1; + + opt_if = argv[1]; + return 0; +} + +int main(int argc, char **argv) +{ + struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; + u8 i = 0; + + if (read_args(argc, argv)) + return -1; + + if (setrlimit(RLIMIT_MEMLOCK, &r)) { + fprintf(stderr, "ERROR: setrlimit(RLIMIT_MEMLOCK) \"%s\"\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + + silence_libbpf_logging(); + + for (i = 0; i < num_tests; i++) { + if (!check_run_test(&all_tests[i])) + exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/lib/libxdp/tests/xdp_dispatcher_v1.c b/lib/libxdp/tests/xdp_dispatcher_v1.c new file mode 100644 index 0000000..00bb426 --- /dev/null +++ b/lib/libxdp/tests/xdp_dispatcher_v1.c @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include <linux/bpf.h> +#include <bpf/bpf_helpers.h> +#include <bpf/bpf_endian.h> + +#include "xdp_dispatcher_v1.h" + +#define XDP_METADATA_SECTION "xdp_metadata" +#define XDP_DISPATCHER_VERSION_V1 1 +#define XDP_DISPATCHER_RETVAL 31 + + +static volatile const struct xdp_dispatcher_config_v1 conf = {}; + +__attribute__ ((noinline)) +int prog0(struct xdp_md *ctx) { + volatile int ret = XDP_DISPATCHER_RETVAL; + + if (!ctx) + return XDP_ABORTED; + return ret; +} +__attribute__ ((noinline)) + +SEC("xdp") +int xdp_dispatcher(struct xdp_md *ctx) +{ + __u8 num_progs_enabled = conf.num_progs_enabled; + int ret; + + if (num_progs_enabled < 1) + goto out; + ret = prog0(ctx); + if (!((1U << ret) & conf.chain_call_actions[0])) + return ret; + +out: + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; +__uint(dispatcher_version, XDP_DISPATCHER_VERSION_V1) SEC(XDP_METADATA_SECTION); diff --git a/lib/libxdp/tests/xdp_dispatcher_v1.h b/lib/libxdp/tests/xdp_dispatcher_v1.h new file mode 100644 index 0000000..55dac37 --- /dev/null +++ b/lib/libxdp/tests/xdp_dispatcher_v1.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __XDP_DISPATCHER_V1_H +#define __XDP_DISPATCHER_V1_H + +#ifndef MAX_DISPATCHER_ACTIONS +#define MAX_DISPATCHER_ACTIONS 10 +#endif + +struct xdp_dispatcher_config_v1 { + __u8 num_progs_enabled; + __u32 chain_call_actions[MAX_DISPATCHER_ACTIONS]; + __u32 run_prios[MAX_DISPATCHER_ACTIONS]; +}; + +#endif diff --git a/lib/libxdp/tests/xdp_pass.c b/lib/libxdp/tests/xdp_pass.c new file mode 100644 index 0000000..6b61a00 --- /dev/null +++ b/lib/libxdp/tests/xdp_pass.c @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/bpf.h> +#include <bpf/bpf_helpers.h> + +SEC("xdp") +int xdp_pass(struct xdp_md *ctx) +{ + return XDP_PASS; +} + +char _license[] SEC("license") = "GPL"; |