eBPF使用XDP(eXpress Data Path)解析数据包是eBPF技术中一个非常重要的应用场景。XDP是Linux内核中一个高性能数据包处理机制,它允许开发者在网络报文进入内核协议栈之前,对其进行处理和操作。通过在数据包的接收路径上运行eBPF程序,XDP提供极低的延迟和高效的网络流量处理能力。
什么是 XDP? XDP全称是eXpress Data Path,它是Linux内核中一个高性能、高吞吐量的数据包处理框架。XDP的工作原理是将eBPF程序附加到网卡驱动的接收路径(RX Path)的最底层,在数据包进入内核协议栈之前对其进行处理。
XDP的关键特性:
低延迟、高性能:XDP在驱动层直接处理数据包,避免了数据包进入内核协议栈和用户态的额外开销。
灵活性:通过eBPF程序实现定制化的数据包处理逻辑。
高吞吐量:适用于高性能网络应用,例如DDoS防御、负载均衡、数据包过滤等。
eBPF与XDP的结合 eBPF程序可以被加载到XDP hook上,用来分析、修改或丢弃数据包。以下是一些常见的操作:
数据包解析:解析数据包头部(如 Ethernet、IP、TCP/UDP 等协议头部)。
数据包过滤:根据协议或字段过滤特定的数据包(如丢弃不需要的流量)。
负载均衡:将数据包转发到不同的目的地。
DDoS防护:识别和丢弃恶意流量。
通过XDP,eBPF程序可以在数据包接收的最早阶段进行处理,从而提供极高的效率。
使用 XDP 解析数据包的实践步骤 以下是一个使用XDP解析数据包的完整流程: 1.编写eBPF程序 eBPF程序需要用C语言编写,然后通过编译工具编译成字节码加载到内核,这里借助libbpf进行编译。 以下是一个简单的eBPF程序示例,用于解析以太网帧和IPv4数据包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include "vmlinux.h" #include <bpf/bpf_helpers.h> #include <bpf/bpf_endian.h> #define ETH_P_IP 0x0800 SEC("xdp" ) int xdp_packet_parser (struct xdp_md *ctx) { void *data = (void *)(long )ctx->data; void *data_end = (void *)(long )ctx->data_end; struct ethhdr *eth = data; if ((void *)(eth + 1 ) > data_end) { return XDP_ABORTED; } if (eth->h_proto == bpf_htons(ETH_P_IP)) { struct iphdr *ip = (void *)(eth + 1 ); if ((void *)(ip + 1 ) > data_end) { return XDP_ABORTED; } if (ip->protocol == IPPROTO_TCP) { struct tcphdr *tcp = (void *)((void *)ip + (ip->ihl * 4 )); if ((void *)(tcp + 1 ) > data_end) { return XDP_ABORTED; } bpf_printk("TCP src port: %d, dst port: %d" , bpf_ntohs(tcp->source), bpf_ntohs(tcp->dest)); } else if (ip->protocol == IPPROTO_UDP) { struct udphdr *udp = (void *)((void *)ip + (ip->ihl * 4 )); if ((void *)(udp + 1 ) > data_end) { return XDP_ABORTED; } bpf_printk("UDP src port: %d, dst port: %d" , bpf_ntohs(udp->source), bpf_ntohs(udp->dest)); } } return XDP_PASS; } char LICENSE[] SEC("license" ) = "GPL" ;
2.编写用户态程序 使用libbpf将eBPF程序加载到XDP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <errno.h> #include <unistd.h> #include <sys/resource.h> #include <net/if.h> #include <bpf/libbpf.h> #include <bpf/bpf.h> #include "xdp.skel.h" static volatile sig_atomic_t exiting = 0 ;static void sig_int (int signo) { exiting = 1 ; } static int libbpf_print_fn (enum libbpf_print_level level, const char *format, va_list args) { return vfprintf (stderr , format, args); } int main (int argc, char **argv) { struct xdp_bpf *skel ; int ifindex; int err; if (argc != 2 ) { fprintf (stderr , "Usage: %s <ifname>\n" , argv[0 ]); return 1 ; } libbpf_set_print(libbpf_print_fn); const char *ifname = argv[1 ]; ifindex = if_nametoindex(ifname); if (ifindex == 0 ) { fprintf (stderr , "Invalid interface name %s\n" , ifname); return 1 ; } skel = xdp_bpf__open(); if (!skel) { fprintf (stderr , "Failed to open BPF skeleton\n" ); return 1 ; } err = xdp_bpf__load(skel); if (err) { fprintf (stderr , "Failed to load and verify BPF skeleton: %d\n" , err); goto cleanup; } err = xdp_bpf__attach(skel); if (err) { fprintf (stderr , "Failed to attach BPF skeleton: %d\n" , err); goto cleanup; } skel->links.xdp_packet_parser = bpf_program__attach_xdp(skel->progs.xdp_packet_parser, ifindex); if (!skel->links.xdp_packet_parser) { err = -errno; fprintf (stderr , "Failed to attach XDP program: %s" , strerror(errno)); goto cleanup; } if (signal(SIGINT, sig_int) == SIG_ERR) { fprintf (stderr , "can't set signal handler: %s\n" , strerror(errno)); goto cleanup; } printf ("Successfully attached XDP program to interface %s\n" , ifname); while (!exiting) { fprintf (stderr , "." ); sleep(1 ); } cleanup: xdp_bpf__destroy(skel); return -err; }
3.编译运行 我这里的编译是将两个文件丢到libbpf-bootstrap项目的examples/c中编译的,需要在Makefile中添加xdp程序的文件名,然后执行make <xdp文件名>生成可执行文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ make xdp BPF .output/xdp.bpf.o GEN-SKEL .output/xdp.skel.h CC .output/xdp.o BINARY xdp $ sudo ./xdp eno1 libbpf: CO-RE relocating [22] struct udphdr: found target candidate [12168] struct udphdr in [vmlinux] libbpf: prog 'xdp_packet_parser': relo #8: <byte_off> [22] struct udphdr.source (0:0 @ offset 0) libbpf: prog 'xdp_packet_parser': relo #8: matching candidate #0 <byte_off> [12168] struct udphdr.source (0:0 @ offset 0) libbpf: prog 'xdp_packet_parser': relo #8: patched insn #47 (LDX/ST/STX) off 0 -> 0 libbpf: prog 'xdp_packet_parser': relo #9: <byte_off> [22] struct udphdr.dest (0:1 @ offset 2) libbpf: prog 'xdp_packet_parser': relo #9: matching candidate #0 <byte_off> [12168] struct udphdr.dest (0:1 @ offset 2) libbpf: prog 'xdp_packet_parser': relo #9: patched insn #49 (LDX/ST/STX) off 2 -> 2 libbpf: map 'xdp_bpf.rodata': created successfully, fd=3 Successfully attached XDP program to interface eno1 .....................................
另起一个终端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ sudo cat /sys/kernel/tracing/trace_pipe <idle>-0 [005] ..s21 4494866.066820: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.068955: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.072233: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.075536: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.078468: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.081657: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.084106: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.086325: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.088399: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.090771: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.093293: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.099956: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.102167: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.104241: bpf_trace_printk: TCP src port: 57771, dst port: 22 <idle>-0 [005] ..s21 4494866.105401: bpf_trace_printk: TCP src port: 57771, dst port: 22
XDP的返回动作 XDP程序的返回值决定了如何处理数据包。常见的动作包括: | 返回值 | 含义 | |:—-|:—-| | XDP_PASS | 允许数据包继续进入内核协议栈处理。 | | XDP_DROP | 丢弃数据包,不进一步处理。 | | XDP_TX | 将数据包直接发回到接收的网络接口(环回)。 | | XDP_REDIRECT | 将数据包重定向到另一个网络接口或用户态处理。 | | XDP_ABORTED | 表示程序发生错误,数据包处理失败。 |
XDP加载的几种模式 在eBPF框架中,XDP(eXpress Data Path)程序可以以不同的模式加载到网络接口上。这些模式决定了XDP程序的运行位置和性能表现。 | 模式 | 描述 | 优点 | 适用场景 | |:—-|:—-|:—-|:—-| | XDP Native | XDP程序直接加载到网卡驱动的最低层(数据包接收的最初阶段),绕过内核协议栈 | 性能最高,因为数据包处理在驱动层完成,最大限度减少了延迟和 CPU 消耗 | 高性能场景:DDoS 防护、负载均衡等 | | XDP Generic | XDP程序运行在内核协议栈的XDP模拟层,不依赖网卡驱动的支持 | 兼容性更好,即使网卡驱动不支持XDP,也可以运行;便于开发和调试 | 测试开发、实验环境,或硬件不支持XDP时使用 | | XDP Offload | XDP程序卸载到支持XDP的智能网卡(SmartNIC)进行运行,由网卡硬件处理XDP操作 | 减少主机CPU负载,进一步提升性能;由硬件完成数据包处理,可以释放更多的计算资源给其他任务 | 大流量场景、高端硬件环境 |
可以使用ip命令加载XDP字节码到相应位置,这里用上面编写的eBPF程序,用clang命令编译:
1 $ clang -O2 -target bpf -c xdp.bpf.c -o xdp.bpf.o
可能会报找不到vmlinux.h的错误,使用bpftool在当前目录生成一个即可:
1 $ bpftool btf dump file /sys/kernel/btf/vmlinux format c > vmlinux.h
1.Native模式加载
1 2 $ sudo ip link set dev eno1 xdpdrv obj xdp.bpf.o sec xdp Error: Underlying driver does not support XDP in native mode.
我这里的驱动不支持,所以会报错。 2.Generic模式加载
1 $ sudo ip link set dev eno1 xdp obj xdp.bpf.o sec xdp
3.Offload模式加载
1 2 3 4 $ sudo ip link set dev eno1 xdpoffload obj xdp.bpf.o sec xdp libbpf: load bpf program failed: Operation not supported libbpf: failed to load program 'xdp_packet_parser' libbpf: failed to load object 'xdp.bpf.o'
也是网卡不支持。 4.查看XDP加载情况
1 2 3 4 5 $ sudo ip link show dev eno1 2: eno1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdpgeneric qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether d8:bb:c1:bb:76:76 brd ff:ff:ff:ff:ff:ff prog/xdp id 4382 tag 625502b268781999 jited <--- altname enp0s31f6
5.卸载XDP
1 $ sudo ip link set dev eno1 xdp off
总结 这一篇中介绍了XDP基本原理和编译加载方式,后面会继续探索XDP的实际用途。
reference https://blog.nsfocus.net/bpf-enable-software-definition-kernel/