架构

云原生-深入 Linux 内核网络技术

转载:https://www.thebyte.com.cn/network/summary.html

创造操作系统,就是去创造一个所有应用程序赖以运行的基础环境。从根本上来说,就是在制定规则:什么可以接受、什么可以做、什么不可以做。事实上,所有的程序都是在制定规则,只不过操作系统是在制定最根本的规则。

本章内容将深入分析 Linux 内核网络技术,根据数据包的处理过程层层推进,首先解析 Linux 内核中关键模块(将介绍 netfilter、iptables 以及 conntrack)如何密切协作、如何影响应用软件的设计。接着,针对 Linux 内核在密集网络系统下的瓶颈问题,探索业内一些“跨内核”思想的解决方案(将介绍 DPDK、RDMA 和 eBPF 技术)。最后,我们将学习虚拟化网络技术,为后续介绍的高级应用(如负载均衡、容器编排、服务网格)储备必要的基础知识。

 

OSI 网络分层模型


本章及后续章节中,会多次出现“L7”、“L4”、“二层”和“三层”类似的术语,因此有必要提前解释。

这些术语源自广为人知的 OSI 七层模型,该模型由 ISO(国际标准化组织)于 20 世纪 80 年代提出,目的是为网络通信提供通用参考标准,使相同规范的网络能够互联。

如表 3-1 所示:OSI 模型通过“分层”思想,将网络通信拆解为七个独立的层次,每个层次解决特定的局部问题。笔者按照从上到下的顺序,介绍各个网络分层的含义,供读者参考。

层级名称含义
7应用层(Application Layer)应用层是 OSI 模型的顶层,直接与用户的应用程序交互。该层的协议有 HTTP、FTP、SMTP 等。
6表示层(Presentation Layer)负责数据的格式转换、加密和解密,确保发送方和接收方之间的数据格式一致。该层的协议有 SSL/TLS、JPEG、MIME 等。
5会话层(Session Layer)管理应用程序之间的会话,负责建立、维护和终止会话。
4传输层(Transport Layer)提供端到端的数据传输服务,负责数据分段、传输可靠性以及错误恢复。该层的协议有 TCP 和 UDP。
3网络层(Network Layer)提供逻辑地址(如 IP 地址),负责数据的路由选择和传输,解决不同网络之间的通信。该层的协议有 IPv4、IPv6、ICMP、ARP 等。
2数据链路层(Data Link Layer)负责局域网(LAN)内的数据传输,对传输的单元(数据帧)提供错误检测和纠正功能。该层的协议有 Ethernet、PPP、HDLC 等。
1物理层(Physical Layer)物理层是 OSI 模型的第一层,负责比特流在物理介质上的传输。

 

通常情况下,数据链路层的数据单元称为“帧”(Frame),网络层的数据单元称为“数据包”(Packet),传输层的数据单元称为“数据段”(Segment),而应用层的数据单元则称为“数据”(Data)。为了简化表述,本书大部分内容不会严格区分这些术语,统一使用“数据包”泛指各层数据单元。

 

Linux 系统收包流程


本节,我们了解数据包进入网卡(eth0)后,Linux 内核中各个模块是如何协作,对 Linux 系统的数据包处理机制有个系统的认识。

总结 Linux 系统收包流程,如图 所示。

  1. 外部数据包到达主机时,首先由网卡 eth0 接收。
  2. 网卡通过 DMA(Direct Memory Access,直接内存访问)技术,将数据包拷贝到内核中的 RingBuffer(环形缓冲区)等待 CPU 处理。RingBuffer 是一种首尾相接的环形数据结构,它作为缓冲区,缓解网卡接收数据的速度快于 CPU 处理数据的速度问题。
  3. 接着,网卡产生 IRQ(Interrupt Request,硬件中断),通知内核有新的数据包到达。
  4. 内核调用中断处理函数,标记新数据到达。接着,唤醒 ksoftirqd 内核线程,执行软中断(SoftIRQ)处理。
  5. 软中断处理中,内核调用网卡驱动的 NAPI(New API)poll 接口,从 RingBuffer 中提取数据包,并转换为 skb(Socket Buffer)格式。skb 是描述网络数据包的核心数据结构,无论是数据包的发送、接收还是转发,Linux 内核都会以 skb 的形式来处理。
  6. skb 被传递到内核协议栈,在多个网络层次间处理:
    • 网络层(L3 Network layer):根据主机中的路由表,判断数据包路由到哪一个网络接口(Network Interface)。这里的网络接口可能是稍后介绍的虚拟设备,也可能是物理网卡 eth0 接口。
    • 传输层(L4 Transport layer):处理网络地址转换(NAT)、连接跟踪(conntrack)等。
  7. 内核协议栈处理完成后,数据包被传递到 socket 接收缓冲区。应用程序利用系统调用(如 Socket API)从缓冲区读取数据。至此,整个收包过程结束。

从上述流程可见,Linux 系统的数据包处理涉及多个网络层协议栈(如数据链路层、网络层、传输层和应用层),需要进行封包/解包操作,并频繁发生上下文切换(Context Switch)。在设计网络密集型系统时,Linux 内核瓶颈不可忽视,优化内核参数是不可或缺的环节。除了优化内核参数,业界还提出了“绕过内核”的解决方案,如图 所示的 XDP 和 DPDK 技术。

 

Linux 内核网络框架


Linux 系统处理网络数据包(见图 3-1)看似是一套固定封闭的机制,实际情况并非如此。 从 Linux 内核 2.4 版本开始,内核引入了一套通用的过滤框架 —— Netfilter,使得“外界”可以在数据包流经内核协议栈时进行干预。

Linux 系统中的各类网络功能,如地址转换、封包处理、地址伪装、协议连接跟踪、数据包过滤、透明代理、带宽限速和访问控制等,都是基于 Netfilter 提供的代码拦截机制实现的。可以说,Netfilter 是整个 Linux 网络系统最重要(没有之一)的基石。

 

Netfilter 的 5 个钩子


Netfilter 围绕网络协议栈(主要在网络层)“埋下”了 5 个钩子(也称 hook)[1],用来干预 Linux 网络通信。Linux 内核中的其他模块(如 iptables、IPVS 等)向这些钩子注册回调函数。当数据包进入内核协议栈并经过钩子时,回调函数会自动触发,从而对数据包进行处理。

这 5 个钩子的名称与含义如下:

  • PREROUTING:只要数据包从设备(如网卡)进入协议栈,就会触发该钩子。当我们需要修改数据包的 “Destination IP” 时,会使用到该钩子,即 PREROUTING 钩子主要用于目标网络地址转换(DNAT,Destination NAT)。
  • FORWARD:顾名思义,指转发数据包。前面的 PREROUTING 钩子并未经过 IP 路由,不管数据包是不是发往本机的,全部照单全收。如果发现数据包不是发往本机,则会触发 FORWARD 钩子进行处理。此时,本机就相当于一个路由器,作为网络数据包的中转站,FORWARD 钩子的作用就是处理这些被转发的数据包,以此来保护其背后真正的“后端”机器。
  • INPUT:如果发现数据包是发往本机的,则会触发本钩子。INPUT 钩子一般用来加工发往本机的数据包,当然也可以做数据过滤,保护本机的安全。
  • OUTPUT:数据包送达到应用层处理后,会把结果送回请求端,在经过 IP 路由之前,会触发该钩子。OUTPUT 钩子 一般用于加工本地进程输出的数据包,同时也可以限制本机的访问权限。比如,将发往 www.example.org 的数据包都丢弃掉。
  • POSTROUTING:数据包出协议栈之前,都会触发该钩子,无论这个数据包是转发的,还是经过本机进程处理过的。POSTROUTING 钩子 一般用于源网络地址转换(SNAT,Source NAT)。

这 5 个钩子在网络协议栈的位置如图 所示。

Netfilter 允许在同一钩子上注册多个回调函数,每个回调函数都有明确的优先级,以确保按预定顺序触发。这些回调函数串联起来形成了一个“回调链”(Chained Callbacks)。这种设计使得基于 Netfilter 构建的上层应用大多带有“链”的概念,如稍后将介绍的 iptables。

Hook 设计模式在许多软件系统中广泛应用。例如,本书后续将介绍的 eBPF 和 Kubernetes。Kubernetes 通过暴露各种接口(hook),使用户能够根据需求插入自定义代码或逻辑,从而扩展其在编排调度、网络和资源定义等方面的功能。

Netfilter 的钩子回调固然强大,但需要通过编写程序才能使用,并不适合系统管理员日常运维。为此,基于 Netfilter 框架开发的应用便出现了,如 iptables。

熟悉 Linux 的工程师通常都接触过 iptables,它常被视为 Linux 内置的防火墙管理工具。严谨地讲,iptables 能做的事情远超防火墙的范畴,它的定位应是能够代替 Netfilter 多数常规功能的 IP 包过滤工具。

 

iptables 表和链


Netfilter 中的钩子在 iptables 中的对应称作“链”(chain)。

iptables 默认包含 5 条规则链 PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING,它们分别对应了 Netfilter 的 5 个钩子。

iptables 将常见的数据包管理操作抽象为具体的规则动作,当数据包在内核协议栈中经过 Netfilter 钩子时(也就是 iptables 的链),iptables 会根据数据包的源/目的 IP 地址、传输层协议(如 TCP、UDP)以及端口等信息进行匹配,并决定是否触发预定义的规则动作。

iptables 常见的动作及含义如下:

  • ACCEPT:允许数据包通过,继续执行后续的规则。
  • DROP:直接丢弃数据包。
  • RETURN:跳出当前规则“链”(Chain,稍后解释),继续执行前一个调用链的后续规则。
  • DNAT:修改数据包的目标网络地址。
  • SNAT:修改数据包的源网络地址。
  • REDIRECT:在本机上做端口映射,比如将 80 端口映射到 8080,访问 80 端口的数据包将会重定向到 8080 端口对应的监听服务。
  • REJECT:功能与 DROP 类似,只不过它会通过 ICMP 协议向发送端返回错误信息,比如返回 Destination network unreachable 错误。
  • MASQUERADE:地址伪装,可以理解为动态的 SNAT。通过它可以将源地址绑定到某个网卡上,因为这个网卡的 IP 可能是动态变化的,此时用 SNAT 就不好实现;
  • LOG:内核对数据包进行日志记录。

在 iptables 规则体系中,不同的链用于处理数据包在协议栈中的不同阶段,将不同类型的动作归类,也更便于管理。如数据包过滤的动作(ACCEPT、DROP、RETURN、REJECT 等)可以合并到一处,数据包的修改动作(DNAT、SNAT)可以合并到另外一处,这便有了规则表的概念。

iptables 共有 5 张规则表,它们的名称与含义如下:

  • raw 表:主要用于绕过数据包的连接追踪机制。默认情况下,内核会对数据包进行连接跟踪,而使用 raw 表可以避免 conntrack 处理,从而减少系统开销,提高数据包转发性能。
  • mangle 表:用于修改数据包的特定字段,主要应用于数据包头的调整。例如,可以修改 ToS(服务类型)、TTL(生存时间)或 Mark(标记)等字段,以影响 QoS 处理或路由决策。
  • nat 表:负责网络地址转换(NAT),用于修改数据包的源地址或目的地址。当数据包进入协议栈时,nat 表中的规则决定是否以及如何进行地址转换,从而影响数据包的路由。例如,可用于访问私有网络或负载均衡。
  • filter 表:用于数据包过滤,决定数据包是放行(ACCEPT)、拒绝(REJECT)还是丢弃(DROP)。如果不指定 -t 选项,iptables 默认操作的就是 filter 表。
  • security 表:主要用于安全策略强化,通常配合 SELinux 使用,以施加更严格的访问控制策略。除 SELinux 相关应用外,security 表并不常用。

举一个具体的例子。如下命令所示,放行 TCP 22 端口的流量,即在 INPUT 链上添加 ACCEPT 动作。

$  iptables -A INPUT -p tcp --dport 22 -j ACCEPT

将规则表与链进行关联,而不是规则本身与链关联,通过一个中间层解耦了链与具体的某条规则,原本复杂的对应关系就变得简单了。

最后,根据图 3-3 总结数据包进入 iptables 处理流程。首先经过 raw 进行连接跟踪处理,接着 mangle 修改数据包字段,随后 nat 进行地址转换,最后 filter 执行最终的放行或丢弃策略,而 security 仅在 SELinux 环境下应用额外的安全规则。

图 3-3 数据包通过 Netfilter 时的流动过程

 

iptables 自定义链与应用


除了 5 个内置链 之外,iptables 还支持管理员创建自定义链。

自定义链 可以看作对调用它的内置链的扩展。当数据包进入自定义链后,可以选择返回调用它的内置链,或继续跳转到其他自定义链,从而实现更复杂的流量处理逻辑。这种机制使 iptables 不仅仅是一个 IP 包过滤工具,还在容器网络等场景中发挥了关键作用。

例如,在 Kubernetes 中,kube-proxy 依赖 iptables 的自定义链 实现 Service 负载均衡,通过规则跳转管理流量转发,从而确保容器服务的高效通信。一旦创建一个 Service,Kubernetes 会在主机添加这样一条 iptable 规则。

-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m tcp --dport 80 -j KUBE-SVC-NWV5X

这条 iptables 规则的含义是:凡是目的地址是 10.0.1.175、目的端口是 80 的 IP 包,都应该跳转到另外一条名叫 KUBE-SVC-NWV5X 的 iptables 链进行处理。10.0.1.175 其实就是 Service 的 VIP(Virtual IP Address,虚拟 IP 地址)。可以看到,它只是 iptables 中一条规则的配置,并没有任何网络设备,所以 ping 不通。

接下来的 KUBE-SVC-NWV5X 是一组规则的集合,如下所示:

-A KUBE-SVC-NWV5X --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2
-A KUBE-SVC-NWV5X --mode random --probability 0.50000000000 -j KUBE-SEP-X3P26
-A KUBE-SVC-NWV5X -j KUBE-SEP-57KPR

可以看到,这一组规则实际上是一组随机模式(–mode random)的自定义链,也是 Service 实现负载均衡的位置。随机转发的目的地为 KUBE-SEP-<hash> 自定义链。

查看自定义链KUBE-SEP-<hash>的明细,我们就很容易理解 Service 进行转发的具体原理了,如下所示:

-A KUBE-SEP-WNBA2 -s 10.244.3.6/32  -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-WNBA2 -p tcp -m tcp -j DNAT --to-destination 10.244.3.6:9376

可以看到,自定义链 KUBE-SEP-<hash> 是一条 DNAT 规则。DNAT 规则的作用是在 PREROUTING 钩子处,也就是在路由之前,将流入 IP 包的目的地址和端口,改成 –to-destination 所指定的新的目的地址和端口。而目的地址和端口 10.244.3.6:9376,正是 Service 代理 Pod 的 IP 地址和端口。这样,访问 Service VIP 的 IP 包经过上述 iptables 处理之后,就已经变成了访问具体某一个后端 Pod 的 IP 包了。

上述实现负载均衡的方式在 kube-proxy 中被称为 iptables 模式。在该模式下,所有容器间的请求和负载均衡操作 都依赖 iptables 规则 进行处理,因此其性能直接受到 iptables 机制的影响。随着 Service 数量的增加,iptables 规则数量也呈现暴涨趋势,导致系统负担加重。

为解决 iptables 模式的性能问题,kube-proxy 新增了 IPVS 模式。该模式使用 Linux 内核四层负载均衡模块 IPVS 实现容器间请求和负载均衡,性能和 Service 规模无关

需要注意的是,内核中的 IPVS 模块仅负责负载均衡和代理功能,而 Service 的完整工作流程还依赖 iptables 进行初始流量捕获和过滤。不过,这些 iptables 规则仅用于辅助,其数量相对有限,不会随着 Service 数量增加而指数级膨胀。

如图 3-4 所示,展示了 iptables 与 IPVS 两种模式在性能方面的对比。可以观察到,当 Kubernetes 集群 中的 Service 数量达到 1,000 个(对应约 10,000 个 Pod)时,两者的性能表现开始出现明显差异。

图 3-4 iptables 与 IPVS 的性能差异(结果越低,性能越好)

现在,你应该理解了,当 Kubernetes 集群规模较大时,应尽量避免使用 iptables 模式,以避免性能瓶颈。如果使用的是 Cilium 作为容器间通信解决方案,还可以构建无需 kube-proxy 组件的 Kubernetes 集群,利用笔者稍后介绍的“内核旁路”技术绕过 iptables 限制,全方位提升容器网络性能。

 

连接跟踪模块 conntrack


conntrack 是“连接跟踪”(connection tracking)的缩写,顾名思义,它用于跟踪 Linux 内核中的通信连接。需要注意的是,conntrack 跟踪的“连接”不仅限于 TCP 连接,还包括 UDP、ICMP 等类型的连接。当 Linux 系统收到数据包时,conntrack 模块会为其创建一个新的连接记录,并根据数据包的类型更新连接状态,如 NEW、ESTABLISHED 等。

以 TCP 三次握手为例,说明 conntrack 模块的工作原理 :

  1. 客户端向服务器发送一个 TCP SYN 包,发起连接请求。
  2. Linux 系统收到 SYN 包后,conntrack 模块为其创建新的连接记录,并将状态标记为 NEW。
  3. 服务器回复 SYN-ACK 包,等待客户端的 ACK。一旦握手完成,连接状态变为 ESTABLISHED。

通过命令 cat /proc/net/nf_conntrack 查看连接记录,如下所示,输出了一个状态为 ESTABLISHED 的 TCP 连接。

$ cat /proc/net/nf_conntrack
ipv4     2 tcp      6 88 ESTABLISHED src=10.0.12.12 dst=10.0.12.14 sport=48318 dport=27017 src=10.0.12.14 dst=10.0.12.12 sport=27017 dport=48318 [ASSURED] mark=0 zone=0 use=2

conntrack 连接记录是 iptables 连接状态匹配的基础,也是实现 SNAT 和 DNAT 的前提。我们知道 Kubernetes 的核心组件 kube-proxy,它作用是负责处理集群中的服务(Service)网络流量。它本质上是一个反向代理(即 NAT),当外部请求访问 Service 时,流量会被 DNAT 转发到 PodIP:Port,响应则经过 SNAT 处理。

举一个具体的例子说明。假设客户端向 my-service(IP 10.0.0.10,端口 80)发送 HTTP 请求,流程如下:

  • 节点中的 kube-proxy 收到请求后,执行 DNAT 操作,将目标地址从 10.0.0.10:80 转换为某个 Pod 的 IP 和端口(如 192.168.1.2:8080)。
  • Pod 处理请求并返回响应,kube-proxy 执行 SNAT 操作,将响应包的源地址从 192.168.1.2:8080 转换为 Service IP 10.0.0.10:80。

conntrack 模块维护的连接记录包含了从客户端到 Pod 的 DNAT 映射、从 Pod 到客户端的 SNAT 映射。这样有来有回,是一条完整的 NAT 映射关系。但是,如果客户端与 Pod 在同一主机上(如图 3-5),则会出现以下问题:

  • 客户端发起请求时,数据包经过网络层,conntrack 模块根据 iptables 规则判断是否需要进行 DNAT;
  • 返回响应时,Linux 网桥发现目标 IP 位于同一网桥上,会直接通过链路层转发数据包,而不会触发网络层的 conntrack 模块,导致 SNAT 操作没有执行。

如图 3-5 所示,通信双方不在同一“频道”上,NAT 映射关系不完整,进而影响容器间通信,产生各种异常。

图 3-5 请求和响应不在一个“频道”上,双方通信失败

为了解决上述问题,Linux 内核引入了 bridge-nf-call-iptables 配置,决定是否在网桥中触发 iptables 匹配规则,从而保证 NAT 处理时 conntrack 连接记录的完整性。这也解释了为什么在部署 Kubernetes 集群时,必须将该配置设置为 1。

 

内核旁路技术

数据平面开发套件 DPDK


2010 年,Intel 主导开发了 DPDK(Data Plane Development Kit,数据平面开发套件),基于“内核旁路”理念构建高性能网络应用方案,并逐步发展为一套成熟的技术体系。

最初,DPDK 是 Intel 为推销自家硬件而开发的高性能网络驱动组件,专门针对 Intel 处理器和网卡。随着 DPDK 开源,越来越多厂商开始贡献代码,DPDK 扩展了对更多硬件的支持:不仅支持 Intel 处理器,还兼容 AMD、ARM 等厂商的处理器;网卡支持范围也涵盖了 Intel、Mellanox、ARM 集成网卡等。因此,DPDK 也逐渐具有广泛的适用性。

图 3-6 展示了 DPDK(Fast Path)与传统内核网络(Slow Path)之间的区别。在 Linux 系统中,DPDK 的库和应用程序在用户空间的编译、链接和加载方式与普通程序相同,但它们的数据包传输路径却大相径庭:

  • 传统内核网络(图左侧):网络数据包从网络接口卡(NIC)出发,经驱动程序、内核协议栈处理,最终通过 Socket 接口传递给用户空间的业务层。
  • DPDK 加速网络(图右侧):在该方案中,网络数据包通过用户空间 I/O(UIO)技术,直接绕过内核协议栈,从网卡传输至 DPDK 基础库,再传递至业务逻辑。也就是说,DPDK 绕过了 Linux 内核协议栈的数据包处理过程,在用户空间直接进行收发和处理。

图 3-6 DPDK 与传统内核网络对比

爱奇艺开源的 DPVS 是 DPDK 技术在负载均衡领域的成功应用。图 3-7 展示了 DPVS 与标准 LVS 的性能对比。从每秒转发数据包数量(Packet Per Second,PPS)的指标来看,DPVS 的性能表现比 LVS 高出 300%。

图 3-7 DPVS 与 LVS 的 PPS 性能指标对比(结果越高,性能越好)

对于海量用户规模的互联网应用,通常需要部署数千甚至数万台服务器。如果能将单机性能提升十倍甚至百倍,无论从硬件投入还是运营成本角度,都能实现显著的成本节约。这种技术变革带来的潜在效益,毫无疑问非常诱人。

DPDK 是由硬件厂商主导的“内核旁路”技术。在下一节,笔者将介绍由社区开发者主导的另一类“内核旁路”技术。

 

eBPF 和 快速数据路径 XDP


由于 DPDK 完全基于“内核旁路”的思想,它天然无法与 Linux 内核生态很好地结合。

2016 年,Linux Netdev 会议,Linux 内核开发者 David S. Miller[1] 喊出了“DPDK is not Linux”的口号。同年,随着 eBPF 技术成熟,Linux 内核终于迎来了属于自己的“高速公路” —— XDP(eXpress Data Path,快速数据路径)。XDP 因其媲美 DPDK 的性能、背靠 Linux 内核,无需第三方代码库和许可、无需专用 CPU 等多种优势,一经推出便备受关注。

DPDK 技术完全绕过内核,直接将数据包透传至用户空间处理。XDP 正好相反,它在内核空间根据用户的逻辑处理数据包。

在内核执行用户逻辑的关键在于 BPF(Berkeley Packet Filter,伯克利包过滤器)技术 —— 一种允许在内核空间运行经过安全验证的代码的机制。Linux 内核 2.5 版本起,Linux 系统就开始支持 BPF 技术了,但早期的 BPF 主要用于网络数据包的捕获和过滤。到了 Linux 内核 3.18 版本,开发者推出了一套全新的 BPF 架构,也就是我们今天所说的 eBPF(Extended Berkeley Packet Filter)。与早期的 BPF 相比,eBPF 的功能不再局限于网络分析,它几乎能访问所有 Linux 内核关联的资源,逐渐发展成一个多功能的通用执行引擎。

至此,相信读者已经能够察觉到,eBPF 访问 Linux 内核资源的方式与 Netfilter 开放钩子的机制相似。两者的主要区别在于,Netfilter 提供的钩子数量有限,主要面向 Linux 的其他内核模块;而 eBPF 则面向普通开发者,Linux 系统提供了大量钩子供开发者挂载 eBPF 程序。

笔者列举部分钩子供读者参考:

  • TC(Traffic Control)钩子:位于内核的网络流量控制层,用于处理流经 Linux 内核的网络数据包。它可以在数据包进入或离开网络栈的各个阶段触发。
  • Tracepoints 钩子:Tracepoints 是内核代码中的静态探测钩子,分布在内核的各个子系统中。主要用于内核的性能分析、故障排查、监控等。例如,可以在调度器、文件系统操作、内存管理等处进行监控。
  • LSM(Linux Security Modules)钩子:位于 Linux 安全模块框架中,允许在内核执行某些安全相关操作(如文件访问、网络访问等)时触发 eBPF 程序。主要用于实现安全策略和访问控制。例如,可以编写 eBPF 程序来强制执行自定义的安全规则或监控系统的安全事件。
  • XDP 钩子:位于网络栈最底层的钩子,直接在网卡驱动程序中触发,用于处理收到的网络数据包,主要用于实现超高速的数据包处理操作,例如 DDoS 防护、负载均衡、数据包过滤等。

从上述钩子可见,XDP 本质上是 Linux 内核在网络路径上设置的钩子,位于网卡驱动层,在数据包进入网络协议栈之前。当 XDP 执行完 eBPF 程序后,通过“返回码”来指示数据包的最终处理决定。

XDP 的 5 种返回码及其含义如下:

  • XDP_ABORTED:表示 XDP 程序处理数据包时遇到错误或异常。
  • XDP_DROP:在网卡驱动层直接将该数据包丢掉,通常用于过滤无效或不需要的数据包,如实现 DDoS 防护时,丢弃恶意数据包。
  • XDP_PASS:数据包继续送往内核的网络协议栈,和传统的处理方式一致。这使得 XDP 可以在有需要的时候,继续使用传统的内核协议栈进行处理。
  • XDP_TX:数据包会被重新发送到入站的网络接口(通常是修改后的数据包)。这种操作可以用于实现数据包的快速转发、修改和回环测试(如用于负载均衡场景)。
  • XDP_REDIRECT:数据包重定向到其他的网卡或 CPU,结合 AF_XDP[2]可以将数据包直接送往用户空间。

eBPF 运行在内核空间,能够极大地减少数据的上下文切换开销,再结合 XDP 钩子,在 Linux 系统收包的早期阶段介入处理,就能实现高性能网络数据包处理和转发。以业内知名的容器网络方案 Cilium 为例,它在 eBPF 和 XDP 钩子(也有其他的钩子)基础上,实现了一套全新的 conntrack 和 NAT 机制。并以此为基础,构建出如 L3/L4 负载均衡、网络策略、观测和安全认证等各类高级功能。

由于 Cilium 实现的底层网络功能独立于 Netfilter,它的连接追踪数据和 NAT 规则不再存储在 Linux 内核默认的 conntrack 表和 NAT 表中。因此,常规的 Linux 命令(如 conntrack、netstat、ss 和 lsof)无法查看这些数据。必须使用 Cilium 提供的查询命令,例如:

$ cilium bpf nat list  // 列出 Cilium 中配置的 NAT 规则。
$ cilium bpf ct list global // 列出 Cilium 中的连接追踪条目
  1. Linux Netdev,专注于 Linux 网络栈和网络技术的会议。David S. Miller 是 Linux 内核网络子系统的主要维护者之一,也是 Linux 内核开发领域的知名人物。 ↩︎
  2. 相较 AF_INET 是基于传统网络的 Linux socket,AF_XDP 则是一套基于 XDP 的高性能 Linux socket。 ↩︎

 

远程直接内存访问 RDMA


近年来,人工智能、分布式训练和分布式存储技术快速发展,对网络传输性能提出了更高要求。但传统以太网在延迟、吞吐量和 CPU 资源消耗方面存在先天不足。在这一背景下,RDMA(Remote Direct Memory Access,远程直接内存访问)技术凭借卓越的性能,逐渐成为满足高性能计算需求的优选方案。

RDMA 设计起源于 DMA(Direct Memory Access)技术[1],它的工作原理如图 3-10 所示,应用程序通过 RDMA Verbs API 直接访问远程主机内存,而无需经过操作系统或 CPU 参与数据拷贝,从而极大地降低延迟和 CPU 开销,提高数据传输效率。

RDMA 网络的协议实现有三类,它们的含义及区别如下。

  • Infiniband(无限带宽)),是一种专门为 RDMA 而生的技术,由 IBTA(InfiniBand Trade Association, InfiniBand 贸易协会)在 2000 年提出,因其极致的性能(能够实现小于 3 μs 时延和 400Gb/s 以上的网络吞吐),在高性能计算领域中备受青睐。 但注意的是,构建 Infiniband 网络需要配置全套专用设备,如专用网卡、专用交换机和专用网线,限制了其普及性。其次,它的技术架构封闭,不兼容现有的以太网标准。这意味着,绝大多数通用数据中心都无法兼容 Infiniband 网络。尽管存在这些局限,InfiniBand 仍因其极致的性能成为特定领域的首选。例如,全球流行的人工智能应用 ChatGPT 背后的分布式机器学习系统,就是基于 Infiniband 网络构建的。
  • iWRAP(Internet Wide Area RDMA Protocol,互联网广域 RDMA 协议)是一种将 RDMA 封装在 TCP/IP 协议中的技术。RDMA 旨在提供高性能传输,而 TCP/IP 侧重于可靠性,其三次握手、拥塞控制等机制削弱了 iWRAP 的 RDMA 技术优势,导致其性能大幅下降。因此,iWRAP 由于先天设计上的局限性,逐渐被业界淘汰。
  • 为降低 RDMA 的使用成本,并推动其在通用数据中心的应用,IBTA 于 2010 年发布了 RoCE(RDMA over Converged Ethernet,融合以太网的远程直接内存访问)技术。RoCE 将 Infiniband 的数据格式(IB Payload)“移植”到以太网,使 RDMA 能够在标准以太网环境下运行。只需配备支持 RoCE 的专用网卡和标准以太网交换机,即可享受 RDMA 技术带来的高性能。如图 3-11 所示,RoCE 在发展过程中演化出两个版本:
    • RoCEv1:基于二层以太网,仅限于同一子网内通信,无法跨子网传输;
    • RoCEv2:基于三层 IP 网络,支持跨子网通信,提高了灵活性和可扩展性。
    RoCEv2 克服了 RoCEv1 不能跨子网的限制,并凭借其低成本和良好的兼容性,在分布式存储、并行计算等通用数据中心场景中得到广泛应用。根据微软 Azure 公开信息,截至 2023 年,Azure 数据中心中 RDMA 流量已占总流量的 70%[1:1]

RDMA 网络对丢包极为敏感,任何数据包丢失都可能触发大量重传,严重影响传输性能。Infiniband 依赖专用设备确保网络可靠性,而 RoCE 构建在标准以太网上,实现 RDMA 通信。因此,RoCE 网络需要无损以太网支持,以避免丢包对性能造成重大影响。

目前,大多数数据中心采用 DCQCN(由微软与 Mellanox 提出)或 HPCC(由阿里巴巴提出)算法,为 RoCE 网络提供可靠性保障。由于这些算法涉及底层技术,超出本书讨论范畴,感兴趣的读者可参考其他资料以进一步了解。