Ping Killer using XDP and eBPF
PingKiller in eBPF
PingKiller is a simple eBPF program that drops ICMP packets. It is written in C and uses the eBPF library to load the program into the kernel.
Terms
eBPF: eBPF is a virtual machine inside the Linux kernel. It can be used to safely execute user-defined programs inside the kernel without the need to change kernel source code or load kernel modules. eBPF programs are written in a restricted C dialect and compiled into bytecode that can be loaded into the kernel with the help of the eBPF library.
XDP: XDP is a Linux kernel technology that provides a high performance, programmable network data path. XDP is supported by most Linux distributions and is used extensively by cloud providers and other environments that require high performance networking.
KSP: The Kernel Space Program uses XDP (eXpress Data Path) which is implemented using the eBPF (extended Berkeley Packet Filter) framework in C, with some help from libbpf. It allows us to intercept packets at an early stage in the Linux kernel’s networking stack and perform custom packet processing logic. That is to reach the packet header and decide what to do with it, ICMP packets are dropped in this case, other packets are passed to the next stage in the networking stack.
USP: The User Space Program is a Golang application that interacts with the XDP eBPF program, providing a user-friendly interface to monitor the packet drop behavior and visualize performance statistics, it also allows us to load the KSP eBPF program into the kernel and attach it to the relevant tracepoint/probe (interface), all with the help of Cilium.
Cilium: Cilium is an open-source project that provides networking and security capabilities powered by eBPF. Their documentation and codebase offer in-depth insights into eBPF and its applications.
BCC: Out-of-our-scope, BCC is a collection of powerful command-line tools and libraries that leverage eBPF for various tracing and performance analysis tasks.
Learning Section
We are currently gathering resources, watching videos, and reading documentations of the Linux Kernel, eBPF use-cases, and Cilium. We will keep updating this learn more section as we progress.
The Linux Kernel Documentation: The Linux kernel documentation includes a comprehensive section on eBPF and XDP, covering various aspects, including API references, usage examples, and implementation details. Link
The Cilium Project: Open-sourced platform Cilium was created by Isovalent Inc. to help address the rising security and networking challenges emerging as cloud-native environments, including Kubernetes, grow in scale and complexity. Cilium
eBPF Community: Community-driven website dedicated to providing resources, tutorials, and news about eBPF. It features articles, case studies, and a curated list of tools and libraries related to eBPF. Link
Environment Set-up
We are using Ubuntu LTS and Linux Manjaro based on Linux kernel 6.1.31 as our development environment, basically eBPF is supported by all Linux Kernels > version 4.
What we will need
- Clang: Clang is a C, C++, Objective-C, or Objective-C++ compiler that is compiled in C++ based on LLVM. It is designed to be compatible with the GNU Compiler Collection (GCC), supporting a wide variety of platforms.
- LLVM: The LLVM Project is a collection of modular and reusable compiler and toolchain technologies. Despite its name, LLVM has little to do with traditional virtual machines. The name “LLVM” itself is not an acronym; it is the full name of the project.
- bpftool: bpftool is a command-line utility in Linux that is used to manage and manipulate BPF (Berkeley Packet Filter) programs and maps. We are going to install this from the source.
- Golang: Go is an open-source programming language that makes it easy to build simple, reliable, and efficient software.
Installation Commands
Ubuntu / Debian-based
- Install Clang and LLVM
1 | sudo apt update |
- Install bpftool
1 | git clone --recurse-submodules https://github.com/libbpf/bpftool.git |
- Install Golang
1 | sudo apt install golang |
Manjaro / Arch-based
- Install Clang and LLVM
1 | sudo pacman -S clang llvm |
- Install bpftool
1 | git clone --recurse-submodules https://github.com/libbpf/bpftool.git |
- Install Golang
1 | sudo pacman -S go |
PingKiller: Our first eBPF program
What is PingKiller?
PingKiller is a simple eBPF program that drops ICMP packets. It is written in C and uses the eBPF library to load the program into the kernel.
Kernel Space Program
The kernel space program that uses XDP to drop all ICMP packets of a given network interface, and will collect statistics and measure the processing time for dropped and passed packets using eBPF perf event mechanism. The program will run in kernel space, and will expose the data to userspace using eBPF maps.
- Clone libbpf and change into the directory
1 | git clone https://github.com/libbpf/libbpf.git |
Get back to project root folder and create a new directory where the kernel space program will live
ksp
and then create a subdirectory namedbpf
and change into it.Create a file
dicmp_kern.c
, this file will contain our XDP eBPF logic.
1 |
The
bpf_helpers.h
header file contains a set of helper functions that are used to interact with the eBPF runtime. Thebpf.h
header file contains definitions for eBPF related data structures and constants. These files are part of the Linux kernel source code and are located in the libbpf source code directory.Next, we define the necessary data structures and maps for XDP,
perf_trace_event
that will represent the perf event data,BPF_MAP_TYPE_PERF_EVENT_ARRAY
map to store the perf events, as follows:
1 | struct perf_trace_event { |
- XDP Program Function
1 | SEC("xdp") |
The
xdp_dicmp
function is the entry point of our XDP program, it will be called for each packet that arrives at the network interface. Thectx
parameter is a pointer to thexdp_md
data structure, which contains information about the packet and the network interface.Now, onto handling perf events, drop and pass packets, and collect statistics.
Inside the xdp_dicmp
function, we will first create a perf_trace_event
object and initialize it with the current timestamp and the packet processing time, then we will store the event in the output_map
map, as follows:
1 | struct perf_trace_event e = {}; |
Next, we will check if the packet is an ICMP packet, if it is, we will drop it and store a perf event with the type
TYPE_DROP
, otherwise, we will pass the packet and store a perf event with the typeTYPE_PASS
, as follows:We start by getting a pointer to the packet data using the
data
field of thexdp_md
data structure, then we get the packet size using thedata_end
field, and finally, we get the packet header using thedata
field.
1 | void *data = (void *)(long)ctx->data; // start of packet data |
The drop and pass logic is the same, we just change the
e.type
value and the return value of the function, we also calculate the packet processing time and store it in thee.processing_time_ns
field.If there is no ethernet header, we will drop the packet and store a perf event with the type
TYPE_DROP
, otherwise, we will check if the ethernet header is an IPv4 header, if it is not, we will pass the packet and store a perf event with the typeTYPE_PASS
, otherwise, we will check if the IP header is an ICMP header, if it is, we will drop the packet and store a perf event with the typeTYPE_DROP
, otherwise, we will check if the IP header is a TCP header, if it is, we will print a message to the kernel log.Finally, we will pass the other packets and store a perf event with the type
TYPE_PASS
, as follows:
1 | e.type = TYPE_PASS; |
The
bpf_printk
function is used to print messages to the kernel log, it is similar to theprintf
function in C, but it is only used for debugging purposes, it is not recommended to use it in production.Now, we are done with the function, do not forget to add the license:
1 | char _license[] SEC("license") = "GPL"; |
- And that is it, we are done with the XDP program, now we will compile it using the following command:
1 | clang -S \ |
- Which will generate the LLVM IR file
dicmp_kern.ll
, then we will use thellc
tool to compile the LLVM IR file to BPF bytecode, as follows:
1 | llc -march=bpf -filetype=obj -O2 -o dicmp_kern.o dicmp_kern.ll |
Notice, that this compilation step can take part in the userspace program since we can compile c programs to bytecode in golang, we will check that next time.
User Space Program
Here, we will write the Golang logic that interacts with the XDP program in order to collect metrics
First, the USP will load the XDP program, then it will create a perf event map, and finally, it will read the perf events from the map and print them to the terminal.
We start by importing the required packages:
1 | package main |
- Then, we will define the perf event data structure:
1 | const ( |
The
ringBuffer
data structure is used to calculate the average processing time of the lastringBufferSize
packets, it is a ring buffer that stores the processing time of the lastringBufferSize
packets, thestart
field is used to keep track of the oldest packet in the buffer, thepointer
field is used to keep track of the next empty slot in the buffer, and thefilled
field is used to check if the buffer is full or not.Implemented below, are the functions to sum and average the processing times in the ring buffer:
1 | func (rb *ringBuffer) add(val uint32) { |
- Now, we will define the
main
function:
1 | func main() { |
We start by loading the eBPF collection from the compiled object file, then we get the
xdp_dicmp
program from the collection, and we attach it to the specified interface.Then, we get the
output_map
map from the collection, and we create a perf event reader for it.We create two ring buffers, one for the processing time of the dropped packets, and one for the processing time of the passed packets.
Then, we start a goroutine to read the perf events from the
output_map
map, and we print the statistics every time we receive a perf event.Finally, we wait for an interrupt signal to exit the program.
Now, we can run the program:
1 | go mod init dicmp |
DEMO
- We will run the program, and we will ping the interface to generate ICMP packets:
Next Steps
- Learning how to deal with eBPF map entries lookups and updates
- Learning how to control the kernel space program from the user space program using eBPF maps
- Dealing with syscalls in eBPF programs
Shout out to Peter McConnell for his great post on Medium about XDP implementation.
- Title: Ping Killer using XDP and eBPF
- Author: ADM-MIDA0UI
- Created at: 2023-08-11 08:00:00
- Updated at: 2024-05-17 21:05:17
- Link: https://admida0ui.de/2023/08/11/ebpf/
- License: This work is licensed under CC BY-NC-SA 4.0.