深入 Cilium 的 BPF 与 XDP 参考指南从 eBPF 架构、程序类型到调试实践【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium本篇技术文章基于 Cilium 仓库中的 BPF 参考指南Documentation/reference-guides/bpf/index.rst及其子文档编写面向希望深入理解 BPF/XDP 的开发者与用户。读完本文后你将掌握 eBPF 指令集与验证器机制、Maps/Tail Calls/JIT 等核心基础设施、XDP 与 tc 两类网络程序类型的差异与挂载方式以及使用 clang、iproute2、bpftool 编译、加载、调试 BPF 程序的完整工作流——这正是 Cilium 数据路径data path的底层原理。1. BPF 与 eBPFCilium 数据路径的核心BPFBerkeley Packet Filter是 Linux 内核中一种高度灵活、类虚拟机的结构它允许在内核的各个钩子点hook point以安全的方式执行字节码被广泛用于网络、追踪与安全如沙箱等子系统。参考指南开篇特别指出虽然深入阅读这份指南有助于理解 Cilium但它不是使用 Cilium 的前置要求初学者应先看入门指南本篇的价值在于把 Cilium 数据路径中看不见的内核侧机制讲透。几个关键历史与概念事实来自 BPF 参考指南索引页BPF 诞生于 1992 年但该指南覆盖的是扩展版eBPF首次出现于 Linux 内核 3.18它已基本取代了经典BPFcBPF即 tcpdump 所用的包过滤语言。如今内核只运行 eBPF加载的 cBPF 字节码会在执行前被透明地翻译为 eBPF 表示。尽管名字叫 Packet FiltereBPF 的指令集已足够通用远不止网络用途。Cilium 在数据路径中大量使用 BPF策略执行、负载均衡、监控等核心功能都由内核中的 BPF 程序完成。参考指南的目标正是帮助读者理解 BPF 本身包括用tctraffic control和XDPeXpress Data Path加载 BPF 程序的网络场景以及如何开发 Cilium 的 BPF 模板。在 Cilium 仓库中这套体系对应着bpf/目录各数据路径程序入口如 bpf_host.c主机网络接口、bpf_lxc.c容器端点、bpf_sock.csocket 层、bpf_xdp.cXDP等大量可复用的 BPF 逻辑放在 bpf/lib/ 下的头文件中如conntrack.h、drop_reasons.h、classifier.h等单元测试与自检测试则位于 bpf/tests/如bpf_ct_tests.c、bpf_nat_tests.c。2. BPF 架构指令集、Helpers、Maps 与 JIT本节内容对应参考指南的 BPF Architecture 子文档。BPF 不只是指令集它还提供 Maps内核键值存储、helper 函数、tail call、安全加固原语、用于固定pinning对象的伪文件系统以及向网卡 offload 的基础设施。2.1 指令集与寄存器模型BPF 是一个通用 RISC 指令集设计目标是让 C 程序子集能通过编译器后端如 LLVM编译为 BPF 指令再由内核中的 JIT 编译为原生机器码。将程序推进内核的好处包括无需跨越内核/用户空间边界即可实现容器策略、负载均衡等程序可为特定用例裁剪例如端点不需要 IPv4 时就只处理 IPv6节省快速路径资源网络程序可原子更新而不中断流量程序状态可通过 Maps 在更新中保持BPF 提供与用户空间的稳定 ABI不需要第三方内核模块且可跨架构移植。BPF 程序执行是事件驱动的网卡 ingress 上收到包触发程序kprobe 地址被执行触发程序。硬件与执行模型11 个 64 位寄存器r0–r10各含 32 位子寄存器、程序计数器、512 字节 BPF 栈r10为只读帧指针访问栈r0–r9为通用读写寄存器helper 调用约定r0返回r1–r5传参r6–r9为跨调用保存的 callee-saved 寄存器该约定可直接映射到x86_64/arm64等 ABIJIT 只需发出 call 指令程序入口处r1初始指向上下文context如网络程序的skb表示一个程序只操作一个上下文单程序指令上限为 4096 条内核 5.1 之后提升为 100 万条验证器verifier会禁止循环以保证程序终止tail call 嵌套上限 33 次指令为双操作数格式、固定 64 位编码op:8, dst_reg:4, src_reg:4, off:16, imm:32当前实现 87 条指令编码定义在内核头linux/bpf.h中指令类包括BPF_LD/LDX加载、BPF_ST/STX存储含原子加、BPF_ALU/ALU64算术32/64 位、BPF_JMP跳转、exit、call、隐藏的 tail call。2.2 Helper 函数BPF 与内核的接口Helper 函数让 BPF 程序调用内核核心定义的函数集可用集合因程序类型而异例如 socket 程序可调用的 helper 是 tc 程序的子集轻量隧道封装/解封装 helper 只在低层 tc 可用事件输出 helper 则 tc 与 XDP 都可用。每个 helper 的签名形如系统调用u64 fn(u64 r1, u64 r2, u64 r3, u64 r4, u64 r5)文档给出了bpf_map_update_elem的内核侧示例通过BPF_CALL_4(...)宏实现并附带struct bpf_func_protoret_type、arg1_type ARG_CONST_MAP_PTR等验证器据此对寄存器内容做类型检查如缓冲区是否已初始化。所有 helper 属于内核核心、不能用模块扩展内核的struct bpf_verifier_ops通过get_func_proto回调把enum bpf_func_id映射到具体 helper。文档提到写作时 tc BPF 程序可选 38 个 helper。2.3 Maps跨调用、跨进程的状态Maps 是驻留在内核空间的高效键值存储BPF 程序用它在多次调用间保持状态用户空间也可通过文件描述符访问并与任意 BPF 程序共享无需同类型。单个 BPF 程序最多直接访问 64 个 Maps。通用 MapsBPF_MAP_TYPE_HASH、ARRAY、PERCPU_HASH、PERCPU_ARRAY、LRU_HASH、LRU_PERCPU_HASH、LPM_TRIE共享同一组 lookup/update/delete helper但后端语义与性能不同非通用 MapsPROG_ARRAY装其他 BPF 程序、PERF_EVENT_ARRAY、CGROUP_ARRAY、STACK_TRACE、ARRAY_OF_MAPS、HASH_OF_MAPS后者两类可原子替换整个 Map 运行时状态。Cilium 的策略、身份identity、连接跟踪、IP cache 等状态本质上都是通过这些 Maps 在内核与cilium-agent用户空间守护进程之间同步的。2.4 对象固定Pinning与 BPF 伪文件系统Maps 与程序在内核中是以匿名 inode 为后端的文件描述符资源——fd 可被 Unix socket 传递但受进程生命周期限制例如 iproute2 用 tc/XDP 加载程序后退出Map 就不再可被用户空间访问。为解决该问题内核实现了 BPF 伪文件系统BPFFSbpf()系统调用扩展了BPF_OBJ_PIN固定与BPF_OBJ_GET取回两条命令BPFFS 支持多挂载实例、硬/软链接。tc 正是借助它共享 ingress/egress 两端的 Map第三方应用也能在程序运行时监视或更新 Map 内容。2.5 Tail Calls 与 BPF-to-BPF 调用Tail call允许一个 BPF 程序跳转到另一个程序而不返回实现上是长跳转、复用同一栈帧开销极小。约束被调程序独立验证状态传递需用 per-CPU Map 或 tc 的skb-cb[]只能同类型调用且 JIT 方式要一致机制由BPF_MAP_TYPE_PROG_ARRAY用户空间写程序的 fd 值bpf_tail_call()helper 组成内核将其内联为专门指令Map 槽位不存在时fall through继续执行原程序。典型用法是把协议解析结构化为多个可用 tail call 原子替换的阶段。BPF-to-BPF 调用是较新的核心特性在 Linux 4.16 LLVM 6.0 之前可复用代码必须写成always_inline并全量内联导致对象文件代码膨胀之后 BPF 程序可自然调用普通静态函数文档给出了always_inline前后两段xdp_drop对比示例。约定与 helper 相同r1–r5传参、r0返回、r6–r9保持最大嵌套 8 层调用者可向下传指针但不可反向。JIT 为每个函数体生成独立镜像并在最后修正调用地址。注意直到内核 5.9tail call 与 BPF 子程序互斥5.10 起允许组合但引入了栈限制——验证器检测到该组合时每个子程序栈上限降到 256 字节整条调用链最多 8KB对比 512 字节栈 × 33 次 tail call 16KB 会溢出且该组合当时仅 x86-64 支持。2.6 JIT、加固与 Offloadx86_64、arm64、ppc64、s390x、mips64、sparc6464 位及 32 位arm、x86_32均自带 eBPF JIT功能等价echo 1 /proc/sys/net/core/bpf_jit_enable启用没有 JIT 的架构回退到内核解释器。JIT 显著降低每条指令的执行成本、缩小镜像尺寸CISC 架构如 x86 会优化输出最短操作码。加固HardeningBPF 在程序生命周期内把解释器镜像struct bpf_prog与 JIT 镜像struct bpf_binary_header锁定为只读防止代码被静默篡改bpf_jit_harden1时为无特权用户做常量遮蔽constant blinding把立即数指令改写为加载rnd ^ imm再异或rnd两步防御 JIT 喷射攻击文档附了启用/禁用加固的两段反汇编对比同时禁用 kallsyms 暴露。CONFIG_BPF_JIT_ALWAYS_ON可整体移除解释器Spectre v2 缓解的一部分。另一个重要 sysctl 是/proc/sys/kernel/unprivileged_bpf_disabled——这是一个一次性开关置 1 后重启前不可复位置位后仅初始命名空间中的CAP_SYS_ADMIN特权进程可使用bpf(2)。文档特别强调Cilium 启动时也会把它置为 1收紧系统攻击面。Offloadtc/XDP 网络程序具备硬件 offload 接口当前 Netronome 的nfp驱动通过 JIT 把 BPF 指令翻译为网卡指令集连 Maps 也可 offload 到网卡lookup/update/delete 都能在卡上完成。2.7 BPF sysctl 速查表Sysctl取值含义/proc/sys/net/core/bpf_jit_enable0 / 1 / 2禁用 JIT 仅解释默认/ 启用 JIT / 启用 JIT 并向内核日志输出调试 trace配合bpf_jit_disasm使用/proc/sys/net/core/bpf_jit_harden0 / 1 / 2禁用默认/ 仅对无特权用户加固 / 对所有用户加固/proc/sys/net/core/bpf_jit_kallsyms0 / 1禁用默认/ 仅特权用户可把 JIT 程序导出为bpf_prog_tag符号供perf与栈展开使用bpf_jit_harden开启时此功能被禁用/proc/sys/kernel/unprivileged_bpf_disabled0 / 1 / 2允许无特权使用bpf(2)默认/ 禁用重启前不可逆/ 禁用但运行时可改回Linux 5.13内核启用BPF_UNPRIV_DEFAULT_OFF时默认为 2。该开关不影响 seccomp、传统 socket filter 等 cBPF 程序3. 程序类型XDP 与 tc参考指南的 Program Types 子文档在 18 种 BPF 程序类型中重点剖析 Cilium 使用的两种XDP 与 tc。3.1 XDP最早期的可编程包处理XDPeXpress Data Path在驱动收到包的瞬间运行 BPF 程序——此时驱动刚从接收环取出包还未分配skb、未进入 GRO 引擎是软件路径上最早的点。XDP 与内核协同而非绕过内核优势包括复用上游驱动与工具、复用路由表/socket 等内核设施、无需跨内核/用户空间边界在 Meltdown/Spectre 时代尤其重要、可平凡地 punt 给内核 TCP/IP 栈、程序运行时原子热替换、无需专用硬件/hugepages/第三方模块且在 4.8 内核的主流发行版中开箱即用。XDP 框架保证包在单个 DMA 页内线性排布、可读可写并提供 256 字节 headroom通过bpf_xdp_adjust_head()做封装/解封装bpf_xdp_adjust_meta()在包前添加对内核协议栈不可见、但对 tc 程序可见的自定义元数据。程序上下文为struct xdp_buff { void *data; void *data_end; void *data_meta; void *data_hard_start; struct xdp_rxq_info *rxq; };指针不变式为data_hard_start data_meta data data_endrxq提供接收队列元数据queue_index等环配置时填充而非 XDP 运行时。返回码linux/bpf.h的enum xdp_actionenum xdp_action { XDP_ABORTED 0, XDP_DROP, XDP_PASS, XDP_TX, XDP_REDIRECT, };XDP_DROP驱动层直接丢弃适合 DDoS 缓解与防火墙XDP_PASS交给内核协议栈分配skb、进 GRO等价于无 XDP 时的默认行为XDP_TX从同一网卡发回hairpin 负载均衡器场景XDP_REDIRECT从另一块网卡发出或重定向到 BPF cpumap让 XDP 服务 CPU 不阻塞、把包推给远端 CPU 处理XDP_ABORTED异常态行为同 DROP 但会经过trace_xdp_exceptiontracepoint便于监控程序误行为。典型用例DDoS 缓解/防火墙XDP_DROP极低每包成本offloaded XDP 可做到线速转发与负载均衡XDP_TX/XDP_REDIRECT headroom 封装解封装协议栈前过滤在协议栈看到无关包之前丢弃缩小攻击面由于此时包还未分配skbBPF 可自由改写包再伪装成网卡刚收到流量采样与监控通过 lockless per-CPU perf ring buffer 上送截断/完整包 自定义元数据。三种运行模式Native XDP默认直接运行在驱动早收路径主流 10G 网卡已支持Offloaded XDPSmartNIC 上的内核 JIT 将 BPF 翻译为网卡指令部分 helper 不可用Generic XDP驱动无需改动在协议栈更晚处运行主要供开发测试性能显著低于前两者。文档附了完整的原生 XDP 驱动支持表Intel i40e/ixgbe/ice、Mellanox mlx4/mlx5、Amazon ena、Netronome nfp、virtio_net、veth 等及各自的内核版本门槛可用ethtool -i eth0查看接口驱动名。3.2 tctraffic control基于sk_buff的双向钩子相对 XDPtc BPF 的三大差异输入上下文是sk_buff而非xdp_buff协议栈已分配缓冲并解析出元数据tc ingress 程序可读写mark、pkt_type、protocol、priority、queue_mapping、napi_id、cb[]、hash、tc_classid/tc_index、VLAN 元数据及 XDP 传递的自定义元数据struct __sk_buff各成员定义在linux/bpf.h。代价是协议栈做这些工作本身有开销——这正是 XDP 与 tc 性能差异的主要来源。sk_buff元数据丰富但改协议更麻烦栈基于元数据而非逐包内容工作xdp_buff改写包简单但拿不到元数据——两者通过XDP 传自定义元数据给 tc互补组合。可挂在 ingress 和 egress 两个方向XDP 仅 ingress。内核钩子sch_handle_ingress()来自__netif_receive_skb_core()与sch_handle_egress()来自__dev_queue_xmit()是数据路径的主收发函数除 XDP 外所有进出包都会经过tc BPF 因此具备全量可见性。不需要驱动改动运行在通用层的钩子可挂到任意网络设备veth 等虚拟设备亦可。代价是性能低于 native XDP但仍处于 GRO 之后、任何协议处理与 iptables/nftables 钩子之前ingressegress 则在 iptables POSTROUTING 之后、交给驱动 GSO 引擎之前的最后一点。tc 层的 BPF 运行于cls_bpf分类器。文档强调这个分类器称谓有误导性cls_bpf实际是全可编程包处理器可读写skb元数据与包数据并直接返回动作裁决是自包含实体。Cilium 部署cls_bpf时每个钩子只挂一个程序且使用direct-action模式——分类与动作融合为单一单元避免传统分类器 动作模块线性遍历的扩展性问题。cls_bpf实例挂在伪 qdiscsch_clsactingressqdisc 的超集可同时管理 ingress/egress 钩子上两个钩子都在 fast-path 中无锁执行egress 不在 qdisc root lock 下仅在 RCU 读侧、禁抢占这使得sch_clsact的 egress 钩子可以先于sch_htb等 qdisc 完成重分类并设置skb-mark/priority降低锁竞争。返回码linux/pkt_cls.hTC_ACT_UNSPEC(-1)、TC_ACT_OK(0)、TC_ACT_SHOT(2)、TC_ACT_STOLEN(4)、TC_ACT_REDIRECT(7)等。语义要点TC_ACT_UNSPEC≈TC_ACT_OK区别是后者会按skb-tc_classid设置skb-tc_indexTC_ACT_SHOT通过kfree_skb()释放 skb 并返回NET_XMIT_DROPperf的 drop monitor 可见TC_ACT_STOLEN通过consume_skb()释放并向上层报告成功drop monitor 不可见TC_ACT_REDIRECT配合bpf_redirect()把 skb 注入任意设备的 ingress/egress 路径目标设备无需另挂cls_bpf。tc BPF FAQ文档原样保留三个高频问题act_bpf还有意义吗——没有cls_bpf是其超集act_bpf需配合cls_matchall使用反而更慢且无 offload 接口cls_bpf不建议用非 direct-action 模式offloadedcls_bpf与 offloaded XDP 性能无本质差异同一内核 JIT 编译到同一目标指令集选择取决于可用 helper 集合。tc BPF 用例与 Cilium 强相关容器策略执行——容器网络命名空间通过 veth 对与宿主相连宿主机侧 veth 的 tc ingress/egress 钩子成为所有容器流量的必经之路veth 是纯skb世界generic XDP 因克隆 skb 与线性化限制不适用tc BPF 正是正确选择转发与负载均衡——bpf_redirect()接管转发逻辑桥接设备变得不必要双向流监控——通过bpf_skb_event_output()上送 per-CPU perf ring buffer文档特别指出Cilium 深度使用这种机制对丢弃的包附带注解endpoint 标签、策略违规原因等以提供更丰富的上下文包调度器预处理——sch_clsactegress 钩子在拿 qdisc root lock 之前完成重活。4. 开发工具链LLVM 编译、C 语言陷阱与 iproute2 加载Development Tools 子文档覆盖了 BPF 生态的用户态工具、内省设施与内核开关。Cilium 的 BPF 程序正是面向iproute2 的 BPF 加载器实现的仓库保留了兼容性以便用 iproute2 开发调试。4.1 环境准备与内核配置开发环境依赖Fedora/Ubuntu/openSUSE 分别给出 dnf/apt/zypper 安装列表核心是 clang、llvm、libelf、libcap、graphviz 等如需构建开发内核net-next树是 BPF 新特性的家。内核.config中需要这些项也是 Cilium 需要的CONFIG_CGROUP_BPFy CONFIG_BPFy CONFIG_BPF_SYSCALLy CONFIG_NET_SCH_INGRESSm CONFIG_NET_CLS_BPFm CONFIG_NET_CLS_ACTy CONFIG_BPF_JITy CONFIG_LWTUNNEL_BPFy CONFIG_HAVE_EBPF_JITy CONFIG_BPF_EVENTSy CONFIG_TEST_BPFmCONFIG_HAVE_EBPF_JIT由架构自动 select无 JIT 的架构回退解释器效率更低。验证方式是在内核树tools/testing/selftests/bpf/下make后运行sudo ./test_verifierverifier 测试会打印全部检查项并以Summary: N PASSED, ...汇总sudo make run_tests跑完整自测。4.2 LLVM/clang最小 XDP 程序与完整编译流程LLVM 是文档写作时唯一提供 BPF 后端的编译器套件。标准工作流BPF 程序用 C 编写 → LLVM 编译为 ELF 对象 → 用户态 BPF ELF 加载器iproute2 等解析并经bpf()系统调用推入内核 → 内核验证 JIT → 返回程序 fd → 挂载到子系统必要时再 offload 到网卡。最小可运行的 XDP drop 程序文档xdp-example.c原文#include linux/bpf.h #ifndef __section # define __section(NAME) \ __attribute__((section(NAME), used)) #endif __section(prog) int xdp_drop(struct xdp_md *ctx) { return XDP_DROP; } char __license[] __section(license) GPL;编译与加载$ clang -O2 -Wall --targetbpf -c xdp-example.c -o xdp-example.o # ip link set dev em1 xdp obj xdp-example.o要点目标三元组bpf跟随宿主字节序推荐、bpfel/bpfeb交叉编译到不同字节序宿主LLVM 3.9 使用官方 BPF 机器值EM_BPF0xf7file命令可见unknown arch 0xf7LLVM 6.0 起支持 BPF 汇编解析llvm-mc -triple bpf -filetypeobj 4.0 可用-g生成 DWARF 调试信息llvm-objdump -S --no-show-raw-insn可反汇编其行号与内核验证器日志一一对应——程序被验证器拒绝时这是把指令关联回 C 源码的关键手段BTFBPF Type Format由 DWARF 转换而来需 elfutils 0.173否则llc加-mattrdwarfrispahole -J完成转换随对象加载进内核后可为 Map 标注 key/value 类型大幅改善内省与 pretty-print详见第 5 节-mcpuprobe是文档推荐的选项且也是 Cilium 内部使用的LLVM BPF 后端会向内核探测指令集扩展可用性并在适当处使用而默认genericv1基础指令集保证 4.9 老内核可加载--targetbpfvs 默认目标的行为差异引用内核bpf_devel_QA.txt头文件内联汇编、.eh_frame段、switch 跳转表可用-fno-jump-tables关闭、以及 32 位架构上指针/long 位宽——网络场景一律首选--targetbpfLLVM 7.0 的-mattralu32启用 32 位子寄存器w寄存器代码生成可减少类型扩展指令序列并利于 32 位架构 JIT。4.3 用 C 写 BPF 程序的 11 个陷阱工具链文档列出的 C 语言编写注意事项是实战中最容易踩坑的部分完整继承如下一切必须内联旧内核/旧 LLVM 上无函数调用、无共享库公共库代码放头文件Cilium 正是重度使用者见bpf/lib/库函数应标注always_inline否则 LLVM 可能不内联并生成加载器无法解决的 relocation一个 C 文件可含多个程序段通过 section 注解组织如__section(ingress)、__section(egress)共享同一个maps段定义的acc_map与account_data()内联 helper。文档给出的完整tc-example.c演示了struct bpf_elf_mapiproute2 私有格式Cilium 遵循该模型type BPF_MAP_TYPE_ARRAY、pinning PIN_GLOBAL_NS、max_elem 2两个程序分别以dir 0/1记账lock_xadd映射为 BPF 原子加指令BPF_STX | BPF_XADD | BPF_W。pinning 语义PIN_GLOBAL_NS固定到/sys/fs/bpf/tc/globals/map跨对象文件共享PIN_OBJECT_NS为对象本地目录PIN_NONE不固定tc 退出后用户空间不可见且每个程序得到独立 Map 实例。加载后 ingress 程序找不到已存在的同名片就创建并固定egress 加载时发现已存在即复用加载器还会校验同名片的 key/value 尺寸等属性一致$ clang -O2 -Wall --targetbpf -c tc-example.c -o tc-example.o # tc qdisc add dev em1 clsact # tc filter add dev em1 ingress bpf da obj tc-example.o sec ingress # tc filter add dev em1 egress bpf da obj tc-example.o sec egresslicense段的 GPL 许可证决定 GPL-only helperbpf_ktime_get_ns()、bpf_probe_read()等是否可见不允许全局变量变通方案是用单槽BPF_MAP_TYPE_PERCPU_ARRAY作 scratch 缓冲区BPF 程序执行期间内核保证不被抢占tail call 间也成立跨调用持状态用普通 Map不允许 const 字符串/数组会生成加载器拒绝的 relocationtrace_printk()需用把 fmt 复制进栈上局部数组的宏包装但文档不推荐生产使用字符串每次上栈、helper 最多 5 个参数只留 3 个变量位推荐skb_event_output()/xdp_event_output()走 lockless per-CPU perf ring buffer——Cilium 的 monitor 正是用这些 helper 实现调试框架与策略违规通知memset/memcpy/memmove 用 LLVM 内建常量尺寸n保证内联memcmp内建有 corner case 暂不推荐当时没有循环验证器以深度优先搜索保证终止常量上限循环可用#pragma unroll文档给出遍历 IPv6 扩展头的例子或用 tail call 自调 per-CPU scratch Map 实现动态循环上限 34 次迭代初始程序 33 次 tail call用 tail call 切分程序通过BPF_MAP_TYPE_PROG_ARRAY__section_tail(ID, KEY)段命名实现iproute2 加载器按段名1/0匹配struct bpf_elf_map的id并把程序装入对应索引运行时用tc exec bpf graft m:globals/jmp_map key 0 obj new.o sec foo原子替换某阶段的程序。文档举了 Cilium 的实例包丢弃通知可运行时开关——skb_event_output()位于被 tail call 的程序里不启用时走 fall-through 零成本启用时由守护进程向对应索引写入程序512 字节栈上限超出的临时数据用单槽 per-CPU Map 扩容内联汇编LLVM 6.0 支持文档给了一个lock *(u64 *)(%00) %1的 64 位原子加玩具示例及其验证器输出#pragma pack消除结构体 padding现代编译器默认对齐会插入 padding若结构体含 padding作 Map value 且整结构体入栈验证器会报invalid indirect read from stack文档附了struct called_info的 24 vs 20 字节布局图与完整验证器报错解法#pragma pack(n)代价是非对齐访问可能降性能或被验证器拒绝或更推荐在末尾显式添加u32 pad失效引用问题bpf_skb_store_bytes等可能改变包大小的 helper 之后之前取的包数据指针被验证器置为失效必须重新从skb-data取引用再访问文档给了ip4-protocol被拒与修复的两段代码及验证器日志R9 invalid mem access inv。4.4 iproute2 加载工作流XDP 与 tcXDP 对象加载ip link set dev em1 xdp obj prog.o默认段名prog否则sec foobar甚至可从.text段加载。已有程序时默认报错替换需-force大多数 XDP 驱动支持无中断原子替换XDP 驱动同一时刻只有一个程序多级逻辑用 tail call 实现。ip link | grep xdp找挂载了 XDP 的接口ip -d link看详情ip link set dev em1 xdp off卸载。三种模式xdpdrvnative、xdpgeneric实验用途、xdpoffloadSmartNIC不支持的 Map 类型/helper 会被验证器拒绝并告知。注意ip link set ... xdp obj会先尝试 native、失败自动回退 generic显式xdpdrv则失败即错、绝不回退模式之间切换不原子generic→offload 直接替换会报File exists必须先off再进新模式。加载后可bpftool prog dump xlated id N/bpftool prog show id N检查示例输出见第 5 节。tc 对象加载# tc qdisc add dev em1 clsact # tc filter add dev em1 ingress bpf da obj prog.o # ingress 钩子 # tc filter add dev em1 egress bpf da obj prog.o # egress 钩子clsact是仅容纳分类器与动作、不做实际排队的哑 qdisc提供 ingress/egress 两个钩子dadirect-action模式文档明确要求应该总是指定——BPF 程序自己完成全部包操作并返回TC_ACT_*裁决无需外部 action 模块。tc filter show输出中的prog.o:[ingress] direct-action id 1 tag c5f7825e5dac396fid是系统唯一 BPF 程序号供 bpftool 使用tag是指令流的哈希可关联对象文件或 perf 栈pref/handle自动生成但若计划原子替换建议首次加载就显式指定以便后续tc filter replace dev em1 ingress pref 1 handle 1 bpf da obj prog.o sec foobar。tc filter del dev em1 ingress删除程序tc qdisc del dev em1 clsact移除整个 qdisc。硬件 offload 需先ethtool -K em1 hw-tc-offload on再bpf skip_sw da输出出现in_hw即已 offloadtc 与 XDP offload 不能同时加载。netdevsim驱动modprobe netdevsimecho 1 1 /sys/bus/netdevsim/new_device提供实现 XDP/tc offload 接口的假网卡供测试内核改动或控制面程序。进阶选项verb在加载成功时也打印验证器日志pinned /sys/fs/bpf/prog或短格式m:prog直接挂载 BPFFS 中已固定的程序iproute2 自动探测已挂载的 BPFFS 实例未找到则自动挂到/sys/fs/bpf/或复如/var/run/bpf下的既有挂载并建立ip/xdp→tc/符号链接使globals命名空间跨程序类型共享 Map。iproute2 安装时提供的iproute2/bpf_elf.h头文件即struct bpf_elf_map的稳定契约。加载器解析顺序先取maps/license辅助段并做兼容性处理 → 创建或复用固定Map → 处理含 Map relocation 的程序段把 map fd 编码进指令立即数→ 通过bpf()系统调用创建程序 → 更新 tail call Map。5. 调试与测试bpftool、JIT 调试、tracepoint 与资源限制Debugging and Testing 子文档给出完整的排障方法论。bpftool位于内核树tools/bpf/bpftool/是主内省工具总览bpftool prog列出全部已加载程序含 tag、xlated/jited/memlock 尺寸、map_idsbpftool map列出全部 Map类型、key/value 尺寸、max_entries、memlock任意命令可加--json --pretty从 Cilium 实际部署出发定位程序——这是文档给出的实战链路# tc filter show dev cilium_host egress filter protocol all pref 1 bpf chain 0 filter protocol all pref 1 bpf chain 0 handle 0x1 bpf_host.o:[from-netdev] \ direct-action not_in_hw id 406 tag e0362f5bd9163a0a jited # bpftool prog show id 406 406: sched_cls tag e0362f5bd9163a0a loaded_at Apr 09/16:24 uid 0 xlated 11144B jited 7721B memlock 12288B map_ids 18,20,8,5,6,14即 Cilium 主机端口的 egress 程序来自bpf_host.o的from-netdev段对应仓库中 bpf_host.c程序类型sched_clsBPF_PROG_TYPE_SCHED_CLS关联 6 个 Map id 可继续下钻指令转储bpftool prog dump xlated id 406输出验证器之后的指令镜像加载器提供的原始指令经验证器多种改写例如 hash Map lookup helper 被内联重写输出中map[id:18]、call bpf_skb_event_output#5656112等关联信息由 bpftool 通过 kallsyms 解析——因此需echo 0 /proc/sys/kernel/kptr_restrict且echo 1 /proc/sys/net/core/bpf_jit_kallsyms否则调用显示为bpf_unspec#0dump jited id 406可加opcodes反汇编 JIT 原生镜像dump xlated id 406 visual生成 dot 文件dot -Tpng转图文档给出了bpf_host.o控制流图的截图示例tail call 在 dump 中呈现为call bpf_tail_call#12形式以便调试Map 转储与 BTF 增强无 BTF 时bpftool map dump id 5输出原始十六进制带 BTF配合 iproute2 的BPF_ANNOTATE_KV_PAIR(map, key_type, value_type)宏编译器流程为clang -g ... | llc -mcpuprobe -mattrdwarfris ... pahole -J ...时dump 变为带结构体字段名的可读 JSON文档以内核 selftesttest_xdp_noinline.o为例程序加载成功且带 BTF 时prog show显示btf_id可用bpftool btf show/btf dump id N format c查看类型信息。此外 bpftool 支持按 key 的 lookup/update/delete/get-next-key 以及把程序/Map pin 入 BPFFS内核自测tools/testing/selftests/bpf/套件覆盖验证器、程序 tag、Map 接口与类型以及针对 LLVM 后端/解释器/JIT 的运行时测试JIT 调试echo 2 /proc/sys/net/core/bpf_jit_enable后每次编译把 JIT 镜像打到内核日志flenBPF 指令数、proglen生成字节数、pass优化遍数、image地址、触发进程内核树tools/bpf/下的bpf_jit_disasm可加-o附带原始操作码读取最近一次 dump 反汇编新版 bpftool 已可直接按程序 id 做同样转储perf 剖析 JIT 程序前置bpf_jit_kallsyms1切换无需重载程序文档演示了perf record -a -g -e skb:kfree_skb捕获bpf_clone_redirect()内克隆 skb 释放失败的完整栈栈帧中出现bpf_prog_tag符号把内核事件与具体 BPF 程序、挂载点设备 ingress/egress精确对应tracepoint 内省内核提供bpf:bpf_map_create、bpf:bpf_prog_load、bpf:bpf_obj_pin_map等一组 tracepointperf record -a -e bpf:*XDP 的xdp:xdp_exception在三种场景触发返回非法 action 码、返回XDP_ABORTED、XDP_TX发送失败端口未 up、发送环满、分配失败等这些事件还可被挂在 tracepoint 上的 BPF 程序自身二次处理经bpf_perf_event_output()上送用户空间tracing pipebpf_trace_printk()的输出经/sys/kernel/debug/tracing/trace_pipe消费tail -f即可资源限制BPF 程序与 Map 的内存计入RLIMIT_MEMLOCKulimit -l查看与 perf 相同。默认限额通常不足以加载复杂程序或大 Mapbpf()会以EPERM失败该限制主要针对无特权用户按需ulimit -l unlimited或设为足够大的值即可。6. 配套资源与延伸阅读BPF 参考指南章节toctree共 5 个子文档可作为深入阅读的路径子文档覆盖内容architecture.rst指令集、helper、Maps、pinning、tail call、bpf2bpf、JIT、加固、offload、sysctltoolchain.rst开发环境、LLVM 编译、C 语言陷阱、iproute2 加载器debug_and_test.rstbpftool、内核自测、JIT 调试、tracepoint、tracing pipeprogtypes.rstXDP 与 tc 两种网络程序类型的架构、返回码、用例与驱动支持resources.rstBPF 生态项目列表即索引页引用的bpf_users锚点所在Cilium 侧的数据路径总览见 eBPF 数据路径文档结合本仓库源码阅读时建议从 bpf/bpf_host.c 的from-netdev段入口出发沿着 bpf/lib/ 中classifiers.h、drop_reasons.h等头文件理解策略与丢包注解的实现并用bpf/tests/下的自测程序如 bpf_ct_tests.c验证连接跟踪等逻辑。掌握以上内容后你就能完整读懂 Cilium 在 tc/XDP 钩子点加载的每一段 BPF 程序并具备开发、加载、验证与调试自有 BPF 数据路径的能力。【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考