Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 137 additions & 19 deletions basic-solutions/xdp_loader.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <id> instead of loading", "<id>"},

{{"reuse-maps", no_argument, NULL, 'M' },
"Reuse pinned maps"},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove the reuse-maps option?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current version's reuse-map is used for map pinning.
This does not look like what is needed for the assignment 2.
So I delete it completely as the assignment does not mention to create a on/off switch.

	if (!cfg.reuse_maps) {
		err = pin_maps_in_bpf_object(xdp_program__bpf_obj(program), &cfg);
		if (err) {
			fprintf(stderr, "ERR: pinning maps\n");
			return err;
		}
	}

If a flag is needed, I can add it back and put it around map-reuse code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the intention of the option is: If the option set, try to reuse maps if they already exist, and if not, pin them. If the option is not set, do neither (no reuse, no pinning)...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That make sense!
I just had a new push to implement above logic. Thanks for the explanation.

Expand All @@ -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);
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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",
Expand All @@ -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;
}
56 changes: 43 additions & 13 deletions basic04-pinning-maps/README.org
Original file line number Diff line number Diff line change
Expand Up @@ -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]])
Expand All @@ -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
Expand Down
Loading