
Build BPF Toolchain on OpenWRT
I set a single goal: build an OpenWRT eBPF toolchain end to end—compile, flash, and verify on real hardware with no guesswork.
I explain the steps I use: enable CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_NET_CLS_BPF, and CONFIG_NET_ACT_BPF in the kernel via make menuconfig. Then I install build deps on my Linux host, clone the official repo, and run make -j$(nproc).
After I flash the produced image from bin/targets, I SSH to the router, run opkg update, install bpftool, and confirm support with bpftool prog list. This yields a firmware image with ebpf enabled and user-space tools ready to run a simple program on the network.
I keep risks explicit: back up config, verify device target, and preserve settings when flashing to avoid downtime. I also measure CPU and performance impact so the change improves the system in a clear way.
Key Takeaways
- I set the goal: build, flash, and verify a firmware image with ebpf support.
- Enable the four kernel options and compile from the official repo.
- Flash from bin/targets, then install bpftool and list programs to verify.
- Back up configs and confirm device targets to reduce risk.
- Measure CPU and performance impact to validate gains.
What this guide covers and who should use it
I walk you through building firmware, enabling kernel hooks, and loading a program on hardware.
This guide shows what you will get: a working build pipeline and a firmware image that can run verified eBPF programs on a router. You will see where to attach a program and how to confirm it runs on the network path.
Who should use this: Linux admins and engineers who manage routers, need packet visibility or policy offload, and can work at the command line. You should be comfortable with Git, shell, SSH, and basic router maintenance at an intermediate level.
Scope and tools: I cover build environment setup, kernel options for eBPF features, firmware compile, safe flashing, and a first program load with a real example. For loaders and inspection I reference libbpf, bcc, bpftrace, and bpftool.
- Use cases: packet filtering, flow accounting, latency tracking, and custom network handling.
- Safety: the kernel verifier enforces constraints and the JIT speeds execution—this keeps the system safe while you run useful programs.
- What I won’t do: GUI-only workflows or abstract theory—you will build the software and run a program on hardware.
Prerequisites, hardware support, and backups
I start with a strict checklist. Confirm the router model and flash size. Match the image profile to the device before flashing.
Check storage and RAM. Building on the host needs tens of gigabytes. The router must have enough flash for the kernel and packages. It also needs RAM for map memory and verifier work.
Supported routers, storage, and CPU considerations
Verify target: model, exact flash layout, and supported image version. Only flash images that match the model.
Note CPU limits. Small cores hit performance bounds — XDP generic mode can cost cycles. Pick hooks with your cpu in mind.
Build host requirements and required packages
Use a modern Linux distro with stable Internet. Install packages: build-essential, git, libncurses5-dev, gawk, gettext, unzip, file, and zlib1g-dev.
Network access, SSH, and router configuration backup
Ensure reliable SSH and the correct management interface IP. Keep Ethernet ports and a serial console ready if available.
- Export a LuCI backup and copy /etc/config via scp.
- Record running version, kernel, and installed package list for post-update comparison.
- Know the device failsafe/recovery method and confirm power stability — use a UPS.
- Have SSH keys, root password, and serial tools ready.
| Item | Check | Action |
|---|---|---|
| Router model | Exact hardware & flash size | Verify vendor model and supported image profile |
| Build host | Disk, packages, network | Install required package set and confirm Internet |
| Recovery plan | Failsafe, serial, UPS | Document recovery steps and secure power |
| System state | Version, kernel, config files | Export backups and save package list |
Prepare the OpenWRT build environment from source
Get the build host ready: install the packages, clone the source code, and pick the right profile. Do this on a Debian/Ubuntu system for the commands below to work as written.
Install build dependencies (Ubuntu/Debian example)
Run a single command to pull the essential packages and save time:
- sudo apt-get install build-essential git libncurses5-dev gawk gettext unzip file zlib1g-dev
Clone the repository and update feeds
Clone the official repo to track upstream kernel fixes and features:
- git clone https://git.openwrt.org/openwrt/openwrt.git && cd openwrt
- ./scripts/feeds update -a && ./scripts/feeds install -a
Select target, profile, and packages
Run make menuconfig to choose target, profile, and kernel options. Pick a minimal package set—include bpftool and build helpers so the image remains small.
Save the .config to lock the build. Start the compile with make -j$(nproc). Watch the logs early for missing headers or feed failures to avoid wasting time. If you change target or core components, run make dirclean; otherwise keep incremental builds to speed the process.
OpenWRT eBPF toolchain components you need
I keep the component list tight: compiler, libs, user tool, and a few kernel flags. Each piece maps to a clear build or runtime need.
clang/LLVM and kernel headers
I install clang/LLVM and matching kernel headers so a program compiles to BPF bytecode that matches the linux kernel ABI. That prevents verifier failures and mismatched types.
libbpf and CO-RE
I use libbpf and CO-RE to build portable object files. CO-RE adapts to type differences across kernels so the same object attaches cleanly on devices with slight ABI changes.
bpftool
I include bpftool in the image. It is the single tool I use on-device to list programs and maps, check attach points, and dump verifier logs.
- Enable CONFIG_BPF and CONFIG_BPF_SYSCALL to allow program and map syscalls.
- Enable CONFIG_NET_CLS_BPF and CONFIG_NET_ACT_BPF to attach to TC and action pipelines for packet handling.
- Rely on the verifier and JIT: verifier enforces safety; JIT gives near-native speed.
- Keep the package list minimal—bpftool and libbpf userspace are enough for first deployments.
| Component | Purpose | Practical step |
|---|---|---|
| clang/LLVM | Compile C to BPF bytecode | Install distro clang and kernel headers |
| libbpf + CO-RE | Load portable objects | Build with libbpf, use CO-RE macros |
| bpftool | Inspect and manage programs/maps | Include in image; run bpftool prog list |
Finally, think about hooks: TC sees post-driver frames; select the hook that matches your features and packet visibility needs. Structure code with small functions and safe helpers for predictable implementation.
Enable eBPF in the kernel configuration
Before you compile, I lock down the kernel options that allow program loading and TC attachment.
Run make menuconfig and navigate to the kernel sections. Enable these symbols: CONFIG_BPF, CONFIG_BPF_SYSCALL, CONFIG_NET_CLS_BPF, and CONFIG_NET_ACT_BPF. These options let the system accept program loads and attach to the traffic control path.
Built-in vs modules — a practical choice
Built-ins: they load early and simplify attach at boot. Use them when you need predictable behavior and zero module ordering headaches.
Modules: they save flash space and let you keep the default image smaller. Expect extra load-order steps when userspace tools must appear before an attach.
| Option | Pros | Cons |
|---|---|---|
| Built-in | Immediate attach, simpler boot | Bigger image size |
| Modules | Smaller base image | Must manage load order |
Check your linux kernel version for moved symbols or new dependencies before you finalize. Document the configuration changes in a fragment. Save, exit, then run a config diff to confirm only intentional changes before you build.
Build, flash, and verify on your router
I move from build host to device and complete the compile-to-flash cycle with precise commands. I keep steps short and repeatable so you can reproduce results fast.
Compile firmware and packages efficiently
I run make -j$(nproc) to compile the kernel and package selection in parallel. This saves build time and uses all CPU cores.
Flash the image and preserve settings safely
Pick the correct file under bin/targets and choose the sysupgrade image for your device and version. Use the web UI or sysupgrade -v –keep to keep settings.
Never interrupt power during flash. Wait for the router to fully reboot before testing the system.
Install bpftool and confirm program support
SSH in, run opkg update then opkg install bpftool. Run bpftool prog list and bpftool feature probe kernel to verify kernel features and that you can load programs.
- Check storage headroom and installed package integrity post-boot.
- Record the running kernel and OpenWrt version strings for traceability.
- Save build logs and sysupgrade output so you can roll back if needed.
| Step | Quick action | Why |
|---|---|---|
| Compile | make -j$(nproc) | Faster, parallel build |
| Flash | Web UI or sysupgrade –keep | Preserve settings, safer |
| Verify | bpftool prog list | Confirm kernel supports BPF |
Write, compile, and load a minimal eBPF program
I show a minimal C example that compiles to a BPF object and attaches to an interface. The goal: count packets safely and inspect results without breaking traffic.
Keep the source small: one function, one map, bounded loops only. This helps the verifier accept the program quickly and keeps runtime cost predictable.
Create the C source
Example source: a packet counter that returns XDP_PASS. Save as prog.c. Keep the function compact and use only supported helpers so verifier checks succeed fast.
Compile to BPF object
Compile with clang: clang -target bpf -O2 -c prog.c -o prog.o. This produces a BPF object the kernel will verify and run.
Load and attach
Load with bpftool:
- bpftool prog load prog.o /sys/fs/bpf/my_prog type xdp
- bpftool net attach xdp id <id> dev <ifname>
Alternative (TC ingress):
- tc qdisc add dev <ifname> clsact
- tc filter add dev <ifname> ingress bpf da obj prog.o sec prog
Attach mode selection
Pick a mode by driver support and needs:
- XDP native — fastest when the NIC supports it.
- XDP generic — works everywhere but slower.
- TC ingress — stable across drivers and flexible for complex programs.
Use maps to exchange data
Expose counters via an array or hash map. Read map data with bpftool map dump or a tiny libbpf-based user program. Keep map structures simple to minimize verifier friction.
| Step | Command | Why |
|---|---|---|
| Compile | clang -target bpf -O2 -c prog.c -o prog.o | Produce valid BPF bytecode for verifier |
| Load (XDP) | bpftool prog load prog.o /sys/fs/bpf/my_prog type xdp | Make the program available in the kernel |
| Attach | bpftool net attach xdp id <id> dev <ifname> | Bind the program to the interface |
| Inspect | bpftool prog show; bpftool map dump | Verify attach and read counters |
Validate with tcpdump and bpftool prog show. Remove with bpftool net detach or by deleting the TC filter. Measure CPU and packet rates to confirm acceptable performance.
Kernel version features, performance tuning, and troubleshooting
I focus on practical kernel checks, performance tweaks, and fixes that stop regressions fast.

Match your program to the kernel version
Match helper availability and BTF types to the running kernel version. Mismatched types cause verifier rejects. I build with the same headers or use CO-RE to avoid failures.
XDP vs TC: driver impact and CPU trade-offs
XDP native is fastest when the NIC driver supports it. XDP generic works everywhere but can trigger SKB headroom copies—default 256 bytes—which raised CPU use in my MIPS tests.
Verifier, JIT, and program size
Keep loops bounded and instruction counts low so the verifier completes. Confirm the JIT is enabled—native code cuts runtime CPU costs versus interpreted execution.
Performance tips and common fixes
- Reduce SKB headroom (32 vs 256 bytes) — lower copy overhead and improve throughput.
- Use per-CPU maps for counters and LRU maps for bounded memory.
- Measure with perf and iperf3; watch for copy-heavy stacks on small CPUs.
| Area | Symptom | Fix |
|---|---|---|
| Load/Verifier | prog rejected | Rebuild with matching headers or CO-RE; shorten loops |
| XDP generic | High CPU, copy paths | Reduce headroom or use TC |
| Flash/modules | Unstable system after flash | Validate image, use failsafe recovery, avoid risky modules |
Where to go next and how to extend your project
Move from experiments to a repeatable release: package a small libbpf-based loader so the program config and map setup run reliably at boot.
Organize your source and source code into a repo with CI that builds against matching linux kernel headers and the SDK. This catches regressions early and ties each version to a build artifact.
Automate tests in QEMU and on spare hardware to validate ebpf programs, counters, and path behavior. Export map data via a tiny CLI so users read state without shell access.
Ship an ipk package, plan upgrade windows, and publish a short post after milestones with version notes, performance data, and known issues. That keeps the project practical and easy to operate.
FAQ
What does "Build BPF Toolchain on OpenWRT" cover?
Who should use this guide?
Which routers and CPUs are supported?
What build host requirements and packages do I need?
How should I back up the router before flashing?
How do I clone the OpenWRT source and update feeds?
How do I select the correct target, profile, and packages?
Which components are required to compile and run BPF programs on the router?
What kernel configuration flags must I enable?
Should I build BPF features as modules or built-in?
How do I compile firmware and packages efficiently?
How do I flash the image while preserving settings?
How do I verify bpftool and kernel support after flashing?
How do I write and compile a minimal packet-handling program?
How do I load and attach the program?
When should I use XDP native vs XDP generic vs TC ingress?
How do BPF maps let user space and kernel exchange data?
How do I match my program to the kernel version?
What are common verifier and JIT issues?
What performance tuning tips should I apply?
What are frequent build or flash failures and how do I troubleshoot?
Where can I extend the project after getting a minimal program running?
Related: Enable eBPF on OpenWrt: Kernel Config Guide
Related: When to Build Your Own Server (and When Not To)
Related: How eBPF Intercepts Packets Before Kernel Routing
Related: OpenWrt Traffic Monitor: Read Raw Kernel Data First
