summaryrefslogtreecommitdiffstats
path: root/lib/libxdp/libxdp.c
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 /lib/libxdp/libxdp.c
parentInitial commit. (diff)
downloadxdp-tools-4ba2b326284765e942044db13a7f0dae702bec93.tar.xz
xdp-tools-4ba2b326284765e942044db13a7f0dae702bec93.zip
Adding upstream version 1.3.1.upstream/1.3.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--lib/libxdp/libxdp.c3408
1 files changed, 3408 insertions, 0 deletions
diff --git a/lib/libxdp/libxdp.c b/lib/libxdp/libxdp.c
new file mode 100644
index 0000000..9689457
--- /dev/null
+++ b/lib/libxdp/libxdp.c
@@ -0,0 +1,3408 @@
+// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
+
+/*
+ * XDP management utility functions
+ *
+ * Copyright (C) 2020 Toke Høiland-Jørgensen <toke@redhat.com>
+ */
+
+#include <linux/bpf.h>
+#define _GNU_SOURCE
+
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/file.h>
+#include <sys/vfs.h>
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <dirent.h>
+
+#include <linux/err.h> /* ERR_PTR */
+#include <linux/if_link.h>
+#include <linux/magic.h>
+
+#include <bpf/libbpf.h>
+#include <bpf/bpf.h>
+#include <bpf/btf.h>
+#include <xdp/libxdp.h>
+#include <xdp/prog_dispatcher.h>
+
+#include "compat.h"
+#include "libxdp_internal.h"
+
+#define XDP_RUN_CONFIG_SEC ".xdp_run_config"
+#define XDP_SKIP_ENVVAR "LIBXDP_SKIP_DISPATCHER"
+
+/* When cloning BPF fds, we want to make sure they don't end up as any of the
+ * standard stdin, stderr, stdout descriptors: fd 0 can confuse the kernel, and
+ * there are orchestration systems that will force-close the others if they
+ * don't point to the "right" things. So just to be safe, use 3 as the minimum
+ * fd number.
+ */
+#define MIN_FD 3
+
+/* Max number of times we retry attachment */
+#define MAX_RETRY 10
+
+#define IFINDEX_LO 1
+
+static const char *dispatcher_feature_err =
+ "This means that the kernel does not support the features needed\n"
+ "by the multiprog dispatcher, either because it is too old entirely,\n"
+ "or because it is not yet supported on the current architecture.\n";
+
+struct xdp_program {
+ /* one of prog or prog_fd should be set */
+ struct bpf_program *bpf_prog;
+ struct bpf_object *bpf_obj;
+ struct btf *btf;
+ enum bpf_prog_type prog_type;
+ int prog_fd;
+ int link_fd;
+ char *prog_name;
+ char *attach_name;
+ __u8 prog_tag[BPF_TAG_SIZE];
+ __u32 prog_id;
+ __u64 load_time;
+ bool from_external_obj;
+ bool is_frags;
+ unsigned int run_prio;
+ unsigned int chain_call_actions; /* bitmap */
+
+ /* for building list of attached programs to multiprog */
+ struct xdp_program *next;
+};
+
+struct xdp_multiprog {
+ struct xdp_dispatcher_config config;
+ struct xdp_program *main_prog; /* dispatcher or legacy prog pointer */
+ struct xdp_program *first_prog; /* uses xdp_program->next to build a list */
+ struct xdp_program *hw_prog;
+ __u32 version;
+ size_t num_links;
+ bool is_loaded;
+ bool is_legacy;
+ bool kernel_frags_support;
+ bool checked_compat;
+ enum xdp_attach_mode attach_mode;
+ int ifindex;
+};
+
+#define XDP_DISPATCHER_VERSION_V1 1
+struct xdp_dispatcher_config_v1 {
+ __u8 num_progs_enabled; /* Number of active program slots */
+ __u32 chain_call_actions[MAX_DISPATCHER_ACTIONS];
+ __u32 run_prios[MAX_DISPATCHER_ACTIONS];
+};
+
+static const char *xdp_action_names[] = {
+ [XDP_ABORTED] = "XDP_ABORTED",
+ [XDP_DROP] = "XDP_DROP",
+ [XDP_PASS] = "XDP_PASS",
+ [XDP_TX] = "XDP_TX",
+ [XDP_REDIRECT] = "XDP_REDIRECT",
+};
+
+static struct xdp_program *xdp_program__create_from_obj(struct bpf_object *obj,
+ const char *section_name,
+ const char *prog_name,
+ bool external);
+
+#ifdef LIBXDP_STATIC
+struct xdp_embedded_obj {
+ const char *filename;
+ const void *data_start;
+ const void *data_end;
+};
+
+extern const char _binary_xdp_dispatcher_o_start;
+extern const char _binary_xdp_dispatcher_o_end;
+extern const char _binary_xsk_def_xdp_prog_o_start;
+extern const char _binary_xsk_def_xdp_prog_o_end;
+extern const char _binary_xsk_def_xdp_prog_5_3_o_start;
+extern const char _binary_xsk_def_xdp_prog_5_3_o_end;
+
+static struct xdp_embedded_obj embedded_objs[] = {
+ {"xdp-dispatcher.o", &_binary_xdp_dispatcher_o_start, &_binary_xdp_dispatcher_o_end},
+ {"xsk_def_xdp_prog.o", &_binary_xsk_def_xdp_prog_o_start, &_binary_xsk_def_xdp_prog_o_end},
+ {"xsk_def_xdp_prog_5.3.o", &_binary_xsk_def_xdp_prog_5_3_o_start, &_binary_xsk_def_xdp_prog_5_3_o_end},
+ {},
+};
+static struct xdp_program *xdp_program__find_embedded(const char *filename,
+ const char *section_name,
+ const char *prog_name,
+ struct bpf_object_open_opts *opts)
+{
+ DECLARE_LIBBPF_OPTS(bpf_object_open_opts, default_opts,
+ .object_name = filename,
+ );
+ struct xdp_embedded_obj *eobj;
+ struct bpf_object *obj;
+ size_t size;
+ int err;
+
+ for (eobj = &embedded_objs[0]; eobj->filename; eobj++) {
+ if (strcmp(filename, eobj->filename))
+ continue;
+
+ size = eobj->data_end - eobj->data_start;
+
+ /* set the object name to the same as if we opened the file from
+ * the filesystem
+ */
+ if (!opts)
+ opts = &default_opts;
+ else if (!opts->object_name)
+ opts->object_name = filename;
+
+ pr_debug("Loading XDP program '%s' from embedded object file\n", filename);
+
+ obj = bpf_object__open_mem(eobj->data_start, size, opts);
+ err = libbpf_get_error(obj);
+ if (err)
+ return ERR_PTR(err);
+ return xdp_program__create_from_obj(obj, section_name, prog_name, false);
+ }
+
+ return NULL;
+}
+#else
+static inline struct xdp_program *xdp_program__find_embedded(__unused const char *filename,
+ __unused const char *section_name,
+ __unused const char *prog_name,
+ __unused struct bpf_object_open_opts *opts)
+{
+ return NULL;
+}
+#endif
+
+static int __base_pr(enum libxdp_print_level level, const char *format,
+ va_list args)
+{
+ if (level == LIBXDP_DEBUG)
+ return 0;
+
+ return vfprintf(stderr, format, args);
+}
+
+static libxdp_print_fn_t __libxdp_pr = __base_pr;
+
+libxdp_print_fn_t libxdp_set_print(libxdp_print_fn_t fn)
+{
+ libxdp_print_fn_t old_print_fn = __libxdp_pr;
+
+ __libxdp_pr = fn;
+ return old_print_fn;
+}
+
+__printf(2, 3) void libxdp_print(enum libxdp_print_level level, const char *format, ...)
+{
+ va_list args;
+
+ if (!__libxdp_pr)
+ return;
+
+ va_start(args, format);
+ __libxdp_pr(level, format, args);
+ va_end(args);
+}
+
+static enum {
+ COMPAT_UNKNOWN,
+ COMPAT_SUPPORTED,
+ COMPAT_UNSUPPORTED
+} kernel_compat = COMPAT_UNKNOWN;
+
+static int xdp_multiprog__attach(struct xdp_multiprog *old_mp,
+ struct xdp_multiprog *mp,
+ enum xdp_attach_mode mode);
+static struct xdp_multiprog *xdp_multiprog__generate(struct xdp_program **progs,
+ size_t num_progs,
+ int ifindex,
+ struct xdp_multiprog *old_mp,
+ bool remove_progs);
+static int xdp_multiprog__pin(struct xdp_multiprog *mp);
+static int xdp_multiprog__unpin(struct xdp_multiprog *mp);
+
+
+/* On NULL, libxdp always sets errno to 0 for old APIs, so that their
+ * compatibility is maintained wrt old libxdp_get_error that called the older
+ * version of libbpf_get_error which did PTR_ERR_OR_ZERO, but newer versions
+ * unconditionally return -errno on seeing NULL, as the libbpf practice changed
+ * to returning NULL or errors.
+ *
+ * The new APIs (like xdp_program__create) which indicate error using NULL set
+ * their errno when returning NULL.
+ */
+long libxdp_get_error(const void *ptr)
+{
+ if (!IS_ERR_OR_NULL(ptr))
+ return 0;
+
+ if (IS_ERR(ptr))
+ errno = -PTR_ERR(ptr);
+ return -errno;
+}
+
+int libxdp_strerror(int err, char *buf, size_t size)
+{
+ return libxdp_err(libbpf_strerror(err, buf, size));
+}
+
+static char *libxdp_strerror_r(int err, char *dst, size_t size)
+{
+ int ret = libxdp_strerror(err, dst, size);
+ if (ret)
+ snprintf(dst, size, "ERROR: strerror_r(%d)=%d", err, ret);
+ return dst;
+}
+
+#ifndef HAVE_LIBBPF_BTF__LOAD_FROM_KERNEL_BY_ID
+static struct btf *btf__load_from_kernel_by_id(__u32 id)
+{
+ struct btf *btf;
+ int err;
+
+ err = btf__get_from_id(id, &btf);
+ if (err)
+ return NULL;
+ return btf;
+}
+#endif
+
+#ifndef HAVE_LIBBPF_BTF__TYPE_CNT
+static __u32 btf__type_cnt(const struct btf *btf)
+{
+ /* old function didn't include 'void' type in count */
+ return btf__get_nr_types(btf) + 1;
+}
+#endif
+
+#ifndef HAVE_LIBBPF_BPF_OBJECT__NEXT_MAP
+static struct bpf_map *bpf_object__next_map(const struct bpf_object *obj,
+ const struct bpf_map *map)
+{
+ return bpf_map__next(map, obj);
+}
+#endif
+
+#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
+
+#ifndef HAVE_LIBBPF_BPF_PROGRAM__INSN_CNT
+#define BPF_INSN_SZ (sizeof(struct bpf_insn))
+static size_t bpf_program__insn_cnt(const struct bpf_program *prog)
+{
+ size_t sz;
+
+ sz = bpf_program__size(prog);
+ return sz / BPF_INSN_SZ;
+}
+#endif
+
+/* This function has been deprecated in libbpf, but we expose an API that uses
+ * section names, so we reimplement it to keep compatibility
+ */
+static struct bpf_program *
+bpf_program_by_section_name(const struct bpf_object *obj,
+ const char *section_name)
+{
+ struct bpf_program *pos;
+ const char *sname;
+
+ bpf_object__for_each_program(pos, obj) {
+ sname = bpf_program__section_name(pos);
+ if (sname && !strcmp(sname, section_name))
+ return pos;
+ }
+ return NULL;
+}
+
+static bool bpf_is_valid_mntpt(const char *mnt)
+{
+ struct statfs st_fs;
+
+ if (statfs(mnt, &st_fs) < 0)
+ return false;
+ if ((unsigned long)st_fs.f_type != BPF_FS_MAGIC)
+ return false;
+
+ return true;
+}
+
+static int bpf_mnt_fs(const char *target)
+{
+ bool bind_done = false;
+ int err;
+
+retry:
+ err = mount("", target, "none", MS_PRIVATE | MS_REC, NULL);
+ if (err) {
+ if (errno != EINVAL || bind_done) {
+ err = -errno;
+ pr_warn("mount --make-private %s failed: %s\n",
+ target, strerror(-err));
+ return err;
+ }
+
+ err = mount(target, target, "none", MS_BIND, NULL);
+ if (err) {
+ err = -errno;
+ pr_warn("mount --bind %s %s failed: %s\n",
+ target, target, strerror(-err));
+ return err;
+ }
+
+ bind_done = true;
+ goto retry;
+ }
+
+ err = mount("bpf", target, "bpf", 0, "mode=0700");
+ if (err) {
+ err = -errno;
+ pr_warn("mount -t bpf bpf %s failed: %s\n",
+ target, strerror(-err));
+ return err;
+ }
+
+ return 0;
+}
+
+static const char *bpf_find_mntpt_single(char *mnt, int len, const char *mntpt, bool mount)
+{
+ int err;
+
+ if (!bpf_is_valid_mntpt(mntpt)) {
+ if (!mount)
+ return NULL;
+
+ pr_debug("No bpffs found at %s, mounting a new one\n",
+ mntpt);
+
+ err = bpf_mnt_fs(mntpt);
+ if (err)
+ return NULL;
+ }
+
+ strncpy(mnt, mntpt, len - 1);
+ mnt[len - 1] = '\0';
+ return mnt;
+}
+
+static const char *find_bpffs()
+{
+ static bool bpf_mnt_cached = false;
+ static char bpf_wrk_dir[PATH_MAX];
+ static const char *mnt = NULL;
+ char *envdir, *envval;
+ bool mount = false;
+
+ if (bpf_mnt_cached)
+ return mnt;
+
+ envdir = secure_getenv(XDP_BPFFS_ENVVAR);
+ envval = secure_getenv(XDP_BPFFS_MOUNT_ENVVAR);
+ if (envval && envval[0] == '1' && envval[1] == '\0')
+ mount = true;
+
+ mnt = bpf_find_mntpt_single(bpf_wrk_dir,
+ sizeof(bpf_wrk_dir),
+ envdir ?: BPF_DIR_MNT,
+ mount);
+ if (!mnt)
+ pr_warn("No bpffs found at %s\n", envdir ?: BPF_DIR_MNT);
+ else
+ bpf_mnt_cached = 1;
+
+ return mnt;
+}
+
+static int mk_state_subdir(char *dir, size_t dir_sz, const char *parent)
+{
+ int err;
+
+ err = try_snprintf(dir, dir_sz, "%s/xdp", parent);
+ if (err)
+ return err;
+
+ err = mkdir(dir, S_IRWXU);
+ if (err && errno != EEXIST)
+ return -errno;
+
+ return 0;
+}
+
+static const char *get_bpffs_dir(void)
+{
+ static char bpffs_dir[PATH_MAX];
+ static const char *dir = NULL;
+ const char *parent;
+ int err;
+
+ if (dir)
+ return dir;
+
+ parent = find_bpffs();
+ if (!parent) {
+ err = -ENOENT;
+ goto err;
+ }
+
+ err = mk_state_subdir(bpffs_dir, sizeof(bpffs_dir), parent);
+ if (err)
+ goto err;
+
+ dir = bpffs_dir;
+ return dir;
+err:
+ return ERR_PTR(err);
+}
+
+static const char *get_lock_dir(void)
+{
+ static const char *dir = NULL;
+ static char rundir[PATH_MAX];
+ int err;
+
+ if (dir)
+ return dir;
+
+ dir = get_bpffs_dir();
+ if (!IS_ERR(dir))
+ return dir;
+
+ err = mk_state_subdir(rundir, sizeof(rundir), RUNDIR);
+ if (err)
+ return ERR_PTR(err);
+
+ dir = rundir;
+ return dir;
+}
+
+int xdp_lock_acquire(void)
+{
+ int lock_fd, err;
+ const char *dir;
+
+ dir = get_lock_dir();
+ if (IS_ERR(dir))
+ return PTR_ERR(dir);
+
+ lock_fd = open(dir, O_DIRECTORY);
+ if (lock_fd < 0) {
+ err = -errno;
+ pr_warn("Couldn't open lock directory at %s: %s\n",
+ dir, strerror(-err));
+ return err;
+ }
+
+ err = flock(lock_fd, LOCK_EX);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't flock fd %d: %s\n", lock_fd, strerror(-err));
+ close(lock_fd);
+ return err;
+ }
+
+ pr_debug("Acquired lock from %s with fd %d\n", dir, lock_fd);
+ return lock_fd;
+}
+
+int xdp_lock_release(int lock_fd)
+{
+ int err;
+
+ err = flock(lock_fd, LOCK_UN);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't unlock fd %d: %s\n", lock_fd, strerror(-err));
+ } else {
+ pr_debug("Released lock fd %d\n", lock_fd);
+ }
+ close(lock_fd);
+ return err;
+}
+
+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 xdp_attach_fd(int prog_fd, int old_fd, int ifindex,
+ enum xdp_attach_mode mode)
+{
+ int err = 0, xdp_flags = 0;
+
+ pr_debug("Replacing XDP fd %d with %d on ifindex %d\n",
+ old_fd, prog_fd, ifindex);
+
+ if (old_fd == -1) {
+ xdp_flags |= XDP_FLAGS_UPDATE_IF_NOEXIST;
+ old_fd = 0;
+ }
+
+ switch (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;
+ }
+again:
+ err = do_xdp_attach(ifindex, prog_fd, old_fd, xdp_flags);
+ if (err < 0) {
+ if (err == -EINVAL && old_fd) {
+ pr_debug("Got 'invalid argument', trying again without old_fd\n");
+ old_fd = 0;
+ goto again;
+ }
+ pr_info("Error attaching XDP program to ifindex %d: %s\n",
+ ifindex, strerror(-err));
+
+ if (err == -EEXIST && old_fd)
+ /* We raced with another attach/detach, have to retry */
+ return -EAGAIN;
+
+ 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;
+ }
+ }
+ return err;
+}
+
+const struct btf *xdp_program__btf(struct xdp_program *xdp_prog)
+{
+ if (!xdp_prog)
+ return libxdp_err_ptr(0, true);
+
+ return xdp_prog->btf;
+}
+
+enum xdp_attach_mode
+xdp_program__is_attached(const struct xdp_program *xdp_prog, int ifindex)
+{
+ struct xdp_program *prog = NULL;
+ struct xdp_multiprog *mp;
+ enum xdp_attach_mode ret = XDP_MODE_UNSPEC;
+
+ if (!xdp_prog || !xdp_prog->prog_id)
+ return ret;
+
+ mp = xdp_multiprog__get_from_ifindex(ifindex);
+ if (IS_ERR_OR_NULL(mp))
+ return ret;
+
+ prog = xdp_multiprog__hw_prog(mp);
+ if (xdp_program__id(prog) == xdp_program__id(xdp_prog)) {
+ ret = XDP_MODE_HW;
+ goto out;
+ }
+
+ if (xdp_multiprog__is_legacy(mp)) {
+ prog = xdp_multiprog__main_prog(mp);
+ if (xdp_program__id(prog) == xdp_program__id(xdp_prog))
+ ret = xdp_multiprog__attach_mode(mp);
+ goto out;
+ }
+
+ while ((prog = xdp_multiprog__next_prog(prog, mp))) {
+ if (xdp_program__id(prog) == xdp_program__id(xdp_prog)) {
+ ret = xdp_multiprog__attach_mode(mp);
+ break;
+ }
+ }
+
+out:
+ xdp_multiprog__close(mp);
+ return ret;
+}
+
+int xdp_program__set_chain_call_enabled(struct xdp_program *prog,
+ unsigned int action, bool enabled)
+{
+ if (IS_ERR_OR_NULL(prog) || prog->prog_fd >= 0 || action >= XDP_DISPATCHER_RETVAL)
+ return libxdp_err(-EINVAL);
+
+ if (enabled)
+ prog->chain_call_actions |= (1U << action);
+ else
+ prog->chain_call_actions &= ~(1U << action);
+
+ return 0;
+}
+
+bool xdp_program__chain_call_enabled(const struct xdp_program *prog,
+ enum xdp_action action)
+{
+ if (IS_ERR_OR_NULL(prog) || action >= XDP_DISPATCHER_RETVAL)
+ return false;
+
+ return !!(prog->chain_call_actions & (1U << action));
+}
+
+unsigned int xdp_program__run_prio(const struct xdp_program *prog)
+{
+ if (IS_ERR_OR_NULL(prog))
+ return XDP_DEFAULT_RUN_PRIO;
+
+ return prog->run_prio;
+}
+
+int xdp_program__set_run_prio(struct xdp_program *prog, unsigned int run_prio)
+{
+ if (IS_ERR_OR_NULL(prog) || prog->prog_fd >= 0)
+ return libxdp_err(-EINVAL);
+
+ prog->run_prio = run_prio;
+ return 0;
+}
+
+bool xdp_program__xdp_frags_support(const struct xdp_program *prog)
+{
+ if (IS_ERR_OR_NULL(prog))
+ return false;
+
+ /* Until we load the program we just check the bpf_program__flags() to
+ * ensure any changes made to those are honoured on the libxdp side. For
+ * loaded programs we keep our own state variable which is populated
+ * either by copying over the program flags in xdp_program__load(), or
+ * by loading the state from the dispatcher state variables if
+ * instantiating the object from the kernel.
+ */
+ if (!prog->bpf_prog || prog->prog_fd >= 0)
+ return prog->is_frags;
+
+ return !!(bpf_program__flags(prog->bpf_prog) & BPF_F_XDP_HAS_FRAGS);
+}
+
+int xdp_program__set_xdp_frags_support(struct xdp_program *prog, bool frags)
+{
+ __u32 prog_flags;
+ int ret;
+
+ if (IS_ERR_OR_NULL(prog) || !prog->bpf_prog || prog->prog_fd >= 0)
+ return libxdp_err(-EINVAL);
+
+ prog_flags = bpf_program__flags(prog->bpf_prog);
+
+ if (frags)
+ prog_flags |= BPF_F_XDP_HAS_FRAGS;
+ else
+ prog_flags &= ~BPF_F_XDP_HAS_FRAGS;
+
+ ret = bpf_program__set_flags(prog->bpf_prog, prog_flags);
+ if (!ret)
+ prog->is_frags = frags;
+
+ return ret;
+}
+
+const char *xdp_program__name(const struct xdp_program *prog)
+{
+ if (IS_ERR_OR_NULL(prog))
+ return libxdp_err_ptr(0, true);
+
+ return prog->prog_name;
+}
+
+struct bpf_object *xdp_program__bpf_obj(struct xdp_program *prog)
+{
+ if (IS_ERR_OR_NULL(prog))
+ return libxdp_err_ptr(0, true);
+
+ return prog->bpf_obj;
+}
+
+const unsigned char *xdp_program__tag(const struct xdp_program *prog)
+{
+ if (IS_ERR_OR_NULL(prog))
+ return libxdp_err_ptr(0, true);
+
+ return prog->prog_tag;
+}
+
+uint32_t xdp_program__id(const struct xdp_program *prog)
+{
+ if (IS_ERR_OR_NULL(prog))
+ return 0;
+
+ return prog->prog_id;
+}
+
+int xdp_program__fd(const struct xdp_program *prog)
+{
+ if (IS_ERR_OR_NULL(prog))
+ return errno = ENOENT, -1;
+
+ return prog->prog_fd;
+}
+
+int xdp_program__print_chain_call_actions(const struct xdp_program *prog,
+ char *buf, size_t buf_len)
+{
+ bool first = true;
+ char *pos = buf;
+ int i, len = 0;
+
+ if (IS_ERR_OR_NULL(prog) || !buf || !buf_len)
+ return libxdp_err(-EINVAL);
+
+ for (i = 0; i <= XDP_REDIRECT; i++) {
+ if (xdp_program__chain_call_enabled(prog, i)) {
+ if (!first) {
+ if (!buf_len)
+ goto err_len;
+ *pos++ = ',';
+ buf_len--;
+ } else {
+ first = false;
+ }
+ len = snprintf(pos, buf_len, "%s", xdp_action_names[i]);
+ if (len < 0 || (size_t)len >= buf_len)
+ goto err_len;
+ pos += len;
+ buf_len -= len;
+ }
+ }
+ return 0;
+err_len:
+ *pos = '\0';
+ return libxdp_err(-ENOSPC);
+}
+
+static const struct btf_type *skip_mods_and_typedefs(const struct btf *btf,
+ __u32 id, __u32 *res_id)
+{
+ const struct btf_type *t = btf__type_by_id(btf, id);
+
+ if (res_id)
+ *res_id = id;
+
+ while (btf_is_mod(t) || btf_is_typedef(t)) {
+ if (res_id)
+ *res_id = t->type;
+ t = btf__type_by_id(btf, t->type);
+ }
+
+ return t;
+}
+
+static bool get_field_int(const struct btf *btf,
+ const char *t_name,
+ const struct btf_type *t,
+ __u32 *res)
+{
+ const struct btf_array *arr_info;
+ const struct btf_type *arr_t;
+
+ if (!btf_is_ptr(t)) {
+ pr_warn("attr '%s': expected PTR, got %u.\n",
+ t_name, btf_kind(t));
+ return false;
+ }
+
+ arr_t = btf__type_by_id(btf, t->type);
+ if (!arr_t) {
+ pr_warn("attr '%s': type [%u] not found.\n",
+ t_name, t->type);
+ return false;
+ }
+ if (!btf_is_array(arr_t)) {
+ pr_warn("attr '%s': expected ARRAY, got %u.\n",
+ t_name, btf_kind(arr_t));
+ return false;
+ }
+ arr_info = btf_array(arr_t);
+ *res = arr_info->nelems;
+ return true;
+}
+
+static bool get_xdp_action(const char *act_name, unsigned int *act)
+{
+ const char **name = xdp_action_names;
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(xdp_action_names); i++, name++) {
+ if (!strcmp(act_name, *name)) {
+ *act = i;
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ * Find BTF func definition for func_name, which may be a truncated prefix of
+ * the real function name.
+ * Return NULL on no, or ambiguous, match.
+ */
+static const struct btf_type *btf_get_function(const struct btf *btf,
+ const char *func_name)
+{
+ const struct btf_type *t, *match;
+ size_t len, matches = 0;
+ const char *name;
+ int nr_types, i;
+
+ if (!btf) {
+ pr_debug("No BTF found for program\n");
+ return NULL;
+ }
+
+ len = strlen(func_name);
+
+ nr_types = btf__type_cnt(btf);
+ for (i = 1; i < nr_types; i++) {
+ t = btf__type_by_id(btf, i);
+ if (!btf_is_func(t))
+ continue;
+
+ name = btf__name_by_offset(btf, t->name_off);
+ if (!strncmp(name, func_name, len)) {
+ pr_debug("Found func %s matching %s\n",
+ name, func_name);
+
+ if (strlen(name) == len)
+ return t; /* exact match */
+
+ /* prefix, may not be unique */
+ matches++;
+ match = t;
+ }
+ }
+
+ if (matches == 1) /* unique match */
+ return match;
+
+ pr_debug("Function '%s' not found or ambiguous (%zu matches).\n",
+ func_name, matches);
+ return NULL;
+}
+
+static const struct btf_type *btf_get_datasec(const struct btf *btf,
+ const char *sec_name)
+{
+ const struct btf_type *t;
+ int nr_types, i;
+ const char *name;
+
+ if (!btf) {
+ pr_debug("No BTF found for program\n");
+ return NULL;
+ }
+
+ nr_types = btf__type_cnt(btf);
+ for (i = 1; i < nr_types; i++) {
+ t = btf__type_by_id(btf, i);
+ if (!btf_is_datasec(t))
+ continue;
+ name = btf__name_by_offset(btf, t->name_off);
+ if (strcmp(name, sec_name) == 0)
+ return t;
+ }
+
+ pr_debug("DATASEC '%s' not found.\n", sec_name);
+ return NULL;
+}
+
+static const struct btf_type *btf_get_section_var(const struct btf *btf,
+ const struct btf_type *sec,
+ const char *var_name,
+ __u16 kind)
+{
+ const struct btf_var_secinfo *vi;
+ const struct btf_var *var_extra;
+ const struct btf_type *var, *def;
+ const char *name;
+ int vlen, i;
+
+ vlen = btf_vlen(sec);
+ vi = btf_var_secinfos(sec);
+ for (i = 0; i < vlen; i++, vi++) {
+ var = btf__type_by_id(btf, vi->type);
+ var_extra = btf_var(var);
+ name = btf__name_by_offset(btf, var->name_off);
+
+ if (strcmp(name, var_name))
+ continue;
+
+ if (!btf_is_var(var)) {
+ pr_warn("struct '%s': unexpected var kind %u.\n",
+ name, btf_kind(var));
+ return ERR_PTR(-EINVAL);
+ }
+ if (var_extra->linkage != BTF_VAR_GLOBAL_ALLOCATED &&
+ var_extra->linkage != BTF_VAR_STATIC) {
+ pr_warn("struct '%s': unsupported var linkage %u.\n",
+ name, var_extra->linkage);
+ return ERR_PTR(-EOPNOTSUPP);
+ }
+
+ def = skip_mods_and_typedefs(btf, var->type, NULL);
+ if (btf_kind(def) != kind) {
+ pr_warn("var '%s': unexpected def kind %u.\n",
+ name, btf_kind(def));
+ return ERR_PTR(-EINVAL);
+ }
+ return def;
+ }
+ return ERR_PTR(-ENOENT);
+}
+
+/**
+ * This function parses the run config information attached to an XDP program.
+ *
+ * This information is specified using BTF, in a format similar to how
+ * BTF-defined maps are done. The definition looks like this:
+ *
+ * struct {
+ * __uint(priority, 10);
+ * __uint(XDP_PASS, 1);
+ * } XDP_RUN_CONFIG(FUNCNAME);
+ *
+ * The priority is simply an integer that will be used to sort programs as they
+ * are attached on the interface (see cmp_xdp_programs() for full sort order).
+ * In addition to the priority, the run config can define an integer value for
+ * each XDP action. A non-zero value means that execution will continue to the
+ * next loaded program if the current program returns that action. I.e., in the
+ * above example, any return value other than XDP_PASS will cause the dispatcher
+ * to exit with that return code, whereas XDP_PASS means execution will
+ * continue.
+ *
+ * Since this information becomes part of the object file BTF info, it will
+ * survive loading into the kernel, and so it can be retrieved for
+ * already-loaded programs as well.
+ */
+static int xdp_program__parse_btf(struct xdp_program *xdp_prog,
+ const struct btf *btf)
+{
+ const struct btf_type *def, *sec;
+ const struct btf_member *m;
+ char struct_name[100];
+ int err, i, mlen;
+
+ if (!btf)
+ btf = xdp_program__btf(xdp_prog);
+
+ /* If the program name is the maximum allowed object name in the kernel,
+ * it may have been truncated, in which case we try to expand it by
+ * looking for a match in the BTF data.
+ */
+ if (strlen(xdp_prog->prog_name) >= BPF_OBJ_NAME_LEN - 1) {
+ const struct btf_type *func;
+ char *name;
+
+ func = btf_get_function(btf, xdp_prog->prog_name);
+ if (func) {
+ name = strdup(btf__name_by_offset(btf, func->name_off));
+ if (!name)
+ return -ENOMEM;
+ free(xdp_prog->prog_name);
+ xdp_prog->prog_name = name;
+ }
+ }
+
+ err = try_snprintf(struct_name, sizeof(struct_name), "_%s",
+ xdp_program__name(xdp_prog));
+ if (err)
+ return err;
+
+ sec = btf_get_datasec(btf, XDP_RUN_CONFIG_SEC);
+ if (!sec)
+ return -ENOENT;
+
+ def = btf_get_section_var(btf, sec, struct_name, BTF_KIND_STRUCT);
+ if (IS_ERR(def)) {
+ pr_debug("Couldn't find run order struct %s\n", struct_name);
+ return PTR_ERR(def);
+ }
+
+ mlen = btf_vlen(def);
+ m = btf_members(def);
+ for (i = 0; i < mlen; i++, m++) {
+ const char *mname = btf__name_by_offset(btf, m->name_off);
+ const struct btf_type *m_t;
+ unsigned int val, act;
+
+ if (!mname) {
+ pr_warn("struct '%s': invalid field #%d.\n", struct_name, i);
+ return -EINVAL;
+ }
+ m_t = skip_mods_and_typedefs(btf, m->type, NULL);
+
+ if (!strcmp(mname, "priority")) {
+ if (!get_field_int(btf, mname, m_t, &xdp_prog->run_prio))
+ return -EINVAL;
+ continue;
+ } else if (get_xdp_action(mname, &act)) {
+ if (!get_field_int(btf, mname, m_t, &val))
+ return -EINVAL;
+ xdp_program__set_chain_call_enabled(xdp_prog, act, val);
+ } else {
+ pr_warn("Invalid mname: %s\n", mname);
+ return -ENOTSUP;
+ }
+ }
+ return 0;
+}
+
+static struct xdp_program *xdp_program__new(void)
+{
+ struct xdp_program *xdp_prog;
+
+ xdp_prog = malloc(sizeof(*xdp_prog));
+ if (!xdp_prog)
+ return ERR_PTR(-ENOMEM);
+
+ memset(xdp_prog, 0, sizeof(*xdp_prog));
+
+ xdp_prog->prog_fd = -1;
+ xdp_prog->link_fd = -1;
+ xdp_prog->run_prio = XDP_DEFAULT_RUN_PRIO;
+ xdp_prog->chain_call_actions = XDP_DEFAULT_CHAIN_CALL_ACTIONS;
+
+ return xdp_prog;
+}
+
+void xdp_program__close(struct xdp_program *xdp_prog)
+{
+ if (!xdp_prog)
+ return;
+
+ if (xdp_prog->link_fd >= 0)
+ close(xdp_prog->link_fd);
+ if (xdp_prog->prog_fd >= 0)
+ close(xdp_prog->prog_fd);
+
+ free(xdp_prog->prog_name);
+ free(xdp_prog->attach_name);
+
+ if (!xdp_prog->from_external_obj) {
+ if (xdp_prog->bpf_obj)
+ bpf_object__close(xdp_prog->bpf_obj);
+ else if (xdp_prog->btf)
+ btf__free(xdp_prog->btf);
+ }
+
+ free(xdp_prog);
+}
+
+static struct xdp_program *xdp_program__create_from_obj(struct bpf_object *obj,
+ const char *section_name,
+ const char *prog_name,
+ bool external)
+{
+ struct xdp_program *xdp_prog;
+ struct bpf_program *bpf_prog;
+ int err;
+
+ if (!obj || (section_name && prog_name))
+ return ERR_PTR(-EINVAL);
+
+ if (section_name)
+ bpf_prog = bpf_program_by_section_name(obj, section_name);
+ else if (prog_name)
+ bpf_prog = bpf_object__find_program_by_name(obj, prog_name);
+ else
+ bpf_prog = bpf_object__next_program(obj, NULL);
+
+ if (!bpf_prog) {
+ pr_warn("Couldn't find xdp program in bpf object%s%s\n",
+ section_name ? " section " : "", section_name ?: "");
+ return ERR_PTR(-ENOENT);
+ }
+
+ xdp_prog = xdp_program__new();
+ if (IS_ERR(xdp_prog))
+ return xdp_prog;
+
+ xdp_prog->prog_name = strdup(bpf_program__name(bpf_prog));
+ if (!xdp_prog->prog_name) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ err = xdp_program__parse_btf(xdp_prog, bpf_object__btf(obj));
+ if (err && err != -ENOENT)
+ goto err;
+
+ xdp_prog->bpf_prog = bpf_prog;
+ xdp_prog->bpf_obj = obj;
+ xdp_prog->btf = bpf_object__btf(obj);
+ xdp_prog->from_external_obj = external;
+
+ return xdp_prog;
+err:
+ xdp_program__close(xdp_prog);
+ return ERR_PTR(err);
+}
+
+struct xdp_program *xdp_program__from_bpf_obj(struct bpf_object *obj,
+ const char *section_name)
+{
+ struct xdp_program *prog;
+
+ prog = xdp_program__create_from_obj(obj, section_name, NULL, true);
+ /* xdp_program__create_from_obj does not return NULL */
+ if (!IS_ERR(prog))
+ return prog;
+ return libxdp_err_ptr(PTR_ERR(prog), false);
+}
+
+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 struct xdp_program *__xdp_program__open_file(const char *filename,
+ const char *section_name,
+ const char *prog_name,
+ struct bpf_object_open_opts *opts)
+{
+ struct xdp_program *xdp_prog;
+ struct bpf_object *obj;
+ int err;
+
+ if (!filename)
+ return ERR_PTR(-EINVAL);
+
+ obj = open_bpf_obj(filename, opts);
+ if (IS_ERR(obj)) {
+ err = PTR_ERR(obj);
+ return ERR_PTR(err);
+ }
+
+ xdp_prog = xdp_program__create_from_obj(obj, section_name, prog_name, false);
+ if (IS_ERR(xdp_prog))
+ bpf_object__close(obj);
+
+ return xdp_prog;
+}
+
+struct xdp_program *xdp_program__open_file(const char *filename,
+ const char *section_name,
+ struct bpf_object_open_opts *opts)
+{
+ struct xdp_program *prog;
+
+ prog = __xdp_program__open_file(filename, section_name, NULL, opts);
+ /* __xdp_program__open_file does not return NULL */
+ if (!IS_ERR(prog))
+ return prog;
+ return libxdp_err_ptr(PTR_ERR(prog), false);
+}
+
+static bool try_bpf_file(char *buf, size_t buf_size, char *path,
+ const char *progname)
+{
+ struct stat sb = {};
+
+ if (try_snprintf(buf, buf_size, "%s/%s", path, progname))
+ return false;
+
+ pr_debug("Looking for '%s'\n", buf);
+ if (stat(buf, &sb))
+ return false;
+
+ return true;
+}
+
+static int find_bpf_file(char *buf, size_t buf_size, const char *progname)
+{
+ static char *bpf_obj_paths[] = {
+#ifdef DEBUG
+ ".",
+#endif
+ BPF_OBJECT_PATH,
+ NULL
+ };
+ char *path, **p;
+
+ path = secure_getenv(XDP_OBJECT_ENVVAR);
+ if (path && try_bpf_file(buf, buf_size, path, progname)) {
+ return 0;
+ } else if (!path) {
+ for (p = bpf_obj_paths; *p; p++)
+ if (try_bpf_file(buf, buf_size, *p, progname))
+ return 0;
+ }
+
+ pr_warn("Couldn't find a BPF file with name %s\n", progname);
+ return -ENOENT;
+}
+
+static struct xdp_program *__xdp_program__find_file(const char *filename,
+ const char *section_name,
+ const char *prog_name,
+ struct bpf_object_open_opts *opts)
+{
+ struct xdp_program *prog;
+ char buf[PATH_MAX];
+ int err;
+
+ prog = xdp_program__find_embedded(filename, section_name, prog_name, opts);
+ if (prog)
+ return prog;
+
+ err = find_bpf_file(buf, sizeof(buf), filename);
+ if (err)
+ return ERR_PTR(err);
+
+ pr_debug("Loading XDP program from '%s' section '%s'\n", buf,
+ section_name ?: (prog_name ?: "(unknown)"));
+ return __xdp_program__open_file(buf, section_name, prog_name, opts);
+}
+
+struct xdp_program *xdp_program__find_file(const char *filename,
+ const char *section_name,
+ struct bpf_object_open_opts *opts)
+{
+ struct xdp_program *prog;
+
+ prog = __xdp_program__find_file(filename, section_name, NULL, opts);
+ /* __xdp_program__find_file does not return NULL */
+ if (!IS_ERR(prog))
+ return prog;
+ return libxdp_err_ptr(PTR_ERR(prog), false);
+}
+
+static int xdp_program__fill_from_fd(struct xdp_program *xdp_prog, int fd)
+{
+ struct bpf_prog_info info = {};
+ __u32 len = sizeof(info);
+ struct btf *btf = NULL;
+ int err = 0, prog_fd;
+
+ if (!xdp_prog)
+ return -EINVAL;
+
+ /* Duplicate the descriptor, as we take ownership of the fd below */
+ prog_fd = fcntl(fd, F_DUPFD_CLOEXEC, MIN_FD);
+ if (prog_fd < 0) {
+ err = -errno;
+ pr_debug("Error on fcntl: %s", strerror(-err));
+ return err;
+ }
+
+ err = bpf_obj_get_info_by_fd(prog_fd, &info, &len);
+ if (err) {
+ err = -errno;
+ pr_warn("couldn't get program info: %s", strerror(-err));
+ goto err;
+ }
+
+ if (!xdp_prog->prog_name) {
+ xdp_prog->prog_name = strdup(info.name);
+ if (!xdp_prog->prog_name) {
+ err = -ENOMEM;
+ pr_warn("failed to strdup program title");
+ goto err;
+ }
+ }
+
+ if (info.btf_id && !xdp_prog->btf) {
+ btf = btf__load_from_kernel_by_id(info.btf_id);
+ if (!btf) {
+ pr_warn("Couldn't get BTF for ID %ul\n", info.btf_id);
+ goto err;
+ }
+ xdp_prog->btf = btf;
+ }
+
+ pr_debug("Duplicated fd %d to %d for prog %s\n", fd, prog_fd, xdp_prog->prog_name);
+ memcpy(xdp_prog->prog_tag, info.tag, BPF_TAG_SIZE);
+ xdp_prog->load_time = info.load_time;
+ xdp_prog->prog_fd = prog_fd;
+ xdp_prog->prog_id = info.id;
+ xdp_prog->prog_type = info.type;
+
+ return 0;
+err:
+ close(prog_fd);
+ btf__free(btf);
+ return err;
+}
+
+struct xdp_program *xdp_program__from_fd(int fd)
+{
+ struct xdp_program *xdp_prog = NULL;
+ int err;
+
+ xdp_prog = xdp_program__new();
+ if (IS_ERR(xdp_prog))
+ return libxdp_err_ptr(PTR_ERR(xdp_prog), false);
+
+ err = xdp_program__fill_from_fd(xdp_prog, fd);
+ if (err)
+ goto err;
+
+ err = xdp_program__parse_btf(xdp_prog, NULL);
+ if (err && err != -ENOENT)
+ goto err;
+
+ return xdp_prog;
+err:
+ xdp_program__close(xdp_prog);
+ return libxdp_err_ptr(err, false);
+}
+
+struct xdp_program *xdp_program__from_id(__u32 id)
+{
+ struct xdp_program *prog;
+ int fd, err;
+
+ fd = bpf_prog_get_fd_by_id(id);
+ if (fd < 0) {
+ err = -errno;
+ pr_warn("couldn't get program fd: %s", strerror(-err));
+ return libxdp_err_ptr(err, false);
+ }
+
+ prog = xdp_program__from_fd(fd);
+ if (IS_ERR(prog)) {
+ err = errno;
+ close(fd);
+ errno = err;
+ }
+ return prog;
+}
+
+struct xdp_program *xdp_program__from_pin(const char *pin_path)
+{
+ struct xdp_program *prog;
+ int fd, err;
+
+ fd = bpf_obj_get(pin_path);
+ if (fd < 0) {
+ err = -errno;
+ pr_warn("couldn't get program fd from %s: %s",
+ pin_path, strerror(-err));
+ return libxdp_err_ptr(err, false);
+ }
+
+ prog = xdp_program__from_fd(fd);
+ if (IS_ERR(prog)) {
+ err = errno;
+ close(fd);
+ errno = err;
+ }
+ return prog;
+}
+
+struct xdp_program *xdp_program__create(struct xdp_program_opts *opts)
+{
+ const char *pin_path, *prog_name, *find_filename, *open_filename;
+ struct bpf_object_open_opts *obj_opts;
+ struct xdp_program *prog;
+ struct bpf_object *obj;
+ __u32 id;
+ int fd;
+
+ if (!opts || !OPTS_VALID(opts, xdp_program_opts))
+ goto err;
+
+ obj = OPTS_GET(opts, obj, NULL);
+ obj_opts = OPTS_GET(opts, opts, NULL);
+ prog_name = OPTS_GET(opts, prog_name, NULL);
+ find_filename = OPTS_GET(opts, find_filename, NULL);
+ open_filename = OPTS_GET(opts, open_filename, NULL);
+ pin_path = OPTS_GET(opts, pin_path, NULL);
+ id = OPTS_GET(opts, id, 0);
+ fd = OPTS_GET(opts, fd, 0);
+
+ if (obj) { /* prog_name is optional */
+ if (obj_opts || find_filename || open_filename || pin_path || id || fd)
+ goto err;
+ prog = xdp_program__create_from_obj(obj, NULL, prog_name, true);
+ } else if (find_filename) { /* prog_name, obj_opts is optional */
+ if (obj || open_filename || pin_path || id || fd)
+ goto err;
+ prog = __xdp_program__find_file(find_filename, NULL, prog_name, obj_opts);
+ } else if (open_filename) { /* prog_name, obj_opts is optional */
+ if (obj || find_filename || pin_path || id || fd)
+ goto err;
+ prog = __xdp_program__open_file(open_filename, NULL, prog_name, obj_opts);
+ } else if (pin_path) {
+ if (obj || obj_opts || prog_name || find_filename || open_filename || id || fd)
+ goto err;
+ prog = xdp_program__from_pin(pin_path);
+ } else if (id) {
+ if (obj || obj_opts || prog_name || find_filename || open_filename || pin_path || fd)
+ goto err;
+ prog = xdp_program__from_id(id);
+ } else if (fd) {
+ if (obj || obj_opts || prog_name || find_filename || open_filename || pin_path || id)
+ goto err;
+ prog = xdp_program__from_fd(fd);
+ } else {
+ goto err;
+ }
+ if (IS_ERR(prog))
+ return libxdp_err_ptr(PTR_ERR(prog), true);
+ return prog;
+err:
+ return libxdp_err_ptr(-EINVAL, true);
+}
+
+static int cmp_xdp_programs(const void *_a, const void *_b)
+{
+ const struct xdp_program *a = *(struct xdp_program * const *)_a;
+ const struct xdp_program *b = *(struct xdp_program * const *)_b;
+ int cmp;
+
+ if (a->run_prio != b->run_prio)
+ return a->run_prio < b->run_prio ? -1 : 1;
+
+ cmp = strcmp(a->prog_name, b->prog_name);
+ if (cmp)
+ return cmp;
+
+ /* Hopefully the two checks above will resolve most comparisons; in
+ * cases where they don't, hopefully the checks below will keep the
+ * order stable.
+ */
+
+ /* loaded before non-loaded */
+ if (a->prog_fd >= 0 && b->prog_fd < 0)
+ return -1;
+ else if (a->prog_fd < 0 && b->prog_fd >= 0)
+ return 1;
+
+ /* two unloaded programs - compare by size */
+ if (a->bpf_prog && b->bpf_prog) {
+ size_t size_a, size_b;
+
+ size_a = bpf_program__insn_cnt(a->bpf_prog);
+ size_b = bpf_program__insn_cnt(b->bpf_prog);
+ if (size_a != size_b)
+ return size_a < size_b ? -1 : 1;
+ }
+
+ cmp = memcmp(a->prog_tag, b->prog_tag, BPF_TAG_SIZE);
+ if (cmp)
+ return cmp;
+
+ /* at this point we are really grasping for straws */
+ if (a->load_time != b->load_time)
+ return a->load_time < b->load_time ? -1 : 1;
+
+ return 0;
+}
+
+int xdp_program__pin(struct xdp_program *prog, const char *pin_path)
+{
+ if (IS_ERR_OR_NULL(prog) || prog->prog_fd < 0)
+ return libxdp_err(-EINVAL);
+
+ return libxdp_err(bpf_program__pin(prog->bpf_prog, pin_path));
+}
+
+static int xdp_program__load(struct xdp_program *prog)
+{
+ bool is_loaded, autoload;
+ int err;
+
+ if (IS_ERR_OR_NULL(prog))
+ return -EINVAL;
+
+ if (prog->prog_fd >= 0)
+ return -EEXIST;
+
+ if (!prog->bpf_obj || !prog->bpf_prog)
+ return -EINVAL;
+
+ /* bpf_program__set_autoload fails if the object is loaded, use this to
+ * detect if it is (since libbpf doesn't expose an API to discover
+ * this). This is necessary because of objects containing multiple
+ * programs: if a user creates xdp_program references to programs in
+ * such an object before loading it, they will get out of sync.
+ */
+ autoload = bpf_program__autoload(prog->bpf_prog);
+ is_loaded = !!bpf_program__set_autoload(prog->bpf_prog, autoload);
+ if (is_loaded) {
+ pr_debug("XDP program %s is already loaded with fd %d\n",
+ xdp_program__name(prog), bpf_program__fd(prog->bpf_prog));
+
+ prog->is_frags = !!(bpf_program__flags(prog->bpf_prog) & BPF_F_XDP_HAS_FRAGS);
+ } else {
+ /* We got an explicit load request, make sure we actually load */
+ if (!autoload)
+ bpf_program__set_autoload(prog->bpf_prog, true);
+
+ /* Make sure we sync is_frags to internal state variable (in case it was
+ * changed on bpf_prog since creation), and unset flag if we're loading
+ * an EXT program (the dispatcher will have the flag set instead in this
+ * case)
+ */
+ prog->is_frags = xdp_program__xdp_frags_support(prog);
+
+ if (bpf_program__type(prog->bpf_prog) == BPF_PROG_TYPE_EXT)
+ bpf_program__set_flags(prog->bpf_prog,
+ bpf_program__flags(prog->bpf_prog) & ~BPF_F_XDP_HAS_FRAGS);
+
+ err = bpf_object__load(prog->bpf_obj);
+ if (err)
+ return err;
+
+ pr_debug("Loaded XDP program %s, got fd %d\n",
+ xdp_program__name(prog), bpf_program__fd(prog->bpf_prog));
+ }
+
+ /* xdp_program__fill_from_fd() clones the fd and takes ownership of the clone */
+ return xdp_program__fill_from_fd(prog, bpf_program__fd(prog->bpf_prog));
+}
+
+struct xdp_program *xdp_program__clone(struct xdp_program *prog, unsigned int flags)
+{
+ if (IS_ERR_OR_NULL(prog) || flags || (prog->prog_fd < 0 && !prog->bpf_obj))
+ return libxdp_err_ptr(-EINVAL, false);
+
+ if (prog->prog_fd >= 0)
+ /* Clone a loaded program struct by creating a new object from the
+ program fd; xdp_program__fill_from_fd() already duplicates the fd
+ before filling in the object, so this creates a completely
+ independent xdp_program object.
+ */
+ return xdp_program__from_fd(prog->prog_fd);
+
+ return xdp_program__create_from_obj(prog->bpf_obj, NULL,
+ prog->prog_name, true);
+}
+
+
+static int xdp_program__attach_single(struct xdp_program *prog, int ifindex,
+ enum xdp_attach_mode mode)
+{
+ int err;
+
+ if (prog->prog_fd < 0) {
+ bpf_program__set_type(prog->bpf_prog, BPF_PROG_TYPE_XDP);
+ err = xdp_program__load(prog);
+ if (err)
+ return err;
+ }
+
+ if (prog->prog_fd < 0)
+ return -EINVAL;
+
+ return xdp_attach_fd(xdp_program__fd(prog), -1, ifindex, mode);
+}
+
+
+static int xdp_multiprog__main_fd(struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp))
+ return -EINVAL;
+
+ if (!mp->main_prog)
+ return -ENOENT;
+
+ return mp->main_prog->prog_fd;
+}
+
+static __u32 xdp_multiprog__main_id(struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp) || !mp->main_prog)
+ return 0;
+
+ return mp->main_prog->prog_id;
+}
+
+static int xdp_multiprog__hw_fd(struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp))
+ return -EINVAL;
+
+ if (!mp->hw_prog)
+ return -ENOENT;
+
+ return mp->hw_prog->prog_fd;
+}
+
+static __u32 xdp_multiprog__hw_id(struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp) || !mp->hw_prog)
+ return 0;
+
+ return mp->hw_prog->prog_id;
+}
+
+static int xdp_program__attach_hw(struct xdp_program *prog, int ifindex)
+{
+ struct bpf_map *map;
+
+ bpf_program__set_ifindex(prog->bpf_prog, ifindex);
+ bpf_object__for_each_map (map, prog->bpf_obj) {
+ bpf_map__set_ifindex(map, ifindex);
+ }
+
+ return xdp_program__attach_single(prog, ifindex, XDP_MODE_HW);
+}
+
+static int xdp_multiprog__detach_hw(struct xdp_multiprog *old_mp)
+{
+ int err = 0, hw_fd = -1, ifindex = -1;
+
+ if (!old_mp)
+ return -EINVAL;
+
+ ifindex = old_mp->ifindex;
+
+ hw_fd = xdp_multiprog__hw_fd(old_mp);
+ if (hw_fd < 0)
+ return -EINVAL;
+
+ err = xdp_attach_fd(-1, hw_fd, ifindex, XDP_MODE_HW);
+ if (err < 0)
+ return err;
+
+ pr_debug("Detached hw program on ifindex '%d'\n", ifindex);
+
+ return 0;
+}
+
+int xdp_program__attach_multi(struct xdp_program **progs, size_t num_progs,
+ int ifindex, enum xdp_attach_mode mode,
+ unsigned int flags)
+{
+ struct xdp_multiprog *old_mp = NULL, *mp;
+ int err = 0, retry_counter = 0;
+
+ if (!progs || !num_progs || flags)
+ return libxdp_err(-EINVAL);
+
+retry:
+ old_mp = xdp_multiprog__get_from_ifindex(ifindex);
+ if (IS_ERR_OR_NULL(old_mp))
+ old_mp = NULL;
+
+ if (mode == XDP_MODE_HW) {
+ bool old_hw_prog = xdp_multiprog__hw_prog(old_mp) != NULL;
+
+ xdp_multiprog__close(old_mp);
+
+ if (old_hw_prog) {
+ pr_warn("XDP program already loaded in HW mode on ifindex %d; "
+ "replacing HW mode programs not supported\n", ifindex);
+ return libxdp_err(-EEXIST);
+ }
+
+ if (num_progs > 1)
+ return libxdp_err(-EINVAL);
+
+ return libxdp_err(xdp_program__attach_hw(progs[0], ifindex));
+ }
+
+ if (num_progs == 1) {
+ char *envval;
+
+ envval = secure_getenv(XDP_SKIP_ENVVAR);
+ if (envval && envval[0] == '1' && envval[1] == '\0') {
+ pr_debug("Skipping dispatcher due to environment setting\n");
+ return libxdp_err(xdp_program__attach_single(progs[0], ifindex, mode));
+ }
+ }
+
+ mp = xdp_multiprog__generate(progs, num_progs, ifindex, old_mp, false);
+ if (IS_ERR(mp)) {
+ err = PTR_ERR(mp);
+ mp = NULL;
+ if (err == -EOPNOTSUPP) {
+ if (num_progs == 1) {
+ pr_info("Falling back to loading single prog "
+ "without dispatcher\n");
+ return libxdp_err(xdp_program__attach_single(progs[0], ifindex, mode));
+ } else {
+ pr_warn("Can't fall back to legacy load with %zu "
+ "programs\n%s\n", num_progs, dispatcher_feature_err);
+ }
+ }
+ goto out;
+ }
+
+ err = xdp_multiprog__pin(mp);
+ if (err) {
+ pr_warn("Failed to pin program: %s\n", strerror(-err));
+ goto out_close;
+ }
+
+ err = xdp_multiprog__attach(old_mp, mp, mode);
+ if (err) {
+ pr_debug("Failed to attach dispatcher on ifindex %d: %s\n",
+ ifindex, strerror(-err));
+ xdp_multiprog__unpin(mp);
+
+ if (err == -EAGAIN) {
+ if (++retry_counter > MAX_RETRY) {
+ pr_warn("Retried more than %d times, giving up\n",
+ retry_counter);
+ err = -EBUSY;
+ goto out_close;
+ }
+
+ pr_debug("Existing dispatcher replaced while building replacement, retrying.\n");
+ xdp_multiprog__close(old_mp);
+ xdp_multiprog__close(mp);
+ usleep(1 << retry_counter); /* exponential backoff */
+ goto retry;
+ }
+ goto out_close;
+ }
+
+ if (old_mp) {
+ err = xdp_multiprog__unpin(old_mp);
+ if (err) {
+ pr_warn("Failed to unpin old dispatcher: %s\n",
+ strerror(-err));
+ err = 0;
+ }
+ }
+
+out_close:
+ xdp_multiprog__close(mp);
+out:
+ if (old_mp)
+ xdp_multiprog__close(old_mp);
+ return libxdp_err(err);
+}
+
+int xdp_program__attach(struct xdp_program *prog, int ifindex,
+ enum xdp_attach_mode mode,
+ unsigned int flags)
+{
+ if (IS_ERR_OR_NULL(prog) || IS_ERR(prog))
+ return libxdp_err(-EINVAL);
+
+ return libxdp_err(xdp_program__attach_multi(&prog, 1, ifindex, mode, flags));
+}
+
+int xdp_program__detach_multi(struct xdp_program **progs, size_t num_progs,
+ int ifindex, enum xdp_attach_mode mode,
+ unsigned int flags)
+{
+ struct xdp_multiprog *new_mp, *mp;
+ int err = 0, retry_counter = 0;
+ size_t i;
+
+ if (flags || !num_progs || !progs)
+ return libxdp_err(-EINVAL);
+
+ retry:
+ new_mp = NULL;
+ mp = xdp_multiprog__get_from_ifindex(ifindex);
+ if (IS_ERR_OR_NULL(mp)) {
+ pr_warn("No XDP dispatcher found on ifindex %d\n", ifindex);
+ return libxdp_err(-ENOENT);
+ }
+
+ if (mode == XDP_MODE_HW || xdp_multiprog__is_legacy(mp)) {
+ __u32 id = (mode == XDP_MODE_HW) ?
+ xdp_multiprog__hw_id(mp) :
+ xdp_multiprog__main_id(mp);
+
+ if (num_progs > 1) {
+ pr_warn("Can only detach one program in legacy or HW mode\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (!xdp_program__id(progs[0])) {
+ pr_warn("Program 0 not loaded\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ if (id != xdp_program__id(progs[0])) {
+ pr_warn("Asked to unload prog %u but %u is loaded\n",
+ xdp_program__id(progs[0]), id);
+ err = -ENOENT;
+ goto out;
+ }
+ }
+
+ if (mode == XDP_MODE_HW) {
+ err = xdp_multiprog__detach_hw(mp);
+ goto out;
+ }
+
+ if (mode != XDP_MODE_UNSPEC && mp->attach_mode != mode) {
+ pr_warn("XDP dispatcher attached in mode %d, requested %d\n",
+ mp->attach_mode, mode);
+ err = -ENOENT;
+ goto out;
+ }
+
+ if (xdp_multiprog__is_legacy(mp)) {
+ err = xdp_multiprog__attach(mp, NULL, mode);
+ goto out;
+ }
+
+ /* fist pass - check progs and count number still loaded */
+ for (i = 0; i < num_progs; i++) {
+ struct xdp_program *p = NULL;
+ bool found = false;
+
+ if (!progs[i]->prog_id) {
+ pr_warn("Program %zu not loaded\n", i);
+ err = -EINVAL;
+ goto out;
+ }
+
+ while ((p = xdp_multiprog__next_prog(p, mp))) {
+ if (progs[i]->prog_id == p->prog_id)
+ found = true;
+ }
+
+ if (!found) {
+ pr_warn("Couldn't find program with id %d on ifindex %d\n",
+ progs[i]->prog_id, ifindex);
+ err = -ENOENT;
+ goto out;
+ }
+ }
+
+ if (num_progs == mp->num_links) {
+ err = xdp_multiprog__attach(mp, NULL, mp->attach_mode);
+ if (err)
+ goto out;
+
+ err = xdp_multiprog__unpin(mp);
+ if (err)
+ goto out;
+ } else {
+ new_mp = xdp_multiprog__generate(progs, num_progs, ifindex, mp, true);
+ if (IS_ERR(new_mp)) {
+ err = PTR_ERR(new_mp);
+ if (err == -EOPNOTSUPP) {
+ pr_warn("Asked to detach %zu progs, but %zu loaded on ifindex %d, "
+ "and partial detach is not supported by the kernel.\n",
+ num_progs, mp->num_links, ifindex);
+ }
+ goto out;
+ }
+ err = xdp_multiprog__pin(new_mp);
+ if (err) {
+ pr_warn("Failed to pin program: %s\n", strerror(-err));
+ goto out;
+ }
+
+ err = xdp_multiprog__attach(mp, new_mp, mode);
+ if (err) {
+ pr_debug("Failed to attach dispatcher on ifindex %d: %s\n",
+ ifindex, strerror(-err));
+ xdp_multiprog__unpin(new_mp);
+ goto out;
+ }
+
+ err = xdp_multiprog__unpin(mp);
+ if (err) {
+ pr_warn("Failed to unpin old dispatcher: %s\n",
+ strerror(-err));
+ err = 0;
+ }
+ }
+
+out:
+ xdp_multiprog__close(mp);
+ xdp_multiprog__close(new_mp);
+ if (err == -EAGAIN) {
+ if (++retry_counter > MAX_RETRY) {
+ pr_warn("Retried more than %d times, giving up\n",
+ retry_counter);
+ return libxdp_err(-EBUSY);
+ }
+
+ pr_debug("Existing dispatcher replaced while building replacement, retrying.\n");
+ usleep(1 << retry_counter); /* exponential backoff */
+ goto retry;
+ }
+ return libxdp_err(err);
+}
+
+int xdp_program__detach(struct xdp_program *prog, int ifindex,
+ enum xdp_attach_mode mode,
+ unsigned int flags)
+{
+ if (IS_ERR_OR_NULL(prog) || IS_ERR(prog))
+ return -EINVAL;
+
+ return libxdp_err(xdp_program__detach_multi(&prog, 1, ifindex, mode, flags));
+}
+
+int xdp_program__test_run(struct xdp_program *prog, struct bpf_test_run_opts *opts, unsigned int flags)
+{
+ struct xdp_multiprog *mp = NULL;
+ int err, prog_fd;
+
+ if (IS_ERR_OR_NULL(prog) || flags)
+ return libxdp_err(-EINVAL);
+
+ if (prog->prog_fd < 0) {
+ err = xdp_program__load(prog);
+ if (err)
+ return libxdp_err(err);
+ }
+
+ if (prog->prog_type == BPF_PROG_TYPE_EXT) {
+ mp = xdp_multiprog__generate(&prog, 1, 0, NULL, false);
+ if (IS_ERR(mp)) {
+ err = PTR_ERR(mp);
+ if (err == -EOPNOTSUPP)
+ pr_warn("Program was already attached to a dispatcher, "
+ "and kernel doesn't support multiple attachments\n");
+ return libxdp_err(err);
+ }
+
+ prog_fd = xdp_multiprog__main_fd(mp);
+ } else if (prog->prog_type != BPF_PROG_TYPE_XDP) {
+ pr_warn("Can't test_run non-XDP programs\n");
+ return libxdp_err(-ENOEXEC);
+ } else {
+ prog_fd = prog->prog_fd;
+ }
+
+ err = bpf_prog_test_run_opts(prog_fd, opts);
+ if (err)
+ err = -errno;
+
+ if (mp)
+ xdp_multiprog__close(mp);
+
+ return libxdp_err(err);
+}
+
+static int xdp_multiprog__check_kernel_frags_support(struct xdp_multiprog *mp)
+{
+ struct xdp_program *test_prog;
+ int err;
+
+ pr_debug("Checking for kernel frags support\n");
+ test_prog = __xdp_program__find_file("xdp-dispatcher.o", NULL, "xdp_pass", NULL);
+ if (IS_ERR(test_prog)) {
+ err = PTR_ERR(test_prog);
+ pr_warn("Couldn't open BPF file xdp-dispatcher.o\n");
+ return err;
+ }
+
+ bpf_program__set_flags(test_prog->bpf_prog, BPF_F_XDP_HAS_FRAGS);
+ err = xdp_program__load(test_prog);
+ if (!err) {
+ pr_debug("Kernel supports XDP programs with frags\n");
+ mp->kernel_frags_support = true;
+ } else {
+ pr_debug("Kernel DOES NOT support XDP programs with frags\n");
+ }
+ xdp_program__close(test_prog);
+
+ return 0;
+}
+
+void xdp_multiprog__close(struct xdp_multiprog *mp)
+{
+ struct xdp_program *p, *next = NULL;
+
+ if (IS_ERR_OR_NULL(mp))
+ return;
+
+ xdp_program__close(mp->main_prog);
+ for (p = mp->first_prog; p; p = next) {
+ next = p->next;
+ xdp_program__close(p);
+ }
+ xdp_program__close(mp->hw_prog);
+
+ free(mp);
+}
+
+static struct xdp_multiprog *xdp_multiprog__new(int ifindex)
+{
+ struct xdp_multiprog *mp;
+
+ mp = malloc(sizeof *mp);
+ if (!mp)
+ return ERR_PTR(-ENOMEM);
+ memset(mp, 0, sizeof(*mp));
+ mp->ifindex = ifindex;
+ mp->version = XDP_DISPATCHER_VERSION;
+
+ return mp;
+}
+
+static int xdp_multiprog__load(struct xdp_multiprog *mp)
+{
+ char buf[100];
+ int err = 0;
+
+ if (IS_ERR_OR_NULL(mp) || !mp->main_prog || mp->is_loaded || xdp_multiprog__is_legacy(mp))
+ return -EINVAL;
+
+ pr_debug("Loading multiprog dispatcher for %d programs %s frags support\n",
+ mp->config.num_progs_enabled,
+ mp->config.is_xdp_frags ? "with" : "without");
+
+ if (mp->config.is_xdp_frags)
+ xdp_program__set_xdp_frags_support(mp->main_prog, true);
+
+ err = xdp_program__load(mp->main_prog);
+ if (err) {
+ pr_info("Failed to load dispatcher: %s\n",
+ libxdp_strerror_r(err, buf, sizeof(buf)));
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+ mp->is_loaded = true;
+out:
+ return err;
+}
+
+int check_xdp_prog_version(const struct btf *btf, const char *name, __u32 *version)
+{
+ const struct btf_type *sec, *def;
+
+ sec = btf_get_datasec(btf, XDP_METADATA_SECTION);
+ if (!sec)
+ return libxdp_err(-ENOENT);
+
+ def = btf_get_section_var(btf, sec, name, BTF_KIND_PTR);
+ if (IS_ERR(def))
+ return libxdp_err(PTR_ERR(def));
+
+ if (!get_field_int(btf, name, def, version))
+ return libxdp_err(-ENOENT);
+
+ return 0;
+}
+
+static int check_dispatcher_version(struct xdp_multiprog *mp,
+ const char *prog_name, const struct btf *btf,
+ __u32 nr_maps, __u32 map_id)
+{
+ __u32 version = 0, map_key = 0, info_len = sizeof(struct bpf_map_info);
+ const char *name = "dispatcher_version";
+ struct bpf_map_info map_info = {};
+ int err, map_fd, i;
+ __u8 *buf = NULL;
+
+ if (prog_name && strcmp(prog_name, "xdp_dispatcher")) {
+ pr_debug("XDP program with name '%s' is not a dispatcher\n", prog_name);
+ return -ENOENT;
+ }
+
+ if (nr_maps != 1) {
+ pr_warn("Expected a single map for dispatcher, found %u\n", nr_maps);
+ return -ENOENT;
+ }
+
+ map_fd = bpf_map_get_fd_by_id(map_id);
+ if (map_fd < 0) {
+ err = -errno;
+ pr_warn("Could not get config map fd for id %u: %s\n", map_id, strerror(-err));
+ return err;
+ }
+
+ err = bpf_obj_get_info_by_fd(map_fd, &map_info, &info_len);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't get map info: %s\n", strerror(-err));
+ goto out;
+ }
+
+ if (map_info.key_size != sizeof(map_key) ||
+ map_info.value_size < 2 ||
+ map_info.max_entries != 1 ||
+ !(map_info.map_flags & BPF_F_RDONLY_PROG)) {
+ pr_warn("Map flags or key/value size mismatch\n");
+ err = -EINVAL;
+ goto out;
+ }
+
+ buf = malloc(map_info.value_size);
+ if (!buf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = bpf_map_lookup_elem(map_fd, &map_key, buf);
+ if (err) {
+ err = -errno;
+ pr_warn("Could not lookup map value: %s\n", strerror(-err));
+ goto out;
+ }
+
+ if (buf[0] == XDP_DISPATCHER_MAGIC) {
+ version = buf[1];
+ } else {
+ err = check_xdp_prog_version(btf, name, &version);
+ if (err)
+ goto out;
+ }
+
+ switch (version) {
+ case XDP_DISPATCHER_VERSION_V1:
+ {
+ struct xdp_dispatcher_config_v1 *config = (void *)buf;
+
+ for (i = 0; i < MAX_DISPATCHER_ACTIONS; i++) {
+ mp->config.chain_call_actions[i] = config->chain_call_actions[i];
+ mp->config.run_prios[i] = config->run_prios[i];
+ }
+ mp->config.num_progs_enabled = config->num_progs_enabled;
+ break;
+ }
+ case XDP_DISPATCHER_VERSION:
+ if (map_info.value_size != sizeof(mp->config)) {
+ pr_warn("Dispatcher version matches, but map size %u != expected %zu\n",
+ map_info.value_size, sizeof(mp->config));
+ err = -EINVAL;
+ goto out;
+ }
+ memcpy(&mp->config, buf, sizeof(mp->config));
+ break;
+
+ default:
+ pr_warn("XDP dispatcher version %u higher than supported %u\n",
+ version, XDP_DISPATCHER_VERSION);
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+ pr_debug("Verified XDP dispatcher version %d <= %d\n",
+ version, XDP_DISPATCHER_VERSION);
+
+ mp->version = version;
+
+out:
+ close(map_fd);
+ free(buf);
+ return err;
+}
+
+static int xdp_multiprog__link_pinned_progs(struct xdp_multiprog *mp)
+{
+ char buf[PATH_MAX], pin_path[PATH_MAX];
+ struct xdp_program *prog, *p = NULL;
+ const char *bpffs_dir;
+ int err, lock_fd, i;
+ struct stat sb = {};
+
+ if (IS_ERR_OR_NULL(mp) || mp->first_prog)
+ return -EINVAL;
+
+ bpffs_dir = get_bpffs_dir();
+ if (IS_ERR(bpffs_dir))
+ return PTR_ERR(bpffs_dir);
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/dispatch-%d-%d",
+ bpffs_dir, mp->ifindex, mp->main_prog->prog_id);
+ if (err)
+ return err;
+
+ lock_fd = xdp_lock_acquire();
+ if (lock_fd < 0)
+ return lock_fd;
+
+ pr_debug("Reading multiprog component programs from pinned directory\n");
+ err = stat(pin_path, &sb);
+ if (err) {
+ err = -errno;
+ pr_debug("Couldn't stat pin_path '%s': %s\n",
+ pin_path, strerror(-err));
+ goto out;
+ }
+
+ for (i = 0; i < mp->config.num_progs_enabled; i++) {
+
+ err = try_snprintf(buf, sizeof(buf), "%s/prog%d-prog",
+ pin_path, i);
+ if (err)
+ goto err;
+
+ prog = xdp_program__from_pin(buf);
+ if (IS_ERR(prog)) {
+ err = PTR_ERR(prog);
+ goto err;
+ }
+ err = try_snprintf(buf, sizeof(buf), "prog%d", i);
+ if (err)
+ goto err;
+ prog->attach_name = strdup(buf);
+ if (!prog->attach_name) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ prog->chain_call_actions = (mp->config.chain_call_actions[i] &
+ ~(1U << XDP_DISPATCHER_RETVAL));
+ prog->run_prio = mp->config.run_prios[i];
+ prog->is_frags = !!(mp->config.program_flags[i] & BPF_F_XDP_HAS_FRAGS);
+
+ if (!p) {
+ mp->first_prog = prog;
+ p = mp->first_prog;
+ } else {
+ p->next = prog;
+ p = prog;
+ }
+ mp->num_links++;
+ }
+
+out:
+ xdp_lock_release(lock_fd);
+ return err;
+err:
+ prog = mp->first_prog;
+ while (prog) {
+ p = prog->next;
+ xdp_program__close(prog);
+ prog = p;
+ }
+ mp->first_prog = NULL;
+ goto out;
+}
+
+static int xdp_multiprog__fill_from_fd(struct xdp_multiprog *mp,
+ int prog_fd, int hw_fd)
+{
+ struct bpf_prog_info info = {};
+ __u32 info_len, map_id = 0;
+ struct xdp_program *prog;
+ struct btf *btf = NULL;
+ int err = 0;
+
+ if (IS_ERR_OR_NULL(mp))
+ return -EINVAL;
+
+ if (prog_fd > 0) {
+ info.nr_map_ids = 1;
+ info.map_ids = (uintptr_t)&map_id;
+ info_len = sizeof(info);
+ err = bpf_obj_get_info_by_fd(prog_fd, &info, &info_len);
+ if (err) {
+ pr_warn("couldn't get program info for fd: %d", prog_fd);
+ return -EINVAL;
+ }
+
+ if (!info.btf_id) {
+ pr_debug("No BTF for prog ID %u\n", info.id);
+ mp->is_legacy = true;
+ goto legacy;
+ }
+
+ btf = btf__load_from_kernel_by_id(info.btf_id);
+ if (!btf) {
+ pr_warn("Couldn't get BTF for ID %ul\n", info.btf_id);
+ goto out;
+ }
+
+ err = check_dispatcher_version(mp, info.name, btf,
+ info.nr_map_ids, map_id);
+ if (err) {
+ if (err != -ENOENT) {
+ pr_warn("Dispatcher version check failed for ID %d\n",
+ info.id);
+ goto out;
+ } else {
+ /* no dispatcher, mark as legacy prog */
+ mp->is_legacy = true;
+ err = 0;
+ goto legacy;
+ }
+ }
+
+legacy:
+ prog = xdp_program__from_fd(prog_fd);
+ if (IS_ERR(prog)) {
+ err = PTR_ERR(prog);
+ goto out;
+ }
+
+ mp->main_prog = prog;
+
+ if (!xdp_multiprog__is_legacy(mp)) {
+ err = xdp_multiprog__link_pinned_progs(mp);
+ if (err) {
+ pr_warn("Unable to read pinned progs: %s\n", strerror(-err));
+ mp->is_legacy = true;
+ err = 0;
+ }
+ }
+
+ pr_debug("Found %s with id %d and %zu component progs\n",
+ xdp_multiprog__is_legacy(mp) ? "legacy program" : "multiprog",
+ mp->main_prog->prog_id, mp->num_links);
+ }
+
+ if (hw_fd > 0) {
+ prog = xdp_program__from_fd(hw_fd);
+ if (IS_ERR(prog)) {
+ err = PTR_ERR(prog);
+ goto out;
+ }
+
+ if (mp->first_prog == NULL)
+ mp->is_legacy = true;
+
+ mp->hw_prog = prog;
+
+ pr_debug("Found hw program with id %d\n", mp->hw_prog->prog_id);
+ }
+
+ mp->is_loaded = true;
+
+out:
+ btf__free(btf);
+ return err;
+}
+
+static struct xdp_multiprog *xdp_multiprog__from_fd(int fd, int hw_fd,
+ int ifindex)
+{
+ struct xdp_multiprog *mp = NULL;
+ int err;
+
+ mp = xdp_multiprog__new(ifindex);
+ if (IS_ERR(mp))
+ return mp;
+
+ err = xdp_multiprog__fill_from_fd(mp, fd, hw_fd);
+ if (err)
+ goto err;
+
+ return mp;
+err:
+ xdp_multiprog__close(mp);
+ return ERR_PTR(err);
+}
+
+
+static struct xdp_multiprog *xdp_multiprog__from_id(__u32 id, __u32 hw_id,
+ int ifindex)
+{
+ struct xdp_multiprog *mp;
+ int hw_fd = 0;
+ int fd = 0;
+ int err;
+
+ if (id) {
+ fd = bpf_prog_get_fd_by_id(id);
+ if (fd < 0) {
+ err = -errno;
+ pr_warn("couldn't get program fd: %s", strerror(-err));
+ goto err;
+ }
+ }
+
+ if (hw_id) {
+ hw_fd = bpf_prog_get_fd_by_id(hw_id);
+ if (hw_fd < 0) {
+ err = -errno;
+ pr_warn("couldn't get program fd: %s", strerror(-err));
+ goto err;
+ }
+ }
+
+ mp = xdp_multiprog__from_fd(fd, hw_fd, ifindex);
+ if (IS_ERR(mp)) {
+ err = PTR_ERR(mp);
+ goto err;
+ }
+ return mp;
+err:
+ if (fd > 0)
+ close(fd);
+ if (hw_fd > 0)
+ close(hw_fd);
+ return ERR_PTR(err);
+}
+
+static int xdp_get_ifindex_prog_id(int ifindex, __u32 *prog_id,
+ __u32 *hw_prog_id, enum xdp_attach_mode *mode)
+{
+ __u32 _prog_id, _drv_prog_id, _hw_prog_id, _skb_prog_id;
+ enum xdp_attach_mode _mode;
+ __u8 _attach_mode;
+
+ if (!hw_prog_id)
+ hw_prog_id = &_prog_id;
+ if (!mode)
+ mode = &_mode;
+ int err;
+#ifdef HAVE_LIBBPF_BPF_XDP_ATTACH
+ LIBBPF_OPTS(bpf_xdp_query_opts, opts);
+ err = bpf_xdp_query(ifindex, 0, &opts);
+ if (err)
+ return err;
+
+ _drv_prog_id = opts.drv_prog_id;
+ _skb_prog_id = opts.skb_prog_id;
+ _hw_prog_id = opts.hw_prog_id;
+ _attach_mode = opts.attach_mode;
+#else
+ struct xdp_link_info xinfo = {};
+ err = bpf_get_link_xdp_info(ifindex, &xinfo, sizeof(xinfo), 0);
+ if (err)
+ return err;
+
+ _drv_prog_id = xinfo.drv_prog_id;
+ _skb_prog_id = xinfo.skb_prog_id;
+ _hw_prog_id = xinfo.hw_prog_id;
+ _attach_mode = xinfo.attach_mode;
+#endif
+ switch (_attach_mode) {
+ case XDP_ATTACHED_SKB:
+ *prog_id = _skb_prog_id;
+ *mode = XDP_MODE_SKB;
+ break;
+ case XDP_ATTACHED_DRV:
+ *prog_id = _drv_prog_id;
+ *mode = XDP_MODE_NATIVE;
+ break;
+ case XDP_ATTACHED_MULTI:
+ if (_drv_prog_id) {
+ *prog_id = _drv_prog_id;
+ *mode = XDP_MODE_NATIVE;
+ } else if (_skb_prog_id) {
+ *prog_id = _skb_prog_id;
+ *mode = XDP_MODE_SKB;
+ }
+ *hw_prog_id = _hw_prog_id;
+ break;
+ case XDP_ATTACHED_HW:
+ *hw_prog_id = _hw_prog_id;
+ *mode = XDP_MODE_UNSPEC;
+ break;
+ case XDP_ATTACHED_NONE:
+ default:
+ *mode = XDP_MODE_UNSPEC;
+ break;
+ }
+ return 0;
+}
+
+struct xdp_multiprog *xdp_multiprog__get_from_ifindex(int ifindex)
+{
+ enum xdp_attach_mode mode = XDP_MODE_UNSPEC;
+ int err, retry_counter = 0;
+ struct xdp_multiprog *mp;
+ __u32 hw_prog_id = 0;
+ __u32 prog_id = 0;
+
+retry:
+ err = xdp_get_ifindex_prog_id(ifindex, &prog_id, &hw_prog_id, &mode);
+ if (err)
+ return libxdp_err_ptr(err, false);
+
+ if (!prog_id && !hw_prog_id)
+ return libxdp_err_ptr(-ENOENT, false);
+
+ mp = xdp_multiprog__from_id(prog_id, hw_prog_id, ifindex);
+ if (!IS_ERR_OR_NULL(mp))
+ mp->attach_mode = mode;
+ else if (IS_ERR(mp)) {
+ err = PTR_ERR(mp);
+ if (err == -ENOENT) {
+ if (++retry_counter > MAX_RETRY) {
+ pr_warn("Retried more than %d times, giving up\n",
+ retry_counter);
+ err = -EBUSY;
+ } else {
+ pr_debug("Dispatcher disappeared before we could load it, retrying.\n");
+ usleep(1 << retry_counter); /* exponential backoff */
+ goto retry;
+ }
+ }
+
+ mp = libxdp_err_ptr(err, false);
+ } else
+ mp = libxdp_err_ptr(0, true);
+ return mp;
+}
+
+int libxdp_check_kern_compat(void)
+{
+ struct xdp_program *tgt_prog = NULL, *test_prog = NULL;
+ const char *bpffs_dir;
+ char buf[PATH_MAX];
+ int lock_fd;
+ int err = 0;
+
+ bpffs_dir = get_bpffs_dir();
+ if (IS_ERR(bpffs_dir)) {
+ err = PTR_ERR(bpffs_dir);
+ pr_warn("Can't use dispatcher without a working bpffs\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (kernel_compat > COMPAT_UNKNOWN)
+ goto skip;
+
+ pr_debug("Checking dispatcher compatibility\n");
+
+ tgt_prog = __xdp_program__find_file("xdp-dispatcher.o", NULL, "xdp_pass", NULL);
+ if (IS_ERR(tgt_prog)) {
+ err = PTR_ERR(tgt_prog);
+ pr_warn("Couldn't open BPF file xdp-dispatcher.o\n");
+ return err;
+ }
+
+ test_prog = __xdp_program__find_file("xdp-dispatcher.o", NULL, "xdp_pass", NULL);
+ if (IS_ERR(test_prog)) {
+ err = PTR_ERR(test_prog);
+ pr_warn("Couldn't open BPF file xdp-dispatcher.o\n");
+ return err;
+ }
+
+ err = xdp_program__load(tgt_prog);
+ if (err) {
+ pr_debug("Couldn't load XDP program: %s\n", strerror(-err));
+ goto out;
+ }
+
+ err = bpf_program__set_attach_target(test_prog->bpf_prog,
+ tgt_prog->prog_fd,
+ "xdp_pass");
+ if (err) {
+ pr_debug("Failed to set attach target: %s\n", strerror(-err));
+ goto out;
+ }
+
+ bpf_program__set_type(test_prog->bpf_prog, BPF_PROG_TYPE_EXT);
+ bpf_program__set_expected_attach_type(test_prog->bpf_prog, 0);
+ err = xdp_program__load(test_prog);
+ if (err) {
+ char buf[100] = {};
+ libxdp_strerror(err, buf, sizeof(buf));
+ pr_debug("Failed to load program %s: %s\n",
+ xdp_program__name(test_prog), buf);
+ goto out;
+ }
+
+ test_prog->link_fd = bpf_raw_tracepoint_open(NULL, test_prog->prog_fd);
+ if (test_prog->link_fd < 0) {
+ err = -errno;
+ pr_debug("Failed to attach test program to dispatcher: %s\n",
+ strerror(-err));
+ goto out;
+ }
+
+ err = try_snprintf(buf, sizeof(buf), "%s/prog-test-link-%i-%i",
+ bpffs_dir, IFINDEX_LO, test_prog->prog_id);
+ if (err)
+ goto out;
+
+ lock_fd = xdp_lock_acquire();
+ if (lock_fd < 0) {
+ err = lock_fd;
+ goto out;
+ }
+
+ err = bpf_obj_pin(test_prog->link_fd, buf);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't pin link FD at %s: %s\n", buf, strerror(-err));
+ goto out_locked;
+ }
+ err = unlink(buf);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't unlink file %s: %s\n", buf, strerror(-err));
+ goto out_locked;
+ }
+
+ kernel_compat = COMPAT_SUPPORTED;
+out_locked:
+ xdp_lock_release(lock_fd);
+out:
+ xdp_program__close(test_prog);
+ xdp_program__close(tgt_prog);
+ if (err) {
+ pr_info("Compatibility check for dispatcher program failed: %s\n",
+ strerror(-err));
+ kernel_compat = COMPAT_UNSUPPORTED;
+ }
+skip:
+ return kernel_compat == COMPAT_SUPPORTED ? 0 : -EOPNOTSUPP;
+}
+
+static int find_prog_btf_id(const char *name, __u32 attach_prog_fd)
+{
+ struct bpf_prog_info info = {};
+ __u32 info_size = sizeof(info);
+ int err = -EINVAL;
+ struct btf *btf;
+
+ err = bpf_obj_get_info_by_fd(attach_prog_fd, &info, &info_size);
+ if (err) {
+ err = -errno;
+ pr_warn("failed get_prog_info for FD %d\n", attach_prog_fd);
+ return err;
+ }
+ if (!info.btf_id) {
+ pr_warn("The target program doesn't have BTF\n");
+ return -EINVAL;
+ }
+ btf = btf__load_from_kernel_by_id(info.btf_id);
+ if (!btf) {
+ pr_warn("Failed to get BTF of the program\n");
+ return -EINVAL;
+ }
+ err = btf__find_by_name_kind(btf, name, BTF_KIND_FUNC);
+ btf__free(btf);
+ if (err <= 0)
+ pr_warn("%s is not found in prog's BTF\n", name);
+
+ return err;
+}
+
+static int xdp_multiprog__link_prog(struct xdp_multiprog *mp,
+ struct xdp_program *prog)
+{
+ DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts);
+ struct xdp_program *new_prog, *p;
+ bool was_loaded = false;
+ char buf[PATH_MAX];
+ int err, lfd = -1;
+ char *attach_func;
+ __s32 btf_id;
+
+ if (IS_ERR_OR_NULL(mp) || IS_ERR_OR_NULL(prog) || !mp->is_loaded ||
+ mp->num_links >= mp->config.num_progs_enabled)
+ return -EINVAL;
+
+ err = libxdp_check_kern_compat();
+ if (err)
+ return err;
+
+ if (!prog->btf) {
+ pr_warn("Program %s has no BTF information, so we can't load it as multiprog\n",
+ xdp_program__name(prog));
+ return -EOPNOTSUPP;
+ }
+
+ pr_debug("Linking prog %s as multiprog entry %zu\n",
+ xdp_program__name(prog), mp->num_links);
+
+ err = try_snprintf(buf, sizeof(buf), "prog%zu", mp->num_links);
+ if (err)
+ goto err;
+
+
+ if (mp->config.num_progs_enabled == 1)
+ attach_func = "xdp_dispatcher";
+ else
+ attach_func = buf;
+
+ btf_id = find_prog_btf_id(attach_func, mp->main_prog->prog_fd);
+ if (btf_id <= 0) {
+ err = btf_id;
+ pr_debug("Couldn't find BTF ID for %s: %d\n", attach_func, err);
+ goto err;
+ }
+
+ if (prog->prog_fd < 0) {
+ err = bpf_program__set_attach_target(prog->bpf_prog,
+ mp->main_prog->prog_fd,
+ attach_func);
+ if (err) {
+ pr_debug("Failed to set attach target: %s\n", strerror(-err));
+ goto err;
+ }
+
+ bpf_program__set_type(prog->bpf_prog, BPF_PROG_TYPE_EXT);
+ bpf_program__set_expected_attach_type(prog->bpf_prog, 0);
+ err = xdp_program__load(prog);
+ if (err) {
+ if (err == -E2BIG) {
+ pr_debug("Got 'argument list too long' error while "
+ "loading component program.\n");
+ err = -EOPNOTSUPP;
+ } else {
+ char buf[100] = {};
+ libxdp_strerror(err, buf, sizeof(buf));
+ pr_debug("Failed to load program %s: %s\n",
+ xdp_program__name(prog), buf);
+ }
+ goto err;
+ }
+
+ was_loaded = true;
+ }
+
+ /* clone the xdp_program ref so we can keep it */
+ new_prog = xdp_program__clone(prog, 0);
+ if (IS_ERR(new_prog)) {
+ err = PTR_ERR(new_prog);
+ pr_warn("Failed to clone xdp_program: %s\n", strerror(-err));
+ goto err;
+ }
+
+ opts.target_btf_id = btf_id;
+
+ /* The attach will disappear once this fd is closed */
+ lfd = bpf_link_create(new_prog->prog_fd, mp->main_prog->prog_fd, 0, &opts);
+ if (lfd < 0) {
+ err = -errno;
+ if (err == -EINVAL) {
+ if (!was_loaded) {
+ pr_debug("Kernel doesn't support re-attaching "
+ "freplace programs.\n");
+ err = -EOPNOTSUPP;
+ } else {
+ pr_debug("Got EINVAL, retrying "
+ "raw_tracepoint_open() without target\n");
+ /* we just loaded the program, so should be able
+ * to attach the old way */
+ lfd = bpf_raw_tracepoint_open(NULL, new_prog->prog_fd);
+ if (lfd < 0)
+ err = -errno;
+ else
+ goto attach_ok;
+ }
+ }
+ if (err == -EPERM) {
+ pr_debug("Got 'permission denied' error while "
+ "attaching program to dispatcher.\n%s\n",
+ dispatcher_feature_err);
+ err = -EOPNOTSUPP;
+ } else {
+ pr_warn("Failed to attach program %s to dispatcher: %s\n",
+ xdp_program__name(new_prog), strerror(-err));
+ }
+ goto err_free;
+ }
+
+attach_ok:
+ new_prog->attach_name = strdup(buf);
+ if (!new_prog->attach_name) {
+ err = -ENOMEM;
+ goto err_free;
+ }
+
+ pr_debug(
+ "Attached prog '%s' with priority %d in dispatcher entry '%s' with fd %d\n",
+ xdp_program__name(new_prog), xdp_program__run_prio(new_prog),
+ new_prog->attach_name, lfd);
+ new_prog->link_fd = lfd;
+
+ if (!mp->first_prog) {
+ mp->first_prog = new_prog;
+ } else {
+ p = mp->first_prog;
+ while (p->next)
+ p = p->next;
+ p->next = new_prog;
+ }
+
+ mp->num_links++;
+ return 0;
+
+err_free:
+ if (lfd >= 0)
+ close(lfd);
+ xdp_program__close(new_prog);
+err:
+ return err;
+}
+
+/*
+ * xdp_multiprog__generate - generate a new multiprog dispatcher
+ *
+ * This generates a new multiprog dispatcher for the programs in progs. If
+ * old_mp is set, the progs will either be added to or removed from the existing
+ * set of programs in the dispatcher represented by old_mp, depending on the
+ * value of remove_progs. If old_mp is not set, a new dispatcher will be created
+ * just holding the programs in progs. In both cases, the full set of programs
+ * will be sorted according to their run order (see cmp_xdp_programs).
+ *
+ * When called with remove_progs set, the caller is responsible for checking
+ * that all the programs in progs are actually present in old_mp.
+ */
+static struct xdp_multiprog *xdp_multiprog__generate(struct xdp_program **progs,
+ size_t num_progs,
+ int ifindex,
+ struct xdp_multiprog *old_mp,
+ bool remove_progs)
+{
+ size_t num_new_progs = old_mp ? old_mp->num_links : 0;
+ struct xdp_program **new_progs = NULL;
+ struct xdp_program *dispatcher;
+ struct xdp_multiprog *mp;
+ struct bpf_map *map;
+ size_t i;
+ int err;
+
+ if (!progs || !num_progs || (!old_mp && remove_progs))
+ return ERR_PTR(-EINVAL);
+
+ num_new_progs += remove_progs ? -num_progs : num_progs;
+
+ if (num_new_progs > MAX_DISPATCHER_ACTIONS)
+ return ERR_PTR(-E2BIG);
+
+ pr_debug("Generating multi-prog dispatcher for %zu programs\n",
+ num_new_progs);
+
+ mp = xdp_multiprog__new(ifindex);
+ if (IS_ERR(mp))
+ return mp;
+
+ err = xdp_multiprog__check_kernel_frags_support(mp);
+ if (err)
+ goto err;
+
+ if (old_mp) {
+ struct xdp_program *prog;
+ size_t j;
+
+ if (xdp_multiprog__is_legacy(old_mp)) {
+ pr_warn("Existing program is not using a dispatcher, can't replace; unload first\n");
+ err = -EBUSY;
+ goto err;
+ }
+
+ if (old_mp->version < mp->version) {
+ pr_warn("Existing dispatcher version %u is older than our version %u. "
+ "Refusing transparent upgrade, unload first\n",
+ old_mp->version, mp->version);
+ err = -EBUSY;
+ goto err;
+ }
+
+ new_progs = calloc(num_new_progs, sizeof(*new_progs));
+ if (!new_progs) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ for (i = 0, prog = old_mp->first_prog; prog; prog = prog->next) {
+ if (remove_progs) {
+ /* remove_new means new_progs is an array of
+ * programs we should remove from old_mp instead
+ * of adding them.
+ */
+ bool found = false;
+
+ for (j = 0; j < num_progs; j++)
+ if (progs[j]->prog_id == prog->prog_id)
+ found = true;
+ if (found)
+ continue;
+
+ /* Sanity check: caller should ensure all
+ * programs to remove actually exist; check here
+ * anyway to ensure we don't overrun the array
+ * if this is not done correctly.
+ */
+ if (i >= num_new_progs) {
+ pr_warn("Not all programs to remove were found\n");
+ err = -EINVAL;
+ goto err;
+ }
+ }
+ new_progs[i++] = prog;
+ }
+ if (!remove_progs)
+ for (j = 0; i < num_new_progs; i++, j++)
+ new_progs[i] = progs[j];
+
+ } else {
+ new_progs = progs;
+ }
+
+ if (num_new_progs > 1)
+ qsort(new_progs, num_new_progs, sizeof(*new_progs), cmp_xdp_programs);
+
+ dispatcher = __xdp_program__find_file("xdp-dispatcher.o",
+ NULL, "xdp_dispatcher", NULL);
+ if (IS_ERR(dispatcher)) {
+ err = PTR_ERR(dispatcher);
+ pr_warn("Couldn't open BPF file 'xdp-dispatcher.o'\n");
+ goto err;
+ }
+
+ mp->main_prog = dispatcher;
+
+ map = bpf_object__next_map(mp->main_prog->bpf_obj, NULL);
+ if (!map) {
+ pr_warn("Couldn't find rodata map in object file 'xdp-dispatcher.o'\n");
+ err = -ENOENT;
+ goto err;
+ }
+
+ mp->config.magic = XDP_DISPATCHER_MAGIC;
+ mp->config.dispatcher_version = mp->version;
+ mp->config.num_progs_enabled = num_new_progs;
+ mp->config.is_xdp_frags = mp->kernel_frags_support;
+ for (i = 0; i < num_new_progs; i++) {
+ mp->config.chain_call_actions[i] =
+ (new_progs[i]->chain_call_actions |
+ (1U << XDP_DISPATCHER_RETVAL));
+ mp->config.run_prios[i] = new_progs[i]->run_prio;
+
+ if (xdp_program__xdp_frags_support(new_progs[i]))
+ mp->config.program_flags[i] = BPF_F_XDP_HAS_FRAGS;
+ else
+ mp->config.is_xdp_frags = false;
+ }
+
+ if (mp->kernel_frags_support) {
+ if (!mp->config.is_xdp_frags)
+ pr_debug("At least one attached program doesn't "
+ "support frags, disabling it for the "
+ "dispatcher\n");
+ else
+ pr_debug("All attached programs support frags, "
+ "enabling it for the dispatcher\n");
+ }
+
+ err = bpf_map__set_initial_value(map, &mp->config, sizeof(mp->config));
+ if (err) {
+ pr_warn("Failed to set rodata for object file 'xdp-dispatcher.o'\n");
+ goto err;
+ }
+
+ err = xdp_multiprog__load(mp);
+ if (err)
+ goto err;
+
+ for (i = 0; i < num_new_progs; i++) {
+ err = xdp_multiprog__link_prog(mp, new_progs[i]);
+ if (err)
+ goto err;
+ }
+
+ if (old_mp)
+ free(new_progs);
+
+ return mp;
+
+err:
+ if (old_mp)
+ free(new_progs);
+ xdp_multiprog__close(mp);
+ return ERR_PTR(err);
+}
+
+static int xdp_multiprog__pin(struct xdp_multiprog *mp)
+{
+ char pin_path[PATH_MAX], buf[PATH_MAX];
+ struct xdp_program *prog;
+ const char *bpffs_dir;
+ int err = 0, lock_fd;
+
+ if (IS_ERR_OR_NULL(mp) || xdp_multiprog__is_legacy(mp))
+ return -EINVAL;
+
+ bpffs_dir = get_bpffs_dir();
+ if (IS_ERR(bpffs_dir))
+ return PTR_ERR(bpffs_dir);
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/dispatch-%d-%d",
+ bpffs_dir, mp->ifindex, mp->main_prog->prog_id);
+ if (err)
+ return err;
+
+ lock_fd = xdp_lock_acquire();
+ if (lock_fd < 0)
+ return lock_fd;
+
+ pr_debug("Pinning multiprog fd %d beneath %s\n",
+ mp->main_prog->prog_fd, pin_path);
+
+ err = mkdir(pin_path, S_IRWXU);
+ if (err && errno != EEXIST) {
+ err = -errno;
+ goto out;
+ }
+
+ for (prog = mp->first_prog; prog; prog = prog->next) {
+ if (prog->link_fd < 0) {
+ err = -EINVAL;
+ pr_warn("Prog %s not linked\n", prog->prog_name);
+ goto err_unpin;
+ }
+
+ err = try_snprintf(buf, sizeof(buf), "%s/%s-link",
+ pin_path, prog->attach_name);
+ if (err)
+ goto err_unpin;
+
+ err = bpf_obj_pin(prog->link_fd, buf);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't pin link FD at %s: %s\n", buf, strerror(-err));
+ goto err_unpin;
+ }
+ pr_debug("Pinned link for prog %s at %s\n", prog->prog_name, buf);
+
+ err = try_snprintf(buf, sizeof(buf), "%s/%s-prog",
+ pin_path, prog->attach_name);
+ if (err)
+ goto err_unpin;
+
+ err = bpf_obj_pin(prog->prog_fd, buf);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't pin prog FD at %s: %s\n", buf, strerror(-err));
+ goto err_unpin;
+ }
+
+ pr_debug("Pinned prog %s at %s\n", prog->prog_name, buf);
+ }
+out:
+ xdp_lock_release(lock_fd);
+ return err;
+
+err_unpin:
+ for (prog = mp->first_prog; prog; prog = prog->next) {
+ if (!try_snprintf(buf, sizeof(buf), "%s/%s-link",
+ pin_path, prog->attach_name))
+ unlink(buf);
+ if (!try_snprintf(buf, sizeof(buf), "%s/%s-prog",
+ pin_path, prog->attach_name))
+ unlink(buf);
+ }
+ rmdir(pin_path);
+ goto out;
+}
+
+static int xdp_multiprog__unpin(struct xdp_multiprog *mp)
+{
+ char pin_path[PATH_MAX], buf[PATH_MAX];
+ struct xdp_program *prog;
+ const char *bpffs_dir;
+ int err = 0, lock_fd;
+
+ if (IS_ERR_OR_NULL(mp) || xdp_multiprog__is_legacy(mp))
+ return -EINVAL;
+
+ bpffs_dir = get_bpffs_dir();
+ if (IS_ERR(bpffs_dir))
+ return PTR_ERR(bpffs_dir);
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/dispatch-%d-%d",
+ bpffs_dir, mp->ifindex, mp->main_prog->prog_id);
+ if (err)
+ return err;
+
+ lock_fd = xdp_lock_acquire();
+ if (lock_fd < 0)
+ return lock_fd;
+
+ pr_debug("Unpinning multiprog fd %d beneath %s\n",
+ mp->main_prog->prog_fd, pin_path);
+
+ for (prog = mp->first_prog; prog; prog = prog->next) {
+ err = try_snprintf(buf, sizeof(buf), "%s/%s-link",
+ pin_path, prog->attach_name);
+ if (err)
+ goto out;
+
+ err = unlink(buf);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't unlink file %s: %s\n",
+ buf, strerror(-err));
+ goto out;
+ }
+ pr_debug("Unpinned link for prog %s from %s\n",
+ prog->prog_name, buf);
+
+ err = try_snprintf(buf, sizeof(buf), "%s/%s-prog",
+ pin_path, prog->attach_name);
+ if (err)
+ goto out;
+
+ err = unlink(buf);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't unlink file %s: %s\n",
+ buf, strerror(-err));
+ goto out;
+ }
+
+ pr_debug("Unpinned prog %s from %s\n", prog->prog_name, buf);
+ }
+
+ err = rmdir(pin_path);
+ if (err)
+ err = -errno;
+ pr_debug("Removed pin directory %s\n", pin_path);
+out:
+ xdp_lock_release(lock_fd);
+ return err;
+}
+
+static int xdp_multiprog__attach(struct xdp_multiprog *old_mp,
+ struct xdp_multiprog *mp,
+ enum xdp_attach_mode mode)
+{
+ int err = 0, prog_fd = -1, old_fd = -1, ifindex = -1;
+
+ if (IS_ERR_OR_NULL(mp) && !old_mp)
+ return -EINVAL;
+
+ if (mode == XDP_MODE_HW)
+ return -EINVAL;
+
+ if (mp) {
+ prog_fd = xdp_multiprog__main_fd(mp);
+ if (prog_fd < 0)
+ return -EINVAL;
+ ifindex = mp->ifindex;
+ }
+
+ if (old_mp) {
+ old_fd = xdp_multiprog__main_fd(old_mp);
+ if (old_fd < 0)
+ return -EINVAL;
+ if (ifindex > -1 && ifindex != old_mp->ifindex)
+ return -EINVAL;
+ ifindex = old_mp->ifindex;
+ }
+
+
+ err = xdp_attach_fd(prog_fd, old_fd, ifindex, mode);
+ if (err < 0)
+ goto err;
+
+ if (mp)
+ pr_debug("Loaded %zu programs on ifindex %d%s\n",
+ mp->num_links, ifindex,
+ mode == XDP_MODE_SKB ? " in skb mode" : "");
+ else
+ pr_debug("Detached %s on ifindex %d%s\n",
+ xdp_multiprog__is_legacy(old_mp) ? "program" : "multiprog",
+ ifindex,
+ mode == XDP_MODE_SKB ? " in skb mode" : "");
+
+ return 0;
+err:
+ return err;
+}
+
+int xdp_multiprog__detach(struct xdp_multiprog *mp)
+{
+ int err = 0;
+
+ if (IS_ERR_OR_NULL(mp) || !mp->is_loaded)
+ return libxdp_err(-EINVAL);
+
+ if (mp->hw_prog) {
+ err = xdp_multiprog__detach_hw(mp);
+ if (err)
+ return libxdp_err(err);
+ }
+
+ if (mp->main_prog) {
+ err = xdp_multiprog__attach(mp, NULL, mp->attach_mode);
+ if (err)
+ return libxdp_err(err);
+
+ if (!xdp_multiprog__is_legacy(mp))
+ err = xdp_multiprog__unpin(mp);
+ }
+ return libxdp_err(err);
+}
+
+struct xdp_program *xdp_multiprog__next_prog(const struct xdp_program *prog,
+ const struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp) || xdp_multiprog__is_legacy(mp))
+ return libxdp_err_ptr(0, true);
+
+ if (prog)
+ return prog->next;
+
+ return mp->first_prog;
+}
+
+struct xdp_program *xdp_multiprog__hw_prog(const struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp))
+ return libxdp_err_ptr(0, true);
+
+ return mp->hw_prog;
+}
+
+enum xdp_attach_mode xdp_multiprog__attach_mode(const struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp))
+ return XDP_MODE_UNSPEC;
+
+ return mp->attach_mode;
+}
+
+struct xdp_program *xdp_multiprog__main_prog(const struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp))
+ return libxdp_err_ptr(0, true);
+
+ return mp->main_prog;
+}
+
+bool xdp_multiprog__is_legacy(const struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp))
+ return false;
+
+ return mp->is_legacy;
+}
+
+int xdp_multiprog__program_count(const struct xdp_multiprog *mp)
+{
+ if (IS_ERR_OR_NULL(mp))
+ return libxdp_err(-EINVAL);
+
+ return mp->num_links;
+}
+
+bool xdp_multiprog__xdp_frags_support(const struct xdp_multiprog *mp)
+{
+ return !xdp_multiprog__is_legacy(mp) && mp->config.is_xdp_frags;
+}
+
+static int remove_pin_dir(const char *subdir)
+{
+ char prog_path[PATH_MAX], pin_path[PATH_MAX];
+ int err;
+ DIR *d;
+
+ const char *dir = get_bpffs_dir();
+ if (IS_ERR(dir))
+ return PTR_ERR(dir);
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/%s", dir, subdir);
+ if (err)
+ return err;
+
+ d = opendir(pin_path);
+ if (!d) {
+ err = -errno;
+ pr_warn("Failed to open pin directory: %s\n", strerror(-err));
+ return err;
+ }
+
+ for (struct dirent *dent = readdir(d); dent; dent = readdir(d)) {
+ /* skip . and .. */
+ if (dent->d_type == DT_DIR)
+ continue;
+
+ err = try_snprintf(prog_path, sizeof(prog_path), "%s/%s",
+ pin_path, dent->d_name);
+ if (err)
+ goto err;
+
+ err = unlink(prog_path);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't unlink file %s/%s: %s\n", subdir,
+ dent->d_name, strerror(-err));
+ goto err;
+ }
+ }
+ err = rmdir(pin_path);
+ if (err) {
+ err = -errno;
+ pr_warn("Failed to remove pin directory %s: %s\n", pin_path,
+ strerror(-err));
+ }
+err:
+ closedir(d);
+ return err;
+}
+
+int libxdp_clean_references(int ifindex)
+{
+ int err = 0, lock_fd, path_ifindex;
+ __u32 dir_prog_id, prog_id = 0;
+ DIR *d;
+
+ const char *dir = get_bpffs_dir();
+ if (IS_ERR(dir))
+ return libxdp_err(PTR_ERR(dir));
+
+ lock_fd = xdp_lock_acquire();
+ if (lock_fd < 0)
+ return libxdp_err(lock_fd);
+
+ d = opendir(dir);
+ if (!d) {
+ err = -errno;
+ pr_debug("Failed to open bpffs directory: %s\n",
+ strerror(-err));
+ goto out;
+ }
+
+ for (struct dirent *dent = readdir(d); dent; dent = readdir(d)) {
+ if (dent->d_type != DT_DIR)
+ continue;
+
+ if (sscanf(dent->d_name, "dispatch-%d-%"PRIu32"",
+ &path_ifindex, &dir_prog_id) != 2)
+ continue;
+
+ /* If ifindex is set, skip this dir if it doesn't match */
+ if (ifindex && path_ifindex != ifindex)
+ continue;
+
+ xdp_get_ifindex_prog_id(path_ifindex, &prog_id, NULL, NULL);
+ if (!prog_id || prog_id != dir_prog_id) {
+ pr_info("Prog id %"PRIu32" no longer attached on ifindex %d, removing pin directory %s\n",
+ dir_prog_id, path_ifindex, dent->d_name);
+ err = remove_pin_dir(dent->d_name);
+ if (err)
+ break;
+ }
+ }
+ closedir(d);
+out:
+ xdp_lock_release(lock_fd);
+ return libxdp_err(err);
+}