14、深入理解Express Data Path(XDP)

深入理解Express Data Path(XDP)

1. Express Data Path(XDP)简介

Express Data Path(XDP)是Linux网络数据路径中一种安全、可编程、高性能且与内核集成的数据包处理器。当NIC驱动接收到数据包时,它会执行BPF程序,从而能在尽可能早的时间点对收到的数据包做出决策,如丢弃、修改或允许其继续传输。

XDP程序之所以快速,不仅在于其执行点,还与以下设计决策有关:
- 数据包处理过程中无需进行内存分配。
- 仅处理线性、未分段的数据包,并拥有数据包的起始和结束指针。
- 不访问完整的数据包元数据,因此这类程序接收的输入上下文是 xdp_buff 类型,而非之前提到的 sk_buff 结构体。
- 作为eBPF程序,XDP程序的执行时间是有界的,这意味着它们在网络管道中的使用成本是固定的。

需要注意的是,XDP并非内核旁路机制,它旨在与其他内核组件和Linux内部安全模型集成。

xdp_buff 结构体用于向使用XDP框架提供的直接数据包访问机制的BPF程序呈现数据包上下文,可以将其视为 sk_buff 的“轻量级”版本。二者的区别在于, sk_buff 还持有并允许操作数据包的元数据(如协议、标记、类型),这些元数据仅在网络管道的更高级别可用。 xdp_buff 创建早且不依赖其他内核层,这是使用XDP获取和处理数据包更快的原因之一;另一个原因是, xdp_buff 不像使用 sk_buff 的程序类型那样持有对路由、流量控制钩子或其他类型数据包元数据的引用。

2. XDP程序概述

XDP程序的主要功能是对收到的数据包进行判断,然后可以编辑数据包内容或返回一个结果代码。结果代码用于确定数据包的后续操作,例如丢弃数据包、通过同一接口传输数据包或将其传递给网络栈的其余部分。此外,为了与网络栈协作,XDP程序可以推送和提取数据包的头部。例如,如果当前内核不支持某种封装格式或协议,XDP程序可以对其进行解封装或转换协议,并将结果发送给内核进行处理。

XDP程序通过 bpf 系统调用进行控制,并使用 BPF_PROG_TYPE_XDP 程序类型进行加载。执行驱动钩子会执行BPF字节码。

编写XDP程序时,需要理解其运行的上下文,也称为操作模式。

3. XDP操作模式

XDP有三种操作模式,以适应不同的测试需求、供应商的定制硬件以及没有定制硬件的通用内核。具体如下:

3.1 Native XDP

这是默认模式。在此模式下,XDP BPF程序直接在网络驱动的早期接收路径中运行。使用此模式时,需要检查驱动是否支持。可以通过以下命令在给定内核版本的源代码树中进行检查:

# 克隆linux-stable仓库
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git linux-stable
# 切换到当前内核版本的标签
cd linux-stable
git checkout tags/v4.18
# 检查可用的驱动
git grep -l XDP_SETUP_PROG drivers/

输出示例如下:

drivers/net/ethernet/broadcom/bnxt/bnxt_xdp.c
drivers/net/ethernet/cavium/thunder/nicvf_main.c
drivers/net/ethernet/intel/i40e/i40e_main.c
drivers/net/ethernet/intel/ixgbe/ixgbe_main.c
drivers/net/ethernet/intel/ixgbevf/ixgbevf_main.c
drivers/net/ethernet/mellanox/mlx4/en_netdev.c
drivers/net/ethernet/mellanox/mlx5/core/en_main.c
drivers/net/ethernet/netronome/nfp/nfp_net_common.c
drivers/net/ethernet/qlogic/qede/qede_filter.c
drivers/net/netdevsim/bpf.c
drivers/net/tun.c
drivers/net/virtio_net.c

由此可见,内核4.18支持以下驱动:
- Broadcom NetXtreme - C/E网络驱动bnxt
- Cavium thunderx驱动
- Intel i40驱动
- Intel ixgbe和ixgvevf驱动
- Mellanox mlx4和mlx5驱动
- Netronome网络流处理器
- QLogic qede NIC驱动
- TUN/TAP
- Virtio

3.2 Offloaded XDP

在此模式下,XDP BPF程序直接卸载到NIC中,而不是在主机CPU上执行。通过将执行从CPU转移,此模式相比Native XDP具有更高的性能提升。

可以使用之前克隆的内核源代码树,通过查找 XDP_SETUP_PROG_HW 来检查4.18版本中哪些NIC驱动支持硬件卸载:

git grep -l XDP_SETUP_PROG_HW drivers/

输出示例如下:

include/linux/netdevice.h
866:    XDP_SETUP_PROG_HW,
net/core/dev.c
8001:           xdp.command = XDP_SETUP_PROG_HW;
drivers/net/netdevsim/bpf.c
200:    if (bpf->command == XDP_SETUP_PROG_HW && !ns->bpf_xdpoffload_accept) {
205:    if (bpf->command == XDP_SETUP_PROG_HW) {
560:    case XDP_SETUP_PROG_HW:
drivers/net/ethernet/netronome/nfp/nfp_net_common.c
3476:   case XDP_SETUP_PROG_HW:

这表明只有Netronome网络流处理器(nfp)支持硬件卸载,能够同时在Native XDP和Offloaded XDP两种模式下运行。

3.3 Generic XDP

这是为希望编写和运行XDP程序,但没有Native或Offloaded XDP功能的开发人员提供的测试模式。自内核版本4.12起开始支持Generic XDP。例如,可以在veth设备上使用此模式,后续示例将使用该模式展示XDP的功能,而无需购买特定硬件。

4. 数据包处理器

XDP数据包处理器是执行BPF程序处理XDP数据包,并协调它们与网络栈之间交互的内核组件。它直接处理NIC呈现的接收(RX)队列中的数据包,确保数据包可读可写,并允许以数据包处理器操作的形式附加后处理裁决。可以在运行时对数据包处理器进行原子程序更新和加载新程序,而不会中断网络服务和相关流量。

XDP在运行时可以使用“忙轮询”模式,该模式会为每个RX队列预留CPU,避免上下文切换,使数据包到达时能立即响应,不受IRQ亲和性的影响;另一种模式是“中断驱动”模式,该模式不预留CPU,而是通过中断作为事件媒介通知CPU处理新事件,同时CPU仍可进行正常处理。

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:#2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([RX数据包]):::startend --> B(数据包处理器):::process
    B --> C{BPF程序}:::decision
    C -->|XDP_DROP| D(丢弃数据包):::process
    C -->|XDP_TX| E(通过同一接口转发):::process
    C -->|XDP_REDIRECT| F(重定向到其他NIC或BPF cpumap):::process
    C -->|XDP_PASS| G(传递给网络栈):::process
    C -->|XDP_ABORTED| H(因错误丢弃数据包):::process
    D --> I([结束]):::startend
    E --> I
    F --> I
    G --> I
    H --> I

5. XDP结果代码(数据包处理器操作)

数据包处理器对数据包做出决策后,会使用以下五种返回代码之一来指示网络驱动如何处理数据包:
| 结果代码 | 操作 | 说明 |
| ---- | ---- | ---- |
| XDP_DROP | 丢弃数据包 | 在驱动的最早RX阶段丢弃数据包,将其回收到刚“到达”的RX环形队列中。尽早丢弃数据包对于缓解拒绝服务(DoS)攻击至关重要,可减少CPU处理时间和功耗。 |
| XDP_TX | 转发数据包 | 可以在修改数据包前后进行转发,即将收到的数据包页面通过同一NIC发送出去。 |
| XDP_REDIRECT | 重定向数据包 | 与XDP_TX类似,能够传输XDP数据包,但通过另一个NIC或BPF cpumap进行。在使用BPF cpumap的情况下,NIC接收队列上服务XDP的CPU可以继续处理新数据包,将当前数据包推送到远程CPU进行上层内核栈处理。 |
| XDP_PASS | 传递数据包 | 将数据包传递给正常的网络栈进行处理,相当于没有XDP时的默认数据包处理行为。可以通过以下两种方式实现:
- 正常接收:分配元数据( sk_buff ),将数据包接收到栈上,并将其导向另一个CPU进行处理,允许与用户空间进行原始接口交互。此过程可以在修改数据包前后进行。
- 通用接收卸载(GRO):可以接收大数据包并合并同一连接的数据包,处理后最终将数据包通过“正常接收”流程传递。 |
| XDP_ABORTED | 代码错误 | 表示eBPF程序出错,导致数据包被丢弃。这不是一个功能正常的程序应使用的返回代码,例如程序进行除零操作时会返回此代码。其值始终为零,会通过 trace_xdp_exception 跟踪点,可用于检测程序的异常行为。 |

这些操作代码在 linux/bpf.h 头文件中定义如下:

enum xdp_action {
    XDP_ABORTED = 0,
    XDP_DROP,
    XDP_PASS,
    XDP_TX,
    XDP_REDIRECT,
};

6. 使用iproute2的ip命令加载XDP程序

大多数Linux机器上的 ip 命令(来自iproute2)可以作为前端来加载编译为ELF文件的XDP程序,并且完全支持映射、映射重定位、尾调用和对象固定。

加载XDP程序可以表示为对现有网络接口的配置,因此加载器作为 ip link 命令的一部分实现,该命令用于配置网络设备。加载XDP程序的语法如下:

# ip link set dev eth0 xdp obj program.o sec mysection

下面对该命令的参数进行详细分析:
- ip :调用 ip 命令。
- link :配置网络接口。
- set :更改设备属性。
- dev eth0 :指定要操作并加载XDP程序的网络设备。
- xdp obj program.o :从名为 program.o 的ELF文件(对象)中加载XDP程序。 xdp 部分表示系统在可用时使用原生驱动,否则回退到通用模式。可以使用更具体的选择器来强制使用某种模式:
- xdpgeneric :使用通用XDP。
- xdpdrv :使用原生XDP。
- xdpoffload :使用卸载XDP。
- sec mysection :指定ELF文件中包含要使用的BPF程序的节名称为 mysection ;如果未指定该参数,则使用名为 prog 的节;如果程序中未指定节,则在 ip 调用中必须指定 sec .text

6.1 实际示例

假设系统中有一个运行在端口8000的Web服务器,需要通过禁止所有TCP连接来阻止对该服务器面向公共的NIC上页面的任何访问。具体操作步骤如下:
1. 启动Web服务器 :如果还没有Web服务器,可以使用Python 3启动一个:

$ python3 -m http.server

启动后,可以使用 ss 命令查看开放的端口:

$ ss -tulpn
Netid  State      Recv-Q Send-Q Local Address:Port   Peer Address:Port
tcp    LISTEN     0      5      *:8000                *:*

可以看到Web服务器绑定到了所有接口的8000端口,任何能够访问公共接口的外部调用者都可以查看其内容。
2. 检查网络接口 :使用 ip a 命令检查运行HTTP服务器的机器上的网络接口:

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 02:1e:30:9c:a3:c0 brd ff::ff:ff:ff:ff:ff
    inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic enp0s3
       valid_lft 84964sec preferred_lft 84964sec
    inet6 fe80::1e:30ff:fe9c:a3c0/64 scope link
       valid_lft forever preferred_lft forever
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:0d:15:7d brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.11/24 brd 192.168.33.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe0d:157d/64 scope link
       valid_lft forever preferred_lft forever

该机器有三个接口,网络拓扑如下:
- lo :回环接口,用于内部通信。
- enp0s3 :管理网络层,管理员将使用此接口连接到Web服务器进行操作。
- enp0s8 :面向公共的接口,需要隐藏Web服务器。
3. 检查开放端口 :在另一个可以访问该服务器网络接口(IPv4地址为192.168.33.11)的服务器上,使用 nmap 命令检查开放端口:

# nmap -sS 192.168.33.11
Starting Nmap 7.70 ( https://nmap.org ) at 2019-04-06 23:57 CEST
Nmap scan report for 192.168.33.11
Host is up (0.0034s latency).
Not shown: 998 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
8000/tcp open  http-alt

可以看到端口8000是开放的,需要对其进行阻塞。
4. 编写XDP程序 :创建一个名为 program.c 的源文件,内容如下:

#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <linux/ip.h>

#define SEC(NAME) __attribute__((section(NAME), used))

SEC("mysection")
int myprogram(struct xdp_md *ctx) {
  int ipsize = 0;
  void *data = (void *)(long)ctx->data;
  void *data_end = (void *)(long)ctx->data_end;
  struct ethhdr *eth = data;
  struct iphdr *ip;

  ipsize = sizeof(*eth);
  ip = data + ipsize;
  ipsize += sizeof(struct iphdr);
  if (data + ipsize > data_end) {
    return XDP_DROP;
  }

  if (ip->protocol == IPPROTO_TCP) {
    return XDP_DROP;
  }
  return XDP_PASS;
}

该程序的主要逻辑是提取IPv4层,检查地址空间是否越界,若越界则丢弃数据包;对于TCP数据包,直接丢弃,其他数据包则允许通过。
5. 编译程序 :使用Clang将 program.c 编译为ELF文件 program.o

$ clang -O2 -target bpf -c program.c -o program.o

由于BPF ELF二进制文件不依赖平台,因此可以在目标机器之外进行编译。
6. 加载XDP程序 :在运行Web服务器的机器上,使用 ip 命令将 program.o 加载到面向公共的网络接口 enp0s8 上:

# ip link set dev enp0s8 xdp obj program.o sec mysection
  1. 检查程序是否加载成功 :使用 ip a show enp0s8 命令检查网络接口:
# ip a show enp0s8
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdpgeneric/id:32
    qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:0d:15:7d brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.11/24 brd 192.168.33.255 scope global enp0s8
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe0d:157d/64 scope link
       valid_lft forever preferred_lft forever

输出中 MTU 后面显示的 xdpgeneric/id:32 表明使用的驱动是 xdpgeneric ,并且程序已成功加载。

7. 总结与注意事项

7.1 总结

通过上述内容,我们对Express Data Path(XDP)有了较为全面的了解。XDP作为Linux网络数据路径中的高性能数据包处理器,具有安全、可编程和与内核集成的特点。它可以在NIC驱动接收到数据包的最早时间点做出决策,对数据包进行处理。

XDP有三种操作模式,分别是Native XDP、Offloaded XDP和Generic XDP,每种模式都有其适用场景。数据包处理器负责执行BPF程序并协调与网络栈的交互,而XDP结果代码则用于指示网络驱动如何处理数据包。此外,我们还学习了如何使用iproute2的ip命令加载XDP程序,并通过实际示例展示了其具体应用。

7.2 注意事项

在使用XDP时,需要注意以下几点:
- 驱动支持 :在使用Native XDP和Offloaded XDP模式时,需要确保NIC驱动支持相应的功能。可以通过前面介绍的命令进行检查。
- 代码正确性 :编写XDP程序时,要确保代码的正确性,避免出现除零等错误,以免返回 XDP_ABORTED 导致数据包被丢弃。
- 性能考虑 :不同的操作模式和处理逻辑会对性能产生影响,需要根据实际需求选择合适的模式和优化程序逻辑。

8. 扩展应用与展望

8.1 扩展应用

XDP的应用场景非常广泛,除了前面提到的阻止TCP连接访问Web服务器外,还可以用于以下方面:
- DDoS攻击防护 :利用 XDP_DROP 结果代码,在数据包到达网络栈之前尽早丢弃恶意数据包,减少CPU处理负担,有效抵御DDoS攻击。
- 流量监控与分析 :通过XDP程序对数据包进行捕获和分析,获取网络流量的统计信息,如流量大小、协议分布等,为网络管理和优化提供依据。
- 负载均衡 :使用 XDP_REDIRECT 结果代码将数据包重定向到不同的NIC或CPU,实现负载均衡,提高网络处理能力。

8.2 展望

随着网络技术的不断发展,XDP作为一种高性能的数据包处理技术,有望在未来得到更广泛的应用和发展。例如,与新兴的网络技术(如SDN、NFV)相结合,实现更灵活、智能的网络管理;进一步优化性能,支持更高的网络带宽和数据包处理速率等。

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:#2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([当前网络]):::startend --> B{是否使用XDP}:::decision
    B -->|是| C(性能提升):::process
    B -->|否| D(考虑使用XDP):::process
    C --> E(支持更多应用场景):::process
    D --> F(评估需求和驱动支持):::process
    F -->|满足条件| G(编写和加载XDP程序):::process
    F -->|不满足条件| H(选择其他解决方案):::process
    G --> C

总之,XDP为网络数据包处理提供了一种高效、灵活的解决方案,通过深入理解和合理应用XDP,可以提升网络性能和安全性,满足不同的网络需求。希望本文能够帮助读者更好地掌握XDP技术,并在实际项目中发挥其优势。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值