summaryrefslogtreecommitdiff
path: root/src/xdp/utils
diff options
context:
space:
mode:
authorChristian Poessinger <christian@poessinger.com>2020-12-18 17:13:13 +0100
committerChristian Poessinger <christian@poessinger.com>2020-12-19 21:56:19 +0100
commitb5c80d310527223b93e4133ac2f4c8c063c70a98 (patch)
tree48ae9e2752b4b94c07f767a019c99a8c2460c014 /src/xdp/utils
parent22308b05dfbcd8da0230bbd8ba3bbc2a55da7786 (diff)
downloadvyos-1x-b5c80d310527223b93e4133ac2f4c8c063c70a98.tar.gz
vyos-1x-b5c80d310527223b93e4133ac2f4c8c063c70a98.zip
xdp: T2666: switch to example code provided by xdp-tutorial
Diffstat (limited to 'src/xdp/utils')
-rw-r--r--src/xdp/utils/Makefile11
-rwxr-xr-xsrc/xdp/utils/xdp_loaderbin0 -> 683072 bytes
-rw-r--r--src/xdp/utils/xdp_loader.c165
-rwxr-xr-xsrc/xdp/utils/xdp_statsbin0 -> 687784 bytes
-rw-r--r--src/xdp/utils/xdp_stats.c295
5 files changed, 471 insertions, 0 deletions
diff --git a/src/xdp/utils/Makefile b/src/xdp/utils/Makefile
new file mode 100644
index 000000000..42d6e63e3
--- /dev/null
+++ b/src/xdp/utils/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+
+USER_TARGETS := xdp_stats xdp_loader
+
+LIBBPF_DIR = ../libbpf/src/
+COMMON_DIR = ../common/
+
+# Extend with another COMMON_OBJS
+COMMON_OBJS += $(COMMON_DIR)/common_libbpf.o
+
+include $(COMMON_DIR)/common.mk
diff --git a/src/xdp/utils/xdp_loader b/src/xdp/utils/xdp_loader
new file mode 100755
index 000000000..887a7788b
--- /dev/null
+++ b/src/xdp/utils/xdp_loader
Binary files differ
diff --git a/src/xdp/utils/xdp_loader.c b/src/xdp/utils/xdp_loader.c
new file mode 100644
index 000000000..d3b05d0c0
--- /dev/null
+++ b/src/xdp/utils/xdp_loader.c
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+static const char *__doc__ = "XDP loader\n"
+ " - Allows selecting BPF section --progsec name to XDP-attach to --dev\n";
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include <locale.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+
+#include <net/if.h>
+#include <linux/if_link.h> /* depend on kernel-headers installed */
+
+#include "../common/common_params.h"
+#include "../common/common_user_bpf_xdp.h"
+#include "../common/common_libbpf.h"
+
+static const char *default_filename = "xdp_prog_kern.o";
+
+static const struct option_wrapper long_options[] = {
+
+ {{"help", no_argument, NULL, 'h' },
+ "Show help", false},
+
+ {{"dev", required_argument, NULL, 'd' },
+ "Operate on device <ifname>", "<ifname>", true},
+
+ {{"skb-mode", no_argument, NULL, 'S' },
+ "Install XDP program in SKB (AKA generic) mode"},
+
+ {{"native-mode", no_argument, NULL, 'N' },
+ "Install XDP program in native mode"},
+
+ {{"auto-mode", no_argument, NULL, 'A' },
+ "Auto-detect SKB or native mode"},
+
+ {{"force", no_argument, NULL, 'F' },
+ "Force install, replacing existing program on interface"},
+
+ {{"unload", no_argument, NULL, 'U' },
+ "Unload XDP program instead of loading"},
+
+ {{"reuse-maps", no_argument, NULL, 'M' },
+ "Reuse pinned maps"},
+
+ {{"quiet", no_argument, NULL, 'q' },
+ "Quiet mode (no output)"},
+
+ {{"filename", required_argument, NULL, 1 },
+ "Load program from <file>", "<file>"},
+
+ {{"progsec", required_argument, NULL, 2 },
+ "Load program in <section> of the ELF file", "<section>"},
+
+ {{0, 0, NULL, 0 }, NULL, false}
+};
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+const char *pin_basedir = "/sys/fs/bpf";
+const char *map_name = "xdp_stats_map";
+
+/* Pinning maps under /sys/fs/bpf in subdir */
+int pin_maps_in_bpf_object(struct bpf_object *bpf_obj, struct config *cfg)
+{
+ char map_filename[PATH_MAX];
+ int err, len;
+
+ len = snprintf(map_filename, PATH_MAX, "%s/%s/%s",
+ pin_basedir, cfg->ifname, map_name);
+ if (len < 0) {
+ fprintf(stderr, "ERR: creating map_name\n");
+ return EXIT_FAIL_OPTION;
+ }
+
+ /* Existing/previous XDP prog might not have cleaned up */
+ if (access(map_filename, F_OK ) != -1 ) {
+ if (verbose)
+ printf(" - Unpinning (remove) prev maps in %s/\n",
+ cfg->pin_dir);
+
+ /* Basically calls unlink(3) on map_filename */
+ err = bpf_object__unpin_maps(bpf_obj, cfg->pin_dir);
+ if (err) {
+ fprintf(stderr, "ERR: UNpinning maps in %s\n", cfg->pin_dir);
+ return EXIT_FAIL_BPF;
+ }
+ }
+ if (verbose)
+ printf(" - Pinning maps in %s/\n", cfg->pin_dir);
+
+ /* This will pin all maps in our bpf_object */
+ err = bpf_object__pin_maps(bpf_obj, cfg->pin_dir);
+ if (err)
+ return EXIT_FAIL_BPF;
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ struct bpf_object *bpf_obj;
+ int err, len;
+
+ struct config cfg = {
+ .xdp_flags = XDP_FLAGS_UPDATE_IF_NOEXIST | XDP_FLAGS_DRV_MODE,
+ .ifindex = -1,
+ .do_unload = false,
+ };
+ /* Set default BPF-ELF object file and BPF program name */
+ strncpy(cfg.filename, default_filename, sizeof(cfg.filename));
+ /* Cmdline options can change progsec */
+ parse_cmdline_args(argc, argv, long_options, &cfg, __doc__);
+
+ /* Required option */
+ if (cfg.ifindex == -1) {
+ fprintf(stderr, "ERR: required option --dev missing\n\n");
+ usage(argv[0], __doc__, long_options, (argc == 1));
+ return EXIT_FAIL_OPTION;
+ }
+ if (cfg.do_unload) {
+ if (!cfg.reuse_maps) {
+ /* TODO: Miss unpin of maps on unload */
+ }
+ return xdp_link_detach(cfg.ifindex, cfg.xdp_flags, 0);
+ }
+
+ len = snprintf(cfg.pin_dir, PATH_MAX, "%s/%s", pin_basedir, cfg.ifname);
+ if (len < 0) {
+ fprintf(stderr, "ERR: creating pin dirname\n");
+ return EXIT_FAIL_OPTION;
+ }
+
+
+ bpf_obj = load_bpf_and_xdp_attach(&cfg);
+ if (!bpf_obj)
+ return EXIT_FAIL_BPF;
+
+ if (verbose) {
+ printf("Success: Loaded BPF-object(%s) and used section(%s)\n",
+ cfg.filename, cfg.progsec);
+ printf(" - XDP prog attached on device:%s(ifindex:%d)\n",
+ cfg.ifname, cfg.ifindex);
+ }
+
+ /* Use the --dev name as subdir for exporting/pinning maps */
+ if (!cfg.reuse_maps) {
+ err = pin_maps_in_bpf_object(bpf_obj, &cfg);
+ if (err) {
+ fprintf(stderr, "ERR: pinning maps\n");
+ return err;
+ }
+ }
+
+ return EXIT_OK;
+}
diff --git a/src/xdp/utils/xdp_stats b/src/xdp/utils/xdp_stats
new file mode 100755
index 000000000..fafb10cd1
--- /dev/null
+++ b/src/xdp/utils/xdp_stats
Binary files differ
diff --git a/src/xdp/utils/xdp_stats.c b/src/xdp/utils/xdp_stats.c
new file mode 100644
index 000000000..5dbec8351
--- /dev/null
+++ b/src/xdp/utils/xdp_stats.c
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+static const char *__doc__ = "XDP stats program\n"
+ " - Finding xdp_stats_map via --dev name info\n";
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+
+#include <locale.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <bpf/bpf.h>
+/* Lesson#1: this prog does not need to #include <bpf/libbpf.h> as it only uses
+ * the simple bpf-syscall wrappers, defined in libbpf #include<bpf/bpf.h>
+ */
+
+#include <net/if.h>
+#include <linux/if_link.h> /* depend on kernel-headers installed */
+
+#include "../common/common_params.h"
+#include "../common/common_user_bpf_xdp.h"
+#include "../common/xdp_stats_kern_user.h"
+
+#include "../include/bpf_util.h" /* bpf_num_possible_cpus */
+
+static const struct option_wrapper long_options[] = {
+ {{"help", no_argument, NULL, 'h' },
+ "Show help", false},
+
+ {{"dev", required_argument, NULL, 'd' },
+ "Operate on device <ifname>", "<ifname>", true},
+
+ {{"quiet", no_argument, NULL, 'q' },
+ "Quiet mode (no output)"},
+
+ {{0, 0, NULL, 0 }}
+};
+
+#define NANOSEC_PER_SEC 1000000000 /* 10^9 */
+static __u64 gettime(void)
+{
+ struct timespec t;
+ int res;
+
+ res = clock_gettime(CLOCK_MONOTONIC, &t);
+ if (res < 0) {
+ fprintf(stderr, "Error with gettimeofday! (%i)\n", res);
+ exit(EXIT_FAIL);
+ }
+ return (__u64) t.tv_sec * NANOSEC_PER_SEC + t.tv_nsec;
+}
+
+struct record {
+ __u64 timestamp;
+ struct datarec total; /* defined in common_kern_user.h */
+};
+
+struct stats_record {
+ struct record stats[XDP_ACTION_MAX];
+};
+
+static double calc_period(struct record *r, struct record *p)
+{
+ double period_ = 0;
+ __u64 period = 0;
+
+ period = r->timestamp - p->timestamp;
+ if (period > 0)
+ period_ = ((double) period / NANOSEC_PER_SEC);
+
+ return period_;
+}
+
+static void stats_print_header()
+{
+ /* Print stats "header" */
+ printf("%-12s\n", "XDP-action");
+}
+
+static void stats_print(struct stats_record *stats_rec,
+ struct stats_record *stats_prev)
+{
+ struct record *rec, *prev;
+ __u64 packets, bytes;
+ double period;
+ double pps; /* packets per sec */
+ double bps; /* bits per sec */
+ int i;
+
+ stats_print_header(); /* Print stats "header" */
+
+ /* Print for each XDP actions stats */
+ for (i = 0; i < XDP_ACTION_MAX; i++)
+ {
+ char *fmt = "%-12s %'11lld pkts (%'10.0f pps)"
+ " %'11lld Kbytes (%'6.0f Mbits/s)"
+ " period:%f\n";
+ const char *action = action2str(i);
+
+ rec = &stats_rec->stats[i];
+ prev = &stats_prev->stats[i];
+
+ period = calc_period(rec, prev);
+ if (period == 0)
+ return;
+
+ packets = rec->total.rx_packets - prev->total.rx_packets;
+ pps = packets / period;
+
+ bytes = rec->total.rx_bytes - prev->total.rx_bytes;
+ bps = (bytes * 8)/ period / 1000000;
+
+ printf(fmt, action, rec->total.rx_packets, pps,
+ rec->total.rx_bytes / 1000 , bps,
+ period);
+ }
+ printf("\n");
+}
+
+
+/* BPF_MAP_TYPE_ARRAY */
+void map_get_value_array(int fd, __u32 key, struct datarec *value)
+{
+ if ((bpf_map_lookup_elem(fd, &key, value)) != 0) {
+ fprintf(stderr,
+ "ERR: bpf_map_lookup_elem failed key:0x%X\n", key);
+ }
+}
+
+/* BPF_MAP_TYPE_PERCPU_ARRAY */
+void map_get_value_percpu_array(int fd, __u32 key, struct datarec *value)
+{
+ /* For percpu maps, userspace gets a value per possible CPU */
+ unsigned int nr_cpus = bpf_num_possible_cpus();
+ struct datarec values[nr_cpus];
+ __u64 sum_bytes = 0;
+ __u64 sum_pkts = 0;
+ int i;
+
+ if ((bpf_map_lookup_elem(fd, &key, values)) != 0) {
+ fprintf(stderr,
+ "ERR: bpf_map_lookup_elem failed key:0x%X\n", key);
+ return;
+ }
+
+ /* Sum values from each CPU */
+ for (i = 0; i < nr_cpus; i++) {
+ sum_pkts += values[i].rx_packets;
+ sum_bytes += values[i].rx_bytes;
+ }
+ value->rx_packets = sum_pkts;
+ value->rx_bytes = sum_bytes;
+}
+
+static bool map_collect(int fd, __u32 map_type, __u32 key, struct record *rec)
+{
+ struct datarec value;
+
+ /* Get time as close as possible to reading map contents */
+ rec->timestamp = gettime();
+
+ switch (map_type) {
+ case BPF_MAP_TYPE_ARRAY:
+ map_get_value_array(fd, key, &value);
+ break;
+ case BPF_MAP_TYPE_PERCPU_ARRAY:
+ map_get_value_percpu_array(fd, key, &value);
+ break;
+ default:
+ fprintf(stderr, "ERR: Unknown map_type(%u) cannot handle\n",
+ map_type);
+ return false;
+ break;
+ }
+
+ rec->total.rx_packets = value.rx_packets;
+ rec->total.rx_bytes = value.rx_bytes;
+ return true;
+}
+
+static void stats_collect(int map_fd, __u32 map_type,
+ struct stats_record *stats_rec)
+{
+ /* Collect all XDP actions stats */
+ __u32 key;
+
+ for (key = 0; key < XDP_ACTION_MAX; key++) {
+ map_collect(map_fd, map_type, key, &stats_rec->stats[key]);
+ }
+}
+
+static int stats_poll(const char *pin_dir, int map_fd, __u32 id,
+ __u32 map_type, int interval)
+{
+ struct bpf_map_info info = {};
+ struct stats_record prev, record = { 0 };
+
+ /* Trick to pretty printf with thousands separators use %' */
+ setlocale(LC_NUMERIC, "en_US");
+
+ /* Get initial reading quickly */
+ stats_collect(map_fd, map_type, &record);
+ usleep(1000000/4);
+
+ while (1) {
+ prev = record; /* struct copy */
+
+ map_fd = open_bpf_map_file(pin_dir, "xdp_stats_map", &info);
+ if (map_fd < 0) {
+ return EXIT_FAIL_BPF;
+ } else if (id != info.id) {
+ printf("BPF map xdp_stats_map changed its ID, restarting\n");
+ return 0;
+ }
+
+ stats_collect(map_fd, map_type, &record);
+ stats_print(&record, &prev);
+ sleep(interval);
+ }
+
+ return 0;
+}
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+const char *pin_basedir = "/sys/fs/bpf";
+
+int main(int argc, char **argv)
+{
+ const struct bpf_map_info map_expect = {
+ .key_size = sizeof(__u32),
+ .value_size = sizeof(struct datarec),
+ .max_entries = XDP_ACTION_MAX,
+ };
+ struct bpf_map_info info = { 0 };
+ char pin_dir[PATH_MAX];
+ int stats_map_fd;
+ int interval = 2;
+ int len, err;
+
+ struct config cfg = {
+ .ifindex = -1,
+ .do_unload = false,
+ };
+
+ /* Cmdline options can change progsec */
+ parse_cmdline_args(argc, argv, long_options, &cfg, __doc__);
+
+ /* Required option */
+ if (cfg.ifindex == -1) {
+ fprintf(stderr, "ERR: required option --dev missing\n\n");
+ usage(argv[0], __doc__, long_options, (argc == 1));
+ return EXIT_FAIL_OPTION;
+ }
+
+ /* Use the --dev name as subdir for finding pinned maps */
+ len = snprintf(pin_dir, PATH_MAX, "%s/%s", pin_basedir, cfg.ifname);
+ if (len < 0) {
+ fprintf(stderr, "ERR: creating pin dirname\n");
+ return EXIT_FAIL_OPTION;
+ }
+
+ for ( ;; ) {
+ stats_map_fd = open_bpf_map_file(pin_dir, "xdp_stats_map", &info);
+ if (stats_map_fd < 0) {
+ return EXIT_FAIL_BPF;
+ }
+
+ /* check map info, e.g. datarec is expected size */
+ err = check_map_fd_info(&info, &map_expect);
+ if (err) {
+ fprintf(stderr, "ERR: map via FD not compatible\n");
+ return err;
+ }
+ if (verbose) {
+ printf("\nCollecting stats from BPF map\n");
+ printf(" - BPF map (bpf_map_type:%d) id:%d name:%s"
+ " key_size:%d value_size:%d max_entries:%d\n",
+ info.type, info.id, info.name,
+ info.key_size, info.value_size, info.max_entries
+ );
+ }
+
+ err = stats_poll(pin_dir, stats_map_fd, info.id, info.type, interval);
+ if (err < 0)
+ return err;
+ }
+
+ return EXIT_OK;
+}