1{0 ocaml-libbpf}
2
3OCaml bindings to {{:https://github.com/libbpf/libbpf}libbpf} C
4library for loading eBPF programs into the linux kernel.
5
6{1 Introduction}
7
8Writing eBPF programs consist of two distinct parts. Implementing the
9code that executes in-kernel and user-level code responsible for
10loading/initializing/linking/teardown of the in-kernel code. This
11OCaml library provides the latter via binding the C libbpf library. It
12exposes both the raw low-level bindings as well as a set of high-level
13API's for handling your eBPF objects. As of now, the kernel part must
14still be written in
15{{:https://stackoverflow.com/questions/57688344/what-is-not-allowed-in-restricted-c-for-ebpf}
16restricted C} and compiled with llvm to eBPF bytecode.
17
18For the high-level APIs: {!Libbpf}
19
20For the low-level bindings: {!Libbpf.C}.
21
22{1:Tutorial Tutorial}
23
24This example assumes the user has knowledge of how to implement the
25kernel part of a eBPF program. If not, you can check out this
26{{:https://nakryiko.com/posts/libbpf-bootstrap/#the-bpf-side}
27resource} first. Consider the following kernel eBPF program named {b
28minimal.bpf.c}:
29
30{@c[
31// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
32/* Copyright (c) 2020 Facebook */
33#include <linux/bpf.h>
34#include "bpf/bpf_helpers.h" /* This is from our libbpf library */
35
36char LICENSE[] SEC("license") = "Dual BSD/GPL";
37
38/* Globals implemented as an array */
39struct {
40 __uint(type, BPF_MAP_TYPE_ARRAY);
41 __uint(max_entries, 1);
42 __type(key, int);
43 __type(value, long);
44} globals SEC(".maps");
45
46int my_pid_index = 0;
47
48SEC("tp/syscalls/sys_enter_write")
49int handle_tp(void *ctx) {
50 int pid = bpf_get_current_pid_tgid() >> 32;
51
52 long *my_pid;
53 my_pid = bpf_map_lookup_elem(&globals, &my_pid_index);
54 if (my_pid == NULL) {
55 bpf_printk("Error got NULL");
56 return 1;
57 };
58
59 if (pid != *my_pid)
60 return 0;
61
62 bpf_printk("Hello, BPF triggered from PID %d", pid);
63
64 return 0;
65}
66]}
67
68After compilation to eBPF ELF file as {b "minimal.o"}. Users just need
69to provide the path to this ELF file along with the name of the
70program and optionally an initialization function. Note that the name
71of the program refers to the function identifier under the SEC(...)
72attribute, in this case it is {b "handle_tp"}.
73
74{@ocaml[
75open Libbpf
76
77let obj_path = "minimal.bpf.o"
78let program_names = [ "handle_tp" ]
79
80let () =
81 with_bpf_object_open_load_link ~obj_path ~program_names ~before_link
82 (fun obj link -> (* Do something *))
83]}
84
85The context manager {{!Libbpf.with_bpf_object_open_load_link}
86with_bpf_object_open_load_link} is a convenience wrapper for all the
87neccessary steps to load up your eBPF program into the kernel.
88
89If we don't specify anything in the body of the function marked with
90{b (* Do something *)}, our loaded kernel program will be unloaded
91immediately. In this case, we will add some looping logic to keep the
92program running in the kernel and add a set of signal handlers to
93escape the loop.
94
95{@ocaml[
96let obj_path = "minimal.bpf.o"
97let program_names = [ "handle_tp" ]
98
99let () =
100 with_bpf_object_open_load_link ~obj_path ~program_names ~before_link
101 (fun obj link ->
102
103 (* Set up signal handlers *)
104 let exitting = ref true in
105 let sig_handler = Sys.Signal_handle (fun _ -> exitting := false) in
106 Sys.(set_signal sigint sig_handler);
107 Sys.(set_signal sigterm sig_handler);
108
109 Printf.printf
110 "Successfully started! Please run `sudo cat \
111 /sys/kernel/debug/tracing/trace_pipe` to see output of the BPF \
112 programs.\n\
113 %!"
114
115 (* Loop until Ctrl-C is called *)
116 while !exitting do
117 Printf.eprintf ".%!";
118 Unix.sleepf 1.0
119 done)
120]}
121
122Our bpf program is now running in the kernel until we decide to
123interrupt it. However, it doesn't do exactly what we want. In
124particular, it doesn't filter for our process PID. This is because we
125haven't loaded our process PID into the BPF map. To do this, we need
126the name of the map we declared by our {b minimal.bpf.c} program. In
127this case, our BPF array map was named {b globals}.
128
129{@ocaml[
130let map = "globals"
131
132(* Load PID into BPF map *)
133let before_link obj =
134 let pid = Unix.getpid () |> Signed.Long.of_int in
135 let global_map = bpf_object_find_map_by_name obj map in
136 (* When updating an element, users need to specify the type of the key and value
137 declared by the map which checks that the key and value size are consistent. *)
138 bpf_map_update_elem ~key_ty:Ctypes.int ~val_ty:Ctypes.long global_map 0 pid
139]}
140
141Now if we combine the two, we can run this program and see the output
142interactively being printed to the trace pipe.
143
144{1 Notice!}
145
146root permissions are required when you run eBPF programs. This is a
147consequence of the fact that they are loaded into the kernel. To offer
148some assurance though, eBPF programs always have to pass through a
149verifier before they can be loaded. This ensures that eBPF programs
150aren't able crash to crash the kernel. For more information, read
151{{:https://ebpf.io/what-is-ebpf/#ebpf-safety} here}.