eBPF系列2 - XDP
文章目录
XDP是指 eXpress data path
, 基于 ebpf 技术上的 高性能 data path. 其主要的意图是在网络 packet 处理的早期(网卡驱动处, 在数据包到达RX queue之后, 用hook的方式),让用户可以编写 ebpf 程序来进行一些决策。 这个hook的位置早于所有的内存分配时期(sk_buffer),也没有上下文切换,系统调用等开销, 所以对于性能的提升是很明显的。测试数据表明,在普通的硬件上, XDP 可以 dop 大概 2600w packets per cpu.
一个网络包的处理流程大致如下:
- 网络包到达网卡
- 从 NIC queue 拷贝到内存(DMA-backed ring buffer)中
- 网卡驱动使用 NAPI look 触发 soft IRQs
- per cpu 特定线程处理网络包
- 分配 socket_buffer(sk_buffer), 作为网络包的基本数据结构
- kernel 填充 metadata, clone sk_buffer 并且交给上层网络处理层
- IP layer 做校验, netfilter hook 处理
- 如果 netfilter 未 drop 此包,交给更上层网络层处理
- ….
- 最终数据被 copy 到 userspace (recv,read,poll等网络调用获取)
其中, XDP hook 在数据到达 NIC RX queue 之后即触发(上面第二步之后).而 iptables 等的处理(上面第八步之后)非常靠后, 需要分配大量资源来处理网络包.
XDP 程序 attch 到一个网卡的时候,有如下三种模式:
- Native XDP: 网卡驱动支持 XDP, 性能比较高
- Offloaded XDP: 网卡直接加载 XDP 程序,处理过程中不需要CPU参与。这个也需要网卡支持
- Generic XDP: 最通用的模式,加载到内核中作为正常网络路径的一部分
插入的 ebpf 程序可以修改 packet data, 当程序返回时,可以决定这个 packet 后续的处理路径:
XDP_PASS
: 继续按原有路径处理XDP_DROP
: 丢弃.这个可以用来在早期处理DOS攻击,userspace的程序可以分析网络pattern并且不断更新处理策略。性能与易用性兼得。XDP_ABORTED
: 丢弃并抛出异常XDP_TX
: 返回到收到这个 packet 的 NIC 上XDP_REDIRECT
: 重定向到另一个NIC或者到 user space 的 socket (AF_XDP
address family,绕过了正常的网络栈)
Python 示例程序
这个示例程序将发往 port 7999的包改为 7998
main.py
|
|
说明:
-
python 文件用于加载 bpf 程序,真正的 bpf 程序实在 filter.c 中。
-
BPF 程序被 attch 到了 lookback 接口上
-
trace_print
打印出 bpf 程序处理过程中的输出 -
remove_xdp发生在程序退出时,用户从 lookback 接口上移除加载的 xdp 程序
filter.c:
|
|
说明:
xdp_md
结构体中包含了 packet data- bpf_trace_printk 的输出会在 main.py 里的 trace_print 中反应出来
- 指针方式确保了我们时原地修改了 packet data
AF_XDP
上面提到了 AF_XDP,他可以让XDP 程序可以将一个 packet 重定向到 user space 的内存空间里.
有一个比较接近的案例是 AF_PACKET/DPDK, 他们是通过在kernel和userspace共享内存来提高性能。而AF_XDP直能绕过很多 kernel stack 的步骤, 用最快的方式将 packet 传到 userspace.另外,它也提供了一些 zero-copy等功能。
具体实现上来说,有一些比较独特的数据结构来的达成此目的。创建的时候,仍然是用 socket
API, 使用 AF_XDP
address family. 然后在 userspace的内存中创建一个叫 UMEM
的 array, 它是一块连续的内存空间,被分成相等的 frames
,每一个 frame 都可以用来存储一个 packet.
每个 frame 的 index 被叫做 descriptor
, 程序创建一个叫 fill queue
的 circular buffer, 并通过 mmap
映射到 userspace. 之后程序可以要求 kernel 将一个 packet 放到一个指定的 frame中(将 frame 的 descriptor 放到 fill queue 中)
receive queue
: 类似 fill Q 的创建和映射方式。当一个 packet 被放到一个 frame 之后,他的 descriptor 会被放到 receive queue 中,poll
这样的API就是 wait 的 receive queue.
上面是接收端的情况。发送端的情况类似。 一个 transmit queue
, 通过将 descriptor 放到 tranmit queue表示要发送,complete queue
用于存放已经发送完的 packet .
通过这个数据结构,就实现了userspace 和 kernel 的 zero-copy. UMEM 数组还可以被多个进程共享,fill queue 和 compeltion queue 只能有一个,但 receive queue 和 tranmit queue 每个进程都需要有自己的,
AF_XDP最终的目的就是让user space code 可以高效地处理 packet, 尽量少的 kernel overhead.
Links
- Express Data Path
- Writing an XDP Network Filter with eBPF
- AF_XDP and its potential | PANTHEON.tech
- Accelerating networking with AF_XDP
- New API
- Understanding Linux Network Internals by Christian Benvenuti
- Understanding the Linux Kernel
- Software interrupts and realtime
- eBPF and XDP for Processing Packets at Bare-metal Speed
- What Is XDP/AF_XDP and its potential
文章作者 涯余
上次更新 2020-11-17