Subscribe to receive notifications of new posts:

Subscription confirmed. Thank you for subscribing!

Cloudflare架构以及BPF如何占据世界

Loading...

最近,在布拉格Linux网络会议Netdev 0x13上,我做了一个简短的演讲,题目是“Cloudflare上的Linux”演讲最后主要是关于BPF(柏克莱封包过滤器)的。似乎,不管问题是什么——BPF都是答案。

下面是这次演讲的稍作调整的笔录。


在Cloudflare,我们在服务器上运行Linux。我们运营两类数据中心:大型“核心”数据中心,用以处理日志,分析攻击,计算分析;以及“边缘”服务器机群,从全球180个(截至19年10月为190多个)位置交付客户内容。

在这次演讲中,我们将重点讨论“边缘”服务器。在这里,我们使用最新的Linux特性,优化性能,并深切关注DoS(拒绝服务攻击)的弹性。


由于我们的网络配置,我们的边缘服务很特别——我们广泛使用anycast(任播)路由。任播意味着所有数据中心都公布相同的IP地址集。

这种设计有很大的优势。首先,它保证了最终用户的最佳速度。无论身处何处,您都可以到达最近的数据中心。并且,任播帮助我们分散DoS流量。在遭受攻击过程中,每个位置接收的流量仅占总流量的一小部分,因此我们可以更容易地提取和过滤掉不需要的流量。


任播使我们能够在所有边缘数据中心保持统一的网络设置。我们在数据中心内部应用了相同的设计——我们的软件堆栈在边缘服务器上是统一的。每一个服务器上都运行着所有的软件。

原则上,每台机器都能处理所有的任务——我们运行着许多不同的、要求很高的任务。我们有完整的HTTP栈、神奇的Cloudflare Workers、两组DNS服务器——权威DNS和解析器,以及许多其他的面向公众的应用程序,如Spectrum和Warp。

尽管每台服务器都运行着所有的软件,但请求通常也会在堆栈的旅程中跨越许多机器。例如,在处理HTTP请求的5个阶段中,每个阶段都有不同的机器进行处理。


让我向您介绍入站数据包处理的早期阶段:

(1)首先,数据包到达我们的路由器。路由器执行ECMP(等价多路径路由),并将数据包转发到我们的Linux服务器上。我们使用ECMP将每个目标IP分布到至少16台机器上。这是一种基本的负载均衡技术。

(2)在服务器上,我们使用XDP eBPF接收数据包。在XDP中,我们执行两个阶段。首先,我们运行大容量的DoS缓解措施,丢弃属于非常大的第3层攻击的数据包。

(3)然后,同样在XDP中,我们执行第4层负载均衡。所有的非攻击包都在计算机之间重定向。这是用来解决ECMP问题的,为我们提供了精细的负载均衡,并允许我们自然而正常地关闭服务器的服务。

(4)重定向之后,数据包到达指定的机器。此时,它们被普通的Linux网络堆栈接收,通过常规的iptables防火墙,并被分派到合适的网络套接字。

(5)最后,数据包被应用程序接收。例如,HTTP连接由“协议”服务器处理,该服务器负责执行TLS加密和处理HTTP、HTTP/2和QUIC协议。

在请求处理的早期阶段,我们使用了最酷的Linux新功能。我们可以将有用的现代功能分为三类:

  • DoS处理
  • 负载均衡
  • 套接字分配

让我们更详细地讨论DoS处理。如前所述,ECMP路由之后的第一步是Linux的XDP堆栈,其中,我们运行DoS缓解措施。

从历史上看,我们对容量攻击的缓解是用经典的BPF和iptables样式的语法表示的。最近,我们对它们进行了调整,从而在XDP eBPF环境中执行,事实证明这非常困难。一起来看看我们冒险的经历吧:

在这个项目中,我们遇到了许多eBPF/XDP限制。其中之一是缺乏并发原语。实现无竞争令牌桶之类的东西非常困难。后来,我们发现Facebook工程师Julia Kartseva遇到了同样的问题。在2月份,这个问题已经通过引入bpf_spin_lock helper得到了解决。


虽然我们现代的容量DoS防御是在XDP层完成的,但我们仍然依赖iptables来减轻应用层(第7层)的影响。在这里,更高级别防火墙的特性起着很大的作用:connlimit、hashlimit和ipset。我们还使用xt_bpf iptables模块在iptables中运行cBPF,从而匹配数据包有效负载。我们以前讨论过这个问题:


在XDP和iptables之后,我们有了最后一个内核端DoS防御层。

考虑UDP缓解失败的情况。在这种情况下,可能会有大量的数据包到达应用程序UDP套接字。这可能会使套接字溢出,导致数据包丢失。这是有问题的——好的数据包和坏的数据包都会被随意丢弃。对于DNS这样的应用程序来说,这一后果是灾难性的。在过去,为了减少危害,我们为每个IP地址运行了一个UDP套接字。无法缓解的洪水攻击是很糟糕的,但至少它没有影响到其他服务器IP地址的流量。

如今,这种架构已不再适用。我们正在运行30,000多个DNS IP,并且运行同样数量的UDP套接字是不可行的。我们现代的解决方案是运行一个带有复杂eBPF套接字过滤器的UDP套接字——使用SO_ATTACH_BPFsocket选项。我们在以前的博客文章中讨论过在网络套接字上运行eBPF:

上面提到的eBPF速率限制了数据包。它将状态(数据包计数)保存在eBPF映射中。我们可以确保一个被“淹没”的IP不会影响其他流量。这种做法效果很好,尽管在进行此项目期间,我们在eBPF验证程序中发现了一个令人担忧的错误:

我猜在UDP套接字上运行eBPF并不如往常般简单。


除了DoS,在XDP中我们还运行了第4层负载均衡器层。这是一个新项目,我们还没作过多的谈论。无需深入探讨:在某些情况下,我们需要从XDP执行套接字查找。

这个问题相对简单——我们的代码需要查找从数据包中提取的5元组的“套接字”内核结构。这通常很简单——可以借助bpf_sk_lookup helper。不出所料的是,这里出现了一些复杂情况。其中有个问题是,当启用SYN cookie时,无法验证接收到的ACK包是否是三方握手的有效部分。我的同事Lorenz Bauer正在为这个特殊情况提供额外支持。


经过DoS和负载均衡层之后,数据包将被传递到通常的Linux TCP / UDP堆栈上。在这里,我们进行套接字分配——例如,将进入端口53的数据包传递到属于我们的DNS服务器的套接字上。

我们尽力使用原始Linux功能,但是当您在服务器上使用数千个IP地址时,事情会变得复杂。

使用“AnyIP”技巧使Linux能够正确地路由数据包是相对比较容易的。但确保数据包被分发到正确的应用程序则是另一回事。不幸的是,标准的Linux套接字分派逻辑不够灵活,不能满足我们的需要。对于TCP/80这样的流行端口,我们希望在多个应用程序之间共享端口,每个应用程序在不同的IP范围内处理它。Linux不支持此功能。您可以在特定的IP或所有(使用0.0.0.0)的IP地址上调用bind()。


为了解决这个问题,我们开发了一个自定义内核补丁,其中添加了一个SO_BINDTOPREFIXsocket选项。顾名思义——它允许我们在选定的IP前缀上调用bind()。这解决了多个应用程序共享流行端口(如53或80)的问题。

然后我们遇到另一个问题。对于我们的Spectrum产品,我们需要监听所有总共65535个端口。运行这么多监听套接字不是一个好主意(请参阅我们过去的战争故事博客),因此我们不得不寻找另一种方法。经过一些实验,我们学会了使用一个鲜有人知的iptables模块——TPROXY——来达到这个目的。点击这里进行阅读:

这个设置起到了作用,但我们不喜欢额外的防火墙规则。我们正在努力正确地解决这个问题——实际上我们扩展了套接字调度逻辑。您猜对了——我们希望利用eBPF扩展套接字分派逻辑。敬请期待我们的一些补丁。


然后还有一种使用eBPF改进应用程序的方法。最近,我们对使用SOCKMAP进行TCP粘合很感兴趣:

这项技术在改善我们许多软件堆栈的尾部延迟方面具有巨大潜力。当前的SOCKMAP实施尚未准备好完全发挥其作用,但它的潜力是巨大的。

同样,新的TCP-BPF又名BPF_SOCK_OPS挂载为检查TCP流的性能参数提供了一种很好的方法。此功能对我们的性能表现团队非常有用。


一些Linux特性没有很好地发展成熟,我们需要解决它们。例如,我们正在突破网络指标的限制。不要误解我的意思——网络指标非常棒,但遗憾的是它们不够精细。像TcpExtListenDrops和TcpExtListenOverflows之类的事物被称为全局计数器,而我们需要在每个应用程序的基础上了解它。

我们的解决方案是使用eBPF探针直接从内核中抽取数字。我的同事Ivan Babrou编写了一个名为“ebpf_exporter”的Prometheus指标导出器,以便进行此操作。了解更多请继续阅读:

使用“ebpf_exporter”,我们可以生成所有形式的详细指标。它非常强大,并在许多情况下极大地帮助了我们。


在本次演讲中,我们讨论了在边缘服务器上运行的6层BPF:

  • 正在XDP eBPF上运行的批量DoS缓解措施
  • 用于应用层攻击的Iptables xt_bpf cBPF
  • 用于UDP套接字速率限制的SO_ATTACH_BPF
  • 在XDP上运行的负载均衡器
  • 用于运行应用帮助进程的eBPF,例如用于TCP套接字粘合的SOCKMAP和用于TCP测量的TCP-BPF
  • 用于获取精细指标的“ebpf_exporter”

我们才刚刚开始!很快,我们将在基于eBPF的套接字调度、在Linux TC(流量控制)层上运行的eBPF以及与控制组eBPF挂载的更多集成上完成更多的工作。然后,我们的SRE团队将维护不断增长的BCC脚本列表,这些脚本对于调试非常有用。

感觉就像Linux停止了开发新的API一样,所有的新特性都要通过eBPF挂载和帮助进程来实现。这很好,并且具有强大的优势。升级eBPF程序比重新编译内核模块更容易、更安全。如果没有eBPF,那么像TCP-BPF之类展现大量性能跟踪数据的东西可能是无法实现的。

有的人说:“软件正在占据世界”,但我想说:“BPF正在占据软件”。

We protect entire corporate networks, help customers build Internet-scale applications efficiently, accelerate any website or Internet application, ward off DDoS attacks, keep hackers at bay, and can help you on your journey to Zero Trust.

Visit 1.1.1.1 from any device to get started with our free app that makes your Internet faster and safer.

To learn more about our mission to help build a better Internet, start here. If you're looking for a new career direction, check out our open positions.

简体中文 (CN) 开发人员 任播 eBPF (ZH)

Follow on Twitter

Marek Majkowski |@majek04
Cloudflare |Cloudflare

Related Posts

June 02, 2019 10:35AM

尽情编写代码吧:改善开发人员使用Cloudflare Workers的体验

我们很高兴地宣布,从今天开始,Cloudflare Workers®将拥有一个CLI(命令行界面)、新的且改进过的说明文件、适用于所有人的多个脚本、在worker .dev上运行应用程序而无需自带域的功能,以及一个令试验比以往任何时候都更容易的免费层。...

March 13, 2018 1:00PM

从现在起,任何人都可以在Cloudflare上使用Workers运行JavaScript!

就在一年前的今天,Cloudflare给了我一个使命:允许人们在Cloudflare的网路运行代码。当时,我们还不知道这意味着什么。它会以容器为基础吗?一种新的图灵不完全的领域特定语言?Lua语言?“函数”?我的脑中涌出很多想法。 最终,我们做出了现在看似理所当然的选择:JavaScript,使用标准的Service Workers API,在基于V8构建的新环境中运行。五个月前,我们向您预览了我们正在构建的内容,并开始了测试。...