可编程网络DataPath
大部分网络数据的最终生产者和消费者都是应用程序,在一个计算机中,网络数据包需要经过网卡 <=> 系统内核 <=> 应用程序,才能完成传输。
Linux 有严格的内核和用户空间隔离,网络数据在内核和应用程序之间的传输需要频繁的进行上下文切换,随之带来额外的CPU cycle 开销。所以为了提升网络性能,在越来越多的SDN场景都采用了kernel-bypass的技术。其中具有代表性就是DPDK,不过DPDK 在带来性能提升的同时,也有一些问题:
- 首先,因为改变了现有操作系统的工作方式,很难与现有操作系统集成
- 因为网络路径中没有了操作系统,相关的网络应用程序需要重新实现之前由操作系统提供的一些功能,例如路由表,4-7层网络协议
- 一些由操作系统提供的熟悉的管理部署工具将不再可用,因为操作系统现在没有相关网络硬件的控制权
- 因为上面的原因带来的复杂性
- 破坏了原有操作系统内核提供了的安全性,这一点在容器场景尤其重要,因为在容器场景中,资源的抽象和隔离主要是由操作系统内核提供的
- 需要消耗1个或者多个CPU核来专门处理网络包
相对于DPDK,XDP具有以下优点
- 无需第三方代码库和许可
- 同时支持轮询式和中断式网络
- 无需分配大页
- 无需专用的CPU
- 无需定义新的安全网络模型
XDP(eXpress Data Path)是近年兴起的网络数据面技术,为Linux内核提供了高性能、可编程的网络数据通路。
不同于kernel-bypass技术,XDP 是在网络包在还未进入网络协议栈之前就处理,所以既没有内核-用户空间的切换开销,又没有保留了操作系统控制网络硬件的能力。
XDP 的基本架构
XDP(eXpress Data Path)提供了一个内核态、高性能、可编程 BPF 包处理框架。
XDP 的处理方式在内核的RX 路径上添加一个早期hook,让用户可以使用eBPF程序控制数据包。该hook 在中断处理之后放置在NIC驱动程序中,并且在网络堆栈本身的所有内存分配之前,因为内存分配可能是一项高成本的操作。由于这种设计,XDP 可以使用商用硬件每秒每核丢弃 2600 万个数据包。
XDP 数据包进程(Packet Processor)包含一个内核组件,该组件通过功能接口直接从驱动程序处理 RX 数据包页(packet-pages),而无需提前分配 skbuff 或软件队列(software queues)。通常,每个 RX 队列分配一个 CPU,但在此模型中,没有加锁 RX 队列,CPU 可以专用于忙轮询或中断模型。BPF 程序执行诸如数据包解析、表查找、创建/管理有状态过滤器、封装/去封装数据包等处理。
XDP 系统由4个主要部分组成:
- XDP driver hook:这是XDP程序的接入点,当网络数据包从硬件中收到时会被执行。
- eBPF virtual machine:执行XDP 程序的字节码,并且JIT 编译到机器码
- BPF maps:key/value store,用来在整个XDP 系统中做数据的交互
- eBPF verifier:在程序加载到内核之前静态的分析、检查代码,以确保代码会Crash 或者损坏运行的内核。
GRO(Generic receive offload):通用receive offload,offload 详见XDP 硬件要求小节。
RPS/RFS(Receive Package Steering / Receive Flow Steering):用以在软件层面实现报文在多个cpu之间的负载均衡以及提高报文处理的缓存命中率。
XDP 的软件要求
Linux kernel 4.8 开始支持XDP,XDP 依赖于eBPF ,所以需求较新的内核支持eBPF,可以参考eBPF 基础架构及使用。
大部分支持 XDP 的驱动都支持在不会引起流量中断(traffic interrupt)的前提下原子地替换运行中的程序。出于性能考虑,支持 XDP 的驱动只允许 attach 一个程序 ,不支持程序链(a chain of programs)。如果有必要的话,可以通过尾调用来对程序进行拆分,以达到与程序链类似的效果。
XDP 的硬件要求
使用XDP 对网卡有一些要求
- 支持多队列的网卡
- 一般的协议通用offload
- TX/RX checksum offload,即校验offload,利用网卡计算校验和,而不是。
- Receive Side Scaling,RSS 即接收端伸缩,是一种网络驱动程序技术,可在多处理器或多处理器核心之间有效分配接收到的网络数据包并处理。
- Transport Segmentation Offload,TSO,即,是一种利用网卡替代CPU对大数据包进行分片,降低CPU负载的技术。
- 最好支持LRO,aRFS
目前越来越多的网卡设备开始支持offload特性,以便提升网络收发和处理的性能。本文所描述的offload特性,主要是指将原本在协议栈中进行的IP分片、TCP分段、重组、checksum校验等操作,转移到网卡硬件中进行,降低系统CPU的消耗,提高处理性能。
XDP 的工作流程及使用
XDP 的工作模式
XDP 总共支持三种工作模式(operation mode):
- xdpdrv
xdpdrv 表示 native XDP(原生 XDP), 意味着 BPF 程序直接在驱动的接收路 径上运行,理论上这是软件层最早可以处理包的位置(the earliest possible point)。这是常规/传统的 XDP 模式,需要驱动实现对 XDP 的支持,目前 Linux 内核中主流的 10G/40G 网卡都已经支持。
- xdpgeneric
xdpgeneric 表示 generic XDP(通用 XDP),用于给那些还没有原生支持 XDP 的驱动进行试验性测试。generic XDP hook 位于内核协议栈的主接收路径(main receive path)上,接受的是 skb 格式的包,但由于 这些 hook 位于 ingress 路 径的很后面(a much later point),因此与 native XDP 相比性能有明显下降。因 此,xdpgeneric 大部分情况下只能用于试验目的,很少用于生产环境。
- xdpoffload
最后,一些智能网卡(例如支持 Netronome’s nfp 驱动的网卡)实现了 xdpoffload 模式 ,允许将整个 BPF/XDP 程序 offload 到硬件,因此程序在网卡收到包时就直接在网卡进行处理。这提供了比native XDP 更高的性能,虽然在这种模式中某些 BPF map 类型和BPF 辅助函数是不能用的。BPF 校验器检测到这种情况时会直接报错,告诉用户哪些东西是不支持的。除了这些不支持的 BPF 特性之外,其他方面与 native XDP 都是一样的。
引入XDP 前的DataPath:
引入XDP 之后的DataPath:包含native 模式、generic 模式和offload 模式
这三种模式 iproute2 都实现了,执行 ip link set dev em1 xdp obj [...] 命令时,内核会先尝试以 native XDP 模 式加载程序,如果驱动不支持再自动回退到 generic XDP 模式。如果显式指定了 xdpdrv 而不是 xdp,那驱动不支持 native XDP 时加载就会直接失败,而不再尝试 generic XDP 模式。
XDP 的工作流程
包含XDP 的Linux 网络栈:
简单的说,XDP 就是在网卡驱动中的hook点,在该hook点处打入eBPF 程序,利用eBPF 的事件驱动来完成网络数据包处理:
- 高级语言程序设计,例如C 来完成。
- 编译成eBPF 字节码,llvm等工具已经支持了将C 语言编译成eBPF 字节码。
- 在加载到内核之前,会交给eBPF verifier 静态的分析代码的安全性。
- 加载到内核。
- 在收到网络包时,通过JIT(Just In Time)编译器翻译成机器指令并执行。
在XDP 程序的结束,需要对packet做出一个结论。结论有4+1 种可能:
- XDP_DROP:直接丢包
- XDP_ABORTED:也是丢包,不过会触发一个eBPF 程序错误,可以通过调试工具查看
- XDP_TX:将处理后的packet 发回给相同的网卡
- XDP_PASS:将处理后的packet 传递给内核协议栈
- XDP_REDIRECT 稍微复杂点,它会需要一个额外的参数来表明Redirect 的目的地,这个额外的参数是在XDP 程序返回之前通过一个helper 函数设置。这种方式使得Redirect 可以非常方便的扩展,增加新的Redirect目的地只需要再增加一个参数值即可。目前Redirect的目的地包含了以下几种可能:
- 将处理后的packet转发给一个不同的网卡,包括了转发给连接虚拟机或者容器的虚拟网卡
- 将处理后的packet转发给一个不同的CPU做进一步处理
- 将处理后的packet转发给一个特定的用户空间socket(AF_XDP),这种方式使得XDP也可以直接bypass网络协议栈,甚至进一步结合zero-copy技术降低包处理的overhead
Hello World
利用eBPF ,嵌入eBPF 网络数据包处理程序至XDP hook 点。
所以重点是如何编写处理网络数据包的程序。
// ping_drop.c
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/ip.h>
#define SEC(NAME) __attribute__((section(NAME), used))
SEC("prog") //prog 作为一个hook ,介于自定义处理程序和XDP之间
int ping_drop(struct xdp_md *ctx)
{
void *data = (void*)(long)ctx->data; //报文数据开始处
void *end = (void*)(long)ctx->data_end; //报文数据结束点
struct ethhdr *eh; //以太头
eh = data;
if (data > end) //这个检测有点多余,一个合格驱动会保证
return XDP_PASS; //data一定是小于end的
if ((void*)(eh+1) > end) //这个检测非常重要,否则在下面读取 eh->h_proto
return XDP_PASS; //的时候,无法通过bpf verifier的验证,程序就无法加载
if (eh->h_proto != __constant_htons(ETH_P_IP)) //不是IP报文,放过
return XDP_PASS;
struct iphdr *iph;
iph = (void*)(eh + 1);
if ((void*)(iph+1) > end) //这里的检测也非常重要,原因同上
return XDP_PASS;
if (iph->protocol == IPPROTO_ICMP) //判断如果是ping报文,丢弃
return XDP_DROP; //返回 XDP_DROP,会导致无法ping通主机,其他如ssh等不受影响
return XDP_PASS;
}
char __license[] SEC("license") = "GPL";
XDP hook 点在网络驱动中,基于eBP 的事件驱动机制,当XDP 收到网络数据包时,我们的处理程序就会被执行。
传入eBPF 处理程序的ctx 其实就是XDP 元数据,没有sk_buff结构,只有一个 struct xdp_md 指针
/* user accessible metadata for XDP packet hook
* new fields must be added to the end of this structure
*/
struct xdp_md {
__u32 data; //数据包开始指针
__u32 data_end; //数据包结束指针
__u32 data_meta; //初始阶段它是一个空闲的内存地址,供XDP程序与其他层交换数据包元数据时使用。
/*
分别是接收数据包接口的索引和对应的RX 队列的索引。当访问这两个值时,BPF 代码会在内核内部重写,
以访问实际持有这些值的内核结构struct xdp_rxq_info。
*/
/* Below access go through struct xdp_rxq_info */
__u32 ingress_ifindex; /* rxq->dev->ifindex */
__u32 rx_queue_index; /* rxq->queue_index */
};
Clang 编译生成对象文件,并加载
[root@dev ~]# clang -Wall -target bpf -c ping_drop.c -o ping_drop.o
然后用iproute2 里面的 ip link 命令加载到某个NIC 上,如ens192
[root@dev ~]# ip link set dev ens192 xdp object ping_drop.o
iproute2 需要打开 HAVE_ELF 这个宏,默认CentOS 7 并没有,需要编译iproute2 并打开。
git clone git://git.kernel.org/pub/pub/scm/network/iproute2/iproute2.git
cd iproute2/
./configure --prefix=/usr
make -j8 && make install
BPF map 和程序作为内核资源只能通过文件描述符访问,其背后是内核中的匿名 inode。如 iproute2,其中的 tc 或 XDP 在准备 环境、加载程序到内核之后最终会退出。在这种情况下,从用户空间也无法访问这些 map 了,而本来这些 map 其实是很有用的,所以内核实现了一个最小内核空间 BPF 文件系统,BPF map 和 BPF 程序 都可以钉到(pin)这个文件系统内,这个过程称为 object pinning(钉住对象)。
挂载BPF FS,允许BPF 程序从虚拟文件系统固定和获取map
mount -t bpf /sys/fs/bpf /sys/fs/bpf
ip link set dev ens192 xdp object ping_drop.o # 之后,在其他节点ping 当前节点就会显示无法ping 通。
ip link set dev ens192 xdp off 之后,ping # 之后,恢复正常。
XDP 的应用
借助XDP/BPF,可以快速、高效、超低副作用的处理网络数据包,可以在以下几个方面应用:
- 防御DDoS 攻击
- CDN 服务调优:CDN 服务突刺分析及处理
- QoS:控制网络传输速率,如 tc
参考
- https://github.com/xdp-project/xdp-tutorial
- https://github.com/iovisor/bpf-docs/blob/master/Express_Data_Path.pdf
- https://zhuanlan.zhihu.com/p/321387418
- https://www.iovisor.org/technology/xdp
- https://docs.cilium.io/en/v1.10/bpf/
- https://arthurchiao.art/blog/cilium-bpf-xdp-reference-guide-zh/
- https://www.kernel.org/doc/html/latest/networking/checksum-offloads.html
- https://blogs.igalia.com/dpino/2019/01/10/the-express-data-path/
- https://cloud.tencent.com/developer/article/1626925
进阶参考
- cilium 容器网络:https://github.com/cilium/cilium
- polycube 网络加速:https://github.com/polycube-network/polycube