How to Build a High-performance Packet Filter Using Ebpf and Xdp on Ubuntu Server
Learning how to build a high-performance packet filter using eBPF and XDP on Ubuntu Server gives you one of the most powerful tools in modern Linux networking. Traditional packet filtering methods like iptables work well for basic tasks. But when you need to handle millions of packets per second with minimal CPU overhead, eBPF and XDP are in a completely different league. This tutorial walks you through writing a real eBPF program, compiling it, and attaching it to a network interface using XDP. You’ll block specific traffic at the earliest possible point in the kernel’s network stack. By the end, you’ll have a working packet filter you can extend for DDoS mitigation, traffic analysis, or custom firewall rules. No prior eBPF experience is required, but you should be comfortable working in the Linux terminal.
Prerequisites for Building a High-performance eBPF and XDP Packet Filter
Before you start, make sure your environment meets these requirements.
System requirements:
- Ubuntu Server 22.04 LTS or 24.04 LTS (kernel 5.15 or higher)
- Root or sudo access
- At least 2 GB RAM
- A network interface you can test on (physical or virtual)
Required knowledge:
- Basic Linux command-line skills
- Familiarity with C programming (you don’t need to be an expert)
- Understanding of basic networking concepts like IP addresses and ports
Estimated time: 45–60 minutes
You’ll install the necessary build tools and libraries as part of the steps below. Make sure your server has internet access for package downloads. You can verify your current kernel version by running uname -r before proceeding.
Step-by-Step Guide to Building Your eBPF XDP Packet Filter on Ubuntu
You might also find this useful: How to Set Up Celery Distributed Task Queues with Redis and Flower Monitoring on Ubuntu Server
Follow these steps carefully. Each one builds on the last.
Step 1: Update your system and install dependencies
Start by updating your package list and installing the required tools.
sudo apt update && sudo apt upgrade -y
sudo apt install -y clang llvm libelf-dev libbpf-dev
linux-headers-$(uname -r) build-essential
iproute2 tcpdump
clang compiles your eBPF C code into BPF bytecode. libbpf-dev provides the userspace library for loading BPF programs. iproute2 includes the ip command you’ll use to attach your XDP program.
Step 2: Create your project directory
Keep your files organized from the start.
mkdir ~/xdp-filter && cd ~/xdp-filter
Step 3: Write the eBPF kernel program
This is the core of your packet filter. Create a file called xdp_filter.c.
nano xdp_filter.c
Paste in the following code. This program drops all incoming ICMP (ping) packets and passes everything else.
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <bpf/bpf_helpers.h>
SEC("xdp")
int xdp_packet_filter(struct xdp_md ctx) {
void data_end = (void )(long)ctx->data_end;
void data = (void )(long)ctx->data;
struct ethhdr eth = data;
if ((void )(eth + 1) > data_end)
return XDP_PASS;
if (eth->h_proto != __constant_htons(ETH_P_IP))
return XDP_PASS;
struct iphdr ip = (void )(eth + 1);
if ((void )(ip + 1) > data_end)
return XDP_PASS;
if (ip->protocol == IPPROTO_ICMP)
return XDP_DROP;
return XDP_PASS;
}
char _license[] SEC("license") = "GPL";
The XDP_DROP return code tells the kernel to discard the packet immediately. This happens before the packet even reaches the kernel’s networking stack. That’s why XDP is so fast.
Step 4: Compile the eBPF program
Use clang to compile your C code into a BPF object file.
clang -O2 -g -target bpf
-I/usr/include/$(uname -m)-linux-gnu
-c xdp_filter.c -o xdp_filter.o
The -target bpf flag tells clang to produce BPF bytecode instead of regular machine code. The -O2 flag enables optimizations. These are important for BPF programs because the kernel verifier requires efficient code.
You can verify the output with:
file xdp_filter.o
You should see output mentioning “ELF” and “BPF.”
Step 5: Identify your network interface
You need to know the name of the interface you want to attach the filter to.
ip link show
Look for your primary interface. On Ubuntu, it’s often named eth0, ens3, or enp0s3. Note the name. You’ll use it in the next step.
Step 6: Attach the XDP program to your interface
Use the ip command to load and attach your compiled program. Replace eth0 with your actual interface name.
sudo ip link set dev eth0 xdp obj xdp_filter.o sec xdp
The sec xdp flag tells the loader which ELF section to use. This matches the SEC("xdp") annotation in your C code. For more details on XDP attachment modes, check the official Linux kernel XDP documentation.
Step 7: Test your packet filter
From another machine on the same network, try pinging your server.
ping <your-server-ip>
The pings should time out. Your XDP filter is dropping ICMP packets before they reach the IP stack. You can confirm the program is attached with:
ip link show eth0
Look for xdp in the output. It will show the program ID if attached correctly.
Step 8: Detach the XDP program
When you’re done testing, remove the filter from your interface.
sudo ip link set dev eth0 xdp off
Pings should start working again immediately.
Troubleshooting Common Errors When Using eBPF and XDP on Ubuntu Server
Even experienced developers run into issues with eBPF. Here are the most common problems and how to fix them.
Error: “Kernel too old” or missing BPF features
Run uname -r and confirm you’re on kernel 5.15 or higher. If not, upgrade Ubuntu or switch to a newer LTS release.
Error: “Permission denied” when attaching XDP
You must use sudo for the ip link set command. BPF program loading requires root privileges.
Error: “libbpf: failed to find valid kernel BTF”
Install the BTF-enabled kernel debug symbols:
sudo apt install linux-image-$(uname -r)-dbg
Compilation fails with missing headers
Make sure you installed linux-headers-$(uname -r) exactly. The kernel version in the package name must match your running kernel.
XDP not supported on your interface
Some virtual network drivers don’t support native XDP mode. Try adding the generic flag as a fallback:
sudo ip link set dev eth0 xdp generic obj xdp_filter.o sec xdp
Generic mode works on any driver but runs slightly later in the stack than native mode. It’s still much faster than iptables for most workloads. For deeper reading on eBPF program types and capabilities, the official eBPF documentation at ebpf.io is an excellent resource.
Conclusion
You now know how to build a high-performance packet filter using eBPF and XDP on Ubuntu Server. You wrote a real eBPF C program, compiled it with clang, attached it to a live network interface, and verified it drops ICMP traffic at line rate. This is just the beginning. You can extend this filter to block traffic by source IP, destination port, or protocol. You can also add BPF maps to make your rules dynamic and update them at runtime
