This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 220 lines 8.5 kB View raw View rendered
1[![OCaml-CI Build Status](https://img.shields.io/endpoint?url=https://ocaml.ci.dev/badge/koonwen/ocaml-libbpf/main&logo=ocaml)](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.