快速上手 超XDP入门 – blog.masa23.jp

快速上手 超XDP入门 – blog.masa23.jp

概要这是一个快速推动你体验XDP的文章。因此,eBPF等的详细说明将被省略。

XDP是一个框架,用于在Linux内核网络栈的初始阶段处理数据包,并允许使用eBPF直接将程序插入网络接口卡(NIC)。

通过在Linux内核的网络栈的初始阶段操作数据包,它可以比iptables等过滤器更快地处理数据。

简单来说,你可以理解为可以将XDP程序通过eBPF附加到NIC接口上。

环境构建本文以Ubuntu 22.04为基础。为了控制XDP的NIC通信,建议在虚拟机等环境中进行测试。

安装必要的软件包

$ sudo apt install build-essential clang llvm gcc-multilib libbpf-dev

构建eBPF(XDP)使用clang而非gcc,并且需要安装libbpf-dev以获取ebpf用的头文件。

gcc-multilib稍后在使用asm/types.h时是必要的。

现在就来试试 彻底丢弃所有数据包确认接口首先查看接口上是否有通信发生

$ sudo tcpdump -i eth0 -n

通常,如果连接到交换机等,应该会看到ARP等信息。如果没有任何通信发生,请从外部ping或其他方式发送数据包以确认。

准备XDP程序使用XDP丢弃所有通信

xdp.c#include

#include

SEC("xdp")

int xdp_drop(struct xdp_md *ctx) {

return XDP_DROP;

}

char _license[] SEC("license") = "MIT";

#include

提供Linux的BPF(伯克利数据包过滤器)相关功能的头文件#include

提供编写eBPF程序所需的辅助函数的头文件SEC("xdp")

宏定义指定接下来定义的函数类型为eBPF程序指定"xdp"意味着接下来定义的函数为XDP程序int xdp_drop(struct xdp_md *ctx)

每次接收到数据包时调用xdp_drop。在此程序中,我们使用return XDP_DROP;丢弃所有数据包。char _license[] SEC("license") = "MIT";

eBPF程序需要指定许可证才能执行如果执行由GPL派生的函数,则必须使用GPL,否则将报错构建执行构建

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

通过将target设置为bpf来构建,将其作为eBPF用对象来构建。

附加到接口附加到接口

$ sudo ip link set dev eth0 xdp obj xdp.o sec xdp

确认数据包$ sudo tcpdump -i eth0 -n

应该没有ARP、ICMP等数据包到达。XDP的处理在tcpdump之前,因此无法通过tcpdump观察到数据包。

分离XDP$ sudo ip link set dev eth0 xdp off

至此,丢弃所有数据包的XDP程序被分离,通信应该能够恢复。

更深入地查看(丢弃ICMP)仅仅丢弃所有数据包对于ip link set down dev eth0来说实在没有太大区别,因此我们尝试使用XDP仅丢弃ICMP数据包。

xdp.c#include

#include

#include

#include

#include

#include

SEC("xdp")

int xdp_drop_icmp(struct xdp_md *ctx) {

void *data_end = (void *)(unsigned long)ctx->data_end;

void *data = (void *)(unsigned long)ctx->data;

// 获取以太网头指针

struct ethhdr *eth = data;

if ((void *)eth + sizeof(*eth) > data_end)

return XDP_PASS;

// 检查以太网帧是否包含IP数据包

if (eth->h_proto != htons(ETH_P_IP))

return XDP_PASS;

// 获取IP头指针

struct iphdr *ip = data + sizeof(*eth);

if ((void *)ip + sizeof(*ip) > data_end)

return XDP_PASS;

// 检查协议是否为ICMP

if (ip->protocol != IPPROTO_ICMP)

return XDP_PASS;

// 获取ICMP头指针

struct icmphdr *icmp = (void *)ip + sizeof(*ip);

if ((void *)icmp + sizeof(*icmp) > data_end)

return XDP_PASS;

// 丢弃ICMP数据包

return XDP_DROP;

}

char _license[] SEC("license") = "MIT";

我们可以通过ctx提取数据包的指针等信息来进行处理。

data指向数据包数据的指针data_enddata的结束指针struct ethhdr *eth = data;

if ((void *)eth + sizeof(*eth) > data_end)

return XDP_PASS;

将data指针赋值给以太网头,以便获取以太网头指针。如果eth的指针加上以太网头大小超过data_end,说明数据包大小小于以太网头大小,这时将不会处理并将其传递给内核。

if (eth->h_proto != htons(ETH_P_IP))

return XDP_PASS;

检查以太网头,确保h_proto为ETH_P_IP(IPv4协议 0x0800)。htons是将主机字节序(主机的字节顺序)转换为通信数据包的字节序(网络字节顺序)的宏。htons(主机到网络短整数)

如果以太网包中包含的协议不是IPv4,则将其XDP_PASS并传递给内核。

struct iphdr *ip = data + sizeof(*eth);

if ((void *)ip + sizeof(*ip) > data_end)

return XDP_PASS;

获取IP头指针。

将data(初始指针)加上以太网头的大小得到以太网头的结束位置,此时ip头指针应为IP头的起始指针。

同时,在这里检查ip头指针的大小是否超过data_end,如果超过则表示不是ip头,返回XDP_PASS并将其传递给内核。

if (ip->protocol != IPPROTO_ICMP)

return XDP_PASS;

检查ip数据包中包含的协议是否为IPPROTO_ICMP(1)。如果是其他协议,则XDP_PASS并传递给内核。

struct icmphdr *icmp = (void *)ip + sizeof(*ip);

if ((void *)icmp + sizeof(*icmp) > data_end)

return XDP_PASS;

获取ICMP头指针。从ip头起始指针进一项加上ip头的大小。在这里也再检查一遍是否超过data_end。

到这里为止,未被XDP_PASS的包就是ICMP包,因此可以使用XDP_DROP丢弃数据包达到目的。

注意:在IPPROTO_ICMP时直接丢弃也是合理的选择。最后虽然必须用C语言编写可能会感觉稍微复杂,但实际操作下来发现其实非常简单就可以实现。

下一步 (2024/09/17 更新)快速上手 超XDP入门 Part2 (eBPF Map篇)

🖌️ 相关文章

Python中的append
日博365在线

Python中的append

📅 10-22 👁️ 9087
鲸鱼怎么生宝宝
365bet足球网投

鲸鱼怎么生宝宝

📅 01-13 👁️ 8802
触手储物柜2下载
365bet足球网投

触手储物柜2下载

📅 10-04 👁️ 2707