xdp 是一个用 Rust 语言(基于 libbpf-rs 库)编写的示例。它会附加到网络设备的入站路径上,记录每个数据包的大小,并返回 XDP_PASS 以允许数据包向上传递到内核的网络协议栈。

前置知识

运行

本文将该代码移植为 C 语言版本。

example/c 文件夹下,新建 eBPF 程序 xdppass.bpf.c,用户态程序 xdppass.c。同时需要在 Makefile 文件中添加该 XDP 程序 xdppass

1
2
3
4
5
$ ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:01:02:03:04:05 brd ff:ff:ff:ff:ff:ff
NOTE:

Linux 不允许同一网卡同时附着多个 XDP 程序,会返回 -EBUSY 忙错误。可使用 bpftool net detach xdp dev eth0 命令将附着在 eth0 的程序 detach。

1
./xdppass 1
1
2
3
4
$ cat /sys/kernel/debug/tracing/trace_pipe
node-50447 [003] ..s21 188234.264490: bpf_trace_printk: packet size: 81
node-50447 [003] ..s21 188234.264556: bpf_trace_printk: packet size: 66
node-50153 [007] ..s21 188236.582558: bpf_trace_printk: packet size: 85

The BPF side

xdppass.bpf.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>

SEC("xdp")
int xdp_pass(struct xdp_md *ctx)
{
void *data = (void *)(long)ctx->data;
void *data_end = (void *)(long)ctx->data_end;
int pkt_sz = data_end - data;

bpf_printk("packet size: %d", pkt_sz);
return XDP_PASS;
}

char __license[] SEC("license") = "GPL";

The user-space side

xdppass.c

用户态程序参考之前的示例代码和 xdppass.c 实现。

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
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/resource.h>
#include <bpf/libbpf.h>
#include "xdppass.skel.h"

static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}

static volatile sig_atomic_t stop;

static void sig_int(int signo)
{
stop = 1;
}

int main(int argc, char ** argv)
{
struct xdppass_bpf *skel = NULL;
int err;
int ifindex = 0;

/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);

if(argc > 1)
ifindex = atoi(argv[1]);

/* Open load and verify BPF application */
skel = xdppass_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open BPF skeleton\n");
return 1;
}

skel->links.xdp_pass = bpf_program__attach_xdp(skel->progs.xdp_pass, ifindex);
if (!skel->links.xdp_pass) {
err = -errno;
fprintf(stderr, "Failed to attach XDP program to ifindex %d\n", ifindex);
goto cleanup;
}

err = xdppass_bpf__attach(skel);
if (err) {
fprintf(stderr, "Failed to auto-attach BPF skeleton: %d\n", err);
goto cleanup;
}

if (signal(SIGINT, sig_int) == SIG_ERR) {
fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
goto cleanup;
}

printf("Successfully started! Please run `sudo cat /sys/kernel/debug/tracing/trace_pipe` "
"to see output of the BPF programs.\n");

while (!stop) {
fprintf(stderr, ".");
sleep(1);
}

cleanup:
xdppass_bpf__destroy(skel);
return -err;
}

other

觅梦随笔: eBPF例程——XDP 这篇博文中还实现了几个进阶示例:

  • 数据包解析以太网头
  • 数据包重定向至其他端口
  • 数据包重定向至用户态