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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
|
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/mman.h>
#include <linux/if_link.h>
#include <xdp/xsk.h>
#include <bpf/libbpf.h>
#include "xdpredirect.skel.h"
#define NUM_FRAMES 4096
#define NUM_FRAMES_PER_RING (NUM_FRAMES / 2)
#define FRAME_SIZE 2048
#define UMEM_SIZE (NUM_FRAMES * FRAME_SIZE)
#define MAX_BATCH_SIZE 64 // 每次最多处理64个desc
// 标记程序是否收到退出信号
static volatile sig_atomic_t stop;
// Ctrl+C信号处理的回调函数
static void handle_sigint(int sig)
{
// 设置标志位,通知主循环退出
stop = 1;
}
// 为随后的UMEM内存创建,预先分配好所需的内存
void *create_umem_area()
{
// UMEM是用户空间存放数据包的地方,是预申请好的连续的一整块内存
// mmap则能够分配一块连续内存区域,大小为UMEM_SIZE
// PROT_READ | PROT_WRITE表示区域可读可写
void *area = mmap(NULL, UMEM_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE,
-1, 0);
if (area == MAP_FAILED)
{
perror("ERROR: mmap for UMEM failed");
return NULL;
}
return area;
}
// 真正创建UMEM内存区域
struct xsk_umem *create_xsk_umem(void *umem_area,
struct xsk_ring_prod *fill,
struct xsk_ring_cons *comp)
{
// UMEM内存区域的配置
struct xsk_umem_config cfg = {
// Fill Ring的大小
.fill_size = NUM_FRAMES_PER_RING,
// Completion Ring的大小
.comp_size = NUM_FRAMES_PER_RING,
// 每个帧的大小
.frame_size = FRAME_SIZE,
// Headroom的大小,此处不设置表示无headroom
.frame_headroom = 0,
.flags = 0,
};
// 在分配好的内存区域umem_area上创建UMEM内存umem
struct xsk_umem *umem = NULL;
int ret = xsk_umem__create(&umem, umem_area, UMEM_SIZE, fill, comp, &cfg);
if (ret)
{
fprintf(stderr, "xsk_umem__create failed: %s\n", strerror(-ret));
return NULL;
}
printf("UMEM created successfully!\n");
return umem;
}
// 创建AF_XDP Socket
struct xsk_socket *create_xsk_socket(const char *ifname,
__u32 queue_id,
struct xsk_umem *umem,
struct xsk_ring_cons *rx,
struct xsk_ring_prod *tx)
{
// AF_XDP Socket的配置选项
struct xsk_socket_config xsk_cfg = {
// 配置RX Ring的大小
.rx_size = NUM_FRAMES_PER_RING,
// 配置TX Ring的大小
.tx_size = NUM_FRAMES_PER_RING,
// 创建Socket的过程会导致自动加载默认的XDP程序,本示例需要加载自己的XDP程序
// XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD可禁用自动加载XDP程序
.libxdp_flags = XSK_LIBBPF_FLAGS__INHIBIT_PROG_LOAD,
// XDP的模式
// XDP_FLAGS_DRV_MODE(Naive模式,驱动程序内处理)
// XDP_FLAGS_HW_MODE(性能模式,卸载到网卡内处理)
// XDP_FLAGS_SKB_MODE(兼容性模式,在网络栈的早期阶段处理)
.xdp_flags = XDP_FLAGS_DRV_MODE,
.bind_flags = 0,
};
// 创建Socket,并绑定到网卡和队列上
struct xsk_socket *xsk = NULL;
int ret = xsk_socket__create(&xsk, ifname, queue_id, umem, rx, tx, &xsk_cfg);
if (ret)
{
fprintf(stderr, "xsk_socket__create failed: %s\n", strerror(-ret));
return NULL;
}
printf("XSK socket created successfully on interface %s queue %u\n", ifname, queue_id);
return xsk;
}
// 在Fill Ring中填充空闲buffer的偏移地址
// 内核需要从Fill Ring中获取空闲buffer的偏移地址,才能将接收到的数据包存在相应位置
int populate_fill_ring(struct xsk_ring_prod *fill)
{
// 尝试一次性在Fill Ring中申请NUM_FRAMES_PER_RING个槽位
// 等价于在UMEM内存中获取NUM_FRAMES_PER_RING个空闲buffer
// idx由函数填入,表示从Fill Ring的第idx个槽位开始,返回值为申请获得的槽位数量
unsigned int idx;
int ret = xsk_ring_prod__reserve(fill, NUM_FRAMES_PER_RING, &idx);
if (ret != (int)NUM_FRAMES_PER_RING)
{
fprintf(stderr, "Failed to reserve fill ring slots: %d\n", ret);
return -1;
}
// 在申请到的槽位中填写空闲buffer的偏移地址
for (unsigned int i = 0; i < NUM_FRAMES_PER_RING; i++)
{
// UMEM由数个FRAME_SIZE大小的buffer组成,此时UMEM中的各buffer均为空闲
// 将第0个buffer的偏移地址填入第idx个槽位中
// 将第1个buffer的偏移地址填入第idx+1个槽位中
// 将第i个buffer的偏移地址填入第idx+i个槽位中
// 而第i个buffer的偏移地址是中UMEM区域内的偏移量,即i*FRAME_SIZE
// 因此通过读取Fill Ring的各槽位,可以映射到UMEM获得空闲的buffer
*xsk_ring_prod__fill_addr(fill, idx + i) = i * FRAME_SIZE;
}
// 用户态正式提交,让内核获知新加入了空闲buffer
xsk_ring_prod__submit(fill, NUM_FRAMES_PER_RING);
printf("Fill ring populated with %u frames.\n", NUM_FRAMES_PER_RING);
return 0;
}
// 接收数据包并进行处理
void process_packets(struct xsk_ring_cons *rx, struct xsk_ring_prod *fill, void *umem_area)
{
__u32 idxs[MAX_BATCH_SIZE];
__u64 addrs[MAX_BATCH_SIZE];
size_t i;
int ret;
// 从RX Ring中一次性取出MAX_BATCH_SIZE个元素,并将其槽位索引放置于idxs中
// 每个元素指向的buffer存储有接收到的数据包
size_t nb_descs = xsk_ring_cons__peek(rx, MAX_BATCH_SIZE, idxs);
if (nb_descs == 0)
return;
// 遍历取出每一个元素
for (i = 0; i < nb_descs; i++)
{
// 根据索引获取槽位中的信息,即buffer的偏移地址与数据包的长度
const struct xdp_desc *desc = xsk_ring_cons__rx_desc(rx, idxs[i]);
__u64 addr = desc->addr;
__u32 len = desc->len;
// 根据获得的buffer偏移地址,在UMEM内存中获得buffer的实际地址
void *data = xsk_umem__get_data(umem_area, addr);
// 输出打印数据包的长度以及第一个字节
printf("Packet %zu: len=%u, first byte=0x%02x\n", i, len, ((unsigned char *)data)[0]);
// 记录该buffer的偏移地址,以便随后放入Fill Ring
addrs[i] = addr;
}
// 把使用过的nb_descs个buffer重新放回Fill Ring
// 首先在Fill Ring中申请nb_descs个槽位,而idx由函数填入起始索引
unsigned int idx;
ret = xsk_ring_prod__reserve(fill, nb_descs, &idx);
if (ret != nb_descs)
{
fprintf(stderr, "fill ring reserve failed: %d\n", ret);
}
// 遍历每一个申请到的槽位
for (i = 0; i < nb_descs; i++)
{
// 将第0个buffer的偏移地址填入第idx个槽位中
// 将第1个buffer的偏移地址填入第idx+1个槽位中
// 将第i个buffer的偏移地址填入第idx+i个槽位中
*xsk_ring_prod__fill_addr(fill, idx + i) = addrs[i];
}
// 用户态正式提交,让内核获知新加入了空闲buffer
xsk_ring_prod__submit(fill, nb_descs);
// 通知内核已经消费了Rx Ring中nb_descs个元素
xsk_ring_cons__release(rx, nb_descs);
}
int main(int argc, char **argv)
{
// 设置需要挂载的网络端口
int ingress_ifindex = 2;
// 该网络端口的名称
char *ifname = "ens160";
// 设置需要操作的队列
__u32 queue_id = 0;
int ret;
// AF_Socket所需的4个Ring Buffer
struct xsk_ring_prod fill;
struct xsk_ring_cons comp;
struct xsk_ring_cons rx;
struct xsk_ring_prod tx;
// 在用户态创建连续的内存区域
void *umem_area = create_umem_area();
if (!umem_area)
{
return 1;
}
// 在准备好的内存区域上创建UMEM区域
struct xsk_umem *umem = create_xsk_umem(umem_area, &fill, &comp);
if (!umem)
{
munmap(umem_area, UMEM_SIZE);
return 1;
}
// 打开并加载eBPF程序
struct xdpredirect_bpf *skel = xdpredirect_bpf__open_and_load();
if (!skel)
{
fprintf(stderr, "Failed to open and load BPF object\n");
goto cleanup;
}
// 将eBPF程序xdp_redirect_prog附加到指定接口索引的网络端口上
skel->links.xdp_redirect_prog = bpf_program__attach_xdp(skel->progs.xdp_redirect_prog, ingress_ifindex);
if (!skel->links.xdp_redirect_prog)
{
fprintf(stderr, "Failed to attach XDP program to ifindex %d\n", ingress_ifindex);
goto cleanup;
}
// 创建AF_XDP Socket
struct xsk_socket *xsk = create_xsk_socket(ifname, queue_id, umem, &rx, &tx);
if (!xsk)
{
goto cleanup;
}
// 在Fill Ring中准备充足的槽位,以供内核进行数据包接收
if (populate_fill_ring(&fill) != 0)
{
goto cleanup;
}
// 设置BPF映射xsks_map,即设置队列中接收到的数据重定向至用户态
__u32 value_fd = xsk_socket__fd(xsk);
ret = bpf_map__update_elem(
skel->maps.xsks_map,
&queue_id, sizeof(queue_id),
&value_fd, sizeof(value_fd),
0);
if (ret)
{
fprintf(stderr, "Failed to update devmap: %s\n", strerror(errno));
goto cleanup;
}
printf("Redirecting packets from ifindex %d to user space\n", ingress_ifindex);
printf("Press Ctrl+C to stop\n");
// 注册SIGINT信号处理函数
signal(SIGINT, handle_sigint);
// 主循环,使程序持续运行
while (!stop)
{
// 不断从RX Ring中获取、接收数据包
process_packets(&rx, &fill, umem_area);
usleep(1000);
}
cleanup:
// 释放、清理eBPF环境
xdpredirect_bpf__destroy(skel);
// 删除AF_XDP Socket
xsk_socket__delete(xsk);
// 释放UMEM内存区域
xsk_umem__delete(umem);
// 释放注册的连续内存空间
munmap(umem_area, UMEM_SIZE);
return 0;
}
|