An Introduction to eBPF: Concepts, Use Cases, and Sample Programs
This article explains what eBPF (extended Berkeley Packet Filter) is, compares it with related tracing tools, describes its core mechanisms and common applications such as network monitoring, security filtering and performance analysis, and provides step‑by‑step Python examples with installation instructions and further reading resources.
eBPF (extended Berkeley Packet Filter) is a Linux kernel technology that lets developers run custom byte‑code programs in the kernel without modifying the kernel source.
Originating from the classic BPF network filter and inspired by DTrace, eBPF first appeared in Linux 3.18 and requires kernel 4.4 or newer for full functionality.
Unlike traditional BPF, which is limited to network packet filtering, eBPF can be used for many scenarios including network monitoring, security filtering, and performance analysis. It works by attaching compiled byte‑code to kernel hooks (e.g., system calls, network events) and is typically developed with the BPF Compiler Collection (BCC), which builds on LLVM/Clang.
Tool
eBPF
SystemTap
DTrace
定位
Kernel technology for many use cases
Kernel module
Dynamic tracing and analysis
工作原理
Dynamic loading and execution of compiled byte‑code
Dynamic loading of kernel modules
Dynamic instrumentation via probes
常见用途
Network monitoring, security filtering, performance analysis
System performance analysis, fault diagnosis
System performance analysis, fault diagnosis
优点
Flexible, safe, multi‑scenario
Powerful, GUI
Powerful, high performance, multi‑language support
缺点
Steep learning curve, safety depends on compiler correctness
Steep learning curve, safety depends on module correctness
Complex configuration, potential performance impact
Typical use cases of eBPF include:
Network monitoring – capture packets and analyze traffic patterns.
Security filtering – block or alert on malicious traffic.
Performance analysis – collect kernel metrics and visualize bottlenecks.
Virtualization – gather VM performance data for load balancing.
The eBPF workflow consists of three steps: loading, compilation, and execution. User‑space programs use system calls to load eBPF byte‑code into the kernel, where a JIT compiler translates it to native instructions. The program is attached to a kernel hook and runs when the corresponding event occurs. Before execution, the kernel performs safety checks to ensure the program cannot crash or compromise the system.
Below is a simple Python example that counts TCP packets using BCC:
#!/usr/bin/python3
from bcc import BPF
from time import sleep
# eBPF program source
bpf_text = """
#include
BPF_HASH(stats, u32);
int count(struct pt_regs *ctx) {
u32 key = 0;
u64 *val, zero = 0;
val = stats.lookup_or_init(&key, &zero);
(*val)++;
return 0;
}
"""
b = BPF(text=bpf_text, cflags=["-Wno-macro-redefined"])
# Attach to the tcp_sendmsg kernel function
b.attach_kprobe(event="tcp_sendmsg", fn_name="count")
name = {0: "tcp_sendmsg"}
while True:
try:
for k, v in b["stats"].items():
print("%s: %d" % (name[k.value], v.value))
sleep(1)
except KeyboardInterrupt:
exit()To run the script, install the BCC tools (e.g., sudo apt install python3-bpfcc ) and ensure the kernel is compiled with eBPF support. After granting execution permission ( chmod +x netstat.py ) and running with sudo, the program prints packet counts every second.
A more advanced example measures TCP latency by attaching to both send and receive hooks:
#!/usr/bin/python3
from bcc import BPF
import time
bpf_text = """
#include
#include
#include
struct packet_t {
u64 ts, size;
u32 pid;
u32 saddr, daddr;
u16 sport, dport;
};
BPF_HASH(packets, u64, struct packet_t);
int on_send(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg, size_t size) {
u64 id = bpf_get_current_pid_tgid();
struct packet_t pkt = {};
pkt.ts = bpf_ktime_get_ns();
pkt.size = size;
pkt.pid = id;
pkt.saddr = sk->__sk_common.skc_rcv_saddr;
pkt.daddr = sk->__sk_common.skc_daddr;
struct inet_sock *isp = (struct inet_sock *)sk;
pkt.sport = isp->inet_sport;
pkt.dport = sk->__sk_common.skc_dport;
packets.update(&id, &pkt);
return 0;
}
int on_recv(struct pt_regs *ctx, struct sock *sk) {
u64 id = bpf_get_current_pid_tgid();
struct packet_t *pkt = packets.lookup(&id);
if (!pkt) return 0;
u64 delta = bpf_ktime_get_ns() - pkt->ts;
bpf_trace_printk("tcp_time: %llu.%llums, size: %llu\n", delta/1000, delta%1000%100, pkt->size);
packets.delete(&id);
return 0;
}
"""
b = BPF(text=bpf_text, cflags=["-Wno-macro-redefined"])
b.attach_kprobe(event="tcp_sendmsg", fn_name="on_send")
b.attach_kprobe(event="tcp_v4_do_rcv", fn_name="on_recv")
print("Tracing TCP latency... Hit Ctrl-C to end.")
while True:
try:
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
except KeyboardInterrupt:
exit()The article also lists a set of useful BCC tools (e.g., bcc‑tools , bpftrace , tcptop , execsnoop ) and provides links to further reading such as Brendan Gregg’s “BPF Performance Tools”, the official eBPF website, Cilium’s reference guide, and the kernel documentation.
Finally, the author introduces himself as Chen Hao, a former senior architect at Alibaba Cloud and senior R&D manager at Amazon, now founder of MegaEase, focusing on Cloud‑Native high‑availability architectures.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.