LabHub

Blog

eBPF Complete Guide 2025: Kernel Programming Revolution for Observability, Networking, Security

한국어English日本語

TL;DR


1. What is eBPF?

1.1 One-line definition

eBPF is a technology that allows you to run sandboxed programs inside the operating system kernel without modifying it.

Traditionally, extending kernel functionality required two approaches:

  1. Add code to the kernel itself — hard to contribute, and even when merged, users wait for new kernel versions
  2. Load kernel modules — risky if poorly written, vulnerable to kernel ABI changes

eBPF offers a third path: safely run custom code inside the kernel.

1.2 How is safety ensured?

Before loading, eBPF programs must pass BPF Verifier static analysis:

After verification, the JIT compiler converts to native machine code, executing at near-native speed.

1.3 Evolution from cBPF to eBPF

FeaturecBPF (1992)eBPF (2014~)
PurposePacket filtering onlyGeneral-purpose kernel programming
Registers2 (32-bit)11 (64-bit)
Instructions22100+
Helper functionsNone200+
MapsNone30+ types
JITSome architecturesMost architectures

cBPF used by tcpdump was a simple packet filter, but eBPF evolved into a mini virtual machine.


2. eBPF Architecture Deep Dive

2.1 Component Overview

┌─────────────────────────────────────────────┐
User Space│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │ bpftool  │  │ bpftrace │  │ Cilium   │  │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  │
│       │             │             │         │
│       └─────────────┼─────────────┘         │
│                     │ bpf() syscall          │
└─────────────────────┼───────────────────────┘
┌─────────────────────────────────────────────┐
Kernel Space│  ┌─────────────────────────────────────┐   │
│  │  BPF Verifier (safety verification) │   │
│  └──────────────┬──────────────────────┘   │
│                 ▼                            │
│  ┌─────────────────────────────────────┐   │
│  │  JIT Compiler (native code)         │   │
│  └──────────────┬──────────────────────┘   │
│                 ▼                            │
│  ┌─────────────────────────────────────┐   │
│  │  Hooks: kprobe, tracepoint, XDP,    │   │
│  │  perf_event, socket, cgroup, LSM    │   │
│  └─────────────────────────────────────┘   │
└─────────────────────────────────────────────┘

2.2 Major Hook Types

eBPF programs can attach to various kernel events:

Hook TypeDescriptionExample
kprobe / kretprobeKernel function entry/exitTrace do_sys_open calls
uprobe / uretprobeUser-space functionsTrace OpenSSL SSL_read
tracepointStatic kernel tracepointssched:sched_switch
perf_eventHardware/software countersCPU cycles, cache misses
XDPNetwork driverDDoS filtering
TC (Traffic Control)Traffic shapingQoS, load balancing
socketSocket operationsSocket filters
cgroupCgroup eventsNetwork isolation
LSM (Linux Security Modules)Security policiesFile access control

2.3 BPF Maps — Core Data Structures

Data sharing between eBPF programs and user space happens through BPF Maps:

Map TypePurpose
BPF_MAP_TYPE_HASHGeneral key-value storage
BPF_MAP_TYPE_ARRAYArray (index-based)
BPF_MAP_TYPE_PERF_EVENT_ARRAYStreaming events to user space
BPF_MAP_TYPE_RINGBUFMore efficient event streaming (kernel 5.8+)
BPF_MAP_TYPE_LRU_HASHLRU cache
BPF_MAP_TYPE_LPM_TRIELongest prefix match (used in routing)
BPF_MAP_TYPE_PROG_ARRAYTail call jump table
// BPF_MAP_TYPE_HASH example: counting syscalls per PID
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, __u32);  // PID
    __type(value, __u64); // count
    __uint(max_entries, 10240);
} syscall_count SEC(".maps");

3. Development Tools Comparison

3.1 BCC (BPF Compiler Collection)

3.2 bpftrace — DTrace's Successor

# Count syscalls per process
bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[comm] = count(); }'

# Trace read() calls taking longer than 1 second
bpftrace -e '
kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ {
    $duration = nsecs - @start[tid];
    if ($duration > 1000000000) {
        printf("%s pid %d took %d ms\n", comm, pid, $duration / 1000000);
    }
    delete(@start[tid]);
}'

# Top CPU functions (on-CPU profiling)
bpftrace -e 'profile:hz:99 { @[ustack] = count(); }'

3.3 libbpf + CO-RE — Production Standard

// libbpf example: tracing openat()
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

char LICENSE[] SEC("license") = "GPL";

SEC("tracepoint/syscalls/sys_enter_openat")
int handle_openat(struct trace_event_raw_sys_enter *ctx) {
    char comm[16];
    bpf_get_current_comm(&comm, sizeof(comm));
    bpf_printk("openat by %s\n", comm);
    return 0;
}

3.4 Tools Comparison

ToolLearning CurvePerformanceDeploymentBest For
BCCMediumMediumHard (needs LLVM)Learning, one-off analysis
bpftraceEasyHighMediumAd-hoc tracing, debugging
libbpf + CO-REHardVery HighEasyProduction tool development
Aya (Rust)HardVery HighEasyRust ecosystem integration
ebpf-goMediumVery HighEasyGo application integration

4. Three Major Use Cases of eBPF

4.1 Observability

eBPF observes system behavior deeply without changing application code.

Pixie — Kubernetes Native Observability

Parca — Continuous Profiling

4.2 Networking

Cilium — eBPF-based K8s Networking Standard

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-api-only
spec:
  endpointSelector:
    matchLabels:
      app: backend
  ingress:
  - fromEndpoints:
    - matchLabels:
        app: frontend
    toPorts:
    - ports:
      - port: '8080'
      rules:
        http:
        - method: 'GET'
          path: '/api/v1/.*'

XDP — Ultra-fast Packet Processing

XDP processes packets at the network driver level — bypassing the kernel network stack.

4.3 Security

Falco — Runtime Security Monitoring

- rule: Terminal shell in container
  desc: A shell was used as the entrypoint/exec point into a container
  condition: >
    spawned_process and container
    and shell_procs and proc.tty != 0
  output: >
    Shell spawned in container (user=%user.name container=%container.name
    shell=%proc.name pid=%proc.pid)
  priority: WARNING

Tetragon — Cilium Team's Next-Generation Security


5. Practical: Debugging with bpftrace

5.1 Scenario: Disk I/O suddenly slow

# 1. Which process is causing disk I/O?
sudo bpftrace -e '
tracepoint:block:block_rq_issue {
    @[comm] = count();
}
interval:s:5 {
    print(@);
    clear(@);
}'

# 2. I/O latency distribution (histogram)
sudo bpftrace -e '
kprobe:blk_account_io_start { @start[arg0] = nsecs; }
kprobe:blk_account_io_done /@start[arg0]/ {
    @ms = hist((nsecs - @start[arg0]) / 1000000);
    delete(@start[arg0]);
}'

5.2 Scenario: Unknown files being opened

sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat {
    printf("%s opened: %s\n", comm, str(args->filename));
}'

6. CO-RE: Compile Once, Run Everywhere

6.1 Problem: Struct definitions vary between kernels

Traditional BCC compiled with kernel headers at runtime. But containers struggle to access host kernel headers, and compile times were long.

6.2 Solution: BTF + CO-RE

BTF (BPF Type Format): Kernel exposes its struct info at /sys/kernel/btf/vmlinux.

CO-RE: Compiler represents struct field accesses "symbolically", and the loader relocates them at runtime using BTF info.

#include <bpf/bpf_core_read.h>

SEC("kprobe/do_unlinkat")
int handle_unlinkat(struct pt_regs *ctx) {
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    // BPF_CORE_READ: BTF-based safe field reading
    pid_t pid = BPF_CORE_READ(task, pid);
    
    bpf_printk("unlink by pid %d\n", pid);
    return 0;
}

Result: A single compiled .o file works on kernels 5.4 to 6.5.


7. Production Case Studies

7.1 Netflix — Performance Debugging with bpftrace

Netflix's Brendan Gregg is eBPF's most influential advocate. Netflix uses BCC and bpftrace across tens of thousands of EC2 instances:

7.2 Cloudflare — DDoS Defense with XDP

7.3 Meta — eBPF Everywhere

7.4 Google — GKE Dataplane V2 with Cilium


8. Limitations and Challenges

8.1 Verifier Constraints

8.2 Kernel Version Dependency

8.3 Debugging Difficulty


9. The Future of eBPF

9.1 BPF in Windows

Microsoft is developing eBPF for Windows. Same eBPF programs can run on Windows → true cross-platform kernel programming.

9.2 sched_ext — Scheduler in eBPF

Introduced in kernel 6.12+. Write the CPU scheduler itself in eBPF → workload-specific custom scheduling.


10. Getting Started — Learning Roadmap

Week 1: Basics

Week 2-3: BCC

Week 4-6: libbpf + CO-RE

Week 7+: Real Projects


Quiz

1. Why are eBPF programs guaranteed to be safe?

Answer: Before loading into the kernel, the BPF Verifier performs static analysis. It checks for infinite loops, memory access validation, only allows approved helper functions, and limits instruction count. After verification passes, the JIT compiler converts to native machine code, running at near-native speed.

2. What problem does CO-RE solve?

Answer: Traditional BCC required compiling with kernel headers at runtime. This caused LLVM dependency, long startup times, and difficulties in container environments. CO-RE uses BTF to allow the compiler to represent struct field accesses "symbolically", with the loader performing runtime relocation. A single compiled .o file works across various kernel versions.

3. Why is XDP faster than iptables?

Answer: XDP processes packets at the network driver level. Packets are processed before they enter the kernel network stack (skb allocation, conntrack, etc.), resulting in very low overhead. Cloudflare processes 10M+ packets per second on a single server with XDP, achieving 10x more efficiency than iptables.

4. How does Cilium replace kube-proxy?

Answer: kube-proxy uses iptables or IPVS rules to route Service ClusterIPs to backend Pods. As clusters grow, iptables rules can reach thousands, degrading performance. Cilium's eBPF programs select backends directly during socket operations and packet processing, eliminating the need for iptables rules. The result is consistent performance with low CPU usage.

5. When should you use bpftrace vs libbpf?

Answer: bpftrace is suitable for ad-hoc tracing and debugging. Its DTrace-like high-level DSL allows powerful analysis in one line. libbpf + CO-RE is suitable for production tool development — small binaries, no compiler dependency, fast startup, broad kernel compatibility. Personal debugging uses bpftrace; tools you ship use libbpf.


References

Comments

No comments yet.

Sign in to leave a comment