summaryrefslogtreecommitdiffstats
path: root/xdp-loader
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:10:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 07:10:00 +0000
commit4ba2b326284765e942044db13a7f0dae702bec93 (patch)
treecbdfaec33eed4f3a970c54cd10e8ddfe3003b3b1 /xdp-loader
parentInitial commit. (diff)
downloadxdp-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/.gitignore1
-rw-r--r--xdp-loader/Makefile12
-rw-r--r--xdp-loader/README.org217
-rw-r--r--xdp-loader/tests/test-xdp-loader.sh97
-rw-r--r--xdp-loader/xdp-loader.8260
-rw-r--r--xdp-loader/xdp-loader.c411
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);
+}