This repository has no description
1[](https://ocaml.ci.dev/github/koonwen/ocaml-libbpf)
2- [API documentation](https://koonwen.github.io/ocaml-libbpf/)
3
4# ocaml-libbpf
5Libbpf C-bindings for loading eBPF ELF files into the kernel with OCaml.
6
7Writing eBPF programs consist of two distinct parts. Implementing the
8code that executes in-kernel **and** user-level code responsible for
9loading/initializing/linking/teardown of the in-kernel code. This
10OCaml library provides the latter via binding the C
11[libbpf](https://github.com/libbpf/libbpf) library. It exposes both
12the raw low-level bindings as well as a set of high-level API's for
13handling your eBPF objects. As of now, the kernel part must still be
14written in [restricted
15C](https://stackoverflow.com/questions/57688344/what-is-not-allowed-in-restricted-c-for-ebpf)
16and compiled with llvm to eBPF bytecode.
17
18The full API set of Libbpf is quite large, see [supported](supported.json) for the list
19of currently bound API's. Contributions are welcome.
20
21### External dependencies
22ocaml-libbpf depends on the system package of `libbpf`.
23
24# Usage
25> ⚠️ **Disambiguation:** The name of this repository and
26> references to it will be "ocaml-libbpf". However, the library's
27> entry module and package name is **Libbpf**. To install it, you
28> would use `opam install libbpf`. To access it's High-level API's use
29> `Libbpf.<api>`. To use the raw bindings, they are exposed in
30> `Libbpf.C.<api>` namespace.
31
32See `examples` directory on how ocaml-libbpf can be used to load eBPF
33ELF files into the kernel and interact with the loaded kernel program.
34The eBPF kernel programs are defined in *.bpf.c source files and are
35compiled with clang as specified in the `dune` rules. ocaml-libbpf
36exposes some high-level API's exposed by the toplevel `Libbpf` module
37to make it easy to perform repetitive tasks such as
38open/load/linking/initializing/teardown of bpf programs.
39
40To run these examples, clone this repository and set up the package with
41```bash
42git clone git@github.com:koonwen/ocaml-libbpf.git
43cd ocaml-libbpf
44opam install . --deps-only
45eval $(opam env)
46```
47
48then run `make < minimal | kprobe | bootstrap | tc >` to try out the
49different bpf programs. These examples are all taken from
50[libbpf-bootstrap](https://github.com/libbpf/libbpf-bootstrap)
51repository and rewritten in OCaml.
52
53### Open/Load/Link
54Now let's run through an example of how we would use
55ocaml-libbpf. This usage tutorial assumes some knowledge of how to
56write eBPF kernel programs in C compile them to ELF files. If not, you
57can check out this
58[resource](https://nakryiko.com/posts/libbpf-bootstrap/#the-bpf-side). ocaml-libbpf
59provides an easy API to install your eBPF program into the kernel. Say
60your eBPF kernel program looks like this where we print something
61whenever the syscall `write` event occurs. We also want to implement a
62filtering mechanism to only print on `write` calls for our process. To
63do this, we initialize a BPF array map with a single entry that works
64like a holder for our global variable. The BPF map is neccessary to
65because it allows us to communicate values between user and kernel
66space.
67
68> The libbpf C library in fact already supports declarations of global
69> variables in the usual form with the ability to manage them in user
70> space. However for various technical reasons, ocaml-libbpf does not
71> enable that feature yet. So we use the old style of working with
72> global variables here.
73
74```c
75// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
76/* Copyright (c) 2020 Facebook */
77#include <linux/bpf.h>
78#include "bpf/bpf_helpers.h" /* This is from our libbpf library */
79
80char LICENSE[] SEC("license") = "Dual BSD/GPL";
81
82/* Globals implemented as an array */
83struct {
84 __uint(type, BPF_MAP_TYPE_ARRAY);
85 __uint(max_entries, 1);
86 __type(key, int);
87 __type(value, long);
88} globals SEC(".maps");
89
90int my_pid_index = 0;
91
92SEC("tp/syscalls/sys_enter_write")
93int handle_tp(void *ctx) {
94 int pid = bpf_get_current_pid_tgid() >> 32;
95
96 long *my_pid;
97 my_pid = bpf_map_lookup_elem(&globals, &my_pid_index);
98 if (my_pid == NULL) {
99 bpf_printk("Error got NULL");
100 return 1;
101 };
102
103 if (pid != *my_pid)
104 return 0;
105
106 bpf_printk("Hello, BPF triggered from PID %d", pid);
107
108 return 0;
109}
110
111```
112
113After compilation to eBPF ELF file as `minimal.o`. Users just need to
114provide the path to this ELF file along with the name of the program
115and optionally an initialization function. Note that the name of the
116program refers to the function identifier under the SEC(...)
117attribute, in this case it is "handle_tp".
118
119```ocaml
120open Libbpf
121
122let obj_path = "minimal.bpf.o"
123let program_names = [ "handle_tp" ]
124
125let () =
126 with_bpf_object_open_load_link ~obj_path ~program_names ~before_link
127 (fun obj link ->
128
129 < user code to interact with bpf program running in kernel >
130
131 )
132```
133
134The API provided by ocaml-libbpf `with_bpf_object_open_load_link` is
135a context manager that ensures the proper cleanup of resources if a
136failure is encountered. Right now our loaded kernel program is
137attached to the kernel and then immediately unloaded, users are
138responsible for keeping the bpf program alive by looping within the
139function block.
140
141> Users may also pin the bpf program to persist after user code
142> exits. Do note that if pinning is desired, users should not use the
143> `with_bpf_object_open_load_link` API and instead manually load and
144> attach their bpf program since the context manager shutdowns all
145> resources on exit.
146
147Now let's add some looping logic to keep the loaded bpf program alive.
148
149```ocaml
150let obj_path = "minimal.bpf.o"
151let program_names = [ "handle_tp" ]
152
153let () =
154 with_bpf_object_open_load_link ~obj_path ~program_names ~before_link
155 (fun obj link ->
156
157 (* Set up signal handlers *)
158 let exitting = ref true in
159 let sig_handler = Sys.Signal_handle (fun _ -> exitting := false) in
160 Sys.(set_signal sigint sig_handler);
161 Sys.(set_signal sigterm sig_handler);
162
163 Printf.printf
164 "Successfully started! Please run `sudo cat \
165 /sys/kernel/debug/tracing/trace_pipe` to see output of the BPF \
166 programs.\n\
167 %!"
168
169 (* Loop until Ctrl-C is called *)
170 while !exitting do
171 Printf.eprintf ".%!";
172 Unix.sleepf 1.0
173 done)
174```
175
176Our bpf program is now running in the kernel until we decide to
177interrupt it. However, it doesn't do exactly what we want. In
178particular, it doesn't filter for our process PID. This is because we
179haven't loaded our process PID into the BPF map. To do this, we need
180the name of the map we declared in the `minimal.bpf.c` program. In
181this case, our BPF array map was named `globals`.
182
183```ocaml
184let map = "globals"
185
186(* Load PID into BPF map *)
187let before_link obj =
188 let pid = Unix.getpid () |> Signed.Long.of_int in
189 let global_map = bpf_object_find_map_by_name obj map in
190 (* When updating an element, users need to specify the type of the key and value
191 declared by the map which checks that the key and value size are consistent. *)
192 bpf_map_update_elem ~key_ty:Ctypes.int ~val_ty:Ctypes.long global_map 0 pid
193```
194
195Put together in [minimal.ml](./examples/minimal.ml), your bpf program
196runs in kernel and print to the trace pipe every second.
197
198### Maps
199`libbpf_maps` is an optional convenience package that provides
200wrappers for BPF maps. Currently only Ringbuffer maps are added. An
201example usage of them can be found in
202[examples/bootstrap.ml](./examples/bootstrap.ml). This has been
203packaged separately since it drags in `libffi` dependency.
204
205## Notes on compatibility
206> The libbpf C library is designed to be kernel-agnostic and work
207> across multitude of kernel versions. It has built-in mechanisms to
208> gracefully handle older kernels, that are missing some of the
209> features, by working around or gracefully degrading functionality.
210
211Vendoring libbpf was a option. However, since bpf programs require
212writing the kernel components that may use libbpf, we made the choice
213to use the system's package versioned instead. This avoids users from
214knowingly/unknowingly using libbpf API's from two different
215versions. As a consequence, this library support operating systems
216that package libbpf.v.1.1 and up. Check ocaml-ci for the list of
217operating systems that successfully builds.
218
219If so desired, you can also checkout the `vendored` branch in this
220repo which builds ocaml-libbpf with the latest libbpf package.