Exploring eBPF with libbpf and ebpf go library
It’s been almost two years since I looked or played with eBPF when I first heard or saw the tech talks about it. At that time, the tooling for eBPF was still in early stages, although there were lots of examples available, it was still hard to write your own eBPF programs.
Recently I noticed that now there are more abstractions built to make it easier for someone to write eBPF programs. Among the tools available for working with eBPF, we now have libbpf, and also ebpf pure go library from Cilium, so I decided to try it again and explore all these options.
What is libbpf? #
libbpf is an open-source library that provides a convenient interface for loading and managing eBPF programs and maps. It is part of the Linux kernel source but is also available as a standalone library at libbpf GitHub repository.
Getting Started with libbpf #
To begin with libbpf, clone the repository from GitHub:
git clone https://github.com/libbpf/libbpf.git
Once cloned, you can build and install the library. The README file in the repository provides detailed instructions on how to do this. Learning and Documentation
For comprehensive learning, the libbpf documentation is an invaluable resource. It covers a wide range of topics, from basic usage to advanced features.
Writing eBPF Programs with Cilium’s Go Library #
The Cilium eBPF library offers an extensive Go-based framework for working with eBPF. Its abstraction layer simplifies the process of writing, loading, and running eBPF programs. Setting Up Cilium eBPF Go Library
To use Cilium’s eBPF library, first, ensure you have a Go environment set up. Then, you can install the library using:
go get github.com/cilium/ebpf
Writing a C eBPF Program #
Start by writing a simple eBPF program in C. For example, you might create a program that tracks system calls.
Creating a basic eBPF program in C to track system calls involves several steps. Below is a simple example of such a program. This example will focus on creating an eBPF program that gets loaded into the kernel and hooks into a syscall (e.g., clone) to track its invocation.
It’s important to note that writing eBPF code requires a good understanding of kernel programming and the eBPF infrastructure. The code provided is for educational purposes and might need adjustments based on your specific kernel version and eBPF capabilities.
#include <uapi/linux/ptrace.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
SEC("tracepoint/syscalls/sys_enter_clone")
int bpf_prog1(struct trace_event_raw_sys_enter *ctx) {
bpf_printk("Clone system call invoked\n");
return 0;
}
char _license[] SEC("license") = "GPL";
Here’s a brief explanation of the code:
-
Includes: We include necessary headers. ptrace.h for tracing capabilities, bpf.h for eBPF specific functions, and bpf_helpers.h for helper functions.
-
SEC Macro: The SEC(“tracepoint/syscalls/sys_enter_clone”) tells the eBPF verifier that this function should be attached to the sys_enter_clone tracepoint, which is triggered at the entry of the clone syscall.
-
bpf_prog1 Function: This is the eBPF program that gets executed when the clone syscall is invoked. The bpf_printk line is used for logging purposes. It prints a message every time the clone syscall is called.
-
_license: eBPF programs in the Linux kernel require a license declaration. Here, we declare the GNU General Public License (GPL).
To compile this eBPF program, you’ll need a recent version of the Linux kernel headers installed and a toolchain capable of compiling eBPF bytecode. This typically involves using clang and llvm tools.
After compiling, you would load this eBPF program into the kernel using a user space loader program, which typically would be written in Go if you’re using the Cilium eBPF library, or in C using libbpf.
Integrating with Go #
After writing your eBPF program in C, use the Cilium eBPF Go library to load and attach the eBPF program. The process involves reading the compiled eBPF program, loading it into the kernel, and attaching it to a hook point.
package main
import (
"github.com/cilium/ebpf"
"log"
)
func main() {
// Load eBPF program from file
spec, err := ebpf.LoadCollectionSpec("syscall_tracker.o")
if err != nil {
log.Fatalf("failed to load eBPF program: %v", err)
}
// Load and attach eBPF program
coll, err := ebpf.NewCollection(spec)
if err != nil {
log.Fatalf("failed to create eBPF collection: %v", err)
}
defer coll.Close()
// Further code to interact with the eBPF program
}
This code snippet demonstrates the basic structure. You’ll need to adjust it according to the specifics of your eBPF program. Further Resources and Getting Started Guide
For more detailed guidance and practical examples, the Getting Started Guide with eBPF and Go is an excellent resource. It provides step-by-step instructions and best practices for writing and deploying eBPF programs using Go. Conclusion
The combination of libbpf for interfacing with eBPF programs and the Cilium eBPF library for Go offers a powerful toolkit for developing high-performance, kernel-level applications. Whether you’re monitoring system behavior, enhancing network performance, or securing your systems, these tools provide a solid foundation for your eBPF journey. Dive into the documentation, experiment with code, and join the vibrant community of eBPF developers. Happy coding!