Reputation: 11
Yes, understand similar questions had been posted several times before, such as
But after all been said and done, we still fell short of reaching an affirmative answer as to how this can be done.
Recently came across an article titled "eBPF application development: Beyond the basics" from developers.redhat.com, https://developers.redhat.com/articles/2023/10/19/ebpf-application-development-beyond-basics.
Most interestingly, among a wealth of takeaway points is an illustration of the architecture of a running eBPF program in Figure 6, which I hope could help to get my question across.
Architecture of a running BPF program
As the old saying goes, a picture is worth more than a thousand words. So my question is:
Regarding the architecture diagram concerned, given the user space program app1 used the bpf() system call with BPF_MAP_CREATE to create "map 1", how can SEC("tc")tc_rx_main(__sk_buf* ctx) of app1.bpf access map 1 from the kernel?
Thanks a lot in advance.
Upvotes: 0
Views: 642
Reputation: 11
Let's recap the architecture diagram as the context of the question.
Architecture of a running eBPF program
To achieve the illustrated result of getting both app1.o and app1.bpf.o to access "map 1", here are the key points to spell out and bear in mind when coding app1.c and app1.bpf.c.
(1) First and foremost, "map 1" must be defined in SEC(.maps) in app1.bpf.c, without which nothing is possible. Here is an example.
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, uint32_t);
__type(value, uint8_t);
} map_1 SEC(".maps");
(2) Creation of map_1 is driven by the user space program, app1.c, where the bpf() syscall for BPF_MAP_CREATE is invoked implicitly via a skeleton function like app1_bpf__open_and_load(), or functions like bpf_object__open() & bpf_object__load() defined in <bpf/libbpf.h>. A map fd is returned to the user space afterward to facilitate access to the map, e.g.
struct app1_bpf* app1 = app1_bpf__open_and_load();
int map_fd = bpf_object__find_map_fd_by_name(app1->obj, "map_1");
Alternatively, the libbpf function bpf_object__find_map_by_name() can be used in app1.c to return a pointer to struct bpf_map for map_1. e.g.
struct bpf_map* map_ptr = bpf_object__find_map_by_name(app1->obj, "map_1");
(3) In the kernel space, &map_1 is used directly in app1.bpf.c to perform CRUD on map_1. The CRUD functions are defined in vmlinux.h or <bpf/bpf_helpers.h>, e.g.
bpf_map_lookup_elem(&map_1, &key);
bpf_map_update_elem(&map_1, &key, &value, BPF_ANY);
(4) Meanwhile, there are two options to perform CRUD on map_1 in app1.c.
- Option 1: Through int map_fd by functions defined in <bpf/bpf.h>, e.g.
bpf_map_lookup_elem(map_fd, &key);
bpf_map_update_elem(map_fd, &key, &value, BPF_ANY);
- Option 2: Through struct bpf_map* map_ptr by functions defined in <bpf/libbpf.h>, e.g
bpf_map__update_elem(map_ptr, &key, sizeof(unsigned int), &value, sizeof(unsigned char), BPF_ANY);
bpf_map__update_elem(map_ptr, &key, sizeof(unsigned int), &value, sizeof(unsigned char), BPF_ANY);
Bear in mind that in the end, both options come down to calling the bpf() syscall with the appropriate command types on map_fd to implement CRUD in the user space.
Upvotes: 1
Reputation: 7978
How can an eBPF program within the kernel access a map created by a user space program?
Regarding the architecture diagram concerned, given the user space program app1 used the bpf() system call with BPF_MAP_CREATE to create "map 1", how can SEC("tc")tc_rx_main(__sk_buf* ctx) of app1.bpf access map 1 from the kernel?
All maps are always created by userspace. A BPF program can't create maps on its own. Whenever you define a map in your eBPF program, the map definition ends up in the maps
/.maps
section of the generated ELF.
The loader program in userspace reads all map definitions and executes BPF_MAP_CREATE
syscalls to actually create the maps. This results in the map creation and it gives the userspace program a file descriptor to these maps.
The ELF also contains your program in their own sections. Whenever a map is referenced in the code, for example with a bpf_map_lookup_elem(&my_map.....
, the compiler creates an eBPF instruction to load an 64-bit number into a register. It also makes a ELF relocation entry for that instruction and the map name.
The loader goes over the eBPF bytecode and will set the value of these load instructions equal to the file descriptors given it got while creating the maps. This modified eBPF bytecode is then loaded into the kernel.
The kernel will then during it verification and JITing process turn these file descriptors into the actual memory address of the map which will be used in the machine code to interact with the map.
The userspace process can still use the file descriptor to access the map via syscalls, thus the shared map access.
Most of this work is done automatically by loader libraries, however, you can often manually do this in a few steps, giving you the opportunity to change map definitions before loading, or using specific already existing maps instead of creating new maps to share maps between programs.
This is a talk I did last year, that goes into a the exact details: https://archive.fosdem.org/2023/schedule/event/bpf_loader/
Upvotes: 0