summaryrefslogtreecommitdiffstats
path: root/lib/testing
diff options
context:
space:
mode:
Diffstat (limited to 'lib/testing')
-rw-r--r--lib/testing/.gitignore1
-rw-r--r--lib/testing/Makefile15
-rwxr-xr-xlib/testing/run_tests.sh19
-rwxr-xr-xlib/testing/setup-netns-env.sh25
-rw-r--r--lib/testing/test-tool.c275
-rw-r--r--lib/testing/test_config.install.sh9
-rw-r--r--lib/testing/test_long_func_name.c31
-rwxr-xr-xlib/testing/test_runner.sh591
-rw-r--r--lib/testing/xdp_drop.c10
-rw-r--r--lib/testing/xdp_pass.c16
10 files changed, 992 insertions, 0 deletions
diff --git a/lib/testing/.gitignore b/lib/testing/.gitignore
new file mode 100644
index 0000000..f5f0d2e
--- /dev/null
+++ b/lib/testing/.gitignore
@@ -0,0 +1 @@
+test-tool
diff --git a/lib/testing/Makefile b/lib/testing/Makefile
new file mode 100644
index 0000000..8d5c153
--- /dev/null
+++ b/lib/testing/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+
+TEST_TARGETS := test-tool
+XDP_TARGETS := test_long_func_name xdp_drop xdp_pass
+SCRIPTS_FILES := test_runner.sh setup-netns-env.sh run_tests.sh
+XDP_OBJ_INSTALL :=
+
+LIB_DIR = ..
+
+include $(LIB_DIR)/common.mk
+
+install_local::
+ install -m 0755 -d $(DESTDIR)$(SCRIPTSDIR)
+ install -m 0644 test_config.install.sh $(DESTDIR)$(SCRIPTSDIR)/test_config.sh
+ install -m 0644 $(XDP_OBJ) $(DESTDIR)$(SCRIPTSDIR)/
diff --git a/lib/testing/run_tests.sh b/lib/testing/run_tests.sh
new file mode 100755
index 0000000..115bedd
--- /dev/null
+++ b/lib/testing/run_tests.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+TEST_PROG_DIR="${TEST_PROG_DIR:-$(dirname "${BASH_SOURCE[0]}")}"
+TESTS_DIR="${TESTS_DIR:-$TEST_PROG_DIR/tests}"
+TEST_RUNNER="$TEST_PROG_DIR/test_runner.sh"
+
+RET=0
+
+echo "Running all tests from $TESTS_DIR"
+for f in "$TESTS_DIR"/*/test-*.sh; do
+ if [[ ! -f "$f" ]]; then
+ echo "No tests found!"
+ exit 1
+ fi
+
+ "$TEST_RUNNER" "$f" || RET=1
+done
+
+exit $RET
diff --git a/lib/testing/setup-netns-env.sh b/lib/testing/setup-netns-env.sh
new file mode 100755
index 0000000..b3db425
--- /dev/null
+++ b/lib/testing/setup-netns-env.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Script to setup things inside a test environment, used by testenv.sh for
+# executing commands.
+#
+# Author: Toke Høiland-Jørgensen (toke@redhat.com)
+# Date: 7 March 2019
+# Copyright (c) 2019 Red Hat
+
+
+die()
+{
+ echo "$1" >&2
+ exit 1
+}
+
+[ -n "$TESTENV_NAME" ] || die "TESTENV_NAME missing from environment"
+[ -n "$1" ] || die "Usage: $0 <command to execute>"
+
+set -o nounset
+
+mount -t bpf bpf /sys/fs/bpf/ || die "Unable to mount /sys/fs/bpf inside test environment"
+
+exec "$@"
diff --git a/lib/testing/test-tool.c b/lib/testing/test-tool.c
new file mode 100644
index 0000000..7e2a828
--- /dev/null
+++ b/lib/testing/test-tool.c
@@ -0,0 +1,275 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+#include <linux/err.h>
+#include <linux/if_link.h>
+
+#include "params.h"
+#include "logging.h"
+#include "util.h"
+#include "xdp_sample.h"
+
+
+#define PROG_NAME "test-tool"
+
+
+#ifndef HAVE_LIBBPF_BPF_OBJECT__NEXT_PROGRAM
+static struct bpf_program *bpf_object__next_program(const struct bpf_object *obj,
+ struct bpf_program *prog)
+{
+ return bpf_program__next(prog, obj);
+}
+#endif
+
+
+struct enum_val xdp_modes[] = {
+ {"native", XDP_MODE_NATIVE},
+ {"skb", XDP_MODE_SKB},
+ {"hw", XDP_MODE_HW},
+ {"unspecified", XDP_MODE_UNSPEC},
+ {NULL, 0}
+};
+
+
+static const struct loadopt {
+ bool help;
+ enum xdp_attach_mode mode;
+ struct iface iface;
+ char *filename;
+} defaults_load = {
+ .mode = XDP_MODE_NATIVE
+};
+
+
+static struct bpf_object *open_bpf_obj(const char *filename,
+ struct bpf_object_open_opts *opts)
+{
+ struct bpf_object *obj;
+ int err;
+
+ obj = bpf_object__open_file(filename, opts);
+ err = libbpf_get_error(obj);
+ if (err) {
+ if (err == -ENOENT)
+ pr_debug(
+ "Couldn't load the eBPF program (libbpf said 'no such file').\n"
+ "Maybe the program was compiled with a too old "
+ "version of LLVM (need v9.0+)?\n");
+ return ERR_PTR(err);
+ }
+
+ return obj;
+}
+
+static int do_xdp_attach(int ifindex, int prog_fd, int old_fd, __u32 xdp_flags)
+{
+#ifdef HAVE_LIBBPF_BPF_XDP_ATTACH
+ LIBBPF_OPTS(bpf_xdp_attach_opts, opts,
+ .old_prog_fd = old_fd);
+ return bpf_xdp_attach(ifindex, prog_fd, xdp_flags, &opts);
+#else
+ DECLARE_LIBBPF_OPTS(bpf_xdp_set_link_opts, opts, .old_fd = old_fd);
+ return bpf_set_link_xdp_fd_opts(ifindex, prog_fd, xdp_flags, old_fd ? &opts : NULL);
+#endif
+}
+
+int do_load(const void *cfg, __unused const char *pin_root_path)
+{
+ const struct loadopt *opt = cfg;
+ struct bpf_program *bpf_prog;
+ char errmsg[STRERR_BUFSIZE];
+ struct bpf_object *obj;
+ int err = EXIT_SUCCESS;
+ int xdp_flags;
+ int prog_fd;
+
+ silence_libbpf_logging();
+retry:
+ obj = open_bpf_obj(opt->filename, NULL);
+
+ if (IS_ERR(obj)) {
+ err = PTR_ERR(obj);
+
+ if (err == -EPERM && !double_rlimit())
+ goto retry;
+
+ libxdp_strerror(err, errmsg, sizeof(errmsg));
+ pr_warn("ERROR: Couldn't open file '%s': %s\n",
+ opt->filename, errmsg);
+ goto out;
+ }
+
+ err = bpf_object__load(obj);
+ if (err) {
+
+ if (err == -EPERM && !double_rlimit()) {
+ bpf_object__close(obj);
+ goto retry;
+ }
+
+ libbpf_strerror(err, errmsg, sizeof(errmsg));
+ pr_warn("ERROR: Can't load eBPF object: %s(%d)\n",
+ errmsg, err);
+ goto out;
+ }
+
+ bpf_prog = bpf_object__next_program(obj, NULL);
+ if (!bpf_prog) {
+ pr_warn("ERROR: Couldn't find xdp program in bpf object!\n");
+ err = -ENOENT;
+ goto out;
+ }
+
+ prog_fd = bpf_program__fd(bpf_prog);
+ if (prog_fd < 0) {
+ err = prog_fd;
+ libxdp_strerror(err, errmsg, sizeof(errmsg));
+ pr_warn("ERROR: Couldn't find xdp program's file descriptor: %s\n",
+ errmsg);
+ goto out;
+ }
+
+ xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST;
+ switch (opt->mode) {
+ case XDP_MODE_SKB:
+ xdp_flags |= XDP_FLAGS_SKB_MODE;
+ break;
+ case XDP_MODE_NATIVE:
+ xdp_flags |= XDP_FLAGS_DRV_MODE;
+ break;
+ case XDP_MODE_HW:
+ xdp_flags |= XDP_FLAGS_HW_MODE;
+ break;
+ case XDP_MODE_UNSPEC:
+ break;
+ }
+ err = do_xdp_attach(opt->iface.ifindex, prog_fd, 0, xdp_flags);
+ if (err < 0) {
+ pr_info("ERROR: Failed attaching XDP program to ifindex %d: %s\n",
+ opt->iface.ifindex, strerror(-err));
+
+ switch (-err) {
+ case EBUSY:
+ case EEXIST:
+ pr_info("XDP already loaded on device.\n");
+ break;
+ case EOPNOTSUPP:
+ pr_info("XDP mode not supported; try using SKB mode.\n");
+ break;
+ default:
+ break;
+ }
+ goto out;
+ }
+out:
+ return err;
+}
+
+
+static struct prog_option load_options[] = {
+ DEFINE_OPTION("mode", OPT_ENUM, struct loadopt, mode,
+ .short_opt = 'm',
+ .typearg = xdp_modes,
+ .metavar = "<mode>",
+ .help = "Load XDP program in <mode>; default native"),
+ DEFINE_OPTION("dev", OPT_IFNAME, struct loadopt, iface,
+ .positional = true,
+ .metavar = "<ifname>",
+ .required = true,
+ .help = "Load on device <ifname>"),
+ DEFINE_OPTION("filename", OPT_STRING, struct loadopt, filename,
+ .positional = true,
+ .metavar = "<filename>",
+ .required = true,
+ .help = "Load program from <filename>"),
+ END_OPTIONS
+};
+
+enum probe_action {
+ PROBE_CPUMAP_PROGRAM,
+};
+
+struct enum_val probe_actions[] = {
+ {"cpumap-prog", PROBE_CPUMAP_PROGRAM},
+ {NULL, 0}
+};
+
+static const struct probeopt {
+ enum probe_action action;
+} defaults_probe = {};
+
+int do_probe(const void *cfg, __unused const char *pin_root_path)
+{
+ const struct probeopt *opt = cfg;
+ bool res = false;
+
+ switch (opt->action) {
+ case PROBE_CPUMAP_PROGRAM:
+ res = sample_probe_cpumap_compat();
+ break;
+ default:
+ return EXIT_FAILURE;
+ }
+
+ pr_debug("Probing for %s: %s\n",
+ probe_actions[opt->action].name,
+ res ? "Supported" : "Unsupported");
+
+ return res ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+
+static struct prog_option probe_options[] = {
+ DEFINE_OPTION("action", OPT_ENUM, struct probeopt, action,
+ .positional = true,
+ .metavar = "<action>",
+ .required = true,
+ .typearg = probe_actions,
+ .help = "Probe for <action>"),
+ END_OPTIONS
+};
+
+
+int do_help(__unused const void *cfg, __unused const char *pin_root_path)
+{
+ fprintf(stderr,
+ "Usage: test-tool COMMAND [options]\n"
+ "\n"
+ "COMMAND can be one of:\n"
+ " load - load an XDP program on an interface\n"
+ " probe - probe for kernel features\n"
+ " help - show this help message\n"
+ "\n"
+ "Use 'test-tool COMMAND --help' to see options for each command\n");
+ return -1;
+}
+
+
+static const struct prog_command cmds[] = {
+ DEFINE_COMMAND(load, "Load an XDP program on an interface"),
+ DEFINE_COMMAND(probe, "Probe for kernel features"),
+ { .name = "help", .func = do_help, .no_cfg = true },
+ END_COMMANDS
+};
+
+
+union all_opts {
+ struct loadopt load;
+ struct probeopt probe;
+};
+
+
+int main(int argc, char **argv)
+{
+ if (argc > 1)
+ return dispatch_commands(argv[1], argc - 1, argv + 1, cmds,
+ sizeof(union all_opts), PROG_NAME, false);
+ return do_help(NULL, NULL);
+}
diff --git a/lib/testing/test_config.install.sh b/lib/testing/test_config.install.sh
new file mode 100644
index 0000000..68c275f
--- /dev/null
+++ b/lib/testing/test_config.install.sh
@@ -0,0 +1,9 @@
+# Test config for having tools in $PATH - to be installed along with the
+# test runners in /usr/share/xdp-tools
+
+XDP_FILTER=xdp-filter
+XDP_LOADER=xdp-loader
+XDP_BENCH=xdp-bench
+XDP_MONITOR=xdp-monitor
+XDP_TRAFFICGEN=xdp-trafficgen
+XDPDUMP=xdpdump
diff --git a/lib/testing/test_long_func_name.c b/lib/testing/test_long_func_name.c
new file mode 100644
index 0000000..867cd0b
--- /dev/null
+++ b/lib/testing/test_long_func_name.c
@@ -0,0 +1,31 @@
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <xdp/xdp_helpers.h>
+
+#define bpf_debug(fmt, ...) \
+ { \
+ char __fmt[] = fmt; \
+ bpf_trace_printk(__fmt, sizeof(__fmt), \
+ ##__VA_ARGS__); \
+ }
+
+SEC("xdp")
+int xdp_test_prog_with_a_long_name(struct xdp_md *ctx)
+{
+ bpf_debug("PASS[1]: prog %u\n", ctx->ingress_ifindex);
+ return XDP_PASS;
+}
+
+SEC("xdp")
+int xdp_test_prog_with_a_long_name_too(struct xdp_md *ctx)
+{
+ bpf_debug("PASS[2]: prog %u\n", ctx->ingress_ifindex);
+ return XDP_PASS;
+}
+
+struct {
+ __uint(priority, 5);
+ __uint(XDP_PASS, 1);
+} XDP_RUN_CONFIG(xdp_test_prog_with_a_long_name);
+
+char _license[] SEC("license") = "GPL";
diff --git a/lib/testing/test_runner.sh b/lib/testing/test_runner.sh
new file mode 100755
index 0000000..726b902
--- /dev/null
+++ b/lib/testing/test_runner.sh
@@ -0,0 +1,591 @@
+#!/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]}")}"
+SETUP_SCRIPT="$TEST_PROG_DIR/setup-netns-env.sh"
+TEST_CONFIG="$TEST_PROG_DIR/test_config.sh"
+IP6_SUBNET=fc42:dead:cafe # must have exactly three :-separated elements
+IP6_PREFIX_SIZE=64 # Size of assigned prefixes
+IP6_FULL_PREFIX_SIZE=48 # Size of IP6_SUBNET
+IP4_SUBNET=10.11
+IP4_PREFIX_SIZE=24 # Size of assigned prefixes
+IP4_FULL_PREFIX_SIZE=16 # Size of IP4_SUBNET
+VLAN_IDS=(1 2)
+GENERATED_NAME_PREFIX="xdptest"
+ALL_TESTS=""
+VERBOSE_TESTS=${V:-0}
+
+NEEDED_TOOLS="capinfos ethtool ip ping sed tc tcpdump timeout nc tshark"
+MAX_NAMELEN=15
+
+if [ -f "$TEST_CONFIG" ]; then
+ source "$TEST_CONFIG"
+fi
+
+if command -v ping6 >/dev/null 2>&1; then
+ PING6=ping6
+else
+ PING6=ping
+fi
+
+# Odd return value for skipping, as only 0-255 is valid.
+SKIPPED_TEST=249
+
+# Global state variables that will be set by options etc below
+GENERATE_NEW=0
+CLEANUP_FUNC=
+STATEDIR=
+STATEFILE=
+CMD=
+NS=
+LEGACY_IP=1
+USE_VLAN=0
+RUN_ON_INNER=0
+
+# State variables that are written to and read from statefile
+STATEVARS=(IP6_PREFIX IP4_PREFIX
+ INSIDE_IP6 INSIDE_IP4 INSIDE_MAC
+ OUTSIDE_IP6 OUTSIDE_IP4 OUTSIDE_MAC
+ ENABLE_IPV4 ENABLE_VLAN)
+IP6_PREFIX=
+IP4_PREFIX=
+INSIDE_IP6=
+INSIDE_IP4=
+INSIDE_MAC=
+OUTSIDE_IP6=
+OUTSIDE_IP4=
+OUTSIDE_MAC=
+ENABLE_IPV4=0
+ENABLE_VLAN=0
+
+is_trace_attach_supported()
+{
+ if [[ -z "${TRACE_ATTACH_SUPPORT:-}" ]]; then
+ [ -f "$STATEDIR/trace_attach_support" ] && \
+ TRACE_ATTACH_SUPPORT=$(< "$STATEDIR/trace_attach_support")
+
+ if [[ -z "${TRACE_ATTACH_SUPPORT:-}" ]]; then
+ RESULT=$($XDP_LOADER load -v "$NS" "$TEST_PROG_DIR/xdp_pass.o" 2>&1)
+ PID=$(start_background "$XDPDUMP -i $NS")
+ RESULT=$(stop_background "$PID")
+ if [[ "$RESULT" == *"The kernel does not support fentry function attach"* ]]; then
+ TRACE_ATTACH_SUPPORT="false"
+ else
+ TRACE_ATTACH_SUPPORT="true"
+ fi
+ echo "$TRACE_ATTACH_SUPPORT" > "$STATEDIR/trace_attach_support"
+ $XDP_LOADER unload "$NS" --all
+ fi
+ fi
+
+ if [[ "$TRACE_ATTACH_SUPPORT" == "true" ]]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+is_multiprog_supported()
+{
+ if [[ -z "${MULTIPROG_SUPPORT:-}" ]]; then
+ RESULT=$($XDP_LOADER load -v "$NS" "$TEST_PROG_DIR/xdp_pass.o" 2>&1)
+ if [[ "$RESULT" == *"Compatibility check for dispatcher program failed"* ]]; then
+ MULTIPROG_SUPPORT="false"
+ else
+ MULTIPROG_SUPPORT="true"
+ fi
+ $XDP_LOADER unload "$NS" --all
+ fi
+
+ if [[ "$MULTIPROG_SUPPORT" == "true" ]]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+is_progmap_supported()
+{
+ if [[ -z "${PROGMAP_SUPPORT:-}" ]]; then
+ RESULT=$(timeout -s INT 1 $XDP_BENCH redirect-cpu "$NS" -c 0 -r drop -vv 2>&1)
+ if [[ "$RESULT" == *"Create CPU entry failed: Cannot allocate memory"* ]]; then
+ PROGMAP_SUPPORT="false"
+ else
+ PROGMAP_SUPPORT="true"
+ fi
+ fi
+
+ if [[ "$PROGMAP_SUPPORT" == "true" ]]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+skip_if_missing_veth_rxq()
+{
+ if ! ethtool -l $NS >/dev/null 2>&1; then
+ exit "$SKIPPED_TEST"
+ fi
+}
+
+skip_if_missing_cpumap_attach()
+{
+ if ! $TEST_PROG_DIR/test-tool probe cpumap-prog; then
+ exit "$SKIPPED_TEST"
+ fi
+}
+
+skip_if_missing_kernel_symbol()
+{
+ if ! grep -q "$1" /proc/kallsyms; then
+ exit "$SKIPPED_TEST"
+ fi
+}
+
+skip_if_legacy_fallback()
+{
+ if ! is_multiprog_supported; then
+ exit "$SKIPPED_TEST"
+ fi
+}
+
+skip_if_missing_trace_attach()
+{
+ if ! is_trace_attach_supported; then
+ exit "$SKIPPED_TEST"
+ fi
+}
+
+die()
+{
+ echo "$1" >&2
+ exit 1
+}
+
+start_background()
+{
+ local TMP_FILE="${STATEDIR}/tmp_proc_$$_$RANDOM"
+ setsid bash -c "$*" &> ${TMP_FILE} &
+ local PID=$!
+ sleep 2 # Wait to make sure the command is executed in the background
+
+ mv "$TMP_FILE" "${STATEDIR}/proc/${PID}" >& /dev/null
+
+ echo "$PID"
+}
+
+start_background_no_stderr()
+{
+ local TMP_FILE="${STATEDIR}/tmp_proc_$$_$RANDOM"
+ setsid bash -c "$*" 1> ${TMP_FILE} 2>/dev/null &
+ local PID=$!
+ sleep 2 # Wait to make sure the command is executed in the background
+
+ mv "$TMP_FILE" "${STATEDIR}/proc/${PID}" >& /dev/null
+
+ echo "$PID"
+}
+
+start_background_ns_devnull()
+{
+ get_nsname && ensure_nsname "$NS"
+
+ local TMP_FILE="${STATEDIR}/tmp_proc_$$_$RANDOM"
+ setsid ip netns exec "$NS" env TESTENV_NAME="$NS" "$SETUP_SCRIPT" bash -c "$*" 1>/dev/null 2>${TMP_FILE} &
+ local PID=$!
+ sleep 2 # Wait to make sure the command is executed in the background
+
+ mv "$TMP_FILE" "${STATEDIR}/proc/${PID}" >& /dev/null
+ echo $PID
+}
+
+stop_background()
+{
+ local PID=$1
+
+ local OUTPUT_FILE="${STATEDIR}/proc/${PID}"
+ if kill -SIGINT "-$PID" 2>/dev/null; then
+ sleep 2 # Wait to make sure the buffer is flushed after the shutdown
+ kill -SIGTERM "-$PID" 2>/dev/null && sleep 1 # just in case SIGINT was not enough
+ fi
+
+ if [ -f "$OUTPUT_FILE" ]; then
+ cat "$OUTPUT_FILE"
+ rm "$OUTPUT_FILE" >& /dev/null
+ fi
+}
+
+check_prereq()
+{
+ local max_locked_mem=$(ulimit -l)
+
+ for t in $NEEDED_TOOLS; do
+ command -v "$t" > /dev/null || die "Missing required tool: $t"
+ done
+
+ if [ "$EUID" -ne "0" ]; then
+ die "This script needs root permissions to run."
+ fi
+
+ STATEDIR="$(mktemp -d --tmpdir=${TMPDIR:-/tmp} --suffix=.xdptest)"
+ if [ $? -ne 0 ]; then
+ die "Unable to create state dir in $TMPDIR"
+ fi
+ mkdir ${STATEDIR}/proc
+
+ if [ "$max_locked_mem" != "unlimited" ]; then
+ ulimit -l unlimited || die "Unable to set ulimit"
+ fi
+}
+
+get_nsname()
+{
+ local GENERATE=${1:-1}
+
+ if [ -z "$NS" ]; then
+ [ -f "$STATEDIR/current" ] && NS=$(< "$STATEDIR/current")
+
+ if [ "$GENERATE" -eq "1" ] && [ -z "$NS" ] || [ "$GENERATE_NEW" -eq "1" ]; then
+ NS="$GENERATED_NAME_PREFIX"
+ while [ -e "$STATEDIR/${NS}.state" ]; do
+ NS=$(printf "%s-%04x" "$GENERATED_NAME_PREFIX" $RANDOM)
+ done
+ fi
+ fi
+
+ if [ "${#NS}" -gt "$MAX_NAMELEN" ]; then
+ die "Environment name '$NS' is too long (max $MAX_NAMELEN)"
+ fi
+
+ STATEFILE="$STATEDIR/${NS}.state"
+}
+
+ensure_nsname()
+{
+ [ -z "$NS" ] && die "No environment selected; use --name to select one or 'setup' to create one"
+ [ -e "$STATEFILE" ] || die "Environment for $NS doesn't seem to exist"
+
+ echo "$NS" > "$STATEDIR/current"
+
+ read_statefile
+}
+
+get_num()
+{
+ local num=1
+ if [ -f "$STATEDIR/highest_num" ]; then
+ num=$(( 1 + $(< "$STATEDIR/highest_num" )))
+ fi
+
+ echo $num > "$STATEDIR/highest_num"
+ printf "%x" $num
+}
+
+write_statefile()
+{
+ [ -z "$STATEFILE" ] && return 1
+ echo > "$STATEFILE"
+ for var in "${STATEVARS[@]}"; do
+ echo "${var}='$(eval echo '$'$var)'" >> "$STATEFILE"
+ done
+}
+
+read_statefile()
+{
+ local value
+ for var in "${STATEVARS[@]}"; do
+ value=$(source "$STATEFILE"; eval echo '$'$var)
+ eval "$var=\"$value\""
+ done
+}
+
+cleanup_setup()
+{
+ echo "Error during setup, removing partially-configured environment '$NS'" >&2
+ set +o errexit
+ ip netns del "$NS" 2>/dev/null
+ ip link del dev "$NS" 2>/dev/null
+ rm -f "$STATEFILE"
+}
+
+cleanup_teardown()
+{
+ echo "Warning: Errors during teardown, partial environment may be left" >&2
+}
+
+
+cleanup()
+{
+ [ -n "$CLEANUP_FUNC" ] && $CLEANUP_FUNC
+
+ local statefiles=("$STATEDIR"/*.state)
+
+ if [ "${#statefiles[*]}" -eq 1 ] && [ ! -e "${statefiles[0]}" ]; then
+ rm -f "${STATEDIR}/highest_num" "${STATEDIR}/current" \
+ "${STATEDIR}/trace_attach_support"
+ rmdir "$STATEDIR"
+ fi
+}
+
+iface_macaddr()
+{
+ local iface="$1"
+ ip -br link show dev "$iface" | awk '{print $3}'
+}
+
+set_sysctls()
+{
+ local iface="$1"
+ local in_ns="${2:-}"
+ local nscmd=
+
+ [ -n "$in_ns" ] && nscmd="ip netns exec $in_ns"
+ local sysctls=(accept_dad
+ accept_ra
+ mldv1_unsolicited_report_interval
+ mldv2_unsolicited_report_interval)
+
+ for s in ${sysctls[*]}; do
+ $nscmd sysctl -w net.ipv6.conf.$iface.${s}=0 >/dev/null
+ done
+}
+
+get_vlan_prefix()
+{
+ # Split the IPv6 prefix, and add the VLAN ID to the upper byte of the fourth
+ # element in the prefix. This will break if the global prefix config doesn't
+ # have exactly three elements in it.
+ local prefix="$1"
+ local vid="$2"
+ (IFS=:; set -- $prefix; printf "%s:%s:%s:%x::" "$1" "$2" "$3" $(($4 + $vid * 4096)))
+}
+
+setup()
+{
+ get_nsname 1
+
+ [ -e "$STATEFILE" ] && die "Environment for '$NS' already exists"
+
+ local NUM=$(get_num "$NS")
+ local PEERNAME="testl-ve-$NUM"
+ [ -z "$IP6_PREFIX" ] && IP6_PREFIX="${IP6_SUBNET}:${NUM}::"
+ [ -z "$IP4_PREFIX" ] && IP4_PREFIX="${IP4_SUBNET}.$((0x$NUM))."
+
+ INSIDE_IP6="${IP6_PREFIX}2"
+ INSIDE_IP4="${IP4_PREFIX}2"
+ OUTSIDE_IP6="${IP6_PREFIX}1"
+ OUTSIDE_IP4="${IP4_PREFIX}1"
+
+ CLEANUP_FUNC=cleanup_setup
+
+ if ! mount | grep -q /sys/fs/bpf; then
+ mount -t bpf bpf /sys/fs/bpf/
+ fi
+
+ ip netns add "$NS"
+ ip link add dev "$NS" type veth peer name "$PEERNAME"
+ OUTSIDE_MAC=$(iface_macaddr "$NS")
+ INSIDE_MAC=$(iface_macaddr "$PEERNAME")
+ set_sysctls $NS
+
+ ethtool -K "$NS" rxvlan off txvlan off
+ ethtool -K "$PEERNAME" rxvlan off txvlan off
+ ip link set dev "$PEERNAME" netns "$NS"
+ ip link set dev "$NS" up
+ ip addr add dev "$NS" "${OUTSIDE_IP6}/${IP6_PREFIX_SIZE}"
+
+ ip -n "$NS" link set dev "$PEERNAME" name veth0
+ ip -n "$NS" link set dev lo up
+ ip -n "$NS" link set dev veth0 up
+ set_sysctls veth0 "$NS"
+ ip -n "$NS" addr add dev veth0 "${INSIDE_IP6}/${IP6_PREFIX_SIZE}"
+
+ # Prevent neighbour queries on the link
+ ip neigh add "$INSIDE_IP6" lladdr "$INSIDE_MAC" dev "$NS" nud permanent
+ ip -n "$NS" neigh add "$OUTSIDE_IP6" lladdr "$OUTSIDE_MAC" dev veth0 nud permanent
+
+ # Add route for whole test subnet, to make it easier to communicate between
+ # namespaces
+ ip -n "$NS" route add "${IP6_SUBNET}::/$IP6_FULL_PREFIX_SIZE" via "$OUTSIDE_IP6" dev veth0
+
+ if [ "$LEGACY_IP" -eq "1" ]; then
+ ip addr add dev "$NS" "${OUTSIDE_IP4}/${IP4_PREFIX_SIZE}"
+ ip -n "$NS" addr add dev veth0 "${INSIDE_IP4}/${IP4_PREFIX_SIZE}"
+ ip neigh add "$INSIDE_IP4" lladdr "$INSIDE_MAC" dev "$NS" nud permanent
+ ip -n "$NS" neigh add "$OUTSIDE_IP4" lladdr "$OUTSIDE_MAC" dev veth0 nud permanent
+ ip -n "$NS" route add "${IP4_SUBNET}/${IP4_FULL_PREFIX_SIZE}" via "$OUTSIDE_IP4" dev veth0
+ ENABLE_IPV4=1
+ else
+ ENABLE_IPV4=0
+ fi
+
+ if [ "$USE_VLAN" -eq "1" ]; then
+ ENABLE_VLAN=1
+ for vid in "${VLAN_IDS[@]}"; do
+ local vlpx="$(get_vlan_prefix "$IP6_PREFIX" "$vid")"
+ local inside_ip="${vlpx}2"
+ local outside_ip="${vlpx}1"
+ ip link add dev "${NS}.$vid" link "$NS" type vlan id "$vid"
+ ip link set dev "${NS}.$vid" up
+ ip addr add dev "${NS}.$vid" "${outside_ip}/${IP6_PREFIX_SIZE}"
+ ip neigh add "$inside_ip" lladdr "$INSIDE_MAC" dev "${NS}.$vid" nud permanent
+ set_sysctls "${NS}/$vid"
+
+ ip -n "$NS" link add dev "veth0.$vid" link "veth0" type vlan id "$vid"
+ ip -n "$NS" link set dev "veth0.$vid" up
+ ip -n "$NS" addr add dev "veth0.$vid" "${inside_ip}/${IP6_PREFIX_SIZE}"
+ ip -n "$NS" neigh add "$outside_ip" lladdr "$OUTSIDE_MAC" dev "veth0.$vid" nud permanent
+ set_sysctls "veth0/$vid" "$NS"
+ done
+ else
+ ENABLE_VLAN=0
+ fi
+
+ write_statefile
+
+ CLEANUP_FUNC=
+
+ echo "$NS" > "$STATEDIR/current"
+}
+
+teardown()
+{
+ get_nsname && ensure_nsname "$NS"
+
+ CLEANUP_FUNC=cleanup_teardown
+
+ ip link del dev "$NS"
+ ip netns del "$NS"
+ rm -f "$STATEFILE"
+ [ -d "/sys/fs/bpf/$NS" ] && rmdir "/sys/fs/bpf/$NS" || true
+
+ if [ -f "$STATEDIR/current" ]; then
+ local CUR=$(< "$STATEDIR/current" )
+ [[ "$CUR" == "$NS" ]] && rm -f "$STATEDIR/current"
+ fi
+
+ for f in ${STATEDIR}/proc/*; do
+ if [ -f "$f" ]; then
+ local pid="${f/${STATEDIR}\/proc\//}"
+ stop_background "$pid" &> /dev/null || true
+ fi
+ done
+
+ rm -rf "$STATEDIR"
+
+ CLEANUP_FUNC=
+}
+
+ns_exec()
+{
+ get_nsname && ensure_nsname "$NS"
+
+ ip netns exec "$NS" env TESTENV_NAME="$NS" "$SETUP_SCRIPT" "$@"
+}
+
+is_func()
+{
+ type "$1" 2>/dev/null | grep -q 'is a function'
+}
+
+check_run()
+{
+ local ret
+
+ "$@"
+ ret=$?
+ echo "Command '$@' exited with status $ret"
+ echo ""
+ 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/^/ /'
+ echo " Test $testn exited with return code: $ret"
+ fi
+ return $ret
+}
+
+run_tests()
+{
+ local TESTS="$*"
+ local ret=0
+ [ -z "$TESTS" ] && TESTS="$ALL_TESTS"
+ setup || return 1
+
+ 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 V=${VERBOSE_TESTS} "$0" "$@"
+ else
+ die "Tests should be run as root"
+ fi
+fi
+
+export XDP_FILTER
+export XDP_LOADER
+export XDPDUMP
+
+TEST_DEFINITIONS="${1:-}"
+[ -f "$TEST_DEFINITIONS" ] || usage
+source "$TEST_DEFINITIONS"
+
+TOOL_TESTS_DIR="$(dirname "$TEST_DEFINITIONS")"
+
+shift
+trap teardown EXIT
+check_prereq
+run_tests "$@"
diff --git a/lib/testing/xdp_drop.c b/lib/testing/xdp_drop.c
new file mode 100644
index 0000000..dc82f5c
--- /dev/null
+++ b/lib/testing/xdp_drop.c
@@ -0,0 +1,10 @@
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+
+SEC("xdp")
+int xdp_drop(struct xdp_md *ctx)
+{
+ return XDP_DROP;
+}
+
+char _license[] SEC("license") = "GPL";
diff --git a/lib/testing/xdp_pass.c b/lib/testing/xdp_pass.c
new file mode 100644
index 0000000..37cbc60
--- /dev/null
+++ b/lib/testing/xdp_pass.c
@@ -0,0 +1,16 @@
+#include <linux/bpf.h>
+#include <bpf/bpf_helpers.h>
+#include <xdp/xdp_helpers.h>
+
+struct {
+ __uint(priority, 10);
+ __uint(XDP_PASS, 1);
+} XDP_RUN_CONFIG(xdp_pass);
+
+SEC("xdp")
+int xdp_pass(struct xdp_md *ctx)
+{
+ return XDP_PASS;
+}
+
+char _license[] SEC("license") = "GPL";