第 42 章 了解 RHEL 9 中的 eBPF 网络功能
扩展的 Berkeley Packet 过滤器(eBPF)是一个内核中的虚拟机,允许在内核空间中执行代码。此代码运行在一个受限的沙箱环境中,仅可访问有限功能集。
在网络中,您可以使用 eBPF 来补充或替换内核数据包处理。根据您使用的 hook,eBPF 程序有:
- 对元数据的读和写的访问权限
- 可以查找套接字和路由
- 可以设置套接字选项
- 可以重定向数据包
42.1. RHEL 9 中网络 eBPF 功能概述
您可以将扩展的 Berkeley 数据包过滤器(eBPF)网络程序附加到 RHEL 中的以下钩子:
- Express Data Path(XDP):在内核网络堆栈处理它们之前,对接收的数据包提供早期的访问权限。
-
带有直接动作标志的
tc
eBPF 分类器:对入口和出口提供强大的数据包处理。 - 控制组版本 2(cgroup v2):在控制组中,对程序所执行的基于套接字的操作启用过滤和覆盖。
- 套接字过滤:启用对从套接字接收的数据包进行过滤。这个功能也可用于经典 Berkeley Packet Filter(cBPF),但已扩展为支持 eBPF 程序。
- 流解析器:启用将流分成单独的消息、过滤并将其重定向到套接字。
-
SO_REUSEPORT
套接字选择:对来自reuseport
套接字组的接收套接字提供可编程选择。 - 流程分析器:在某些情况下,启用覆盖内核解析数据包头的方式。
- TCP 拥塞控制回调:启用实现一个自定义 TCP 拥塞控制算法。
- 带有封装的路由: 允许创建自定义隧道封装。
XDP
您可以将 BPF_PROG_TYPE_XDP
类型的程序附加到网络接口。然后,在内核网络堆栈开始处理之前,内核会在接收的数据包上执行该程序。在某些情况下,这允许快速数据包转发,如快速数据包丢弃以防止分布式拒绝服务(DDoS)攻击,以及负载均衡场景的快速数据包重定向。
您还可以使用 XDP 进行不同类型的数据包监控和抽样。内核允许 XDP 程序修改数据包,并将其传送到内核网络堆栈进行进一步处理。
以下的 XDP 模式可用:
- 原生(驱动程序)XDP:内核在数据包接收过程从最早可能的点执行程序。目前,内核无法解析数据包,因此无法使用内核提供的元数据。这个模式要求网络接口驱动程序支持 XDP,但并非所有驱动程序都支持这种原生模式。
- 通用 XDP:内核网络栈在进程早期执行 XDP 程序。此时内核数据结构已被分配,数据包已被预先处理。如果数据包被丢弃或重定向,与原生模式相比,这需要大量开销。但是,通用模式不需要支持网络接口驱动,它可适用于所有网络接口。
- Offloaded XDP:内核在网络接口而不是主机 CPU 上执行 XDP 程序。请注意,这需要特定的硬件,这个模式中只有某些 eBPF 功能可用。
在 RHEL 上,使用 libxdp
库加载所有 XDP 程序。这个程序库启用系统控制的 XDP 使用。
目前,XDP 程序有一些系统配置限制。例如:您必须禁用接收接口中某些硬件卸载功能。另外,并非所有功能都可用于支持原生模式的所有驱动程序。
在 RHEL 9 中,红帽仅在使用 libxdp
库将程序加载到内核中时,才支持 XDP 功能。
AF_XDP
使用过滤并将数据包重定向到给定的 AF_XDP
套接字的 XDP 程序,您可以使用 AF_XDP
协议系列中的一个或多个套接字来快速将数据包从内核复制到用户空间。
流量控制
流量控制(tc
)子系统提供以下 eBPF 程序类型:
-
BPF_PROG_TYPE_SCHED_CLS
-
BPF_PROG_TYPE_SCHED_ACT
这些类型允许您在 eBPF 中编写自定义的 tc
分类器和 tc
操作。与 tc
生态系统的各个部分一起,这为强大的数据包处理提供了能力,是一些容器编排解决方案的核心部分。
在大多数情况下,只有类符被使用,与 direct-action 标记一样,eBPF 分类器可以直接从同一 eBPF 程序执行操作。clsact
排队规程(qdisc
)被设计为在入口端启用此功能。
请注意,使用流解析器 eBPF 程序可能会影响其他 qdiscs
和 tc
分类器的操作,如 flower
。
套接字过滤器
一些实用程序会使用或在过去使用了 classic Berkeley Packet Filter(cBPF)过滤套接字上接收到的数据包。例如,tcpdump
工具允许用户指定表达式,tcpdump
然后将它们转换为 cBPF 码。
作为 cBPF 的替代方案,内核允许 BPF_PROG_TYPE_SOCKET_FILTER
类型的 eBPF 程序实现相同的目的。
控制组群
在 RHEL 中,您可以使用多种 eBPF 程序,供您附加到 cgroup。当给定 cgroup 中的某个程序执行某个操作时,内核会执行这些程序。请注意,您只能使用 cgroups 版本 2。
RHEL 中提供以下与网络相关的 cgroup eBPF 程序:
-
BPF_PROG_TYPE_SOCK_OPS
:内核对各种 TCP 事件调用该程序。程序可以调整内核 TCP 堆栈的行为,包括自定义 TCP 头选项等。 -
BPF_PROG_TYPE_CGROUP_SOCK_ADDR
:在connect
、bind
、sendto
、recvmsg
、getpeername
和getockname
操作过程中,内核调用该程序。该程序允许更改 IP 地址和端口。当您在 eBPF 中实现基于套接字的网络地址转换(NAT)时,这很有用。 -
BPF_PROG_TYPE_CGROUP_SOCKOPT
:在setockopt
和getsockopt
过程中,内核调用该程序,并允许更改选项。 -
BPF_PROG_TYPE_CGROUP_SOCK
:在套接字创建、套接字释放和绑定到地址的过程中,内核调用该程序。您可以使用这些程序来允许或拒绝操作,或者只检查套接字创建统计信息。 -
BPF_PROG_TYPE_CGROUP_SKB
:该程序在入口和出口处过滤单个数据包,并可以接受或拒绝数据包。 -
BPF_PROG_TYPE_CGROUP_SYSCTL
:该程序允许访问系统控制的过滤(sysctl
)。
流解析器(Stream Parser)
流解析器对添加到特殊 eBPF 映射中的一组套接字进行操作。然后 eBPF 程序处理内核在那些套接字上接收或发送的数据包。
RHEL 中提供了以下流解析程序 eBPF 程序:
-
BPF_PROG_TYPE_SK_SKB
:eBPF 程序将来自套接字的数据包解析为单独的消息,并指示内核丢弃这些消息或将其发送给组中的另一个套接字。 -
BPF_PROG_TYPE_SK_MSG
:此程序过滤出口消息。eBPF 程序将数据包解析到单个信息中,并批准或拒绝它们。
SO_REUSEPORT 套接字选择
使用这个套接字选项,您可以绑定多个套接字到相同的 IP 地址和端口。如果没有 eBPF,内核会根据连接散列选择接收套接字。有了 BPF_PROG_TYPE_SK_REUSEPORT
程序,接收套接字的选择是完全可编程的。
dissector 流程
当内核需要处理数据包头,而不需要查看全部协议解码时,会对它们进行 剖析
。例如,这会在 tc
子系统、多路径路由、绑定或者计算数据包哈希时发生。在这种情况下,内核解析数据包的标头,并使用数据包标头中的信息填充内部结构。您可以使用 BPF_PROG_TYPE_FLOW_DISSECTOR
程序替换此内部解析。请注意,您只能在 RHEL 的 eBPF 的 IPv4 和 IPv6 上分离 TCP 和 UDP。
TCP 阻塞控制
您可以使用一组实现 struct tcp_congestion_oops
回调的 BPF_PROG_TYPE_STRUCT_OPS
程序来编写一个自定义的 TCP 阻塞控制算法。通过这种方法的算法可以和内置内核算法一起提供给系统。
带有封装的路由
您可以将以下 eBPF 程序类型之一附加到路由表中作为隧道封装属性的路由:
-
BPF_PROG_TYPE_LWT_IN
-
BPF_PROG_TYPE_LWT_OUT
-
BPF_PROG_TYPE_LWT_XMIT
这样的 eBPF 程序的功能仅限于特定的隧道配置,它不允许创建通用封装或封装解决方案。
套接字查找
要绕过 bind
系统调用的限制,请使用 BPF_PROG_TYPE_SK_LOOKUP
类型的 eBPF 程序。此类程序可以为新传入的 TCP 连接选择侦听套接字,或为 UDP 数据包选择一个未连接的套接字。