diff options
Diffstat (limited to 'src/xdp/utils')
-rw-r--r-- | src/xdp/utils/Makefile | 11 | ||||
-rwxr-xr-x | src/xdp/utils/xdp_loader | bin | 0 -> 683072 bytes | |||
-rw-r--r-- | src/xdp/utils/xdp_loader.c | 165 | ||||
-rwxr-xr-x | src/xdp/utils/xdp_stats | bin | 0 -> 687784 bytes | |||
-rw-r--r-- | src/xdp/utils/xdp_stats.c | 295 |
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 Binary files differnew file mode 100755 index 000000000..887a7788b --- /dev/null +++ b/src/xdp/utils/xdp_loader 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 Binary files differnew file mode 100755 index 000000000..fafb10cd1 --- /dev/null +++ b/src/xdp/utils/xdp_stats 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; +} |