引言
网络是分布式系统的命脉。传统的网络观测工具(tcpdump、iptables LOG、systemtap)要么性能开销大,要么需要重新编译内核。eBPF的出现改变了这一切——它在内核中安全地执行用户定义的字节码,实现零拷贝、低开销的全链路网络观测。
1. eBPF 网络观测的三层架构
Linux 网络栈为不同层次的观测提供了丰富的 eBPF hook 点:
┌──────────────────────────────────────────────────────┐
│ 应用层: socket / syscall (tracepoint/kprobe) │
├──────────────────────────────────────────────────────┤
│ 协议栈层: TC, cgroup, setsockopt (cgroup/BPF) │
├──────────────────────────────────────────────────────┤
│ 驱动层: XDP (eXpress Data Path) — 最早拦截点 │
└──────────────────────────────────────────────────────┘
2. XDP:驱动层的极速包处理
XDP 在网卡驱动收到数据包后、甚至在 sk_buff 分配之前执行 eBPF 程序,实现包处理的最快路径:
- 性能:单核可处理 2400 万 pps(比 DPDK 在某些场景更优)。
- 典型场景:DDoS 防护、负载均衡、包过滤。
- 程序返回码:
XDP_PASS:继续走正常网络栈。XDP_DROP:直接丢弃。XDP_TX:从原接口发回。XDP_REDIRECT:转发到另一个接口或 CPU。
2.1 XDP 程序示例:简单 SYN 洪水检测
SEC("xdpDropSYN")
int xdp_drop_syn(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 != bpf_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_TCP)
return XDP_PASS;
struct tcphdr *tcp = (void *)(ip + 1);
if ((void *)(tcp + 1) > data_end)
return XDP_PASS;
// 检测 SYN 包
if (tcp->syn && !tcp->ack) {
// 可以查询 count map 中的 SYN 计数
return XDP_DROP;
}
return XDP_PASS;
}
3. TC eBPF:流量控制层的深度观测
TC(Traffic Control)eBPF 在协议栈中比 XDP 更早于协议处理的位置执行:
- ingress:在包进入协议栈之前。
- egress:在包离开协议栈之后。
- 优势:可以看到
sk_buff、sk 结构体,做出更精细的决策。
3.1 TC BPF 程序标记 sk
TC eBPF 可以给 socket 打标记,实现跨协议栈的流量分类和策略:
SEC("tc_mark")
int tc_mark_skb(struct __sk_buff *skb)
{
// 从 skb 中提取五元组
__u32 src_ip = skb->remote_ip4;
__u32 dst_ip = skb->local_ip4;
__u16 src_port = skb->remote_port & 0xFFFF;
__u16 dst_port = bpf_ntohs(skb->local_port);
// 查找策略 BPF map
struct policy_key key = { .dst_port = dst_port };
struct policy_val *val = bpf_map_lookup_elem(&policy_map, &key);
if (val) {
// 标记该 skb,后续 eBPF 程序或 classifier 可使用
bpf_skb_store_bytes(skb, offsetof(struct sk_buff, mark),
&val->mark, sizeof(val->mark), 0);
}
return TC_ACT_OK;
}
4. Socket 层追踪:tracepoint 与 kprobe
应用层的网络追踪主要通过三种机制:
4.1 Tracepoint 追踪
内核中预定义的稳定观测点,性能开销极低:
sock:inet_sock_set_state:TCP 状态机变化(ESTABLISHED、CLOSE_WAIT 等)。tcp:tcp_retransmit_skb:TCP 重传事件。syscalls:sys_enter_sendto / sys_exit_sendto:系统调用入口/出口。
4.2 kprobe/kretprobe 动态追踪
几乎可以挂载到任何内核函数,灵活但接口不稳定:
SEC("kprobe/tcp_sendmsg")
int BPF_KPROBE(trace_tcp_sendmsg, struct sock *sk, struct msghdr *msg, size_t size)
{
u32 pid = bpf_get_current_pid_tgid() >> 32;
u16 family = sk->sk_family;
struct event evt = {0};
evt.pid = pid;
evt.size = size;
evt.family = family;
if (family == AF_INET) {
BPF_CORE_READ_INTO(&evt.daddr, sk, __sk_common.skc_v4_daddr);
BPF_CORE_READ_INTO(&evt.dport, sk, __sk_common.skc_dport);
}
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &evt, sizeof(evt));
return 0;
}
4.3 Socket Filter 与 cgroup BPF
- Socket Filter:传统 BPF,用于
SO_ATTACH_FILTER。 - cgroup/BPF:绑定到 cgroup,对该 cgroup 内所有进程生效。
- BPF_PROG_TYPE_SK_SKB:socket 级别的包过滤和重定向。
- BPF_PROG_TYPE_SK_MSG:socket 级别消息重定向。
5. 用户态工具链
5.1 BCC(BPF Compiler Collection)
基于 Python 的快速原型开发工具,内置丰富脚本:
# 追踪 TCP 连接建立和关闭
$ tcpconnect.py -t
# 统计 TCP 重传次数
$ tcpretrans.py
# 观测 HTTP 请求延迟
$ biolatency.py -m
5.2 bpftool
lsmod 时代的 BPF 管理和调试工具:
# 查看已加载的 BPF 程序
$ bpftool prog show
# 查看 BPF 映射
$ bpftool map show
# 导出程序 JIT 后的 x86 汇编
$ bpftool prog dump xlated id 42
# 导出 map 中的数据
$ bpftool map dump id 56
5.3 libbpf / BPF CO-RE
用户态直接使用 C 编写可移植的 BPF 程序:
// 通过 BPF CO-RE (Compile Once, Run Everywhere)
// 无需在每台目标机器上编译,自动适配不同内核版本
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 1024);
__type(key, u32);
__type(value, struct conn_info);
} conn_map SEC(".maps");
SEC("tracepoint/sock/inet_sock_set_state")
int trace_tcp_state(struct trace_event_raw_inet_sock_set_state *ctx)
{
// 自动处理 __vmlinux.h 中的结构体定义差异
u32 pid = bpf_get_current_pid_tgid() >> 32;
bpf_map_update_elem(&conn_map, &pid, &info, BPF_ANY);
return 0;
}
6. 生产环境最佳实践
- 返回码语义:XDP/TC 的返回码直接决定包的处理流程,务必准确。
- Map 大小控制:BPF Map 预分配内存,生产环境有严格上限(
RLIMIT_MEMLOCK)。 - 尾调用(Tail Call):复杂逻辑拆分多个程序,通过
bpf_tail_call跳转,突破指令数限制。 - Verifier 兼容:严格遵守 verifier 规则(边界检查、无死循环、有限栈空间)。
- 性能观测:BPF 程序自身的执行时间也要监控,避免"观测系统"成为瓶颈。
7. 内核 BPF 子系统演进
Linux 内核 BPF 子系统持续快速迭代:
- BPF trampoline:替代 kprobe 的轻量级调用,性能提升 10 倍+。
- BPF iterator:安全遍历内核数据结构(任务、网络连接、BPF map)。
- BPF crypto:内核 TLS(kTLS)的加密操作卸载到 BPF。
- BPF smp:BPF 程序直接执行
smp_call_function,跨 CPU 操作。
总结
eBPF 网络观测体系覆盖了从驱动层到应用栈的全部路径。XDP 提供极速包处理,TC 实现灵活策略控制,socket 层追踪则深入到应用行为的微观细节。掌握这些工具的组合运用,可以实现生产级别的网络全链路可观测性,而无需修改内核源码或引入探针。

发表评论 取消回复