diff --git a/basic-solutions/xdp_loader.c b/basic-solutions/xdp_loader.c index 04edd840..53a86397 100644 --- a/basic-solutions/xdp_loader.c +++ b/basic-solutions/xdp_loader.c @@ -45,8 +45,8 @@ static const struct option_wrapper long_options[] = { {{"force", no_argument, NULL, 'F' }, "Force install, replacing existing program on interface"}, - {{"unload", no_argument, NULL, 'U' }, - "Unload XDP program instead of loading"}, + {{"unload", required_argument, NULL, 'U' }, + "Unload XDP program instead of loading", ""}, {{"reuse-maps", no_argument, NULL, 'M' }, "Reuse pinned maps"}, @@ -70,21 +70,95 @@ static const struct option_wrapper long_options[] = { const char *pin_basedir = "/sys/fs/bpf"; const char *map_name = "xdp_stats_map"; -/* Pinning maps under /sys/fs/bpf in subdir */ + +/* Load BPF and XDP program with map reuse using libxdp */ +struct xdp_program *load_bpf_and_xdp_attach_reuse_maps(struct config *cfg, bool *map_reused) +{ + struct xdp_program *prog; + struct bpf_map *map; + char map_path[PATH_MAX]; + int len, pinned_map_fd; + int err; + + if (!cfg || !cfg->filename[0] || !cfg->progname[0] || !map_reused) { + fprintf(stderr, "ERR: invalid arguments\n"); + return NULL; + } + + *map_reused = false; + + /* 1) Create XDP program through libxdp */ + DECLARE_LIBXDP_OPTS(xdp_program_opts, xdp_opts, + .prog_name = cfg->progname, + .open_filename = cfg->filename); + + prog = xdp_program__create(&xdp_opts); + if (!prog) { + err = errno; + fprintf(stderr, "ERR: xdp_program__create: %s\n", strerror(err)); + goto out; + } + + /* 2) Get BPF object from xdp_program and reuse the specific map + * At this point: BPF object and maps have not been loaded into the kernel + */ + if (cfg->reuse_maps) { + map = bpf_object__find_map_by_name(xdp_program__bpf_obj(prog), map_name); + if (!map) { + fprintf(stderr, "ERR: Map %s not found!\n", map_name); + goto out; + } + + len = snprintf(map_path, PATH_MAX, "%s/%s", cfg->pin_dir, map_name); + if (len < 0 || len >= PATH_MAX) { + fprintf(stderr, "ERR: map path too long\n"); + goto out; + } + + pinned_map_fd = bpf_obj_get(map_path); + if (pinned_map_fd >= 0) { + err = bpf_map__reuse_fd(map, pinned_map_fd); + if (err) { + close(pinned_map_fd); + fprintf(stderr, "ERR: bpf_map__reuse_fd: %s\n", strerror(-err)); + goto out; + } + *map_reused = true; + if (verbose) + printf(" - Reusing pinned map: %s\n", map_path); + } + } + + /* 3) Attach XDP program to interface + * BPF object will be loaded into the kernel as part of XDP attachment + */ + err = xdp_program__attach(prog, cfg->ifindex, cfg->attach_mode, 0); + if (err) { + fprintf(stderr, "ERR: xdp_program__attach: %s\n", strerror(-err)); + goto out; + } + + return prog; + +out: + xdp_program__close(prog); + return NULL; +} + +/* Pinning maps under /sys/fs/bpf */ 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", - cfg->pin_dir, cfg->ifname, map_name); + len = snprintf(map_filename, PATH_MAX, "%s/%s", cfg->pin_dir, 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 (access(map_filename, F_OK) != -1 ) { if (verbose) printf(" - Unpinning (remove) prev maps in %s/\n", cfg->pin_dir); @@ -109,10 +183,36 @@ int pin_maps_in_bpf_object(struct bpf_object *bpf_obj, struct config *cfg) return 0; } +/* Unpinning map under /sys/fs/bpf */ +void unpin_map(struct config *cfg) +{ + char map_path[PATH_MAX]; + int len; + + len = snprintf(map_path, PATH_MAX, "%s/%s", cfg->pin_dir, map_name); + if (len < 0) { + fprintf(stderr, "ERR: creating map filename for unpin\n"); + return; + } + + /* If the map file exists, unpin it */ + if (access(map_path, F_OK) == 0) { + if (verbose) + printf(" - Unpinning map %s\n", map_path); + + /* Use unlink to remove the pinned map file */ + if (unlink(map_path)) { + fprintf(stderr, "ERR: Failed to unpin map %s: %s\n", + map_path, strerror(errno)); + } + } +} + int main(int argc, char **argv) { struct xdp_program *program; int err, len; + bool map_reused = false; struct config cfg = { .attach_mode = XDP_MODE_NATIVE, @@ -130,12 +230,6 @@ int main(int argc, char **argv) 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); */ - } /* Initialize the pin_dir configuration */ len = snprintf(cfg.pin_dir, 512, "%s/%s", pin_basedir, cfg.ifname); @@ -144,10 +238,30 @@ int main(int argc, char **argv) return EXIT_FAIL_OPTION; } + if (cfg.do_unload) { + if (!cfg.reuse_maps) { + unpin_map(&cfg); + } + + /* unload the program */ + err = do_unload(&cfg); + if (err) { + char errmsg[1024]; + libxdp_strerror(err, errmsg, sizeof(errmsg)); + fprintf(stderr, "Couldn't unload XDP program: %s\n", errmsg); + return err; + } + + printf("Success: Unloaded XDP program\n"); + return EXIT_OK; + } - program = load_bpf_and_xdp_attach(&cfg); - if (!program) - return EXIT_FAIL_BPF; + /* Try to reuse existing pinned maps before loading */ + program = load_bpf_and_xdp_attach_reuse_maps(&cfg, &map_reused); + if (!program) { + err = EXIT_FAIL_BPF; + goto out; + } if (verbose) { printf("Success: Loaded BPF-object(%s) and used program(%s)\n", @@ -156,14 +270,18 @@ int main(int argc, char **argv) cfg.ifname, cfg.ifindex); } - /* Use the --dev name as subdir for exporting/pinning maps */ - if (!cfg.reuse_maps) { + if (cfg.reuse_maps && !map_reused) { + /* Use the --dev name as subdir for exporting/pinning maps */ err = pin_maps_in_bpf_object(xdp_program__bpf_obj(program), &cfg); if (err) { fprintf(stderr, "ERR: pinning maps\n"); - return err; + goto out; } } - return EXIT_OK; + err = EXIT_OK; + +out: + xdp_program__close(program); + return err; } diff --git a/basic04-pinning-maps/README.org b/basic04-pinning-maps/README.org index 3c792610..d5e29d43 100644 --- a/basic04-pinning-maps/README.org +++ b/basic04-pinning-maps/README.org @@ -113,18 +113,50 @@ reloading via =xdp_loader=, else it will be watching the wrong FD. *** Reusing maps with libbpf -The libbpf library can *reuse and replace* a map with an existing map file -descriptor, via the libbpf API call: =bpf_map__reuse_fd()=. But you cannot -use =bpf_prog_load()= for this; instead you have to code it yourself, as you -need a step in-between =bpf_object__open()= and =bpf_object__load=. The -basic steps needed looks like: +Sometimes you want multiple XDP programs to share the same map +(for example, a stats map pinned in /sys/fs/bpf/…). +The libbpf API provides a way to *reuse and replace* the map inside +your BPF object with an already existing pinned map, using: + +=bpf_map__reuse_fd()= + +This call must be made *after* the object has been opened +with =bpf_object__open()= but *before* it is loaded +with =bpf_object__load()=. + +When using the higher-level XDP program APIs: + +- =xdp_program__create()= → internally calls =bpf_object__open()= +- =xdp_program__attach()= → internally calls =bpf_object__load()= + +Therefore, you need to inject the =bpf_map__reuse_fd()= step in between, +by getting the underlying =bpf_object= from the XDP program. + +Here is a minimal example: #+begin_src C - int pinned_map_fd = bpf_obj_get("/sys/fs/bpf/veth0/xdp_stats_map"); - struct bpf_object *obj = bpf_object__open(cfg.filename); - struct bpf_map *map = bpf_object__find_map_by_name(obj, "xdp_stats_map"); - bpf_map__reuse_fd(map, pinned_map_fd); - bpf_object__load(obj); +struct xdp_program *prog; +struct bpf_object *bpf_obj; +struct bpf_map *map; +int pinned_map_fd; + +/* 1. Create program (opens bpf_object) */ +prog = xdp_program__create(&xdp_opts); + +/* 2. Access the underlying bpf_object */ +bpf_obj = xdp_program__bpf_obj(prog); + +/* 3. Look up the map in the object */ +map = bpf_object__find_map_by_name(bpf_obj, "xdp_stats_map"); + +/* 4. Get FD of the pinned map */ +pinned_map_fd = bpf_obj_get("/sys/fs/bpf/veth0/xdp_stats_map"); + +/* 5. Reuse pinned FD instead of creating a new map */ +bpf_map__reuse_fd(map, pinned_map_fd); + +/* 6. Now attach program (this will load the object) */ +xdp_program__attach(prog, ifindex, attach_mode, 0); #+end_src (Hint: see [[#assignment2-xdp_loaderc-reuse-pinned-map][Assignment 2]]) @@ -147,9 +179,7 @@ BPF program ID. ** Assignment 2: (xdp_loader.c) reuse pinned map -As mentioned above, libbpf can reuse and replace a map with an existing map, -it just requires writing your own =bpf_prog_load()= (or -=bpf_prog_load_xattr=). +As mentioned above, libbpf can reuse and replace a map with an existing map. The *assignment* is to check in [[file:xdp_loader.c][xdp_loader]] if there already is a pinned version of the map "xdp_stats_map" and use libbpf =bpf_map__reuse_fd()= API diff --git a/basic04-pinning-maps/xdp_loader.c b/basic04-pinning-maps/xdp_loader.c index 457afbc5..716c4b91 100644 --- a/basic04-pinning-maps/xdp_loader.c +++ b/basic04-pinning-maps/xdp_loader.c @@ -22,7 +22,6 @@ static const char *__doc__ = "XDP loader\n" #include "../common/common_params.h" #include "../common/common_user_bpf_xdp.h" #include "../common/common_libbpf.h" -#include "common_kern_user.h" static const char *default_filename = "xdp_prog_kern.o"; @@ -46,8 +45,8 @@ static const struct option_wrapper long_options[] = { {{"force", no_argument, NULL, 'F' }, "Force install, replacing existing program on interface"}, - {{"unload", no_argument, NULL, 'U' }, - "Unload XDP program instead of loading"}, + {{"unload", required_argument, NULL, 'U' }, + "Unload XDP program instead of loading", ""}, {{"quiet", no_argument, NULL, 'q' }, "Quiet mode (no output)"}, @@ -68,54 +67,73 @@ static const struct option_wrapper long_options[] = { 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, const char *subdir) +/* Pinning maps under /sys/fs/bpf */ +int pin_maps_in_bpf_object(struct bpf_object *bpf_obj, struct config *cfg) { char map_filename[PATH_MAX]; - char pin_dir[PATH_MAX]; int err, len; - len = snprintf(pin_dir, PATH_MAX, "%s/%s", pin_basedir, subdir); - if (len < 0) { - fprintf(stderr, "ERR: creating pin dirname\n"); - return EXIT_FAIL_OPTION; - } - - len = snprintf(map_filename, PATH_MAX, "%s/%s/%s", - pin_basedir, subdir, map_name); + len = snprintf(map_filename, PATH_MAX, "%s/%s", cfg->pin_dir, 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 (access(map_filename, F_OK) != -1 ) { if (verbose) printf(" - Unpinning (remove) prev maps in %s/\n", - pin_dir); + cfg->pin_dir); /* Basically calls unlink(3) on map_filename */ - err = bpf_object__unpin_maps(bpf_obj, pin_dir); + err = bpf_object__unpin_maps(bpf_obj, cfg->pin_dir); if (err) { - fprintf(stderr, "ERR: UNpinning maps in %s\n", pin_dir); + fprintf(stderr, "ERR: UNpinning maps in %s\n", cfg->pin_dir); return EXIT_FAIL_BPF; } } if (verbose) - printf(" - Pinning maps in %s/\n", pin_dir); + 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, pin_dir); - if (err) + err = bpf_object__pin_maps(bpf_obj, cfg->pin_dir); + if (err) { + fprintf(stderr, "ERR: Pinning maps in %s\n", cfg->pin_dir); return EXIT_FAIL_BPF; + } return 0; } +/* Unpinning map under /sys/fs/bpf */ +void unpin_map(struct config *cfg) +{ + char map_path[PATH_MAX]; + int len; + + len = snprintf(map_path, PATH_MAX, "%s/%s", cfg->pin_dir, map_name); + if (len < 0) { + fprintf(stderr, "ERR: creating map filename for unpin\n"); + return; + } + + /* If the map file exists, unpin it */ + if (access(map_path, F_OK) == 0) { + if (verbose) + printf(" - Unpinning map %s\n", map_path); + + /* Use unlink to remove the pinned map file */ + if (unlink(map_path)) { + fprintf(stderr, "ERR: Failed to unpin map %s: %s\n", + map_path, strerror(errno)); + } + } +} + int main(int argc, char **argv) { struct xdp_program *program; - int err; + int err, len; struct config cfg = { .attach_mode = XDP_MODE_NATIVE, @@ -133,14 +151,35 @@ int main(int argc, char **argv) usage(argv[0], __doc__, long_options, (argc == 1)); return EXIT_FAIL_OPTION; } + + /* Initialize the pin_dir configuration */ + len = snprintf(cfg.pin_dir, 512, "%s/%s", pin_basedir, cfg.ifname); + if (len < 0) { + fprintf(stderr, "ERR: creating pin dirname\n"); + return EXIT_FAIL_OPTION; + } + if (cfg.do_unload) { - /* TODO: Miss unpin of maps on unload */ - /* return xdp_link_detach(cfg.ifindex, cfg.xdp_flags, 0); */ + unpin_map(&cfg); + + /* unload the program */ + err = do_unload(&cfg); + if (err) { + char errmsg[1024]; + libxdp_strerror(err, errmsg, sizeof(errmsg)); + fprintf(stderr, "Couldn't unload XDP program: %s\n", errmsg); + return err; + } + + printf("Success: Unloaded XDP program\n"); + return EXIT_OK; } program = load_bpf_and_xdp_attach(&cfg); - if (!program) - return EXIT_FAIL_BPF; + if (!program) { + err = EXIT_FAIL_BPF; + goto out; + } if (verbose) { printf("Success: Loaded BPF-object(%s) and used program(%s)\n", @@ -150,11 +189,15 @@ int main(int argc, char **argv) } /* Use the --dev name as subdir for exporting/pinning maps */ - err = pin_maps_in_bpf_object(xdp_program__bpf_obj(program), cfg.ifname); + err = pin_maps_in_bpf_object(xdp_program__bpf_obj(program), &cfg); if (err) { fprintf(stderr, "ERR: pinning maps\n"); - return err; + goto out; } - return EXIT_OK; + err = EXIT_OK; + +out: + xdp_program__close(program); + return err; } diff --git a/common/common_user_bpf_xdp.c b/common/common_user_bpf_xdp.c index cd21f0b5..aae6cd60 100644 --- a/common/common_user_bpf_xdp.c +++ b/common/common_user_bpf_xdp.c @@ -99,6 +99,7 @@ struct xdp_program *load_bpf_and_xdp_attach(struct config *cfg) /* if (cfg->xdp_flags & XDP_FLAGS_HW_MODE) */ /* offload_ifindex = cfg->ifindex; */ + /* BPF program from cfg->filename will be opened and loaded in user space */ struct xdp_program *prog = xdp_program__create(&xdp_opts); err = libxdp_get_error(prog); if (err) { @@ -107,17 +108,8 @@ struct xdp_program *load_bpf_and_xdp_attach(struct config *cfg) fprintf(stderr, "ERR: loading program: %s\n", errmsg); exit(EXIT_FAIL_BPF); } - - /* At this point: All XDP/BPF programs from the cfg->filename have been - * loaded into the kernel, and evaluated by the verifier. Only one of - * these gets attached to XDP hook, the others will get freed once this - * process exit. - */ - - /* At this point: BPF-progs are (only) loaded by the kernel, and prog_fd - * is our select file-descriptor handle. Next step is attaching this FD - * to a kernel hook point, in this case XDP net_device link-level hook. - */ + + /* BPF program will be loaded into the kernel and attached to network interface */ err = xdp_program__attach(prog, cfg->ifindex, cfg->attach_mode, 0); if (err) exit(err);