拆解 eBPF 验证器原理与高性能安全检测引擎实现

学 k8s 离不开 Calico,学 calico 离不开 eBPF,简单学习一下,记一下笔记

什么是 eBPF

eBPF(extened Berkeley Packet Filter)是一项革命性的技术,起源于 Linux 内核,它可以在特权上下文中(如操作系统内核)运行沙盒程序

简单来说就是可以通过在操作系统内核中执行沙盒程序,在不改变内核源码或加载内核模块的前提下安全便携的扩展内核能力

从历史上看,由于内核具有监督和控制整个系统的特权,操作系统一直是实现可观测性、安全性和网络功能的理想场所。同时,由于操作系统内核的核心地位和对稳定性和安全性的高要求,操作系统内核很难快速迭代发展。因此在传统意义上,与在操作系统本身之外实现的功能相比,操作系统级别的创新速度要慢一些。

整体架构

用户态

程序编写:开发者使用 eBPF 汇编受限 C 语言编写内核逻辑。由于内核环境的特殊性,编写时需遵循 eBPF 特有的辅助函数(Helpers)和语法约束。

字节码编译:借助 LLVM/Clang 编译器前端及 eBPF 后端,将源代码编译为体系结构无关的 eBPF 字节码(Bytecode),确保程序具备跨内核版本的兼容性。

内核加载:通过调用 bpf() 系统调用,将生成的字节码注入内核。在正式运行前,内核验证器(Verifier)会对指令进行静态分析,确保其安全且不会导致系统崩溃。

内核态

安全验证 (Verifier):内核验证器对注入的字节码进行严格的静态分析。它会检查代码是否存在死循环、非法内存访问或堆栈溢出,确保内核的绝对安全。

即时编译 (JIT Compiler):通过验证后,JIT 编译器将通用字节码翻译成当前 CPU 架构(如 x86, ARM)的原生机器码,以实现近乎原生的运行性能。

挂载与触发 (Hooks):程序被附加到特定的内核事件(如网络包到达、系统调用、kprobes 等)。当事件触发时,eBPF 程序立即执行。

状态共享 (Maps):程序在执行过程中,通过 eBPF Maps 将处理结果(如统计数据、监控日志)传回用户态,实现两个层级的数据闭环。

手搓一个 eBPF

纯手写 bpf() 太几把费劲了,这里看阿里的简单教程理解一下,安装环境

1
2
sudo apt update
sudo apt install -y bpftrace

用下面这行指令来实时监控系统里面谁在执行 openat 系统调用(即谁在打开文件)

1
sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s (PID %d) is opening a file\n", comm, pid); }'

image-20260328004024956

我们在新的终端页输入命令上面这个监控就会实时弹出,BCC 允许我们自己用 C 来编写内核逻辑,用 Python 来写用户态逻辑,先安装 BCC

1
sudo apt install -y bpfcc-tools python3-bpfcc

编写一个程序,一样的逻辑会监控所有 clone() 系统调用(即创建新的进程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# hello_ebpf.py
from bcc import BPF
program = """
int hello_world(void *ctx) {
bpf_trace_printk("Hello Ubuntu! New process created.\\n");
return 0;
}
"""
# 加载程序
b = BPF(text=program)
b.attach_kprobe(event=b.get_syscall_fnname("clone"), fn_name="hello_world")
print("正在监控进程创建")
try:
b.trace_print()
except KeyboardInterrupt:
exit()

image-20260328004316589

懂了上面这些逻辑后我们可以简单做一个防火墙出来,这一点需要你提前知道一些计算机网络的小知识,这里用 XDP(eXpress Data Path)来做防火墙,XDP 运行在网卡驱动层,甚至在内核协议栈处理数据包之前,这意味着可以以极高的性能丢弃(Drop)攻击包,而不会消耗 CPU 去解析复杂的协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from bcc import BPF
import time
import sys

# 拦截一下 8.8.8.8
BLOCK_IP = "8.8.8.8"

# 内核态 C 代码
program = """
#include <uapi/linux/bpf.h>
#include <uapi/linux/if_ether.h>
#include <uapi/linux/ip.h>

int xdp_firewall(struct xdp_md *ctx) {
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
struct ethhdr *eth = data;
if ((void*)eth + sizeof(*eth) > data_end)
return XDP_PASS;
if (eth->h_proto != htons(ETH_P_IP))
return XDP_PASS;
struct iphdr *iph = data + sizeof(*eth);
if ((void*)iph + sizeof(*iph) > data_end)
return XDP_PASS;
if (iph->saddr == 0x08080808) {
bpf_trace_printk("Drop packet from 8.8.8.8\\n");
return XDP_DROP;
}
return XDP_PASS;
}
"""
device = "enp1s0"
b = BPF(text=program)
fn = b.load_func("xdp_firewall", BPF.XDP)
print(f"正在 {device} 上启动防火墙,拦截 {BLOCK_IP}...")
try:
b.attach_xdp(device, fn, 0)
b.trace_print()
except Exception as e:
print(e)
finally:
b.remove_xdp(device, 0)
print("\n已卸载防火墙。")

image-20260328004611095

简单拓展一下 XDP 防火墙的知识

  • XDP_PASS:允许通过

  • XDP_DROP:原地丢弃(防火墙核心)

  • XDP_TX:原路发回(可用于反射攻击防护)

  • XDP_REDIRECT:转发到其他网卡

代表项目

https://ebpf.io/zh-hans/applications/

  1. 网络和负载均衡
    1. Cilium
    2. Calico
    3. Katran
  2. 系统可观测性与监控
    1. SkyWalking
    2. bcc
  3. 系统安全
    1. Falco
    2. tetragon
    3. Cloudflare
  4. 性能调优与性能剖析
    1. Parca / Pyroscope

References

Support via Solana

Solana

Solana

Solana Pay

Solana Pay

WeChat

WeChat