diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 07:10:00 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 07:10:00 +0000 |
commit | 4ba2b326284765e942044db13a7f0dae702bec93 (patch) | |
tree | cbdfaec33eed4f3a970c54cd10e8ddfe3003b3b1 /xdp-loader | |
parent | Initial commit. (diff) | |
download | xdp-tools-upstream.tar.xz xdp-tools-upstream.zip |
Adding upstream version 1.3.1.upstream/1.3.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xdp-loader')
-rw-r--r-- | xdp-loader/.gitignore | 1 | ||||
-rw-r--r-- | xdp-loader/Makefile | 12 | ||||
-rw-r--r-- | xdp-loader/README.org | 217 | ||||
-rw-r--r-- | xdp-loader/tests/test-xdp-loader.sh | 97 | ||||
-rw-r--r-- | xdp-loader/xdp-loader.8 | 260 | ||||
-rw-r--r-- | xdp-loader/xdp-loader.c | 411 |
6 files changed, 998 insertions, 0 deletions
diff --git a/xdp-loader/.gitignore b/xdp-loader/.gitignore new file mode 100644 index 0000000..f9174ff --- /dev/null +++ b/xdp-loader/.gitignore @@ -0,0 +1 @@ +xdp-loader diff --git a/xdp-loader/Makefile b/xdp-loader/Makefile new file mode 100644 index 0000000..5165ed6 --- /dev/null +++ b/xdp-loader/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) + +TOOL_NAME := xdp-loader +USER_TARGETS := xdp-loader +TEST_FILE := tests/test-xdp-loader.sh +MAN_PAGE := xdp-loader.8 + +LIB_DIR = ../lib + +include $(LIB_DIR)/common.mk + + diff --git a/xdp-loader/README.org b/xdp-loader/README.org new file mode 100644 index 0000000..955fdc5 --- /dev/null +++ b/xdp-loader/README.org @@ -0,0 +1,217 @@ +#+EXPORT_FILE_NAME: xdp-loader +#+TITLE: xdp-loader +#+OPTIONS: ^:nil +#+MAN_CLASS_OPTIONS: :section-id "8\" \"DATE\" \"VERSION\" \"XDP program loader" +# This file serves both as a README on github, and as the source for the man +# page; the latter through the org-mode man page export support. +# . +# To export the man page, simply use the org-mode exporter; (require 'ox-man) if +# it's not available. There's also a Makefile rule to export it. + +* xdp-loader - an XDP program loader + +XDP-loader is a simple loader for XDP programs with support for attaching +multiple programs to the same interface. To achieve this it exposes the same +load and unload semantics exposed by the libxdp library. See the =libxdp(3)= man +page for details of how this works, and what kernel features it relies on. + +** Running xdp-loader +The syntax for running xdp-loader is: + +#+begin_src sh +xdp-loader COMMAND [options] + +Where COMMAND can be one of: + load - load an XDP program on an interface + unload - unload an XDP program from an interface + status - show current XDP program status + clean - clean up detached program links in XDP bpffs directory + help - show the list of available commands +#+end_src + +Each command, and its options are explained below. Or use =xdp-loader COMMAND +--help= to see the options for each command. + +* The LOAD command +The =load= command loads one or more XDP programs onto an interface. + +The syntax for the =load= command is: + +=xdp-loader load [options] <ifname> <programs>= + +Where =<ifname>= is the name of the interface to load the programs onto, and the +=<programs>= is one or more file names containing XDP programs. The programs +will be loaded onto the interface in the order of their preference, as +specified by the program metadata (see *libxdp(3)*). + +The supported options are: + +** -m, --mode <mode> +Specifies which mode to load the XDP program to be loaded in. The valid values +are 'native', which is the default in-driver XDP mode, 'skb', which causes the +so-called /skb mode/ (also known as /generic XDP/) to be used, 'hw' which causes +the program to be offloaded to the hardware, or 'unspecified' which leaves it up +to the kernel to pick a mode (which it will do by picking native mode if the +driver supports it, or generic mode otherwise). Note that using 'unspecified' +can make it difficult to predict what mode a program will end up being loaded +in. For this reason, the default is 'native'. Note that hardware with support +for the 'hw' mode is rare: Solarflare cards (using the 'sfc' driver) are the +only devices with support for this in the mainline Linux kernel. + +** -p, --pin-path <path> +This specifies a root path under which to pin any maps that define the 'pinning' +attribute in their definitions. This path must be located on a =bpffs= file +system. If not set, maps will not be pinned, even if they specify pinning in +their definitions. When pinning maps, if the pinned location for a map already +exist, the map pinned there will be reused if it is compatible with the type of +the map being loaded. + +** -s, --section <section> +Specify which ELF section to load the XDP program(s) from in each file. The +default is to use the first program in each file. If this option is set, it +applies to all programs being loaded. + +** -n, --prog-name <prog_name> +Specify which BPF program with the name to load the XDP program(s) from in each +file. The default is to use the first program in each file. Only one of +--section and --prog-name may be specified. If this option is set, it applies to +all programs being loaded. + +** -P, --prio <priority> +Specify the priority to load the XDP program(s) with (this affects the order of +programs running on the interface). The default is to use the value from the metadata +in the program ELF file, or a value of 50 if the program has no such metadata. +If this option is set, it applies to all programs being loaded. + +** -A, --actions <actions> +Specify the "chain call actions" of the loaded XDP program(s). These are the XDP +actions that will cause the next program loaded on the interface to be called, +instead of returning immediately. The default is to use the value set in the metadata +in the program ELF file, or XDP_PASS if no such metadata is set. If this option is set, +it applies to all programs being loaded. + +** -v, --verbose +Enable debug logging. Specify twice for even more verbosity. + +** -h, --help +Display a summary of the available options + +* The UNLOAD command +The =unload= command is used for unloading programs from an interface. + +The syntax for the =unload= command is: + +=xdp-loader unload [options] <ifname>= + +Where =<ifname>= is the name of the interface to load the programs onto. Either +the =--all= or =--id= options must be used to specify which program(s) to unload. + +The supported options are: + +** -i, --id <id> +Unload a single program from the interface by ID. Use =xdp-loader status= to +obtain the ID of the program being unloaded. If this program is the last program +loaded on the interface, the dispatcher program will also be removed, which +makes the operation equivalent to specifying =--all=. + +** -a, --all +Unload all XDP programs on the interface, as well as the multi-program +dispatcher. + +** -v, --verbose +Enable debug logging. Specify twice for even more verbosity. + +** -h, --help +Display a summary of the available options + +* The STATUS command +The =status= command displays a list of interfaces in the system, and the XDP +program(s) loaded on each interface. For each interface, a list of programs are +shown, with the run priority and "chain actions" for each program. See the +section on program metadata for the meaning of this metadata. + +** -v, --verbose +Enable debug logging. Specify twice for even more verbosity. + +** -h, --help +Display a summary of the available options + +* The CLEAN command + +The syntax for the =clean= command is: + +=xdp-loader clean [options] [ifname]= + +The =clean= command cleans up any detached program links in the XDP bpffs +directory. When a network interface disappears, any programs loaded in software +mode (e.g. skb, native) remain pinned in the bpffs directory, but become +detached from the interface. These need to be unlinked from the filesystem. The +=clean= command takes an optional interface parameter to only unlink detached +programs corresponding to the interface. By default, all detached programs for +all interfaces are unlinked. + +The supported options are: + +** -v, --verbose +Enable debug logging. Specify twice for even more verbosity. + +** -h, --help +Display a summary of the available options + +* Examples + +To load an XDP program on the eth0 interface simply do: + +#+begin_src sh +# xdp-loader load eth0 xdp_drop.o +# xdp-loader status + +CURRENT XDP PROGRAM STATUS: + +Interface Prio Program name Mode ID Tag Chain actions +------------------------------------------------------------------------------------- +lo <no XDP program> +eth0 xdp_dispatcher native 50 d51e469e988d81da + => 50 xdp_drop 55 57cd311f2e27366b XDP_PASS + +#+end_src + +Which shows that a dispatcher program was loaded on the interface, and the +xdp_drop program was installed as the first (and only) component program after +it. In this instance, the program does not specify any of the metadata above, so +the defaults (priority 50 and XDP_PASS as its chain call action) was used. + +To use the automatic map pinning, include the =pinning= attribute into the map +definition in the program, something like: + +#+begin_src c +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 10); + __type(key, __u32); + __type(value, __u64); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} my_map SEC(".maps"); +#+end_src + +And load it with the =--pin-path= attribute: + +#+begin_src sh +# xdp-loader load eth0 my_prog.o --pin-path /sys/fs/bpf/my-prog +#+end_src + +This will pin the map at =/sys/fs/bpf/my-prog/my_map=. If this already exists, +the pinned map will be reused instead of creating a new one, which allows +different BPF programs to share the map. + +* SEE ALSO +=libxdp(3)= for details on the XDP loading semantics and kernel compatibility +requirements. + +* BUGS + +Please report any bugs on Github: https://github.com/xdp-project/xdp-tools/issues + +* AUTHOR + +xdp-loader and this man page were written by Toke Høiland-Jørgensen. diff --git a/xdp-loader/tests/test-xdp-loader.sh b/xdp-loader/tests/test-xdp-loader.sh new file mode 100644 index 0000000..1d32853 --- /dev/null +++ b/xdp-loader/tests/test-xdp-loader.sh @@ -0,0 +1,97 @@ +XDP_LOADER=${XDP_LOADER:-./xdp-loader} +ALL_TESTS="test_load test_section test_prog_name test_load_multi test_load_incremental test_load_clobber" + +test_load() +{ + check_run $XDP_LOADER load $NS $TEST_PROG_DIR/xdp_drop.o -vv + check_run $XDP_LOADER unload $NS --all -vv +} + +test_section() +{ + check_run $XDP_LOADER load $NS $TEST_PROG_DIR/xdp_drop.o -s xdp -vv + check_run $XDP_LOADER unload $NS --all -vv +} + +test_prog_name() +{ + check_run $XDP_LOADER load $NS $TEST_PROG_DIR/xdp_drop.o -n xdp_drop -vv + check_run $XDP_LOADER unload $NS --all -vv +} + +check_progs_loaded() +{ + local iface="$1" + local num=$2 + local num_loaded + + num_loaded=$($XDP_LOADER status $NS | grep -c '=>') + if [ "$num_loaded" -ne "$num" ]; then + echo "Expected $num programs loaded, found $num_loaded" + exit 1 + fi +} + +test_load_multi() +{ + skip_if_legacy_fallback + + check_run $XDP_LOADER load $NS $TEST_PROG_DIR/xdp_drop.o $TEST_PROG_DIR/xdp_pass.o -vv + check_progs_loaded $NS 2 + check_run $XDP_LOADER unload $NS --all -vv +} + +test_load_incremental() +{ + skip_if_legacy_fallback + + local output + local ret + local id + + check_run $XDP_LOADER load $NS $TEST_PROG_DIR/xdp_drop.o -vv + + check_progs_loaded $NS 1 + + output=$($XDP_LOADER load $NS $TEST_PROG_DIR/xdp_pass.o -vv 2>&1) + ret=$? + + if [ "$ret" -ne "0" ] && echo $output | grep -q "Falling back to loading single prog"; then + ret=$SKIPPED_TEST + check_run $XDP_LOADER unload $NS --all -vv + else + check_progs_loaded $NS 2 + + id=$($XDP_LOADER status $NS | grep xdp_pass | awk '{print $4}') + check_run $XDP_LOADER unload $NS --id $id + check_progs_loaded $NS 1 + + id=$($XDP_LOADER status $NS | grep xdp_drop | awk '{print $4}') + check_run $XDP_LOADER unload $NS --id $id + check_progs_loaded $NS 0 + fi + return $ret +} + +test_load_clobber() +{ + skip_if_legacy_fallback + + check_run env LIBXDP_SKIP_DISPATCHER=1 $XDP_LOADER load $NS $TEST_PROG_DIR/xdp_drop.o -vv + check_progs_loaded $NS 0 # legacy prog so should show up as 0 + $XDP_LOADER load $NS $TEST_PROG_DIR/xdp_pass.o -vv + ret=$? + + if [ "$ret" -eq "0" ]; then + echo "Should not have been able to load prog with legacy prog loaded" + return 1 + fi + check_progs_loaded $NS 0 + check_run $XDP_LOADER unload $NS --all -vv +} + + +cleanup_tests() +{ + $XDP_LOADER unload $NS --all >/dev/null 2>&1 +} diff --git a/xdp-loader/xdp-loader.8 b/xdp-loader/xdp-loader.8 new file mode 100644 index 0000000..7e0b8eb --- /dev/null +++ b/xdp-loader/xdp-loader.8 @@ -0,0 +1,260 @@ +.TH "xdp-loader" "8" "JANUARY 9, 2023" "V1.3.1" "XDP program loader" + +.SH "NAME" +xdp-loader \- an XDP program loader +.SH "SYNOPSIS" +.PP +XDP-loader is a simple loader for XDP programs with support for attaching +multiple programs to the same interface. To achieve this it exposes the same +load and unload semantics exposed by the libxdp library. See the \fIlibxdp(3)\fP man +page for details of how this works, and what kernel features it relies on. + +.SS "Running xdp-loader" +.PP +The syntax for running xdp-loader is: + +.RS +.nf +\fCxdp-loader COMMAND [options] + +Where COMMAND can be one of: + load - load an XDP program on an interface + unload - unload an XDP program from an interface + status - show current XDP program status + clean - clean up detached program links in XDP bpffs directory + help - show the list of available commands +\fP +.fi +.RE + +.PP +Each command, and its options are explained below. Or use \fIxdp\-loader COMMAND +\-\-help\fP to see the options for each command. + +.SH "The LOAD command" +.PP +The \fIload\fP command loads one or more XDP programs onto an interface. + +.PP +The syntax for the \fIload\fP command is: + +.PP +\fIxdp\-loader load [options] <ifname> <programs>\fP + +.PP +Where \fI<ifname>\fP is the name of the interface to load the programs onto, and the +\fI<programs>\fP is one or more file names containing XDP programs. The programs +will be loaded onto the interface in the order of their preference, as +specified by the program metadata (see \fBlibxdp(3)\fP). + +.PP +The supported options are: + +.SS "-m, --mode <mode>" +.PP +Specifies which mode to load the XDP program to be loaded in. The valid values +are 'native', which is the default in-driver XDP mode, 'skb', which causes the +so-called \fIskb mode\fP (also known as \fIgeneric XDP\fP) to be used, 'hw' which causes +the program to be offloaded to the hardware, or 'unspecified' which leaves it up +to the kernel to pick a mode (which it will do by picking native mode if the +driver supports it, or generic mode otherwise). Note that using 'unspecified' +can make it difficult to predict what mode a program will end up being loaded +in. For this reason, the default is 'native'. Note that hardware with support +for the 'hw' mode is rare: Solarflare cards (using the 'sfc' driver) are the +only devices with support for this in the mainline Linux kernel. + +.SS "-p, --pin-path <path>" +.PP +This specifies a root path under which to pin any maps that define the 'pinning' +attribute in their definitions. This path must be located on a \fIbpffs\fP file +system. If not set, maps will not be pinned, even if they specify pinning in +their definitions. When pinning maps, if the pinned location for a map already +exist, the map pinned there will be reused if it is compatible with the type of +the map being loaded. + +.SS "-s, --section <section>" +.PP +Specify which ELF section to load the XDP program(s) from in each file. The +default is to use the first program in each file. If this option is set, it +applies to all programs being loaded. + +.SS "-n, --prog-name <prog_name>" +.PP +Specify which BPF program with the name to load the XDP program(s) from in each +file. The default is to use the first program in each file. Only one of +--section and --prog-name may be specified. If this option is set, it applies to +all programs being loaded. + +.SS "-P, --prio <priority>" +.PP +Specify the priority to load the XDP program(s) with (this affects the order of +programs running on the interface). The default is to use the value from the metadata +in the program ELF file, or a value of 50 if the program has no such metadata. +If this option is set, it applies to all programs being loaded. + +.SS "-A, --actions <actions>" +.PP +Specify the "chain call actions" of the loaded XDP program(s). These are the XDP +actions that will cause the next program loaded on the interface to be called, +instead of returning immediately. The default is to use the value set in the metadata +in the program ELF file, or XDP_PASS if no such metadata is set. If this option is set, +it applies to all programs being loaded. + +.SS "-v, --verbose" +.PP +Enable debug logging. Specify twice for even more verbosity. + +.SS "-h, --help" +.PP +Display a summary of the available options + +.SH "The UNLOAD command" +.PP +The \fIunload\fP command is used for unloading programs from an interface. + +.PP +The syntax for the \fIunload\fP command is: + +.PP +\fIxdp\-loader unload [options] <ifname>\fP + +.PP +Where \fI<ifname>\fP is the name of the interface to load the programs onto. Either +the \fI\-\-all\fP or \fI\-\-id\fP options must be used to specify which program(s) to unload. + +.PP +The supported options are: + +.SS "-i, --id <id>" +.PP +Unload a single program from the interface by ID. Use \fIxdp\-loader status\fP to +obtain the ID of the program being unloaded. If this program is the last program +loaded on the interface, the dispatcher program will also be removed, which +makes the operation equivalent to specifying \fI\-\-all\fP. + +.SS "-a, --all" +.PP +Unload all XDP programs on the interface, as well as the multi-program +dispatcher. + +.SS "-v, --verbose" +.PP +Enable debug logging. Specify twice for even more verbosity. + +.SS "-h, --help" +.PP +Display a summary of the available options + +.SH "The STATUS command" +.PP +The \fIstatus\fP command displays a list of interfaces in the system, and the XDP +program(s) loaded on each interface. For each interface, a list of programs are +shown, with the run priority and "chain actions" for each program. See the +section on program metadata for the meaning of this metadata. + +.SS "-v, --verbose" +.PP +Enable debug logging. Specify twice for even more verbosity. + +.SS "-h, --help" +.PP +Display a summary of the available options + +.SH "The CLEAN command" +.PP +The syntax for the \fIclean\fP command is: + +.PP +\fIxdp\-loader clean [options] [ifname]\fP + +.PP +The \fIclean\fP command cleans up any detached program links in the XDP bpffs +directory. When a network interface disappears, any programs loaded in software +mode (e.g. skb, native) remain pinned in the bpffs directory, but become +detached from the interface. These need to be unlinked from the filesystem. The +\fIclean\fP command takes an optional interface parameter to only unlink detached +programs corresponding to the interface. By default, all detached programs for +all interfaces are unlinked. + +.PP +The supported options are: + +.SS "-v, --verbose" +.PP +Enable debug logging. Specify twice for even more verbosity. + +.SS "-h, --help" +.PP +Display a summary of the available options + +.SH "Examples" +.PP +To load an XDP program on the eth0 interface simply do: + +.RS +.nf +\fC# xdp-loader load eth0 xdp_drop.o +# xdp-loader status + +CURRENT XDP PROGRAM STATUS: + +Interface Prio Program name Mode ID Tag Chain actions +------------------------------------------------------------------------------------- +lo <no XDP program> +eth0 xdp_dispatcher native 50 d51e469e988d81da + => 50 xdp_drop 55 57cd311f2e27366b XDP_PASS + +\fP +.fi +.RE + +.PP +Which shows that a dispatcher program was loaded on the interface, and the +xdp_drop program was installed as the first (and only) component program after +it. In this instance, the program does not specify any of the metadata above, so +the defaults (priority 50 and XDP_PASS as its chain call action) was used. + +.PP +To use the automatic map pinning, include the \fIpinning\fP attribute into the map +definition in the program, something like: + +.RS +.nf +\fCstruct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 10); + __type(key, __u32); + __type(value, __u64); + __uint(pinning, LIBBPF_PIN_BY_NAME); +} my_map SEC(".maps"); +\fP +.fi +.RE + +.PP +And load it with the \fI\-\-pin\-path\fP attribute: + +.RS +.nf +\fC# xdp-loader load eth0 my_prog.o --pin-path /sys/fs/bpf/my-prog +\fP +.fi +.RE + +.PP +This will pin the map at \fI/sys/fs/bpf/my\-prog/my_map\fP. If this already exists, +the pinned map will be reused instead of creating a new one, which allows +different BPF programs to share the map. + +.SH "SEE ALSO" +.PP +\fIlibxdp(3)\fP for details on the XDP loading semantics and kernel compatibility +requirements. + +.SH "BUGS" +.PP +Please report any bugs on Github: \fIhttps://github.com/xdp-project/xdp-tools/issues\fP + +.SH "AUTHOR" +.PP +xdp-loader and this man page were written by Toke Høiland-Jørgensen. diff --git a/xdp-loader/xdp-loader.c b/xdp-loader/xdp-loader.c new file mode 100644 index 0000000..aed52a7 --- /dev/null +++ b/xdp-loader/xdp-loader.c @@ -0,0 +1,411 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#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 <xdp/libxdp.h> +#include <linux/err.h> + +#include "params.h" +#include "logging.h" +#include "util.h" + +#define PROG_NAME "xdp-loader" + +static const struct loadopt { + bool help; + struct iface iface; + struct multistring filenames; + char *pin_path; + char *section_name; + char *prog_name; + enum xdp_attach_mode mode; + __u32 prio; + __u32 actions; +} defaults_load = { + .mode = XDP_MODE_NATIVE +}; + +struct enum_val xdp_modes[] = { + {"native", XDP_MODE_NATIVE}, + {"skb", XDP_MODE_SKB}, + {"hw", XDP_MODE_HW}, + {"unspecified", XDP_MODE_UNSPEC}, + {NULL, 0} +}; + +struct flag_val load_actions[] = { + {"XDP_ABORTED", 1U << XDP_ABORTED}, + {"XDP_DROP", 1U << XDP_DROP}, + {"XDP_PASS", 1U << XDP_PASS}, + {"XDP_TX", 1U << XDP_TX}, + {"XDP_REDIRECT", 1U << XDP_REDIRECT}, + {} +}; + +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("pin-path", OPT_STRING, struct loadopt, pin_path, + .short_opt = 'p', + .help = "Path to pin maps under (must be in bpffs)."), + DEFINE_OPTION("section", OPT_STRING, struct loadopt, section_name, + .metavar = "<section>", + .short_opt = 's', + .help = "ELF section name of program to load (default: first in file)."), + DEFINE_OPTION("prog-name", OPT_STRING, struct loadopt, prog_name, + .metavar = "<prog_name>", + .short_opt = 'n', + .help = "BPF program name of program to load (default: first in file)."), + DEFINE_OPTION("dev", OPT_IFNAME, struct loadopt, iface, + .positional = true, + .metavar = "<ifname>", + .required = true, + .help = "Load on device <ifname>"), + DEFINE_OPTION("filenames", OPT_MULTISTRING, struct loadopt, filenames, + .positional = true, + .metavar = "<filenames>", + .required = true, + .help = "Load programs from <filenames>"), + DEFINE_OPTION("prio", OPT_U32, struct loadopt, prio, + .short_opt = 'P', + .help = "Set run priority of program"), + DEFINE_OPTION("actions", OPT_FLAGS, struct loadopt, actions, + .short_opt = 'A', + .typearg = load_actions, + .metavar = "<actions>", + .help = "Chain call actions (default: XDP_PASS). e.g. XDP_PASS,XDP_DROP"), + END_OPTIONS +}; + +int do_load(const void *cfg, __unused const char *pin_root_path) +{ + const struct loadopt *opt = cfg; + struct xdp_program **progs, *p; + char errmsg[STRERR_BUFSIZE]; + int err = EXIT_SUCCESS; + size_t num_progs, i; + DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts, + .pin_root_path = opt->pin_path); + + if (opt->section_name && opt->prog_name) { + pr_warn("Only one of --section or --prog-name can be set\n"); + return EXIT_FAILURE; + } + + num_progs = opt->filenames.num_strings; + if (!num_progs) { + pr_warn("Need at least one filename to load\n"); + return EXIT_FAILURE; + } else if (num_progs > 1 && opt->mode == XDP_MODE_HW) { + pr_warn("Cannot attach multiple programs in HW mode\n"); + return EXIT_FAILURE; + } + + progs = calloc(num_progs, sizeof(*progs)); + if (!progs) { + pr_warn("Couldn't allocate memory\n"); + return EXIT_FAILURE; + } + + pr_debug("Loading %zu files on interface '%s'.\n", + num_progs, opt->iface.ifname); + + /* libbpf spits out a lot of unhelpful error messages while loading. + * Silence the logging so we can provide our own messages instead; this + * is a noop if verbose logging is enabled. + */ + silence_libbpf_logging(); + +retry: + for (i = 0; i < num_progs; i++) { + DECLARE_LIBXDP_OPTS(xdp_program_opts, xdp_opts, 0); + struct bpf_program *bpf_prog = NULL; + + p = progs[i]; + if (p) + xdp_program__close(p); + + if (opt->prog_name) { + xdp_opts.open_filename = opt->filenames.strings[i]; + xdp_opts.prog_name = opt->prog_name; + xdp_opts.opts = &opts; + + p = xdp_program__create(&xdp_opts); + } else { + p = xdp_program__open_file(opt->filenames.strings[i], + opt->section_name, &opts); + } + + err = libxdp_get_error(p); + if (err) { + if (err == -EPERM && !double_rlimit()) + goto retry; + + libxdp_strerror(err, errmsg, sizeof(errmsg)); + pr_warn("Couldn't open file '%s': %s\n", + opt->filenames.strings[i], errmsg); + goto out; + } + + /* Disable autoload for all programs in the bpf object; libxdp + * will make sure to turn it back on for the program that we're + * actually loading + */ + bpf_object__for_each_program(bpf_prog, xdp_program__bpf_obj(p)) + bpf_program__set_autoload(bpf_prog, false); + + if (opt->prio) { + err = xdp_program__set_run_prio(p, opt->prio); + if (err) { + pr_warn("Error setting run priority: %u\n", opt->prio); + goto out; + } + } + + if (opt->actions) { + __u32 a; + + for (a = XDP_ABORTED; a <= XDP_REDIRECT; a++) { + err = xdp_program__set_chain_call_enabled(p, a, opt->actions & (1U << a)); + if (err) { + pr_warn("Error setting chain call action: %u\n", a); + goto out; + } + } + } + + xdp_program__print_chain_call_actions(p, errmsg, sizeof(errmsg)); + pr_debug("XDP program %zu: Run prio: %d. Chain call actions: %s\n", + i, xdp_program__run_prio(p), errmsg); + + if (!opt->pin_path) { + struct bpf_map *map; + + bpf_object__for_each_map(map, xdp_program__bpf_obj(p)) { + err = bpf_map__set_pin_path(map, NULL); + if (err) { + pr_warn("Error clearing map pin path: %s\n", + strerror(-err)); + goto out; + } + } + } + + progs[i] = p; + } + + err = xdp_program__attach_multi(progs, num_progs, + opt->iface.ifindex, opt->mode, 0); + if (err) { + if (err == -EPERM && !double_rlimit()) + goto retry; + + if (err == -EOPNOTSUPP && + (opt->mode == XDP_MODE_NATIVE || opt->mode == XDP_MODE_HW)) { + pr_warn("Attaching XDP program in %s mode not supported - try %s mode.\n", + opt->mode == XDP_MODE_NATIVE ? "native" : "HW", + opt->mode == XDP_MODE_NATIVE ? "SKB" : "native or SKB"); + } else { + libbpf_strerror(err, errmsg, sizeof(errmsg)); + pr_warn("Couldn't attach XDP program on iface '%s': %s(%d)\n", + opt->iface.ifname, errmsg, err); + } + goto out; + } + +out: + for (i = 0; i < num_progs; i++) + if (progs[i]) + xdp_program__close(progs[i]); + free(progs); + return err; +} + +static const struct unloadopt { + bool all; + __u32 prog_id; + struct iface iface; +} defaults_unload = {}; + +static struct prog_option unload_options[] = { + DEFINE_OPTION("dev", OPT_IFNAME, struct unloadopt, iface, + .positional = true, + .metavar = "<ifname>", + .help = "Unload from device <ifname>"), + DEFINE_OPTION("id", OPT_U32, struct unloadopt, prog_id, + .metavar = "<id>", + .short_opt = 'i', + .help = "Unload program with id <id>"), + DEFINE_OPTION("all", OPT_BOOL, struct unloadopt, all, + .short_opt = 'a', + .help = "Unload all programs from interface"), + END_OPTIONS +}; + +int do_unload(const void *cfg, __unused const char *pin_root_path) +{ + const struct unloadopt *opt = cfg; + struct xdp_multiprog *mp = NULL; + enum xdp_attach_mode mode; + int err = EXIT_FAILURE; + DECLARE_LIBBPF_OPTS(bpf_object_open_opts, opts, + .pin_root_path = pin_root_path); + + if (!opt->all && !opt->prog_id) { + pr_warn("Need prog ID or --all\n"); + goto out; + } + + if (!opt->iface.ifindex) { + pr_warn("Must specify ifname\n"); + goto out; + } + + mp = xdp_multiprog__get_from_ifindex(opt->iface.ifindex); + if (IS_ERR_OR_NULL(mp)) { + pr_warn("No XDP program loaded on %s\n", opt->iface.ifname); + mp = NULL; + goto out; + } + + if (opt->all) { + err = xdp_multiprog__detach(mp); + if (err) { + pr_warn("Unable to detach XDP program: %s\n", + strerror(-err)); + goto out; + } + } else { + struct xdp_program *prog = NULL; + + while ((prog = xdp_multiprog__next_prog(prog, mp))) { + if (xdp_program__id(prog) == opt->prog_id) { + mode = xdp_multiprog__attach_mode(mp); + goto found; + } + } + + if (xdp_multiprog__is_legacy(mp)) { + prog = xdp_multiprog__main_prog(mp); + if (xdp_program__id(prog) == opt->prog_id) { + mode = xdp_multiprog__attach_mode(mp); + goto found; + } + } + + prog = xdp_multiprog__hw_prog(mp); + if (xdp_program__id(prog) == opt->prog_id) { + mode = XDP_MODE_HW; + goto found; + } + + pr_warn("Program with ID %u not loaded on %s\n", + opt->prog_id, opt->iface.ifname); + err = -ENOENT; + goto out; + +found: + pr_debug("Detaching XDP program with ID %u from %s\n", + xdp_program__id(prog), opt->iface.ifname); + err = xdp_program__detach(prog, opt->iface.ifindex, mode, 0); + if (err) { + pr_warn("Unable to detach XDP program: %s\n", + strerror(-err)); + goto out; + } + } + +out: + xdp_multiprog__close(mp); + return err ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static const struct statusopt { + struct iface iface; +} defaults_status = {}; + +static struct prog_option status_options[] = { + DEFINE_OPTION("dev", OPT_IFNAME, struct statusopt, iface, + .positional = true, .metavar = "[ifname]", + .help = "Show status for device [ifname] (default all interfaces)"), + END_OPTIONS +}; + +int do_status(const void *cfg, __unused const char *pin_root_path) +{ + const struct statusopt *opt = cfg; + + printf("CURRENT XDP PROGRAM STATUS:\n\n"); + return iface_print_status(opt->iface.ifindex ? &opt->iface : NULL); +} + +static const struct cleanopt { + struct iface iface; +} defaults_clean = {}; + +static struct prog_option clean_options[] = { + DEFINE_OPTION("dev", OPT_IFNAME, struct cleanopt, iface, + .positional = true, .metavar = "[ifname]", + .help = "Clean up detached program links for [ifname] (default all interfaces)"), + END_OPTIONS +}; + +int do_clean(const void *cfg, __unused const char *pin_root_path) +{ + const struct cleanopt *opt = cfg; + + printf("Cleaning up detached XDP program links for %s\n", opt->iface.ifindex ? + opt->iface.ifname : "all interfaces"); + return libxdp_clean_references(opt->iface.ifindex); +} + +int do_help(__unused const void *cfg, __unused const char *pin_root_path) +{ + fprintf(stderr, + "Usage: xdp-loader COMMAND [options]\n" + "\n" + "COMMAND can be one of:\n" + " load - load an XDP program on an interface\n" + " unload - unload an XDP program from an interface\n" + " status - show current XDP program status\n" + " clean - clean up detached program links in XDP bpffs directory\n" + " help - show this help message\n" + "\n" + "Use 'xdp-loader 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(unload, "Unload an XDP program from an interface"), + DEFINE_COMMAND(clean, "Clean up detached program links in XDP bpffs directory"), + DEFINE_COMMAND(status, "Show XDP program status"), + { .name = "help", .func = do_help, .no_cfg = true }, + END_COMMANDS +}; + +union all_opts { + struct loadopt load; + struct unloadopt unload; + struct statusopt status; +}; + +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); +} |