摘要
为了提升服务器处理网络流量的性能,涌现了一些技术,XDP就是其中的一种。XDP 是一种 Linux 下的基于 eBPF 实现的内核旁路技术,是一个位于内核协议栈之前的数据包处理器,并拥有将数据包绕过内核协议栈重定向至应用层的能力,因此具有很高的性能,可用于DDoS防御、防火墙、负载均衡等领域。
XDP 架构
XDP 工作在驱动层,位于物理网卡与内核协议栈之间的部分。其支持 3 种工作模式,
分别是:
- Native:在此模式下,XDP BPF 程序运行在网络驱动的早期接收路径上(RX队列),因此,使用该模式时需要网卡驱动程序支持,性能很高。
- Offloaded:在此模式下,XDP BFP程序直接在NIC(Network Interface Controller)中处理数据包,而不使用主机CPU,相比 Native 模式,性能最高,但需要硬件支持。
- Generic:对于网卡或驱动无法支持 Native 或 Offloaded 模式的情况,内核提供了通用的generic模式,运行在协议栈入口处,不需要对驱动做任何修改,此模式性能不如前两者。
其中,Native 模式最为常见,为默认使用的模式。
返回码
类似于 netfilter 框架,其上的 hook 点对数据包的处理有 drop 或者 pass 返回码,XDP 也有一套类似的返回码。
- XDP_DROP:丢弃数据包。
- XDP_PASS:放行数据包,数据包接下来将走到协议栈。
- XDP_TX:转发数据包,将接收到的数据包发送回数据包到达的同一网卡。一般为 echo 响应时将数据包简单修改后发送出去的场景下使用。
- XDP_REDIRECT:数据包重定向,将数据包送到其它网卡发送或者是传给 eBPF 的 map 交给用户程序。
Test
试着编写一个简单的 XDP 代码,丢弃掉目的端口为 6666 的 TCP 报文:
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
/* linux/include/net/xdp.h
struct xdp_rxq_info {
struct net_device *dev;
u32 queue_index;
u32 reg_state;
struct xdp_mem_info mem;
} ____cacheline_aligned;
struct xdp_buff {
void *data;
void *data_end;
void *data_meta;
void *data_hard_start;
unsigned long handle;
struct xdp_rxq_info *rxq;
};
*/
int test(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(struct ethhdr);
ip = data + ipsize;
ipsize += sizeof(struct iphdr);
if (data + ipsize > data_end) {
return XDP_DROP;
}
if (ip->protocol == IPPROTO_TCP) {
struct tcphdr *tcp = (void *)ip + sizeof(*ip);
ipsize += sizeof(struct tcphdr);
if (data + ipsize > data_end) {
return XDP_DROP;
}
if (tcp->dest == ntohs(6666)) {
bpf_trace_printk("drop tcp dst port 6666\n");
return XDP_DROP;
}
}
return XDP_PASS;
}
作为一个 eBPF 程序,当然还是需要一个加载器将其加载到 kernel 中去执行才行,用 python 来实现这个加载器比较方便:
#!/usr/bin/python3
from bcc import BPF
import time
import sys
if len(sys.argv) <= 3:
print("Usage: %s xxx.c func dev" % (sys.argv[0]))
exit(0)
src_file = sys.argv[1]
func = sys.argv[2]
device = sys.argv[3]
bpf = BPF(src_file)
fn = bpf.load_func(func, BPF.XDP)
bpf.attach_xdp(device, fn, 0)
try:
bpf.trace_print()
except KeyboardInterrupt:
pass
bpf.remove_xdp(device, 0)
调用加载器将 XDP 测试程序加载到 eth0 上试试:
[root@localhost ebpf]# ./xdp_load.py xdp_test.c test eth0
然后在另一台机器上给这个网卡发包:
C:\Users\kevin>telnet 172.29.10.208 6666
It's work on my machine!
这个只是最简单的应用,下次再学习一下把数据重定向到应用层。
附
最后还是附上一下源码:https://github.com/Fireplusplus/Linux/tree/master/ebpf