本文地址:https://www.ebpf.top/post/cloudflare-replatforming-2

原文地址:https://ebpf.io/blog/cloudflare-replatforming-2/
作者:Cloudflare Engineering
发布时间:2026-09-07
翻译:狄卫华

本系列记录 Cloudflare 与 eBPF 的八年历程:从专用的 DDoS 工具,成长为其全球网络的可编程骨干。内容涵盖向 Linux 内核贡献代码、技术挑战、业务投资回报(ROI),以及给其他希望采用 eBPF 的公司的建议与运维策略。

在第 1 部分中,我们说明了 Cloudflare 为何把 eBPF 当作一个平台来押注,而不是一堆单点方案。本文将深入具体细节:标准 Linux 网络 API 无法支撑 Cloudflare 需求的那些地方,以及团队为填补这些缺口所构建的内容。

Cloudflare 运行着全球若干规模最大的网络。随着硬件日趋多样化、性能要求不断上升、边缘服务持续增多,Linux 生态中的缺口变得愈发明显。eBPF 使 Cloudflare 能够以可编程的方式,安全且高效地扩展操作系统内核,从而克服标准 Linux 网络 API 中那些未能经受住时间考验的僵硬限制——它们无法满足我们在规模、可见性与可靠性方面的特定需求。

eBPF 至少带来了十一大类能力;若依赖传统内核机制、自定义内核模块或用户态方案,这些能力要么不可能实现,要么会困难得多。

1. 缺口 1:线速 DDoS 缓解

Cloudflare 借助 eBPF 自动缓解了一场创世界纪录的 3.8 Tbps DDoS 攻击。传统手段会因多重原因而力不从心:

  • iptables 作用点在协议栈更靠后的位置(在分配 sk_buff 之后)。生产数据表明,把缓解从 iptables 迁到 XDP 后,CPU 开销降低了数倍,同时能处理显著更多的攻击流量。
  • 内核旁路(kernel bypass,例如 DPDK 或专有方案) 需要在专用 CPU 上忙轮询(busy-polling),且与硬件绑定。我们此前的方案与 ARM 服务器及较新的网卡不兼容。XDP 可在多颗 CPU 上常开运行、无需忙轮询,并适用于任何具备驱动支持的网卡,从而消除厂商锁定。
  • 通用的 iptables 到 XDP 转换器(例如 bpfilter)只能通过 eBPF map 匹配标准五元组(5-tuple)。我们的 DDoS 缓解规则需要任意报文匹配、p0f 签名、DNS 查询检查以及跨头部比较;为此我们可通过定制的 BPF 到 C 编译器,把这些规则直接编译成 eBPF 字节码。
  • 自定义内核模块 有内核崩溃风险,并在内核升级时需要重新编译。eBPF 程序会由内核做安全性验证。它们不会崩溃、挂起,也不会对内核造成负面干扰。eBPF 的 XDP(eXpress Data Path)钩子(hook)在网卡驱动层运行报文处理逻辑——甚至早于内核网络协议栈接触该报文。Cloudflare 的 l4drop 系统用 XDP eBPF 在单颗 CPU 上丢弃超过每秒 1000 万个报文,在 8 Mpps 攻击期间总 CPU 开销仅约 10%。该系统取代了此前绑定特定网卡厂商的内核旁路方案。(L4Drop 博客,如何每秒丢弃 1000 万个报文)

2. 缺口 2:XDP 流量对标准工具不可见

我们基于 XDP 的 DDoS 缓解(L4Drop)与负载均衡器,会在尽可能早的位置——直接在网卡驱动中——丢弃或重定向报文。像 tcpdump 这类标准工具,会在网络驱动处理完报文之后才挂接到网络协议栈。由于 eBPF/XDP 程序直接在驱动中运行(在操作系统分配 sk_buff 之前),在这一层被丢弃或重定向的报文,对标准调试工具是不可见的。这会让最关键缓解层的事故调试无法进行。

我们构建了 xdpcap,这是一款直接钩入我们的 XDP 程序、把这类流量暴露给用户态的抓包工具。它提供:

  • 在 XDP 流水线各阶段(sampler → l4drop → l4lb)捕获报文,并支持与 tcpdump 兼容的过滤语法
  • 按动作统计报文计数(XDPDrop、XDPPass、XDPTx、XDPAborted),精确展示接收到多少报文、匹配到多少报文
  • 可管道传给 tcpdump 或在 Wireshark 中打开的 pcap 输出
  • 基于偏移的过滤,用于匹配 Unimog 封装流量内部的原始(封装前)报文

这让工程师在活跃事故期间能够完整看见被丢弃的恶意流量,从而验证缓解是否正确生效、且没有误杀合法流量。没有它,在生产环境中调试报文级问题本来就不可能。

3. 缺口 3:BSD Socket API 无法扩展

Cloudflare 在数百万个 IP 上运行服务,并需要多个服务在不同地址范围内共享同一端口——标准 bind() API 做不到这一点(你只能绑定到某个具体 IP,或绑定到 0.0.0.0,没有中间选项)。我们的 Spectrum 产品还需要同时监听全部 65,535 个端口。然而,绑定到 0.0.0.0(通配)会阻止在同一端口上再绑定具体 IP;而逐个绑定具体 IP 又无法扩展到成千上万个地址。像 TPROXY 这类变通办法复杂、易错,在我们的规模下难以维护。

Cloudflare 向上游 Linux 内核贡献了 sk_lookup eBPF 钩子,并在其上构建了 Tubular,从而实现可编程的套接字分发(socket dispatch):按任意逻辑把入站连接路由到正确的应用。(Tubular 博客)【译者注:sk_lookup 允许在套接字查找阶段用 eBPF 决定报文交给哪个监听套接字。】

它取代了什么(以及那些方案为何不够用):

  • 绑定通配地址(0.0.0.0):被视为安全风险并被劝阻。还会因冲突而阻止多个服务共享同一端口。
  • SO_BINDTOPREFIX:自 2017 年起携带的定制内核补丁,未获上游接受,持续带来维护与开发者教育负担。【译者注:该选项用于按地址前缀绑定,未进入主线内核。】
  • 创建成千上万个套接字:受 DDoS 约束影响,我们的 DNS 解析器曾绑定到数万个 IP,这对性能与资源占用都不现实。同时运行 65,535 个监听套接字同样不可行。
  • TPROXY:配置复杂、难以维护,还会让 TCP 套接字更容易遭受 SYN 洪水攻击。

有了 eBPF 套接字分发后,服务仍会创建套接字,但绑定到哪个地址/端口已不再重要。eBPF 程序会查询 (IP, port) 到服务的映射,并据此投递报文。映射可动态重编程,而无需强制服务重启。这使得以下能力成为可能:

  • 任意服务与任意 IP/端口的随意组合——此前因 Linux 内核限制而「非常困难」
  • 基于任意报文条件的 A/B 服务测试(源 IP、TTL、ISP、地理位置、从 SYN 报文推断的操作系统)
  • 无停机的 IP 迁移,因为服务器本身不必感知 IP 变更
  • 无停机的重载:缓慢推出新版本而不中断服务

4. 缺口 4:DDoS 规则不够灵活

在构建 L4Drop 时,我们评估了业界常见做法:把规则存放在 eBPF map 中(若干大规模运营商采用此法)。这对我们行不通,因为:

  • 我们的过滤器依赖任意报文数据匹配与跨头部比较(单个 p0f 签名可能同时检查 IP 与 TCP 选项)
  • eBPF 验证器(verifier)禁止无界循环;再叠加任意匹配需求,就无法在 map 中迭代规则
  • 我们希望复用既有的 tcpdump/libpcap 过滤语法

我们构建了 cbpfc,这是一款把经典 BPF(cBPF)语法转换成 eBPF 或 C 的编译器,用于从仅靠 map 难以表达的复杂 DDoS 规则生成 eBPF 程序。这样我们就能把既有过滤规则编译成可通过验证器检查的高性能 eBPF 字节码。

5. 缺口 5:软件定义的有状态 DDoS 防护(flowtrackd)

iptables 的 conntrack 需要双向可见性,并非为非对称路由(asymmetric routing)——例如直接服务器返回(Direct Server Return,DSR)——而设计。另一方面,硬件 DDoS 设备僵化、依赖厂商,且往往只部署在有限的清洗中心。

Cloudflare 的软件定义 eBPF 方案运行在每一个数据中心(全球 330+ 座城市),可即时更新,且不受单一设备能力所限。Cloudflare 的 flowtrackd 以状态机实现单向 TCP 流跟踪,并用 eBPF 钩入网络接口。对 Magic Transit 客户(Cloudflare 只能看到入站流量)而言,它会根据连接状态决定每个 TCP 报文应转发还是丢弃,能以零误报自动缓解 6+ Mpps 的 ACK 洪水。(flowtrackd 博客)

6. 缺口 6:可编程报文过滤

在 Cloudflare 的规模下,网络侧具备满足海量客户用例的灵活性,与原始吞吐同等重要。nftables 提供强大的声明式语法,但受限于内置匹配类型。它无法窥探任意协议载荷,也无法实现复杂的自定义解析逻辑。标准防火墙工具同样难以轻松完成复杂逻辑,例如检查报文载荷中的应用层签名(如 SIP VoIP 攻击),或以高效方式使用包含数百万 IP 的动态列表。

eBPF 兼具编写 C 程序的灵活性与内核内执行的速度,从而支持 SIP 协议防护与高级内容匹配等能力。通过 xt_bpf 扩展把 eBPF 程序嵌入 nftables 规则,Cloudflare 的 Magic Firewall 能够执行深度包检测(deep packet inspection)、协议校验以及任意载荷匹配——这些都是单靠 nftables 无法表达的。(Magic Firewall 博客)

7. 缺口 7:面向内部系统的灵活报文封装与元数据

对内部报文路由与封装系统而言,eBPF 允许在抽象栈的正确位置评估报文是否符合条件并执行相应功能,且全部集中在一处完成。

采用传统做法(在不同的 netfilter 钩子处做报文标记,再叠加新的封装设备)时,每增加一个新功能都需要仔细验证既有逻辑不会被破坏。此外,若要把更多的每报文元数据传给下游系统,就需要添加配置各异的新封装设备,管理难度不断上升。

有了 eBPF,报文可以用自定义元数据字段进行封装,程序本身还能根据相关客户配置评估报文是否符合条件,无需再像传统方案那样把「评估」与「执行」拆开。

8. 缺口 8:不存在零停机的 UDP 重启

重启 UDP 服务器——尤其是对 QUIC 这类有状态协议——而不丢包,历来极其困难。标准内核负载均衡在重启期间套接字数量变化时,会破坏流粘滞性(flow stickiness)。此前没有现成机制支持「工作世代」(working generation),以便把流平滑地从旧进程过渡到新进程。

我们构建了 udpgrm,这是一个管理 SO_REUSEPORT 组与 eBPF map 的守护进程,在升级期间把已建立流的报文路由到正确的进程世代。它会创建一套套接字的「工作世代」,把既有流的报文路由到旧进程,同时把新流发给新进程,从而实现 UDP 协议真正的零停机升级。(QUIC 重启的慢问题:udpgrm 来救场)

9. 缺口 9:标准指标掩盖真相

Linux 内核指标(例如 TcpExtListenDrops、TcpExtListenOverflows)是全局计数器,没有按应用划分的粒度。标准计数器只能算平均值,从而掩盖性能尖峰——一块磁盘平均看起来正常,却可能偶发读操作耗时 500ms。

传统可观测性(observability)无法实现的能力:

  • IO 延迟直方图:把块设备 IO 请求排队与服务过程可视化为 traces。像 blktrace 这类标准工具无法接入现代可观测性流水线,也不适合在生产环境持续使用。
  • 套接字级分布式追踪:探测与套接字生命周期相关的内核函数(dial、write、read、close),并把内核 span 与用户态应用 traces 关联起来。例如,把 TCP 重传追踪挂到 nginx 上,使得「如果我们看到某个请求的 trace,中间有一段无法解释的空白,就可能有一段内核 span 告诉我们发生了一次重传,从而解释该空白。」
  • 进程执行追踪:跨命令执行追踪 execve() 调用,并输出完整的 OpenTelemetry span。
  • 按 cgroup、按应用的指标:按地址族、协议与 cgroup 细分跟踪 TCP/UDP 临时端口请求——这用全局 /proc 计数器做不到。

传统方案为何不够:

  • node_exporter 只提供来自 /proc 的计数器,没有直方图,也没有按应用细分。平均值会掩盖延迟问题的「长尾」——用标准计数器在数学上根本看不见。
  • blktrace 或 ftrace 对持续生产使用不现实,因为开销过大,且缺乏安全沙箱。
  • 用于指标的自定义内核模块 会危及稳定性,并需要跨内核版本维护。

我们构建了 ebpf_exporter,以低开销直接从内核生成高分辨率直方图(磁盘 I/O 延迟、运行队列延迟),再导出到 Prometheus 与 OpenTelemetry。(ebpf_exporter 博客)这种可见性此前根本不可能,它使那些用传统工具看不见的性能事故得以做根因分析。例如,运行队列延迟直方图可以揭示进程何时在等待调度(CPU 饱和),即便平均 CPU 使用率看起来仍正常。

10. 缺口 10:在线修补安全漏洞(LSM BPF)

当在某个内核功能中发现安全漏洞时(例如 unshare 系统调用允许提权),传统修复方式是修补内核源码并重启服务器——这是一个破坏性过程。

利用 LSM(Linux Security Module)BPF 钩子,Cloudflare 可以注入动态阻止或限制特定系统调用的安全策略。它相当于「热补丁」(hot patch),无需等待上游补丁或重启机器,即可立即加固系统。

11. 缺口 11:生产可靠性不可妥协

对于像 Tubular 这样的工具,我们需要现成 eBPF 工具链无法提供的保证:例如跨数千台机器的无人值守发布、失败时仍保留上一版本继续运行的故障安全(fail-safe)部署,以及用户态崩溃不影响报文流转的崩溃韧性。

我们通过把状态持久化在内核 BPF map 中实现这一点。当 Tubular 崩溃时,报文仍继续流动,内核持有状态。我们还需要能动态启用或禁用特定规则、而无需重载整个程序的机制,这要求在我们的加载器中对 ELF 二进制做定制操控。

12. 结语

Cloudflare 所处的规模,使通用方案捉襟见肘。我们不只是做变通绕过,还把改进贡献回上游——例如 sk_lookup 钩子——并开源了 ebpf_exporter、Tubular 与 xdpcap 等工具,供面临类似挑战的其他人使用。

这里描述的缺口并非只是工程上的不便——解决它们需要深入的内核工作、持续的组织投入,并且在若干情形下,还需要把贡献回馈给 Linux 内核本身。第 3 部分我们将讲述其中最艰难的挑战,以及由此产生的技术成果。

延伸阅读

若还想顺着读,可看这几篇:

  1. Cloudflare: 深入剖析 BPF LPM Trie 性能与优化
  2. Cloudflare 基于 BPF LSM 热修高危提权 Copy Fail
  3. 基于 veth 场景 XDP 加速:出向流量转发性能提升约 10 倍