summaryrefslogtreecommitdiffstats
path: root/lib/util/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/util/util.c')
-rw-r--r--lib/util/util.c946
1 files changed, 946 insertions, 0 deletions
diff --git a/lib/util/util.c b/lib/util/util.c
new file mode 100644
index 0000000..70c5d18
--- /dev/null
+++ b/lib/util/util.c
@@ -0,0 +1,946 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/resource.h>
+#include <sys/vfs.h>
+#include <sys/stat.h>
+#include <linux/if_link.h> /* Need XDP flags */
+#include <linux/magic.h> /* BPF FS magic */
+#include <linux/err.h> /* ERR_PTR */
+#include <bpf/bpf.h>
+#include <dirent.h>
+#include <net/if.h>
+
+#include "util.h"
+#include "logging.h"
+
+static struct enum_val xdp_modes[] = {
+ {"native", XDP_MODE_NATIVE},
+ {"skb", XDP_MODE_SKB},
+ {"hw", XDP_MODE_HW},
+ {"unspecified", XDP_MODE_UNSPEC},
+ {NULL, 0}
+};
+
+int try_snprintf(char *buf, size_t buf_len, const char *format, ...)
+{
+ va_list args;
+ int len;
+
+ va_start(args, format);
+ len = vsnprintf(buf, buf_len, format, args);
+ va_end(args);
+
+ if (len < 0)
+ return -EINVAL;
+ else if ((size_t)len >= buf_len)
+ return -ENAMETOOLONG;
+
+ return 0;
+}
+
+static int set_rlimit(unsigned int min_limit)
+{
+ struct rlimit limit;
+ int err = 0;
+
+ err = getrlimit(RLIMIT_MEMLOCK, &limit);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't get current rlimit\n");
+ return err;
+ }
+
+ if (limit.rlim_cur == RLIM_INFINITY || limit.rlim_cur == 0) {
+ pr_debug("Current rlimit is infinity or 0. Not raising\n");
+ return -ENOMEM;
+ }
+
+ if (min_limit) {
+ if (limit.rlim_cur >= min_limit) {
+ pr_debug("Current rlimit %ju already >= minimum %u\n",
+ (uintmax_t)limit.rlim_cur, min_limit);
+ return 0;
+ }
+ pr_debug("Setting rlimit to minimum %u\n", min_limit);
+ limit.rlim_cur = min_limit;
+ } else {
+ pr_debug("Doubling current rlimit of %ju\n", (uintmax_t)limit.rlim_cur);
+ limit.rlim_cur <<= 1;
+ }
+ limit.rlim_max = max(limit.rlim_cur, limit.rlim_max);
+
+ err = setrlimit(RLIMIT_MEMLOCK, &limit);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't raise rlimit: %s\n", strerror(-err));
+ return err;
+ }
+
+ return 0;
+}
+
+int double_rlimit(void)
+{
+ pr_debug("Permission denied when loading eBPF object; "
+ "raising rlimit and retrying\n");
+
+ return set_rlimit(0);
+}
+
+static const char *_libbpf_compile_version = LIBBPF_VERSION;
+static char _libbpf_version[10] = {};
+
+const char *get_libbpf_version(void)
+{
+ /* Start by copying compile-time version into buffer so we have a
+ * fallback value in case we are dynamically linked, or can't find a
+ * version in /proc/self/maps below.
+ */
+ strncpy(_libbpf_version, _libbpf_compile_version,
+ sizeof(_libbpf_version)-1);
+
+#ifdef LIBBPF_DYNAMIC
+ char path[PATH_MAX], buf[PATH_MAX], *s;
+ bool found = false;
+ FILE *fp;
+
+ /* When dynamically linking against libbpf, we can't be sure that the
+ * version we discovered at compile time is actually the one we are
+ * using at runtime. This can lead to hard-to-debug errors, so we try to
+ * discover the correct version at runtime.
+ *
+ * The simple solution to this would be if libbpf itself exported a
+ * version in its API. But since it doesn't, we work around this by
+ * parsing the mappings of the binary at runtime, looking for the full
+ * filename of libbpf.so and using that.
+ */
+ fp = fopen("/proc/self/maps", "r");
+ if (fp == NULL)
+ goto out;
+
+ while ((s = fgets(buf, sizeof(buf), fp)) != NULL) {
+ /* We are looking for a line like:
+ * 7f63c2105000-7f63c2106000 rw-p 00032000 fe:02 4200947 /usr/lib/libbpf.so.0.1.0
+ */
+ if (sscanf(s, "%*x-%*x %*4c %*x %*5c %*d %s\n", path) == 1 &&
+ (s = strstr(path, "libbpf.so.")) != NULL) {
+ strncpy(_libbpf_version, s+10, sizeof(_libbpf_version)-1);
+ found = true;
+ break;
+ }
+ }
+
+ fclose(fp);
+out:
+ if (!found)
+ pr_warn("Couldn't find runtime libbpf version - falling back to compile-time value!\n");
+
+#endif
+ _libbpf_version[sizeof(_libbpf_version)-1] = '\0';
+ return _libbpf_version;
+}
+
+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
+ };
+ struct stat sb = {};
+ char **path;
+ int err;
+
+ for (path = bpf_obj_paths; *path; path++) {
+ err = try_snprintf(buf, buf_size, "%s/%s", *path, progname);
+ if (err)
+ return err;
+
+ pr_debug("Looking for '%s'\n", buf);
+ err = stat(buf, &sb);
+ if (err)
+ continue;
+
+ return 0;
+ }
+
+ pr_warn("Couldn't find a BPF file with name %s\n", progname);
+ return -ENOENT;
+}
+
+struct bpf_object *open_bpf_file(const char *progname,
+ struct bpf_object_open_opts *opts)
+{
+ char buf[PATH_MAX];
+ int err;
+
+ err = find_bpf_file(buf, sizeof(buf), progname);
+ if (err)
+ return ERR_PTR(err);
+
+ pr_debug("Loading bpf file '%s' from '%s'\n", progname, buf);
+ return bpf_object__open_file(buf, opts);
+}
+
+static int get_pinned_object_fd(const char *path, void *info, __u32 *info_len)
+{
+ char errmsg[STRERR_BUFSIZE];
+ int pin_fd, err;
+
+ pin_fd = bpf_obj_get(path);
+ if (pin_fd < 0) {
+ err = -errno;
+ libbpf_strerror(-err, errmsg, sizeof(errmsg));
+ pr_debug("Couldn't retrieve pinned object '%s': %s\n", path, errmsg);
+ return err;
+ }
+
+ if (info) {
+ err = bpf_obj_get_info_by_fd(pin_fd, info, info_len);
+ if (err) {
+ err = -errno;
+ libbpf_strerror(-err, errmsg, sizeof(errmsg));
+ pr_debug("Couldn't retrieve object info: %s\n", errmsg);
+ return err;
+ }
+ }
+
+ return pin_fd;
+}
+
+int make_dir_subdir(const char *parent, const char *dir)
+{
+ char path[PATH_MAX];
+ int err;
+
+ err = try_snprintf(path, sizeof(path), "%s/%s", parent, dir);
+ if (err)
+ return err;
+
+ err = mkdir(parent, S_IRWXU);
+ if (err && errno != EEXIST) {
+ err = -errno;
+ return err;
+ }
+
+ err = mkdir(path, S_IRWXU);
+ if (err && errno != EEXIST) {
+ err = -errno;
+ return err;
+ }
+
+ return 0;
+}
+
+int attach_xdp_program(struct xdp_program *prog, const struct iface *iface,
+ enum xdp_attach_mode mode, const char *pin_root_path)
+{
+ char pin_path[PATH_MAX];
+ int err = 0;
+
+ if (!prog || !pin_root_path)
+ return -EINVAL;
+
+ err = make_dir_subdir(pin_root_path, "programs");
+ if (err) {
+ pr_warn("Unable to create pin directory: %s\n", strerror(-err));
+ return err;
+ }
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/programs/%s/%s",
+ pin_root_path, iface->ifname,
+ xdp_program__name(prog));
+ if (err)
+ return err;
+
+ err = xdp_program__attach(prog, iface->ifindex, mode, 0);
+ if (err) {
+ if (pin_root_path && err != -EEXIST)
+ unlink(pin_path);
+ return err;
+ }
+
+ pr_debug("Program '%s' loaded on interface '%s'%s\n",
+ xdp_program__name(prog), iface->ifname,
+ mode == XDP_MODE_SKB ? " in skb mode" : "");
+
+ err = xdp_program__pin(prog, pin_path);
+ if (err) {
+ pr_warn("Unable to pin XDP program at %s: %s\n",
+ pin_path, strerror(-err));
+ goto unload;
+ }
+ pr_debug("XDP program pinned at %s\n", pin_path);
+ return err;
+
+unload:
+ xdp_program__detach(prog, iface->ifindex, mode, 0);
+ return err;
+}
+
+int detach_xdp_program(struct xdp_program *prog, const struct iface *iface,
+ enum xdp_attach_mode mode, const char *pin_root_path)
+{
+ char pin_path[PATH_MAX];
+ int err;
+
+ err = xdp_program__detach(prog, iface->ifindex, mode, 0);
+ if (err)
+ goto out;
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/programs/%s/%s",
+ pin_root_path, iface->ifname,
+ xdp_program__name(prog));
+ if (err)
+ return err;
+
+ err = unlink(pin_path);
+ if (err && errno != ENOENT)
+ goto out;
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/programs/%s",
+ pin_root_path, iface->ifname);
+ if (err)
+ goto out;
+
+ err = rmdir(pin_path);
+ if (err && errno == ENOENT)
+ err = 0;
+ else if (err)
+ err = -errno;
+out:
+ return err;
+}
+
+int get_pinned_program(const struct iface *iface, const char *pin_root_path,
+ enum xdp_attach_mode *mode,
+ struct xdp_program **xdp_prog)
+{
+ int ret = -ENOENT, err, ifindex = iface->ifindex;
+ char pin_path[PATH_MAX];
+ bool remove_all = false;
+ enum xdp_attach_mode m;
+ struct dirent *de;
+ DIR *dr;
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/programs/%s",
+ pin_root_path, iface->ifname);
+ if (err)
+ return err;
+
+ dr = opendir(pin_path);
+ if (!dr) {
+ err = -errno;
+ pr_debug("Couldn't open pin directory %s: %s\n",
+ pin_path, strerror(-err));
+ return err;
+ }
+
+ if (!ifindex)
+ ifindex = if_nametoindex(iface->ifname);
+ if (!ifindex) {
+ pr_debug("Interface %s no longer exists\n", iface->ifname);
+ remove_all = true;
+ ret = -ENODEV;
+ }
+
+ while ((de = readdir(dr)) != NULL) {
+ DECLARE_LIBXDP_OPTS(xdp_program_opts, opts, 0);
+ struct xdp_program *prog;
+
+ if (!strcmp(".", de->d_name) || !strcmp("..", de->d_name))
+ continue;
+
+ err = try_snprintf(pin_path, sizeof(pin_path),
+ "%s/programs/%s/%s", pin_root_path,
+ iface->ifname, de->d_name);
+ if (err)
+ goto out;
+
+ if (remove_all) {
+ err = unlink(pin_path);
+ if (err)
+ ret = err;
+ continue;
+ }
+
+ opts.pin_path = pin_path;
+ prog = xdp_program__create(&opts);
+ if (libxdp_get_error(prog) ||
+ !(m = xdp_program__is_attached(prog, iface->ifindex))) {
+ ret = libxdp_get_error(prog) ?: -ENOENT;
+ pr_debug("Program %s no longer loaded on %s: %s\n",
+ de->d_name, iface->ifname, strerror(-ret));
+ err = unlink(pin_path);
+ if (err)
+ ret = err;
+ if (prog)
+ xdp_program__close(prog);
+ } else {
+ if (strcmp(xdp_program__name(prog), de->d_name)) {
+ pr_warn("Pinned and kernel prog names differ: %s/%s\n",
+ xdp_program__name(prog), de->d_name);
+ ret = -EFAULT;
+ xdp_program__close(prog);
+ } else {
+ ret = 0;
+ *xdp_prog = prog;
+ if (mode)
+ *mode = m;
+ }
+ break;
+ }
+ }
+out:
+ closedir(dr);
+ return ret;
+}
+
+int iterate_pinned_programs(const char *pin_root_path, program_callback cb,
+ void *arg)
+{
+ char pin_path[PATH_MAX];
+ struct dirent *de;
+ int err = 0;
+ DIR *dr;
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/programs",
+ pin_root_path);
+ if (err)
+ return err;
+
+ dr = opendir(pin_path);
+ if (!dr)
+ return -ENOENT;
+
+ while ((de = readdir(dr)) != NULL) {
+ enum xdp_attach_mode mode = XDP_MODE_UNSPEC;
+ struct xdp_program *prog = NULL;
+ struct iface iface = {};
+
+ if (!strcmp(".", de->d_name) || !strcmp("..", de->d_name))
+ continue;
+
+ iface.ifname = de->d_name;
+ iface.ifindex = if_nametoindex(iface.ifname);
+
+ err = try_snprintf(pin_path, sizeof(pin_path), "%s/programs/%s",
+ pin_root_path, iface.ifname);
+ if (err)
+ goto out;
+
+ err = get_pinned_program(&iface, pin_root_path, &mode, &prog);
+ if (err == -ENOENT || err == -ENODEV) {
+ err = rmdir(pin_path);
+ if (err)
+ goto out;
+ continue;
+ } else if (err) {
+ goto out;
+ }
+
+ err = cb(&iface, prog, mode, arg);
+ xdp_program__close(prog);
+ if (err)
+ goto out;
+ }
+
+out:
+ closedir(dr);
+ return err;
+}
+
+int iterate_iface_multiprogs(multiprog_callback cb, void *arg)
+{
+ struct if_nameindex *idx, *indexes = NULL;
+ int err = 0;
+
+ indexes = if_nameindex();
+ if (!indexes) {
+ err = -errno;
+ pr_warn("Couldn't get list of interfaces: %s\n", strerror(-err));
+ return err;
+ }
+
+ for (idx = indexes; idx->if_index; idx++) {
+ struct xdp_multiprog *mp;
+ struct iface iface = {
+ .ifindex = idx->if_index,
+ .ifname = idx->if_name,
+ };
+
+ mp = xdp_multiprog__get_from_ifindex(iface.ifindex);
+ if (IS_ERR_OR_NULL(mp)) {
+ if (PTR_ERR(mp) != -ENOENT) {
+ err = PTR_ERR(mp);
+ pr_warn("Error getting XDP status for interface %s: %s\n",
+ idx->if_name, strerror(-err));
+ goto out;
+ }
+ mp = NULL;
+ }
+
+ err = cb(&iface, mp, arg);
+ xdp_multiprog__close(mp);
+ if (err)
+ goto out;
+ }
+
+out:
+ if_freenameindex(indexes);
+ return err;
+}
+
+static bool bpf_is_valid_mntpt(const char *mnt, unsigned long magic)
+{
+ struct statfs st_fs;
+
+ if (statfs(mnt, &st_fs) < 0)
+ return false;
+ if ((unsigned long)st_fs.f_type != magic)
+ return false;
+
+ return true;
+}
+
+static const char *bpf_find_mntpt_single(unsigned long magic, char *mnt,
+ int len, const char *mntpt)
+{
+ if (bpf_is_valid_mntpt(mntpt, magic)) {
+ strncpy(mnt, mntpt, len - 1);
+ mnt[len - 1] = '\0';
+ return mnt;
+ }
+
+ return NULL;
+}
+
+static const char *bpf_find_mntpt(const char *fstype, unsigned long magic,
+ char *mnt, int len,
+ const char * const *known_mnts)
+{
+ const char * const *ptr;
+ char type[100];
+ FILE *fp;
+
+ if (known_mnts) {
+ ptr = known_mnts;
+ while (*ptr) {
+ if (bpf_find_mntpt_single(magic, mnt, len, *ptr))
+ return mnt;
+ ptr++;
+ }
+ }
+
+ if (len != PATH_MAX)
+ return NULL;
+
+ fp = fopen("/proc/mounts", "r");
+ if (fp == NULL)
+ return NULL;
+
+ while (fscanf(fp, "%*s %" textify(PATH_MAX) "s %99s %*s %*d %*d\n", mnt,
+ type) == 2) {
+ if (strcmp(type, fstype) == 0)
+ break;
+ }
+
+ fclose(fp);
+ if (strcmp(type, fstype) != 0)
+ return NULL;
+
+ return mnt;
+}
+
+static int bpf_mnt_check_target(const char *target)
+{
+ int ret;
+
+ ret = mkdir(target, S_IRWXU);
+ if (ret && errno != EEXIST) {
+ ret = -errno;
+ pr_warn("mkdir %s failed: %s\n", target, strerror(-ret));
+ return ret;
+ }
+
+ return 0;
+}
+/* simplified version of code from iproute2 */
+static const char *bpf_get_work_dir()
+{
+ static char bpf_tmp[PATH_MAX] = BPF_DIR_MNT;
+ static char bpf_wrk_dir[PATH_MAX];
+ static const char *mnt;
+ static bool bpf_mnt_cached;
+ static const char *const bpf_known_mnts[] = {
+ BPF_DIR_MNT,
+ "/bpf",
+ 0,
+ };
+ int ret;
+
+ if (bpf_mnt_cached)
+ return mnt;
+
+ mnt = bpf_find_mntpt("bpf", BPF_FS_MAGIC, bpf_tmp, sizeof(bpf_tmp),
+ bpf_known_mnts);
+ if (!mnt) {
+ mnt = BPF_DIR_MNT;
+ ret = bpf_mnt_check_target(mnt);
+ if (ret || !bpf_is_valid_mntpt(mnt, BPF_FS_MAGIC)) {
+ mnt = NULL;
+ goto out;
+ }
+ }
+
+ strncpy(bpf_wrk_dir, mnt, sizeof(bpf_wrk_dir));
+ bpf_wrk_dir[sizeof(bpf_wrk_dir) - 1] = '\0';
+ mnt = bpf_wrk_dir;
+out:
+ bpf_mnt_cached = true;
+ return mnt;
+}
+
+int get_bpf_root_dir(char *buf, size_t buf_len, const char *subdir, bool fatal)
+{
+ const char *bpf_dir;
+
+ bpf_dir = bpf_get_work_dir();
+ if (!bpf_dir) {
+ logging_print(fatal ? LOG_WARN : LOG_DEBUG,
+ "Could not find BPF working dir - bpffs not mounted?\n");
+ return -ENOENT;
+ }
+
+ if (subdir)
+ return try_snprintf(buf, buf_len, "%s/%s", bpf_dir, subdir);
+ else
+ return try_snprintf(buf, buf_len, "%s", bpf_dir);
+}
+
+int get_pinned_map_fd(const char *bpf_root, const char *map_name,
+ struct bpf_map_info *info)
+{
+ __u32 info_len = sizeof(*info);
+ char buf[PATH_MAX];
+ int err;
+
+ err = try_snprintf(buf, sizeof(buf), "%s/%s", bpf_root, map_name);
+ if (err)
+ return err;
+
+ pr_debug("Getting pinned object from %s\n", buf);
+ return get_pinned_object_fd(buf, info, &info_len);
+}
+
+int unlink_pinned_map(int dir_fd, const char *map_name)
+{
+ struct stat statbuf = {};
+ int err;
+
+ err = fstatat(dir_fd, map_name, &statbuf, 0);
+ if (err && errno == ENOENT) {
+ pr_debug("Map name %s not pinned\n", map_name);
+ return 0;
+ } else if (err) {
+ err = -errno;
+ pr_warn("Couldn't stat pinned map %s: %s\n",
+ map_name, strerror(-err));
+ return err;
+ }
+
+ pr_debug("Unlinking pinned map %s\n", map_name);
+ err = unlinkat(dir_fd, map_name, 0);
+ if (err) {
+ err = -errno;
+ pr_warn("Couldn't unlink pinned map %s: %s\n",
+ map_name, strerror(-err));
+ return -errno;
+ }
+
+ return 0;
+}
+
+#define XDP_UNKNOWN (XDP_REDIRECT + 1)
+#ifndef XDP_ACTION_MAX
+#define XDP_ACTION_MAX (XDP_UNKNOWN + 1)
+#endif
+
+static const char *xdp_action_names[XDP_ACTION_MAX] = {
+ [XDP_ABORTED] = "XDP_ABORTED",
+ [XDP_DROP] = "XDP_DROP",
+ [XDP_PASS] = "XDP_PASS",
+ [XDP_TX] = "XDP_TX",
+ [XDP_REDIRECT] = "XDP_REDIRECT",
+ [XDP_UNKNOWN] = "XDP_UNKNOWN",
+};
+
+const char *action2str(__u32 action)
+{
+ if (action < XDP_ACTION_MAX)
+ return xdp_action_names[action];
+ return NULL;
+}
+
+int check_bpf_environ(void)
+{
+ init_lib_logging();
+
+ if (geteuid() != 0) {
+ pr_warn("This program must be run as root.\n");
+ return 1;
+ }
+
+ /* Try to avoid probing errors due to rlimit exhaustion by starting out
+ * with an rlimit of 1 MiB. This is not going to solve all issues, but
+ * it will at least make things work when there is nothing else loaded.
+ *
+ * Ignore return code because an error shouldn't abort running.
+ */
+ set_rlimit(1024 * 1024);
+
+ return 0;
+}
+
+static const char *lock_dir = RUNDIR;
+static char *prog_lock_file = NULL;
+static int prog_lock_fd = -1;
+static pid_t prog_pid = 0;
+
+void prog_lock_release(int signal)
+{
+ struct sigaction sigact = { .sa_flags = SA_RESETHAND };
+ int err;
+
+ if (prog_lock_fd < 0 || !prog_lock_file)
+ return;
+
+ sigaction(SIGHUP, &sigact, NULL);
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGSEGV, &sigact, NULL);
+ sigaction(SIGFPE, &sigact, NULL);
+ sigaction(SIGTERM, &sigact, NULL);
+
+ err = unlink(prog_lock_file);
+ if (err) {
+ err = -errno;
+ pr_warn("Unable to unlink lock file: %s\n", strerror(-err));
+ goto out;
+ }
+
+ close(prog_lock_fd);
+ free(prog_lock_file);
+ prog_lock_fd = -1;
+ prog_lock_file = NULL;
+
+out:
+ if (signal) {
+ pr_debug("Exiting on signal %d\n", signal);
+ if (prog_pid)
+ kill(prog_pid, signal);
+ else
+ exit(signal);
+ }
+}
+
+int prog_lock_get(const char *progname)
+{
+ char buf[PATH_MAX];
+ int err;
+ struct sigaction sigact = { .sa_handler = prog_lock_release };
+
+ if (prog_lock_fd >= 0) {
+ pr_warn("Attempt to get prog_lock twice.\n");
+ return -EFAULT;
+ }
+
+ if (!prog_lock_file) {
+ err = try_snprintf(buf, sizeof(buf), "%s/%s.lck", lock_dir,
+ progname);
+ if (err)
+ return err;
+
+ prog_lock_file = strdup(buf);
+ if (!prog_lock_file)
+ return -ENOMEM;
+ }
+
+ prog_pid = getpid();
+
+ if (sigaction(SIGHUP, &sigact, NULL) ||
+ sigaction(SIGINT, &sigact, NULL) ||
+ sigaction(SIGSEGV, &sigact, NULL) ||
+ sigaction(SIGFPE, &sigact, NULL) ||
+ sigaction(SIGTERM, &sigact, NULL)) {
+ err = -errno;
+ pr_warn("Unable to install signal handler: %s\n", strerror(-err));
+ return err;
+ }
+
+ prog_lock_fd = open(prog_lock_file, O_WRONLY | O_CREAT | O_EXCL, 0644);
+ if (prog_lock_fd < 0) {
+ err = -errno;
+ if (err == -EEXIST) {
+ pid_t pid = 0;
+ char buf[100];
+ ssize_t len;
+ int fd;
+
+ fd = open(prog_lock_file, O_RDONLY);
+ if (fd < 0) {
+ err = -errno;
+ pr_warn("Unable to open lockfile for reading: %s\n",
+ strerror(-err));
+ return err;
+ }
+
+ len = read(fd, buf, sizeof(buf) - 1);
+ err = -errno;
+ close(fd);
+ if (len > 0) {
+ buf[len] = '\0';
+ pid = strtoul(buf, NULL, 10);
+ }
+ if (!pid || err) {
+ pr_warn("Unable to read PID from lockfile: %s\n",
+ strerror(-err));
+ return err;
+ }
+ pr_warn("Unable to get program lock: Already held by pid %d\n",
+ pid);
+ } else {
+ pr_warn("Unable to get program lock: %s\n", strerror(-err));
+ }
+ return err;
+ }
+
+ err = dprintf(prog_lock_fd, "%d\n", prog_pid);
+ if (err < 0) {
+ err = -errno;
+ pr_warn("Unable to write pid to lock file: %s\n", strerror(-err));
+ goto out_err;
+ }
+
+ err = fsync(prog_lock_fd);
+ if (err) {
+ err = -errno;
+ pr_warn("Unable fsync() lock file: %s\n", strerror(-err));
+ goto out_err;
+ }
+
+ return 0;
+out_err:
+ unlink(prog_lock_file);
+ close(prog_lock_fd);
+ free(prog_lock_file);
+ prog_lock_file = NULL;
+ prog_lock_fd = -1;
+ return err;
+}
+
+static char *print_bpf_tag(char buf[BPF_TAG_SIZE * 2 + 1],
+ const unsigned char tag[BPF_TAG_SIZE])
+{
+ int i;
+
+ for (i = 0; i < BPF_TAG_SIZE; i++)
+ sprintf(&buf[i * 2], "%02x", tag[i]);
+ buf[BPF_TAG_SIZE * 2] = '\0';
+ return buf;
+}
+
+static int print_iface_status(const struct iface *iface,
+ const struct xdp_multiprog *mp,
+ __unused void *arg)
+{
+ struct xdp_program *prog, *dispatcher, *hw_prog;
+ char tag[BPF_TAG_SIZE * 2 + 1];
+ char buf[STRERR_BUFSIZE];
+ int err;
+
+ if (!mp) {
+ printf("%-22s <No XDP program loaded!>\n", iface->ifname);
+ return 0;
+ }
+
+ hw_prog = xdp_multiprog__hw_prog(mp);
+ if (hw_prog) {
+ printf("%-16s %-5s %-17s %-8s %-4d %-17s\n",
+ iface->ifname,
+ "",
+ xdp_program__name(hw_prog),
+ get_enum_name(xdp_modes, XDP_MODE_HW),
+ xdp_program__id(hw_prog),
+ print_bpf_tag(tag, xdp_program__tag(hw_prog)));
+ }
+
+ dispatcher = xdp_multiprog__main_prog(mp);
+ if (dispatcher) {
+ printf("%-16s %-5s %-17s %-8s %-4d %-17s\n",
+ iface->ifname,
+ "",
+ xdp_program__name(dispatcher),
+ get_enum_name(xdp_modes, xdp_multiprog__attach_mode(mp)),
+ xdp_program__id(dispatcher),
+ print_bpf_tag(tag, xdp_program__tag(dispatcher)));
+
+
+ for (prog = xdp_multiprog__next_prog(NULL, mp);
+ prog;
+ prog = xdp_multiprog__next_prog(prog, mp)) {
+
+ err = xdp_program__print_chain_call_actions(prog, buf,
+ sizeof(buf));
+ if (err)
+ return err;
+
+ printf("%-16s %-5d %-16s %-8s %-4u %-17s %s\n",
+ " =>", xdp_program__run_prio(prog),
+ xdp_program__name(prog),
+ "", xdp_program__id(prog),
+ print_bpf_tag(tag, xdp_program__tag(prog)),
+ buf);
+ }
+ }
+
+ return 0;
+}
+
+int iface_print_status(const struct iface *iface)
+{
+ int err = 0;
+
+ printf("%-16s %-5s %-17s Mode ID %-17s %s\n",
+ "Interface", "Prio", "Program name", "Tag", "Chain actions");
+ printf("--------------------------------------------------------------------------------------\n");
+
+ if (iface) {
+ struct xdp_multiprog *mp;
+
+ mp = xdp_multiprog__get_from_ifindex(iface->ifindex);
+ if (IS_ERR_OR_NULL(mp)) {
+ if (PTR_ERR(mp) != -ENOENT) {
+ err = PTR_ERR(mp);
+ pr_warn("Error getting XDP status for interface %s: %s\n",
+ iface->ifname, strerror(-err));
+ goto out;
+ }
+ mp = NULL;
+ }
+ print_iface_status(iface, mp, NULL);
+ } else {
+ err = iterate_iface_multiprogs(print_iface_status, NULL);
+ }
+ printf("\n");
+out:
+ return err;
+}