Content uploaded by Niclas Hedam
Author content
All content in this area was uploaded by Niclas Hedam on May 26, 2023
Content may be subject to copyright.
PREPRINT - May 26, 2023
eBPF - From a Programmer’s Perspective
Niclas Hedam
IT University of Copenhagen
Denmark
nhed@itu.dk
ABSTRACT
eBPF allows software developers to write programs
that are executed in the kernel without requiring re-
compilation and system restart. These programs can
collect critical performance metrics when a kernel func-
tion is invoked. In this paper, we will describe and dis-
cuss the architecture of eBPF using libbpf as well as
the core components of it. We will look at key dier-
ences between eBPF programs and typical user-space
C programs. Lastly, we will look into some real-world
use-cases of eBPF. We will, however, not discuss perfor-
mance numbers or formal proofs. This paper is merely
a summary of countless hours of reading through eBPF
textbooks, blog posts, eBPF samples and kernel code.
This work is licensed under a Creative Commons “Attribution 4.0 International”
license.
1 ACKNOWLEDGEMENTS
I would like to extend thanks to Quentin Monnet, who
have contributed tremendously in verifying correctness
of the paper as well providing valuable feedback.
2 INTRODUCTION
Berkeley Packet Filter or BPF emerged as an ecient
network packet lter in 1992 [4, 13]. A network packet
lter is a network security mechanism for controlling
what ows from and to a network by inspecting pack-
ets as they pass through the lter. BPF was described
by the authors as 20 times faster than the state of the
art. BPF diered from previous systems by running
programs in a virtual machine built for register-based
CPUs and having per-application buers that did not
require copying all information to make a decision [4].
BPF became state of the art and was adopted as the
technology of choice for network packet ltering.
Alexei Starovoitov introduced eBPF in 2014 [17, 4] as
a redesign of BPF for modern hardware. The eBPF VM
is faster as it resembles the contemporary processors
more and thus allows eBPF instructions to be mapped
closely to the hardware instruction set architecture (ISA)
[8]. In Alexei’s commit from 2014 [17], eBPF was ex-
posed to user-space and soon after, eBPF stopped being
limited to the networking stack and over time, it became
much more broad and generic. eBPF makes it possible to
update the behaviour of the kernel without the need to
recompile it and to reboot the system, while oering a
simpler and safer interface than module programming.
eBPF is built with a static verier that ensures that
a program cannot cause a kernel crash and that it will
always terminate. After the program is compiled, the
eBPF verier checks that the program is safe to run [4,
5].
Before the kernel can run eBPF programs, it must
know where to attach it. The execution point is dened
by the eBPF program types, which will be described
later in this paper. The eBPF architecture also contains
maps, which are bidirectional data structures allowing
eBPF programs to asynchronously share data with user-
space [4].
In this paper we will look at eBPF mainly from the per-
spective of libbpf. While there exist other approaches,
libbpf is the recommended way to go with C programs.
We will rst look into the high-level architecture of
eBPF and libbpf and then shift towards some practical
examples and practical use cases.
3 ARCHITECTURE
Listing 1 is an example of an eBPF program that at-
taches to the kill system call. It can be used for security
and auditing purposes by logging and documenting
when processes are not gracefully terminated. Since
the eBPF program is running in the kernel, there is no
way of preventing this logging without having esca-
lated privileges on the system.
The SEC macro is used to tell the compiler to place
the bytecode in a specied ELF section. The section
name is later picked up by the loader, which then de-
duces the attach type. The section names are not a eBPF
1
PREPRINT - May 26, 2023
Niclas Hedam
1# i nc lu d e < li n ux / b p f .h >
2# i nc lu d e < bp f / bp f _h el p er s . h>
3
4struct syscalls_enter_kill_args {
5long long pa d ;
6
7long syscall_nr;
8long pi d ;
9long si g ;
10 };
11
12 SE C ( " t ra c ep oi n t / sy s ca l ls / s y s_ e nt e r_ k il l " )
13 int kill_example(st ruc t
syscalls_enter_kill_args *ctx) {
14
15 if ( c tx - > si g ! = 9) return 0;
16
17 char fm t [] = " P ID % u i s b ei n g k il l e d ! \ n" ;
18 bpf_trace_printk(fmt, s i ze o f ( fm t ) , ctx ->
pi d , si ze o f ( ctx - > pi d ) );
19
20 return 0;
21 }
22
23 char _ li c en s e [ ] SE C ( " li c en s e ")=" G PL " ;
Listing 1: A kill eBPF example written in C. This
example is of a tracepoint eBPF program (see
section 4.2.1).
convention, but a convention of the program loading
the eBPF program. In libbpf, other than the reserved
keywords such as maps, the probes are rst dened
by the type and then the hook. In listing 1 for exam-
ple, the program type is tracepoint and the hook is
syscalls/sys_enter_kill.
The SEC macro is dened in the
bpf/bpf_helpers.h
le in libbpf. When compiling the le, the SEC macro
is replaced by an __attribute__ statement, which is a
mechanism in GNU C to attach characteristics to func-
tion declarations [9].
We use
bpf_trace_printk
, which is dened in the
kernel, to print out trace information to the common
trace pipe
1
. This function oer printf -like functionality,
but in kernel-space. We will describe probe types in
more details in section 4.
All eBPF programs takes a context as parameter. For
tracing programs, the context contains information
about the information that the kernel is currently pro-
cessing including registers or function parameters [4].
1/sys/kernel/debug/tracing/trace_pipe
The context depends on the type of eBPF program as
well as the location of the probe. In the listing before, the
context is a
syscalls_enter_kill_args
struct, which
follows the format published by the kernel
2
. The rst
8 bytes are unused and should be ignored. We will de-
scribe the context parameter in more details in section
4.
In the bottom of listing 1, the license of the eBPF pro-
gram is declared. Since the kernel is licensed under GPL,
some eBPF programs are required to be GPL-compatible.
Other programs, like networking programs, do not have
to be GPL-compatible and may be under proprietary
licenses. Whether or not an eBPF program must be
GPL-compatible depends on, among other things, the
program type and the used helpers.
3.1 Scope
eBPF programs cannot call arbitrary kernel functions
[5]. This is a design choice, as it would bind the eBPF
program to specic kernel versions and thus complicate
compatibility. eBPF programs can, however, invoke a
set of helper functions oered by the kernel.
Examples of eBPF helper functions include
•Random number generation.
•Access to current time.
•Access to eBPF maps.
•Get process/cgroup context.
•Alter network packets.
eBPF programs can in principle invoke external li-
brary functions if they adhere to the requirements of
the verier, which are described in section 6, and if the
compiler is able to inline them in the code.
3.2 Compilation
The rest of this paper assumes that you have a working
eBPF environment to run the examples. If you have not
compiled or run an eBPF program on your system before,
read appendix A.
eBPF is a low-level language, an ISA, that can be
compiled from high-level languages such as Rust and C
[4, 7]. In this paper, we will focus on C and compilation
using clang and llc. The choice of clang over GCC is
rooted in the maturity of eBPF in the two compilers.
Clang have had a longer history with eBPF and it is
2
Published in
/sys/kernel/debug/tracing/events/syscalls/
sys_enter_kill/format
2
PREPRINT - May 26, 2023
eBPF - From a Programmer’s Perspective
therefore regarded as the tool of reference in the eBPF
community.
When using the clang/LLVM toolchain, one can com-
pile in one or two steps. Compiling rst to LLVM In-
termediate Representation (LLVM IR) and using llc in a
second step oers a ner control on the options passed
to llc. Compiling listing 1 in two steps can be done by
rst calling the following command.
$ cla ng -ta rg e t bpf - S -D _ _ BP F _TR ACI N G_ _
-I./libbpf/src/root/usr/include/ -Wall
-We rr o r -O2 - e mi t -l l vm -c - g kil l.c
We choose to compile with optimisation level 2 as this
is a necessary level for most eBPF programs. Without
it, suboptimal code with heavy stacks may be gener-
ated and some invoked functions may be referenced
incorrectly. We compile with the -S,-c and -emit-llvm
arguments to emit an LLVM IR le instead of a typical
object le. We set the target architecture to BPF to avoid
compiling with the native system architecture. Doing
so may produce invalid code or include invalid ELF sec-
tions. We furthermore compile with the -D argument,
which enables some functionality required by eBPF,
such as ASM_GOTO support. -I includes the libbpf li-
brary and the -Wall and -Werror arguments will stop
compilation if the eBPF program has any warnings. The
-g argument will emit source-level debug information,
which for example enables bpftool to read the contents
of eBPF maps in a structured manner.
$ ll c - m ar c h = bp f - f il e t yp e = o bj - o k er n .o
ker n. l l
When the IR le has been emitted, we convert it to
an eBPF object le using llc. The arguments here are
quite self-explanatory.
$ gcc -I./libbpf/src/root/usr/include/
-L./libbpf/src/ -o ebpf-kill-example
user.c \
-W l , - rp a th = . / li b bp f /s r c/ - lb pf - le lf
Since the loader will run in user-space, we can com-
pile this with gcc with typical arguments. We include
the libbpf library as before, and we tell the linker where
to look for the library at runtime using the -Wl,-rpath
argument.
When loading eBPF code, the Just-In-Time step trans-
lates the generic eBPF byte-code instructions into in-
structions specic for the machine [5]. This optimises
the execution speed of the program and makes it run
as eciently as natively compiled Linux code and code
loaded as modules. The generic eBPF byte-code is be-
ing translated after the program is veried to avoid
any overhead when executing the program [4]. The re-
sulting machine-code is then placed at the pre-dened
location next to kernel machine-code.
3.3 Loading
At a low-level, loading eBPF programs is done through
the bpf() system call. Various languages have libraries
wrapping around this call. For example, libbpf oers an
interface in C to work with eBPF. It oers functions to
build a struct bpf_object by reading the bytecode of a
program and the associated metadata (map information,
BTF information, etc.) from an ELF object le, and to
later reuse this object to manipulate, load, and attach
the eBPF programs and its related components.
In practise, loading eBPF programs can be done by in-
voking the
bpf_object__open_file
and
bpf_object_
_load
libbpf function with the name of the le. After
a successful load, the program can be attached with
the
bpf_program__attach
function. This takes a
bpf_
program
as parameter, which can be retrieved using the
bpf_object__find_program_by_name
helper. The by
name refers to the function name of the eBPF program.
One can also nd a program by title, which refers to
the declared ELF section described in section 3. Further-
more, the
bpf
syscall can be used to perform commands
on BPF maps or programs.
Listing 2 shows an example of a loader program, that
will load the program seen in listing 1. The while loop
will keep the eBPF program loaded while we listen to
the trace pipe, which is located at
/sys/kernel/debug/
tracing/trace_pipe.
Put very simply, eBPF programs are by default un-
loaded when the user-space program that loaded the
eBPF program terminates [18].
4 PROGRAM TYPES
In this section, we will describe a subset of the eBPF
programs types. The full list of program types can be
examined in appendix B.
4.1 Networking
Networking eBPF programs are used to read, modify,
retransmit, redirect or drop network packets. The ac-
tions that can be performed on the packet (cloning,
3
PREPRINT - May 26, 2023
Niclas Hedam
1# i nc lu d e < bp f / bp f . h >
2# i nc lu d e < bp f / li b bp f .h >
3# i nc lu d e < s td i o . h >
4# i nc lu d e < u ni s td . h >
5
6int main(int argc , char * * ar g v ) {
7char pa th [ 12 8 ];
8s pr i n tf ( p a th , " ki ll . o " );
9
10 struct bpf_object *obj;
11 struct b pf _ li n k * li n k = NU LL ;
12
13 int er r = - 1;
14
15 // O pe n e BP F obj ect w it h the p at h
16 ob j = b p f _o b j e ct _ _ o p en _ f i le ( pa th , NU L L ) ;
17 if ( libbpf_get_error(obj)) {
18 f pr i nt f ( s td e rr , " op en BP F o bj f a il e d \ n"
);
19 return er r ;
20 }
21
22 // F in d the p rog ra m w ith in t he ob j fi le
23 struct b pf _ pr o gr a m * p ro g =
24 bpf_object__find_program_by_name(obj,
" k il l _e xa m pl e " );
25 if ( ! p ro g ) {
26 f pr i nt f ( s td e rr , " pr o gr a m no t f ou n d \n " );
27 goto c le an u p ;
28 }
29
30 // L oa d the e BP F o bj ect i nt o the k ern el
31 if ( bpf_object__load(obj)) {
32 f pr i nt f ( s td e rr , " lo a di ng f ai l ed \ n ") ;
33 goto c le an u p ;
34 }
35
36 // A t ta ch th e p ro gra m to the tr a cep oin t
37 link = bpf_program__attach(prog);
38 if ( libbpf_get_error(link)) {
39 f pr i nt f ( s td e rr , " at ta c h f ai l ed \ n ") ;
40 link = NULL;
41 goto c le an u p ;
42 }
43
44 er r = 0;
45
46 while( 1 ) s le e p (1 ) ;
47
48 c le an u p :
49 b pf _ li n k __ d e st r oy ( li n k );
50 b pf _ o bj e c t_ _ c lo s e ( o bj ) ;
51
52 return er r ;
53 }
Listing 2: An example of an eBPF loader program.
retransmission, redirection, ...) and the amount of data
accessible from the context vary depending on the pro-
gram type.
4.1.1 Socket Filter Programs. The eBPF Socket Filter
type was the rst type to be added to the kernel [4]. This
type enables an eBPF program to attach to sockets and
read packets going through the socket. It also allows
truncation and dropping of packets.
4.1.2 XDP Programs. The eBPF XDP type enables eBPF
programs to inspect incoming network packets early
in the network stack [4]. This allows the the eBPF pro-
gram to drop the packet, before the kernel has used a
signicant amount of time on it. Furthermore, contrary
to DPDK, eBPF programs work with the kernel and can
benet from all it implements. It is also possible for
some network drivers to ooad XDP eBPF programs
directly to network interface cards (NIC).
XDP programs can return XDP_PASS to allow it to
continue to the next subsystem, XDP_DROP to drop
it or XDP_TX to forward it back to the NIC that orig-
inally received it. Lastly, an XDP program can return
XDP_REDIRECT to send the packet through a dierent
NIC and possibly bypass the normal network stack.
XDP is very well suited for ecient low-level ltering
such as a DDoS rewall.
4.2 Tracing
Tracing eBPF programs are used to debug or trace per-
formance of either the kernel or user-space applica-
tions.
4.2.1 Tracepoint Programs. The eBPF Tracepoint type
enables eBPF programs to attach to the tracepoint han-
dler provided by the kernel [4]. Tracepoints are static
marks in the kernel that can be used for tracing and
debugging purposes. All tracepoints are dened in the
/sys/kernel/debug/tracing/events directory.
When talking about tracepoints, it is important to
remember that these are dened as certain marks or
events in the kernel. A tracepoint can therefore not nec-
essarily be reduced to a specic location or function in
the kernel, but it tends to be much more stable between
dierent kernel versions.
The ’kill’ example from listing 1 is an example of a
tracepoint program.
4
PREPRINT - May 26, 2023
eBPF - From a Programmer’s Perspective
4.2.2 Raw Tracepoint Programs. The eBPF Raw Tra-
cepoint type works like the Tracepoint type, but can
access the tracepoint more directly [4]. For example,
the context parameter is no longer a struct with the
values, but instead a struct containing an array with
pointers to the arguments. This may yield more detailed
information about the kernel’s current task and comes
with a performance increase, as the kernel can skip
argument processing.
4.2.3 Kprobe Programs. The eBPF Kprobe type enables
eBPF programs to dynamically attach to any function in
the kernel [4]. Kprobe programs dier from tracepoints
in the section header and the context parameter. Kprobe
programs are used for tracing in the situations where
no suitable tracepoint exist. The important dierence
between kprobes and tracepoints is that tracepoints are
statically dened in the kernel while kprobes can be
placed in any named function in the kernel. Due to this,
kprobes are also more likely to break between dierent
kernel versions, because the functions or structs may
change.
Since tracepoints are statically dened, it is much
easier to extract contextual information. In listing 1
for example, we can access information about the the
syscall from a struct that is passed to the eBPF program.
Since Kprobe programs can hook into any kernel func-
tion, the context parameter is dierent from tracepoints.
Instead, the parameter is a
structpt_regs
. This struct
is dened in
asm/ptrace.h
and provides access to all
CPU registers.
A Kprobe attaching to the
sys_exec
kernel func-
tion should set the section header (see section 3) to
either
kprobe/sys_exec
or
kretprobe/sys_exec
. Set-
ting the probe type to kprobe invokes the program as the
rst instruction of
sys_exec
, while setting the program
to kretprobe invokes the program as the last instruction
of sys_exec.
4.2.4 Perf Event Programs. The eBPF Perf Event type
allows eBPF programs to attach to the kernels internal
Perf proler [4]. Perf emits performance data events
for hardware and software. Low level examples of per-
formance data are CPU cycles and CPU cache misses.
Examples of more high level performance data are the
number of context switches and page faults.
1# i nc lu d e < li n ux / b p f .h >
2# i nc lu d e < bp f / bp f _h el p er s . h>
3
4struct {
5_ _u i nt ( t y pe , B PF _ M A P_ T Y P E_ A R R AY );
6_ _t y p e ( ke y , int);
7_ _t y pe ( v a lu e , int );
8_ _u in t ( m ax _ en tr i es , 4 2) ;
9} m y_ m ap SE C ( " . ma ps " ) ;
Listing 3: An example of an eBPF map denition.
5 EBPF MAPS
eBPF maps oer a two-way data structure for transfer-
ring data in and out of kernel-space. Maps are the only
way for an eBPF program to communicate with other
eBPF program invocations and/or user-space. In the
context of tracing, maps are often used to register key
statistics about the current invocation. For example a
networking eBPF program may store information about
network latency or increment an IP address counter to
keep track of popularity of remote hosts. The user-space
program can at any point in time look into the maps
and inspect their current state.
Maps are created by invoking the bpf syscall with
the
BPF_MAP_CREATE
argument [4]. One can also make
use the SEC attribute discussed earlier to automatically
create it as shown in listing 3.
It is important to remember that eBPF maps are not
built with functionality guaranteeing integrity, which
means that the developer should take extra care in en-
suring that data is not overwritten by accident. Further-
more, data is shared across eBPF program invocations.
Lastly, all privileged user-space programs can access
eBPF maps, which allows usage of debug tools such as
bpftool.
An interesting property of eBPF maps is the in-kernel
aggregation. If you, for example, want to compute the
minimum or maximum value, you can determine this
value in the eBPF program and thus not stream all val-
ues to user-space. This signicantly decreases the over-
head compared to systems that transfer all samples to
user-space for processing.
5.1 Denition
Listing 3 shows an example of a simple eBPF map of
the array-type. There exist many dierent map-types
and the eBPF developer should consider the character-
istics of each type. For example, the array-based map
5
PREPRINT - May 26, 2023
Niclas Hedam
1# i nc lu d e < bp f / bp f . h >
2# i nc lu d e < bp f / li b bp f .h >
3
4/* c rea te or u pd ate i f ex ist s */
5# d ef in e B PF_ ANY 0
6
7/* c re ate , but do no u p da te */
8# d ef in e B P F_ N OE X IS T 1
9
10 /* d o not cr eat e , but on ly u pd at e */
11 # d ef in e BPF_EXIST 2
12
13 int bpf_map_lookup_elem(
14 int fd , void * k ey , void * va l ue
15 );
16
17 int bpf_map_update_elem(
18 int fd , void * k ey ,
19 void * va l ue , __ u 64 f la g s
20 );
21
22 int bpf_map_delete_elem(
23 int fd , void * k ey
24 );
Listing 4: A list of the functions used to interact
with eBPF maps in user-space.
has a xed key-size of 4 bytes and the whole array is
preallocated in memory, while a hash-based map can
have any key-size and is not preallocated in memory
[4]. However, an array-based map is faster than a hash-
based map, since lookups do not require computing
the hash of the entry. Furthermore, there exist some
more complex map-types that can cover more specic
use-cases. For example, one can initialise a map that is
per-CPU or based on LRU-principles. A full list of eBPF
map types are available in appendix C.
5.2 Usage
The user-space program can interact with maps by
using the three methods shown in listing 4. These fol-
low the typical interface for a map structure with the
exception of the ag argument of update. Listing 4 also
shows the update ags, which denotes whether the map
should create or update, only create or only update.
The fd argument should be the le descriptor of the
map. The le descriptor can be retrieved by rst call-
ing
bpf_object__find_map_by_name
with the
bpf_
object
from the loading step and the map name. This
1# i nc lu d e < li n ux / b p f .h >
2# i nc lu d e < bp f / bp f _h el p er s . h>
3
4void *bpf_map_lookup_elem(
5void * ma p , void * k e y
6);
7
8int bpf_map_update_elem(
9void * ma p , void * k ey , void * va l ue ,
10 uns ign ed lon g lo ng flags
11 );
12
13 int bpf_map_delete_elem(
14 void * ma p , void * k e y
15 );
Listing 5: A list of the functions used to interact
with eBPF maps in kernel-space.
will return a
bpf_map
, which can then be passed to
bpf_map__fd to retrieve the le descriptor.
The eBPF program can interact with the map by us-
ing the three methods shown in listing 5. This inter-
face diers from from the user-space interface by us-
ing pointers instead of le descriptors. For example,
bpf_map_lookup_elem
returns a direct pointer to the
value in kernel memory-space, while the user-space
received a copy of the value.
The map argument should be a pointer to the struct
containing the map denition.
6 EBPF VERIFIER
As described in section 2, all eBPF programs are veri-
ed before being loaded into the kernel [4, 7, 5]. There
exist a set of rules to ensure the safety and stability
of the kernel. Common rules include type checking of
operations, a stack limit of 512 bytes, no signed division
and the absence of loops [7, 4, 5]. The verier will also
ensure that the eBPF program is always terminating.
The guarantee of termination is given by converting
the program into a direct acyclic graph (DAG). The
verier can then check, using depth rst search (DFS),
that the program always nishes and does not include
any dangerous paths [4]. Loops may be used if they are
unrolled doing compilation or guaranteed to terminate.
While the eBPF verier has been under scrutiny to
guarantee its reliability, some critical security vulnera-
bilities have been found in the past. For example, CVE-
2017-16995 describes a way to read and write kernel
memory and bypass the eBPF verier [14, 4].
6
PREPRINT - May 26, 2023
eBPF - From a Programmer’s Perspective
6.1 Hardening
When an eBPF program passes verication it is run
though a hardening step [5]. In this step, the kernel
memory holding the eBPF program is made read-only
to protect from malicious manipulation. The kernel will
then crash, when an adversary or bug tries to invoke
the eBPF program in an untimely manner. Constants
may also be blinded such that code in constants cannot
be executed. Blinded means that the memory address
of the constant is randomised such that it is harder
to guess. This ensures that an attacker cannot inject
arbitrary code into the constant and execute it.
The verier will also make sure that the eBPF pro-
gram does not leak kernel-space memory to user-space,
for example by reading any chunk of memory and for-
ward it via eBPF maps to user-space. An example of a
rule to prevent this; A register or a stack portion must
always have been initialised before an eBPF program
can read them
6.2 Risky Operations
One thing that diers signicantly between typical user-
space C programs and eBPF programs, is the safety
guarantees of operations. When writing a user-space C
program, invalid memory accesses are caught as seg-
mentation faults. In eBPF programs, invalid memory
accesses must not happen in any circumstances.
Programs that do not have the necessary safeguards
will not be accepted by the verier. To follow a pointer,
for example, the program must use the
bpf_probe_
read
function. This function will verify that the pointer
is valid and copy the desired memory space before con-
tinuing the program execution.
7 PRACTICAL DIFFERENCES
In the previous sections, we have gone trough high-
level descriptions of the architecture of eBPF and the
dierences between eBPF and typical user-space C pro-
grams. In this section, we will see a few select examples
of code that works in a normal environment, but will
not pass verication in the context of eBPF.
Listing 6 shows an example of an eBPF program that
will hook into an arbitrary kprobe. The program will
read the rst parameter of the hooked function using
the rdi entry of the context, as the rst parameter is
stored in the rdi register.
1# i nc lu d e < li n ux / b p f .h >
2# i nc lu d e < bp f / bp f _h el p er s . h>
3# i nc lu d e < as m / pt r ac e .h >
4
5struct my_struct {
6unsigned int fo o ;
7};
8
9SE C ( " k pr o be / . .. " )
10 int bpf_prog(st ru c t pt _ r eg s * c tx ) {
11
12 struct my_struct *my_struct =
13 (void *) c tx -> r d i ;
14
15 char fm t [] = " F oo co n t ai n s % u\ n " ;
16
17 bpf_trace_printk(
18 fm t ,
19 sizeof( f mt ) ,
20 my _ s tr u c t - > f oo ,
21 sizeof( m y _s t ru c t - > f oo )
22 );
23
24 return 0;
25 }
26
27 char _ li c en s e [ ] SE C ( " li c en s e ")=" G PL " ;
Listing 6: An example of a risky operation that
fails verication.
Bear in mind that the location of the rst function
parameter and the structure of pt_regs may dier be-
tween systems. One can use the macros dened in
bpf_tracing.h
in libbpf to increase portability by let-
ting the host select the appropriate registers. For exam-
ple, the PT_REGS_PARM1 will expand to ctx->rdi on the
author’s system.
Compiling listing 6 succeeds, but when loading the
program, you will see output like listing 7. The rst lines
(until line 18) shows what the eBPF verier checked
before failing verication. The hexadecimal numbers
in the parenthesises denote the eBPF opcodes.
At line 19 the eBPF verier informs that there was an
inv on R1, which translates to invalid memory access on
register 1, which on the author’s system is equivalent
to rdi.
Put more simply, the eBPF verier fails verication,
since the deferencing of the foo member of my_struct
is risky. As described in section 6.2, this is due to the
risk of segmentation faults as the pointer may not be
valid. Section 6.2 also describes the solution to this
7
PREPRINT - May 26, 2023
Niclas Hedam
1l ib bp f : l oa d b pf p r og r am fa i le d : P er m is s io n
denied
2l ib bp f : - - BE GI N D UM P L OG -- -
3libbpf:
4btf _vm lin u x is m alf orm ed
5U nr e co gn i ze d a rg # 0 t yp e PT R
6; ( void *) c tx - > r di ;
70: ( 7 9 ) r1 = * ( u6 4 * ) ( r1 + 1 1 2)
81: (18) r2 = 0xa752520736e6961
9;char fm t [] = " F oo co n t ai n s % u\ n " ;
10 3: ( 7 b ) *( u 6 4 *) ( r 1 0 - 24 ) = r2
11 4: ( 1 8) r 2 = 0x 7 46 e 6f 6 32 0 6f 6f 4 6
12 6: ( 7 b ) *( u 6 4 *) ( r 1 0 - 32 ) = r2
13 7: ( b 7) r2 = 0
14 8: ( 7 3 ) *( u 8 * ) ( r1 0 -1 6) = r 2
15 las t_i dx 8 f i rs t _i d x 0
16 re gs =4 s t ac k =0 be f or e 7 : (b7 ) r2 = 0
17 ; bpf_trace_printk(
18 9: ( 6 1 ) r3 = * ( u3 2 * ) ( r1 + 0 )
19 R1 i n va lid m em acc es s 'inv'
20 pro ces sed 8 i nsn s ( li mit 1 0 00 0 00 )
max_states_per_insn 0 total_states 0
pea k_s tat e s 0 ma r k_ r ea d 0
21
22 l ib bp f : - - EN D LO G - -
Listing 7: The result of compiling and loading
listing 6.
1# i nc lu d e < li n ux / b p f .h >
2# i nc lu d e < bp f / bp f _h el p er s . h>
3# i nc lu d e < as m / pt r ac e .h >
4
5SE C ( " k pr o be / . .. " )
6int bpf_prog(st ru c t pt _ r eg s * c tx ) {
7
8unsigned int tr ies = 0 ;
9
10 while( 1 ) {
11 if ( bpf_get_prandom_u32() > 0) break ;
12 tr i es + +;
13 }
14
15 return 0;
16 }
17
18 char _ li c en s e [ ] SE C ( " li c en s e ")=" G PL " ;
Listing 8: An example of a dynamic program that
cannot be veried.
problem, which is to safely copy the memory space
using a dedicated helper before accessing it.
The eBPF program seen in listing 8 will continuously
sample random numbers. The program will terminate
1l ib bp f : - - BE GI N D UM P L OG -- -
2libbpf:
3btf _vm lin u x is m alf orm ed
4U nr e co gn i ze d a rg # 0 t yp e PT R
5;if ( bpf_get_prandom_u32() > 0) b r ea k ;
60: (85) call bpf_get_prandom_u32#7
71: ( 6 7) r 0 < <= 3 2
82: ( 7 7) r 0 > >= 3 2
9;if ( bpf_get_prandom_u32() > 0) b r ea k ;
10 3: ( 15) if r0 = = 0 x0 goto p c -4
11
12 fr o m 3 to 0 : R0 _ w = in v 0 R 10 = f p0
13 ;if ( bpf_get_prandom_u32() > 0) b r ea k ;
14 0: (85) call bpf_get_prandom_u32#7
15 1: ( 6 7) r 0 < <= 3 2
16 2: ( 7 7) r 0 > >= 3 2
17 ;if ( bpf_get_prandom_u32() > 0) b r ea k ;
18 3: ( 15) if r0 = = 0 x0 goto p c -4
19
20 fr o m 3 to 0 : R0 _ w = in v 0 R 10 = f p0
21 ;if ( bpf_get_prandom_u32() > 0) b r ea k ;
22 0: (85) call bpf_get_prandom_u32#7
23 inf ini te l o op d ete cte d at i ns n 1
24 p ro c es s ed 1 4 i ns n s ( li m it 1 0 00 0 00 )
max_states_per_insn 0 total_states 1
pea k_s tat e s 1 ma r k_ r ea d 1
25
26 l ib bp f : - - EN D LO G - -
Listing 9: The result of compiling and loading
listing 8.
if and only if the sampled number is greater than zero.
Since the used random number generator is providing
unsigned 32-bit integers, the chance of the program
not terminating immediately is negligible. Actually, the
chance of the program not terminating in the rst loop
iteration is 1
4294967296 or ≈0.00000002%.
Compiling listing 8 succeeds, but when loading the
program, you will see output like listing 9. The output
of the verier states that an innite loop was detected,
although there is virtually no chance of an innite loop
occurring. This is, however, not a strong enough guar-
antee for loading the eBPF program.
Listing 10 shows an example of a simple program that
samples a single random number and adds 42
.
0to it.
Floating point arithmetic is approximate by denition,
due to the sheer number of values that can be repre-
sented [3]. There exist a range of issues with oating
points including rounding issues, where numbers are
incorrectly rounded up or down due to an approxima-
tion.
8
PREPRINT - May 26, 2023
eBPF - From a Programmer’s Perspective
1# i nc lu d e < li n ux / b p f .h >
2# i nc lu d e < bp f / bp f _h el p er s . h>
3# i nc lu d e < as m / pt r ac e .h >
4
5SE C ( " k pr o be / . .. " )
6int bpf_prog(st ru c t pt _ r eg s * c tx ) {
7
8float f = bpf_get_prandom_u32 () + 42.0;
9
10 char fm t [] = " F lo a t is % f \n " ;
11
12 bpf_trace_printk(fmt, s i ze o f ( fm t ) , f,
sizeof(f));
13
14 return 0;
15 }
16
17 char _ li c en s e [ ] SE C ( " li c en s e ")=" G PL " ;
Listing 10: An example of an eBPF program with
oating point arithmetic.
1er r or : k i ll . c : 31 : 13 : i n f un c ti o n
k il l_ e xa mp l e i3 2 (% s t ru ct . p t _ re g s * ) : A
ca l l t o bu il t - in f u n ct i o n '
__floatunsidf'is not supported.
2
3er r or : k i ll . c : 31 : 35 : i n f un c ti o n
k il l_ e xa mp l e i3 2 (% s t ru ct . p t _ re g s * ) : A
ca l l t o bu il t - in f u n ct i o n '__adddf3'i s
not supported.
4
5er r or : k i ll . c : 31 : 13 : i n f un c ti o n
k il l_ e xa mp l e i3 2 (% s t ru ct . p t _ re g s * ) : A
ca l l t o bu il t - in f u n ct i o n '__truncdfsf2
'is not supported.
6
7er r or : k i ll . c : 35 : 38 : i n f un c ti o n
k il l_ e xa mp l e i3 2 (% s t ru ct . p t _ re g s * ) : A
ca l l t o bu il t - in f u n ct i o n '
__extendsfdf2'is not supported.
Listing 11: The result of compiling listing 10.
It is hard, if not impossible, to guarantee the cor-
rect execution of a program when using oating points.
Therefore, when compiling a program with oating
points that cannot be optimised away, the compiler
forcefully stops and warns that the built-in oating
point arithmetical functions are not supported.
8 PRACTICAL USE CASES
In this section we will show some practical use cases
of eBPF.
8.1 DDoS rewall
Cloudare is currently transitioning into using XDP
as their DDoS mitigator [1]. A clear benet of using
eBPF XDP over IPTables is the ability to match specic
patterns that are not expressible using IPTables.
Cloudare is interested in eBPF XDP for two main
reasons. First, eBPF XDP oer a way to inspect packet
in the lowest possible layer with a very low cost to
drop packets. Second, it is possible to express rewall
rules using high-level languages like C while maintain-
ing strong guarantees about program termination and
memory access.
8.2 ExtFUSE
ExtFUSE is a framework for developing extensible user
le systems which enables applications to register spe-
cialised request handlers in the kernel [2]. This allows
the application to meet their specic operative needs,
while still having the advanced functionality of user-
space programs.
ExtFUSE leverages eBPF to load and verify the user
le system extensions, which enables the user to write
extensions in a high-level language like C. It also guar-
antees the safety and stability of the le system exten-
sions.
8.3 Cilium
Cilium is an open source system providing connectiv-
ity between applications in a secure and transparent
manner [10]. Cilium works with Linux container man-
agement systems like Kubernetes, Docker and Mesos.
Cilium aims to make Linux aware of microservices,
including their containers and APIs. This allows users
to insert exible and powerful security, visibility and
networking control logic into the kernel using eBPF.
8.4 Katran
Katran is a high-performance XDP-based layer 4 load
balancer built by Facebook Incubator [12]. Since Katran
uses XDP, it is able to run packet handling routines
right after packet has been received by the NIC.
8.5 bcc
bcc is a toolkit for enabling ecient kernel tracing [15].
bcc uses eBPF under the hood and as such, it enables
developers to write eBPF programs more easily. BCC
9
PREPRINT - May 26, 2023
Niclas Hedam
is suitable for a wide amount of tasks including perfor-
mance analysis and network trac control.
8.6 bpftrace
bpftrace is a high-level tracing language based on eBPF
[16]. bpftrace enables developers to write one-liners to
gauge the performance of a system. It is inspired by
awk and C, and predecessor tracers such as DTrace and
SystemTap.
9 COMPUTATIONAL STORAGE
NVMe, the abbreviation for Non-Volatile Memory Ex-
press, is a specialised protocol designed exclusively for
solid-state drives (SSDs) that utilise PCIe interfaces.
This innovative protocol oers an ecient means of
accessing and transferring data between computer sys-
tems and storage devices.
With the introduction of TP 4091 in NVMe, eBPF is
expected to become the standardised method for of-
oading programs to storage. The combination of eBPF
and NVMe’s TP 4091 permits the swift ooading of pro-
grams directly to storage devices with a vendor-neutral
instruction set architecture.
As of this writing, TP 4091’s contents remain con-
dential, making it challenging to predict how eBPF will
be used in computational storage devices. Important is-
sues such as state management and resource allocation
have yet to be resolved.
The IT University of Copenhagen is actively experi-
menting with a PCIe-based computational storage pro-
cessor (CSP) that utilises eBPF as part of the DAPHNE
project [6]. This system, known as Delilah [11], allows
user-space applications to queue up eBPF programs and
execute them on the device while taking into account
the underlying storage.
10 NEXT STEPS
This paper only scratches the surface of what you can
do with eBPF. In this section we will briey highlight
some of the next steps an eBPF developer can take.
•
High-level inspection of eBPF objects. An eBPF
developer can use tools such as bpftool to view
and debug eBPF programs and maps.
•
Low-level inspection of eBPF programs. Part of
understanding and debugging the behaviour of
eBPF programs is dumping and reading the byte-
code. The eBPF developer can use tools such as
llvm-objdump to see the underlying bytecode of
an eBPF program.
•
Evaluate performance of eBPF programs. Since
eBPF programs are often running in performance
sensitive environments, it is valuable for an eBPF
developer to understand the overhead of eBPF
programs. In Linux, one can enable a ag that
will collect performance metrics about loaded
eBPF programs.
•
CO-RE. One of the challenges of eBPF is the
portability of code. Compile Once - Run Every-
where allows eBPF developers to compile code
on a system to be run on any other system with
varying operating systems and kernel versions.
•
.. and much more. eBPF has a wide range of dif-
ferent systems and mechanisms. This includes,
for example, the ability to trace eBPF programs
with eBPF and using the virtual lesystem for
eBPF.
11 CONCLUSION
Extended Berkeley Packet Filter or eBPF is a low-level
language based on the Berkeley Packet Filter system
from 1992, but with an architecture that more closely
resembles contemporary processors.
eBPF programs can be compiled from several high-
level languages like C and Python. However, there are
key dierences between typical user-space C programs
and eBPF programs. This is due to the strict security
guarantees of eBPF programs, which requires programs
to always be predictable and stable.
In this paper we showed how to write, compile and
load eBPF programs. We furthermore discussed the
dierent types of eBPF programs available as well as a
short overview of eBPF maps.
We discussed some of the dierences between typical
user-space C programs and eBPF C programs. We saw
how these programs may seem correct and functional,
but have edge cases that prevents giving strong enough
safety guarantees.
We described several use cases of eBPF including a
DDoS rewall and le system extensions. We showed
how these project leverage eBPF and the properties of
eBPF.
10
PREPRINT - May 26, 2023
eBPF - From a Programmer’s Perspective
Lastly, we described the eBPF verier and discussed
the security of it.
REFERENCES
[1]
Gilberto Bertin. “XDP in practice: integrating
XDP into our DDoS mitigation pipeline”. In: Tech-
nical Conference on Linux Networking, Netdev.
Vol. 2. 2017.
[2]
Ashish Bijlani and Umakishore Ramachandran.
“Extension framework for le systems in user
space”. In: 2019 USENIX Annual Technical Confer-
ence (USENIX ATC 19). 2019, pp. 121–134.
[3]
Randal E. Bryant and David R. O’Hallaron. Com-
puter Systems: A Programmer’s Perspective. Global
Edition. Pearson, 2016. isbn: 9781292101767.
[4]
D. Calavera and L. Fontana. Linux Observability
with BPF: Advanced Programming for Performance
Analysis and Networking. O’Reilly Media, Incor-
porated, 2019. isbn: 9781492050209.
[5]
Cilium. What is eBPF? An Introduction and Deep
Dive into the eBPF Technology.url: https://ebpf.
io/what-is-ebpf/ (visited on 12/14/2020).
[6]
Patrick Damme et al. “DAPHNE: An Open and Ex-
tensible System Infrastructurefor Integrated Data
Analysis Pipelines”. en. In: 12th Annual Confer-
ence on Innovative Data Systems Research (CIDR
‘22). Santa Cruz, California, USA: CIDR, Jan. 2022.
url: https://www.cidrdb.org/cidr2022/papers/p4-
damme.pdf.
[7]
Henri Maxime Demoulin et al. “Detecting Asym-
metric Application-layer Denial-of-Service At-
tacks In-Flight with FineLame”. In: 2019 USENIX
Annual Technical Conference (USENIX ATC 19).
Renton, WA: USENIX Association, 2019, pp. 693–
708. isbn: 9781939133038. url: https : / / www .
usenix . org / conference / atc19 / presentation /
demoulin.
[8]
Matt Fleming. A thorough introduction to eBPF.
2017. url: https : / / lwn . net / Articles / 740157/
(visited on 11/10/2020).
[9]
Stephen J. Friedl. Using GNU C __attribute__.url:
http://unixwiz.net/ techtips/gnu - c-attributes.
html (visited on 11/03/2020).
[10]
Thomas Graf. How to Make Linux Microservice-
Aware with Cilium and eBPF. 2018. url: https:
//www.youtube.com/watch?v=_Iq1xxNZOAo
(visited on 12/10/2020).
[11]
Niclas Hedam et al. “Delilah: eBPF-ooad on
Computational Storage”. en. In: 19th International
Workshop on Data Management on New Hardware
(DaMoN ’23). Seattle, Washington, USA: DaMoN,
2023. doi: 10.1145/3592980.3595319. url: https:
//hed.am/papers/2023-DaMoN.pdf .
[12]
Facebook Incubator. facebookincubator / katran.
2021. url: https://github.com/facebookincubator
/katran (visited on 02/18/2021).
[13]
S. Miano et al. “Creating Complex Network Ser-
vices with eBPF: Experience and Lessons Learned”.
In: 2018 IEEE 19th International Conference on
High Performance Switching and Routing (HPSR).
2018.
[14]
NVD - CVE-2017-16995. 2017. url: https://nvd.
nist.gov/vuln/detail/CVE-2017-16995.
[15]
IO Visor Project. iovisor / bcc. 2021. url: https:
//github.com/iovisor/bcc (visited on 02/18/2021).
[16]
IO Visor Project. iovisor / bpftrace. 2021. url: h
ttps://github.com/iovisor/ bpftrace (visited on
02/18/2021).
[17]
Alexei Starovoitov. daedfb22451d. 2014. url: ht
tps://git.kernel.org/pub/scm/linux/kernel/git/
torvalds/linux.git/commit/?id=daedfb22451dd
02b35c0549566cbb7cc06bdd53b.
[18]
Alexei Starovoitov. Lifetime of BPF objects. 2018.
url: https://facebookmicrosites.github.io/bpf /
blog/2018/08/31/object-lif etime.html (visited on
02/17/2021).
A EXAMPLE SETUP
When compiling eBPF programs, a set of helper func-
tions and libraries are needed. In this section, we will
go through all the steps necessary to compile any of the
examples in this paper. Bear in mind that as the kernel,
operating systems and architecture of eBPF continues
to be developed, this walk-through may become out-
dated. The walk-through was written in May 2023 and
tested on an Ubuntu 22.04 LTS with kernel 5.19 and
libbpf 1.2.
To compile the examples, you will need the following
dependencies.
•
build-essential, git, make – These tools provide
the necessary framework for compilation. Git
will be used to clone libbpf.
•
gcc, clang, llvm – GCC is used to compile the
kernel, while clang is used to compile eBPF code.
11
PREPRINT - May 26, 2023
Niclas Hedam
LLVM is used to transform LLVM IR code to BPF
byte-code.
•
libelf-dev, gcc-multilib – These libraries are re-
quired by libbpf.
Start out by retrieving new lists of packages.
$ su do apt -ge t u pd a te
After completion of the update, install all of the de-
pendencies listed above.
$ su do ap t i ns t al l - y b ui l d- e sse nti a l git
ma ke gc c cl ang l lvm l ibe lf- dev
gcc -mu l ti l ib m ake
Download a copy of libbpf. We will fetch only a single
commit containing the release of version 1.2.
$ gi t c lo n e - -d ept h 1 - -si ngl e -b r an c h
--b ran ch v 1. 2.0 \
https://github.com/libbpf/libbpf libbpf
Compile libbpf and install the headers locally.
$ ma k e - -d ir e ct o ry = l i bb p f/ s rc a ll
$ D ES T DI R = r oo t m ak e - - di r ec t o ry = l i b bp f /s r c
install_headers
Now that we have all the dependencies in place, we
can continue to the eBPF-related les. Grab the loader
from listing 2. We will save it as
loader.c
. We also
need to grab one of the examples. Let us grab the kill-
example from listing 1 and save as
kill.c
. Please be
aware that copying code from a PDF may not always
work as expected.
Then, we need to compile the eBPF program using
clang.
$ cla ng -ta rg e t bpf - S -D _ _ BP F _TR ACI N G_ _
-I./libbpf/src/root/usr/include/ -Wall
-We rr o r -O2 - e mi t -l l vm -c - g kil l.c
$ ll c - m ar c h = bp f - f il e t yp e = o bj - o k il l .o
kil l. l l
Compile the loader.
$ gcc -I./libbpf/src/root/usr/include/
-L. /li b bp f /sr c/ -o e bpf l oad er. c \
-W l , - rp a th = . / li b bp f /s r c/ - lb pf - le lf
This is all we need to do to. All that is left is to actu-
ally run the loader program.
$ su do ./e bp f &
$ su do ca t
/sys/kernel/debug/tracing/trace_pipe
You may have to press enter after loading the pro-
gram.
To stop the eBPF program, stop the cat command
using CTRL + C and run fg. This will bring the eBPF
loader back to the front and you can stop it using CTRL
+ C.
B EBPF PROGRAM TYPES
1enum bpf_prog_type {
2BPF_PROG_TYPE_UNSPEC ,
3BPF_PROG_TYPE_SOCKET_FILTER ,
4BPF_PROG_TYPE_KPROBE ,
5BPF_PROG_TYPE_SCHED_CLS ,
6BPF_PROG_TYPE_SCHED_ACT ,
7BPF_PROG_TYPE_TRACEPOINT ,
8BPF _PR OG_ TYP E_ XD P ,
9BPF_PROG_TYPE_PERF_EVENT ,
10 BPF_PROG_TYPE_CGROUP_SKB ,
11 BPF_PROG_TYPE_CGROUP_SOCK ,
12 BPF_PROG_TYPE_LWT_IN ,
13 BPF_PROG_TYPE_LWT_OUT ,
14 BPF_PROG_TYPE_LWT_XMIT ,
15 BPF_PROG_TYPE_SOCK_OPS ,
16 BPF_PROG_TYPE_SK_SKB ,
17 BPF_PROG_TYPE_CGROUP_DEVICE ,
18 BPF_PROG_TYPE_SK_MSG ,
19 BPF_PROG_TYPE_RAW_TRACEPOINT ,
20 BPF_PROG_TYPE_CGROUP_SOCK_ADDR ,
21 BPF_PROG_TYPE_LWT_SEG6LOCAL ,
22 BPF_PROG_TYPE_LIRC_MODE2 ,
23 BPF_PROG_TYPE_SK_REUSEPORT ,
24 BPF_PROG_TYPE_FLOW_DISSECTOR ,
25 BPF_PROG_TYPE_CGROUP_SYSCTL ,
26 BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE ,
27 BPF_PROG_TYPE_CGROUP_SOCKOPT ,
28 BPF_PROG_TYPE_TRACING ,
29 BPF_PROG_TYPE_STRUCT_OPS ,
30 BPF _PR OG_ TYP E_ EX T ,
31 BPF _PR OG_ TYP E_ LS M ,
32 BPF_PROG_TYPE_SK_LOOKUP ,
33 BPF_PROG_TYPE_SYSCALL ,
34 };
Listing 12: The declaration of all eBPF program
types. From bpf.h of kernel version 6.2.11.
C EBPF MAP TYPES
12
PREPRINT - May 26, 2023
eBPF - From a Programmer’s Perspective
1enum bpf_map_type {
2BPF _MA P_T YPE _UN SPE C ,
3BPF _MA P_T YPE _H AS H ,
4BPF_MAP_TYPE_ARRAY ,
5BPF_MAP_TYPE_PROG_ARRAY ,
6BPF_MAP_TYPE_PERF_EVENT_ARRAY ,
7BPF_MAP_TYPE_PERCPU_HASH ,
8BPF_MAP_TYPE_PERCPU_ARRAY ,
9BPF_MAP_TYPE_STACK_TRACE ,
10 BPF_MAP_TYPE_CGROUP_ARRAY ,
11 BPF_MAP_TYPE_LRU_HASH ,
12 BPF_MAP_TYPE_LRU_PERCPU_HASH ,
13 BPF_MAP_TYPE_LPM_TRIE ,
14 BPF_MAP_TYPE_ARRAY_OF_MAPS ,
15 BPF_MAP_TYPE_HASH_OF_MAPS ,
16 BPF _MA P_T YPE _DE VMA P ,
17 BPF_MAP_TYPE_SOCKMAP ,
18 BPF _MA P_T YPE _CP UMA P ,
19 BPF _MA P_T YPE _XS KMA P ,
20 BPF_MAP_TYPE_SOCKHASH ,
21 BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED ,
22 BPF_MAP_TYPE_CGROUP_STORAGE =
BPF_MAP_TYPE_CGROUP_STORAGE_DEPRECATED ,
23 BPF_MAP_TYPE_REUSEPORT_SOCKARRAY ,
24 BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE ,
25 BPF_MAP_TYPE_QUEUE ,
26 BPF_MAP_TYPE_STACK ,
27 BPF_MAP_TYPE_SK_STORAGE ,
28 BPF_MAP_TYPE_DEVMAP_HASH ,
29 BPF_MAP_TYPE_STRUCT_OPS ,
30 BPF_MAP_TYPE_RINGBUF ,
31 BPF_MAP_TYPE_INODE_STORAGE ,
32 BPF_MAP_TYPE_TASK_STORAGE ,
33 BPF_MAP_TYPE_BLOOM_FILTER ,
34 BPF_MAP_TYPE_USER_RINGBUF ,
35 BPF_MAP_TYPE_CGRP_STORAGE ,
36 };
Listing 13: The declaration of all eBPF map types.
From bpf.h of kernel version 6.2.11.
13