From: Toke Høiland-Jørgensen <toke@xxxxxxxxxx> This adds new self tests for the XDP chain call functionality. Signed-off-by: Toke Høiland-Jørgensen <toke@xxxxxxxxxx> --- tools/testing/selftests/bpf/.gitignore | 1 tools/testing/selftests/bpf/Makefile | 3 tools/testing/selftests/bpf/progs/xdp_dummy.c | 6 tools/testing/selftests/bpf/test_xdp_chain.sh | 77 ++++++ tools/testing/selftests/bpf/xdp_chain.c | 313 +++++++++++++++++++++++++ 5 files changed, 399 insertions(+), 1 deletion(-) create mode 100755 tools/testing/selftests/bpf/test_xdp_chain.sh create mode 100644 tools/testing/selftests/bpf/xdp_chain.c diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore index 7470327edcfe..e9d2d765cc8f 100644 --- a/tools/testing/selftests/bpf/.gitignore +++ b/tools/testing/selftests/bpf/.gitignore @@ -39,3 +39,4 @@ libbpf.so.* test_hashmap test_btf_dump xdping +xdp_chain diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index 6889c19a628c..97e8f6ae4a15 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -29,7 +29,7 @@ TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test test_sock test_btf test_sockmap get_cgroup_id_user test_socket_cookie \ test_cgroup_storage test_select_reuseport test_section_names \ test_netcnt test_tcpnotify_user test_sock_fields test_sysctl test_hashmap \ - test_btf_dump test_cgroup_attach xdping + test_btf_dump test_cgroup_attach xdping xdp_chain BPF_OBJ_FILES = $(patsubst %.c,%.o, $(notdir $(wildcard progs/*.c))) TEST_GEN_FILES = $(BPF_OBJ_FILES) @@ -71,6 +71,7 @@ TEST_PROGS := test_kmod.sh \ test_tc_tunnel.sh \ test_tc_edt.sh \ test_xdping.sh \ + test_xdp_chain.sh \ test_bpftool_build.sh TEST_PROGS_EXTENDED := with_addr.sh \ diff --git a/tools/testing/selftests/bpf/progs/xdp_dummy.c b/tools/testing/selftests/bpf/progs/xdp_dummy.c index 43b0ef1001ed..454a1f0763a1 100644 --- a/tools/testing/selftests/bpf/progs/xdp_dummy.c +++ b/tools/testing/selftests/bpf/progs/xdp_dummy.c @@ -10,4 +10,10 @@ int xdp_dummy_prog(struct xdp_md *ctx) return XDP_PASS; } +SEC("xdp_drop") +int xdp_drop_prog(struct xdp_md *ctx) +{ + return XDP_DROP; +} + char _license[] SEC("license") = "GPL"; diff --git a/tools/testing/selftests/bpf/test_xdp_chain.sh b/tools/testing/selftests/bpf/test_xdp_chain.sh new file mode 100755 index 000000000000..3997655d4e45 --- /dev/null +++ b/tools/testing/selftests/bpf/test_xdp_chain.sh @@ -0,0 +1,77 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 + +# xdp_chain tests +# Here we setup and teardown configuration required to run +# xdp_chain, exercising its options. +# +# Setup is similar to xdping tests. +# +# Topology: +# --------- +# root namespace | tc_ns0 namespace +# | +# ---------- | ---------- +# | veth1 | --------- | veth0 | +# ---------- peer ---------- +# +# Device Configuration +# -------------------- +# Root namespace with BPF +# Device names and addresses: +# veth1 IP: 10.1.1.200 +# +# Namespace tc_ns0 with BPF +# Device names and addresses: +# veth0 IPv4: 10.1.1.100 +# xdp_chain binary run inside this +# + +readonly TARGET_IP="10.1.1.100" +readonly TARGET_NS="xdp_ns0" + +readonly LOCAL_IP="10.1.1.200" + +setup() +{ + ip netns add $TARGET_NS + ip link add veth0 type veth peer name veth1 + ip link set veth0 netns $TARGET_NS + ip netns exec $TARGET_NS ip addr add ${TARGET_IP}/24 dev veth0 + ip addr add ${LOCAL_IP}/24 dev veth1 + ip netns exec $TARGET_NS ip link set veth0 up + ip link set veth1 up +} + +cleanup() +{ + set +e + ip netns delete $TARGET_NS 2>/dev/null + ip link del veth1 2>/dev/null +} + +die() +{ + echo "$@" >&2 + exit 1 +} + +test() +{ + args="$1" + + ip netns exec $TARGET_NS ./xdp_chain $args || die "XDP chain test error" +} + +set -e + +server_pid=0 + +trap cleanup EXIT + +setup + +test "-I veth0 -S $LOCAL_IP" + +echo "OK. All tests passed" +exit 0 diff --git a/tools/testing/selftests/bpf/xdp_chain.c b/tools/testing/selftests/bpf/xdp_chain.c new file mode 100644 index 000000000000..4b3fa26224fa --- /dev/null +++ b/tools/testing/selftests/bpf/xdp_chain.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. */ + +#include <linux/bpf.h> +#include <linux/if_link.h> +#include <arpa/inet.h> +#include <assert.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <libgen.h> +#include <sys/resource.h> +#include <net/if.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#include "bpf/bpf.h" +#include "bpf/libbpf.h" + +static int ifindex; +static __u32 xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST; +static char *dest = NULL, *ifname = NULL; + +static void cleanup(int sig) +{ + int ret; + + fprintf(stderr, " Cleaning up\n"); + if ((ret = bpf_set_link_xdp_fd(ifindex, -1, xdp_flags))) + fprintf(stderr, "Warning: Unable to clear XDP prog: %s\n", + strerror(-ret)); + if (sig) + exit(1); +} + +static void show_usage(const char *prog) +{ + fprintf(stderr, + "usage: %s [OPTS] -I interface destination\n\n" + "OPTS:\n" + " -I interface interface name\n" + " -N Run in driver mode\n" + " -S Run in skb mode\n" + " -p pin_path path to pin chain call map\n" + " -x Exit after setup\n" + " -c Cleanup and exit\n" + " -v Verbose eBPF logging\n", + prog); +} + +static int run_ping(bool should_fail, const char *msg) +{ + char cmd[256]; + bool success; + int ret; + + snprintf(cmd, sizeof(cmd), "ping -c 1 -W 1 -I %s %s >/dev/null", ifname, dest); + + printf(" %s: ", msg); + + ret = system(cmd); + + success = (!!ret == should_fail); + printf(success ? "PASS\n" : "FAIL\n"); + + return !success; +} + +struct bpf_program { + /* Index in elf obj file, for relocation use. */ + int idx; + char *name; + int prog_ifindex; + char *section_name; + /* section_name with / replaced by _; makes recursive pinning + * in bpf_object__pin_programs easier + */ + char *pin_name; + struct bpf_insn *insns; + size_t insns_cnt, main_prog_cnt; + enum bpf_prog_type type; + + struct reloc_desc { + enum { + RELO_LD64, + RELO_CALL, + RELO_DATA, + } type; + int insn_idx; + union { + int map_idx; + int text_off; + }; + } *reloc_desc; + int nr_reloc; + int log_level; + + struct { + int nr; + int *fds; + } instances; + bpf_program_prep_t preprocessor; + + struct bpf_object *obj; + void *priv; + bpf_program_clear_priv_t clear_priv; + + enum bpf_attach_type expected_attach_type; + void *func_info; + __u32 func_info_rec_size; + __u32 func_info_cnt; + + struct bpf_capabilities *caps; + + void *line_info; + __u32 line_info_rec_size; + __u32 line_info_cnt; + __u32 prog_flags; +}; + +static int printfunc(enum libbpf_print_level level, const char *format, va_list args) +{ + return vfprintf(stderr, format, args); +} + +int main(int argc, char **argv) +{ + __u32 mode_flags = XDP_FLAGS_DRV_MODE | XDP_FLAGS_SKB_MODE; + struct rlimit r = {RLIM_INFINITY, RLIM_INFINITY}; + bool setup_only = false, cleanup_only = false; + struct bpf_program *pass_prog, *drop_prog, *prog; + int pass_prog_fd = -1, drop_prog_fd = -1; + const char *filename = "xdp_dummy.o"; + int opt, ret = 1, log_level = 0; + const char *optstr = "I:NSxcv"; + struct bpf_object *obj; + u32 prog_id; + + struct bpf_object_open_attr open_attr = { + .file = filename, + .prog_type = BPF_PROG_TYPE_XDP, + }; + + while ((opt = getopt(argc, argv, optstr)) != -1) { + switch (opt) { + case 'I': + ifname = optarg; + ifindex = if_nametoindex(ifname); + if (!ifindex) { + fprintf(stderr, "Could not get interface %s\n", + ifname); + return 1; + } + break; + case 'N': + xdp_flags |= XDP_FLAGS_DRV_MODE; + break; + case 'S': + xdp_flags |= XDP_FLAGS_SKB_MODE; + break; + case 'x': + setup_only = true; + break; + case 'v': + log_level = 7; + break; + case 'c': + cleanup_only = true; + break; + default: + show_usage(basename(argv[0])); + return 1; + } + } + + if (!ifname) { + show_usage(basename(argv[0])); + return 1; + } + + if (cleanup_only) { + cleanup(0); + return 0; + } + + if (!setup_only && optind == argc) { + show_usage(basename(argv[0])); + return 1; + } + dest = argv[optind]; + + if ((xdp_flags & mode_flags) == mode_flags) { + fprintf(stderr, "-N or -S can be specified, not both.\n"); + show_usage(basename(argv[0])); + return 1; + } + + if (setrlimit(RLIMIT_MEMLOCK, &r)) { + perror("setrlimit(RLIMIT_MEMLOCK)"); + return 1; + } + + if (log_level) + libbpf_set_print(printfunc); + + obj = bpf_object__open_xattr(&open_attr); + + bpf_object__for_each_program(prog, obj) { + bpf_program__set_type(prog, BPF_PROG_TYPE_XDP); + prog->prog_flags = BPF_F_CHAIN_CALLS; + prog->log_level = log_level; + if ((ret = bpf_program__load(prog, "GPL", 0))) { + fprintf(stderr, "unable to load program: %s\n", strerror(-ret)); + return 1; + } + } + pass_prog = bpf_object__find_program_by_title(obj, "xdp_dummy"); + drop_prog = bpf_object__find_program_by_title(obj, "xdp_drop"); + + if (!pass_prog || !drop_prog) { + fprintf(stderr, "could not find xdp programs\n"); + return 1; + } + pass_prog_fd = bpf_program__fd(pass_prog); + drop_prog_fd = bpf_program__fd(drop_prog); + if (pass_prog_fd < 0 || drop_prog_fd < 0) { + fprintf(stderr, "could not find xdp programs\n"); + goto done; + } + + +#define RUN_PING(should_fail, err) if ((ret = run_ping(should_fail, err))) goto done; + + if (!setup_only) { + RUN_PING(false, "Pre-setup ping test"); + + signal(SIGINT, cleanup); + signal(SIGTERM, cleanup); + } + + if ((ret = bpf_set_link_xdp_fd(ifindex, pass_prog_fd, xdp_flags)) < 0) { + fprintf(stderr, "Link set xdp fd failed for %s: %s\n", ifname, + strerror(-ret)); + goto done; + } + + if (!setup_only) { + sleep(1); + RUN_PING(false, "Empty map test"); + } + + if (bpf_prog_chain_add(pass_prog_fd, -1, drop_prog_fd)) { + fprintf(stderr, "unable to add chain prog wildcard: %s (%d)\n", strerror(errno), errno); + goto done; + } + + if (bpf_prog_chain_get(pass_prog_fd, -1, &prog_id)) { + fprintf(stderr, "unable to get chain prog wildcard: %s (%d)\n", strerror(errno), errno); + goto done; + } + printf("Next program attached with ID: %u\n", prog_id); + + if (setup_only) { + printf("Setup done; exiting.\n"); + ret = 0; + goto done; + } + + sleep(1); + + RUN_PING(true, "Wildcard act test"); + + if (bpf_prog_chain_del(pass_prog_fd, -1)) { + fprintf(stderr, "unable to delete chain prog: %s\n", strerror(errno)); + goto done; + } + sleep(1); + + RUN_PING(false, "Post-delete map test"); + + if (bpf_prog_chain_add(pass_prog_fd, XDP_PASS, drop_prog_fd)) { + fprintf(stderr, "unable to add chain prog PASS: %s\n", strerror(errno)); + goto done; + } + sleep(1); + + RUN_PING(true, "Pass act test"); + + + if ((ret = bpf_set_link_xdp_fd(ifindex, -1, xdp_flags)) < 0) { + fprintf(stderr, "Link clear xdp fd failed for %s: '%s'\n", ifname, strerror(-ret)); + goto done; + } + sleep(1); + + RUN_PING(false, "Post-delete prog test"); + + +done: + if (!setup_only) + cleanup(ret); + + if (pass_prog_fd > 0) + close(pass_prog_fd); + if (drop_prog_fd > 0) + close(drop_prog_fd); + + return ret; +}