eBPF
Initially, the Berkeley Packet Filter (BPF) was created to filter out or discard unwanted network packets in the kernel space instead of user space for performance reasons.
eBPF (enhanced Berkeley Packet Filter) on the other hand, extends the original BPF functionality to run sandboxed programs in a privileged context. In other words, it can extend the functionality of the Linux Kernel in a safe and efficient manner.
Consider eBPF as a superpower that allows you to install hooks for things that happen in the Linux Kernel. Whether the Linux Kernel is executing an application or a user logging in - you can see and intercept those events.
To write an eBPF program, you will need to define the hooks that trigger the eBF program to run. These hooks are typically defined by Kernel sub-systems that they monitor or control.
Hooks
Tracepoints enable the capture of data and the execution of actions when a given Kernel event takes place. eBPF can be used to trace things like system calls, network packets, and disk I/O.
eXpress Data Path (XDP) is a packet processing system that allows eBPF programs to run before the Linux network stack. Through XDP, it is possible to implement forwarding, load balancing, and packet filtering.
Kprobes enables the debugging of Kernel code, modification of function behaviour, and tracing of function calls. It is useful when a predefined hook does not exist.
Uprobes are a similar feature that allows for user space tracing and debugging. Again, this is useful when a predefined hook does not exist.
Cgroup hooks can be used for attaching cgroups or controlling groups which manage system resources such as CPUs, memory and network bandwidth.
Sched hooks can be used to attach to Linux schedulers, which are responsible for allocating CPU time between running processes.
Once you have defined your hooks, you can write eBPF programs in C using eBPF-specific helper functions provided by the Kernel. These helper functions provide access to data structures and functions available in the Kernel space.
Verification and Security
Before the Kernel attaches the eBPF program to the hook(s) it verifies the following [1]:
- That the process loading the eBPF program holds the required privileges.
- Unless unprivileged eBPF is enabled, only privileged processes can load eBPF programs.
- The program does not crash or otherwise harm the system.
- The program always runs to completion.
You can find more information around the safety of eBPF programs over here.
Executing an eBPF program
After writing an eBPF program, you will need to compile it using the LLVM compiler which produces an eBPF object file that can be loaded into the Kernel at runtime. Finally, you will need to attach the eBPF program to the appropriate hooks which is done with the 'bpf()' system call.
Of course, you can simplify your life by using tools such as bcc or Cilium to write eBPF programs.
The bcc repository contains a number of examples. I replicated the hello world example from the bcc repository below:
#!/usr/bin/python
# Copyright (c) PLUMgrid, Inc.
# Licensed under the Apache License, Version 2.0 (the "License")
# run in project examples directory with:
# sudo ./hello_world.py"
# see trace_fields.py for a longer example
from bcc import BPF
# This may not work for 4.17 on x64, you need replace kprobe__sys_clone with kprobe____x64_sys_clone
BPF(text='int kprobe__sys_clone(void *ctx) { bpf_trace_printk("Hello, World!\\n"); return 0; }').trace_print()