我们为什么使用Linux内核的TCP栈

本文是 Why we use the Linux kernel’s TCP stack 的翻译。

最近,有一篇文章提出了一个非常有趣的问题,我们为什么使用Linux内核的TCP栈? 这在Hacker News上引发了非常有趣的讨论。

在CloudFlare工作的时候,我也一直在想这个问题。我的经验主要来自于和数千台生产机器打交道,我也会从这个角度来尝试回答这个问题。

CC BY 2.0 图片 来自 John Vetterli

让我们从一个更加宽泛的问题开始——跑起来一个操作系统是为了啥?如果你仅仅打算运行一个应用程序,那么运行数百万行代码的内核听起来绝对是一个负担。

但,我们通常都决定跑一个操作系统,有两个原因。第一,操作系统层提供了硬件独立性,以及很容易使用的API。这样,我们就可以为任何机器写代码了——不仅仅是当前运行代码的这种机器。第二,操作系统提供了时分复用层。这让我们能同时运行多个程序。不管它是另一个http服务,还是仅仅是一个bash会话,这种不同进程共享资源的能力是非常重要的。所有由内核暴露出来的资源都是能够被多个进程共享的!

用户态网络

对于网络栈,这也没什么不同。运行通用操作系统的网络栈,我们能够运行多个网络程序。如果为了运行用户态网络栈,而让单个应用程序独享网卡硬件,那就会丢失这种能力。将一个网卡分配给另一个程序,那么很可能就没法同时与服务器进行ssh会话了。

这听起来很疯狂,但这正是很多用户态网络栈所建议的。通用术语叫“全内核旁路”(full kernel bypass)。即绕过内核,用户态进程直接使用网络硬件 。

CC BY 2.0 图片 来自 Audiotecna Música

在Linux生态系统内,有好几种类似的技术。并不是所有的都是开源的:

我在之前的博文中提到过它们。这些技术都需要将整个网卡交给一个进程。换句话说,你完全可以写自己的网络栈,精心设计它,提供新特性,并优化性能。但这有一个巨大的成本——每个网卡只能被一个进程使用。

关于虚拟网卡(virtualized network cards,VIF),情况有一点点复杂,但是我们不做深入,它完全没用。我在“虚拟化方法”一节中谈到了这一点。

但是即使有这些障碍,我也不能忽视内核旁路的好处。许多人却是在运行自定义网络栈,主要有两个原因:

  • 延迟
  • 性能(更低的cpu消耗,更高的吞吐量)

延迟对高频交易人员很重要。他们可以负担的起定制硬件、流行的专有网络栈。运行一个闭源的TCP栈会让我感觉很不舒服。

CloudFlare的内核旁路

尽管如此,在CloudFlare,我们仍然使用内核旁路技术。我们术语第二种类型——我们关注性能。更确切的说,我们遭受了IRQ风暴。Linux网络栈每秒处理的包个数有限,当到达这个限制的时候,所有CPU都忙着接受数据包。在这种情况下,要么丢弃一些包,要么应用程序没有足够的CPU资源。虽然在正常情况下我们不需要应对IRQ风暴,但当我们成为L3(OSI模型中的第三层)攻击目标时,这种情况确实会发生。这是一种攻击类型,目标被无效的TCP包淹没,即这些TCP包不属于任何有效的TCP连接。

CC BY-SA 2.0 图片 来自 Howard Lake

在有些网络攻击中,每个服务器被每秒多达三百万个数据包淹没。一般的经验法则是,Linux iptables在一个不错的处理器上每秒能处理一百万个包,同时仍然有足够的CPU资源给应用程序。这个极限也可以通过适当的调优来增加。

在这种规模的攻击下,Linux 内核是不够的。我们必须解决这个问题。我们不使用之前提到的“完全内核旁路”,而是运行我们所称的“部分内核旁路”。这样内核就保留了网卡的所有权,我们也可以仅在一个“RX队列”上执行内核旁路。我们在Solarflare网卡上,使用Solarflare的EFVI API。为了支持Intel网卡,我们给Netmap添加了部分内核旁路的支持:这在这篇文章有说明。有了这种技术,我们可以将反DDos的iptables规则卸载到一个非常快的用户态进程。使Linux内核免于处理攻击包,避免IRQ风暴。

那么,纯用户空间网络栈呢?

我的同事经常问我:为什么我们不直接在Solarflare 的OpenOnload 框架上运行NGINX,使用超级快的用户态TCP呢?

是的,那样会很快,但没有证据表明它会产生很多实际影响。大部分CPU都用于用户态的NGINX进程,而不是操作系统内核。CPU主要用于通常的NGINX逻辑和Lua应用程序逻辑,而不是网络处理。我估计使用内核旁路我们可以节省大约5-10%的CPU,这(目前)不值得尝试。

CC BY 2.0 图片 来自 Charlie

其次,为NGINX使用内核旁路会干扰我们常用的调试工具。我们的systemtap脚本将变得完全没用。Linux netstat也不再记录关键事件,tcpdump也不能使用。

然后是如何缓解DDoS攻击。正如我在这个BlackHat演讲中所说的,我们是iptables的忠实用户。自定义TCP栈没有”hashlimits”和”ipset”之类的东西。

但不仅是防火墙的功能。Linux TCP栈对诸如RFC4821sys.net.ipv4.tcp_mtu_probe这样的sysctl配置等提供了一些非常有用的支持。当用户位于ICMP黑洞的后面时,对这一点的支持是至关重要的。阅读关于PMTU的博文了解更多。

最后,每个TCP栈都有自己的缺陷和妥协。我们已经发现了Linux TCP栈中的三个不明显的妥协:

想象一下,在一个闭源的,或者新的(或者新的且闭源的)TCP栈中调试这些问题。

结论

有两点:第一,目前还没有一个稳定的、开源的部分内核旁路技术。我们希望Netmap能够占领这个市场,我们也在用自己的补丁来支持它。第二,Linux TCP栈有很多关键特性以及非常好的调试功能。与Linux TCP生态系统竞争需要数年时间。

基于这些原因,用户态网络不太可能成为主流。在实践中,我只能想到几个合理的内核旁路技术应用:

  • 软件交换机或者路由器。你希望将网卡交给应用程序,让应用程序处理原始数据包,并完全跳过内核。
  • 专用负载均衡。类似的,如果机器只是分发数据包,那么跳过内核是有意义的。
  • 将高吞吐的、低延迟的应用程序部分旁路。这是我们用于应对DDoS攻击的设置。然而,我不知道有没有一个稳定的、开源的TCP栈适合这个场景。

对于一般用户来说,Linux网路栈是正确的选择。尽管它没有重写TCP堆栈那么令人兴奋,但是我们应该集中精力理解Linux堆栈性能并修复它的问题。有一些重要的举措也正在推进,以改进旧的Linux TCP堆栈的性能。

作者

Robert Lu

发布于

2019-05-02

许可协议

评论