1 // SPDX-License-Identifier: GPL-2.0
2
3 #include <test_progs.h>
4 #include <linux/pkt_cls.h>
5
6 #include "test_tc_bpf.skel.h"
7
8 #define LO_IFINDEX 1
9
10 #define TEST_DECLARE_OPTS(__fd) \
11 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_h, .handle = 1); \
12 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_p, .priority = 1); \
13 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_f, .prog_fd = __fd); \
14 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hp, .handle = 1, .priority = 1); \
15 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hf, .handle = 1, .prog_fd = __fd); \
16 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_pf, .priority = 1, .prog_fd = __fd); \
17 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hpf, .handle = 1, .priority = 1, .prog_fd = __fd); \
18 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hpi, .handle = 1, .priority = 1, .prog_id = 42); \
19 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hpr, .handle = 1, .priority = 1, \
20 .flags = BPF_TC_F_REPLACE); \
21 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_hpfi, .handle = 1, .priority = 1, .prog_fd = __fd, \
22 .prog_id = 42); \
23 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts_prio_max, .handle = 1, .priority = UINT16_MAX + 1);
24
test_tc_bpf_basic(const struct bpf_tc_hook * hook,int fd)25 static int test_tc_bpf_basic(const struct bpf_tc_hook *hook, int fd)
26 {
27 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts, .handle = 1, .priority = 1, .prog_fd = fd);
28 struct bpf_prog_info info = {};
29 __u32 info_len = sizeof(info);
30 int ret;
31
32 ret = bpf_obj_get_info_by_fd(fd, &info, &info_len);
33 if (!ASSERT_OK(ret, "bpf_obj_get_info_by_fd"))
34 return ret;
35
36 ret = bpf_tc_attach(hook, &opts);
37 if (!ASSERT_OK(ret, "bpf_tc_attach"))
38 return ret;
39
40 if (!ASSERT_EQ(opts.handle, 1, "handle set") ||
41 !ASSERT_EQ(opts.priority, 1, "priority set") ||
42 !ASSERT_EQ(opts.prog_id, info.id, "prog_id set"))
43 goto end;
44
45 opts.prog_id = 0;
46 opts.flags = BPF_TC_F_REPLACE;
47 ret = bpf_tc_attach(hook, &opts);
48 if (!ASSERT_OK(ret, "bpf_tc_attach replace mode"))
49 goto end;
50
51 opts.flags = opts.prog_fd = opts.prog_id = 0;
52 ret = bpf_tc_query(hook, &opts);
53 if (!ASSERT_OK(ret, "bpf_tc_query"))
54 goto end;
55
56 if (!ASSERT_EQ(opts.handle, 1, "handle set") ||
57 !ASSERT_EQ(opts.priority, 1, "priority set") ||
58 !ASSERT_EQ(opts.prog_id, info.id, "prog_id set"))
59 goto end;
60
61 end:
62 opts.flags = opts.prog_fd = opts.prog_id = 0;
63 ret = bpf_tc_detach(hook, &opts);
64 ASSERT_OK(ret, "bpf_tc_detach");
65 return ret;
66 }
67
test_tc_bpf_api(struct bpf_tc_hook * hook,int fd)68 static int test_tc_bpf_api(struct bpf_tc_hook *hook, int fd)
69 {
70 DECLARE_LIBBPF_OPTS(bpf_tc_opts, attach_opts, .handle = 1, .priority = 1, .prog_fd = fd);
71 DECLARE_LIBBPF_OPTS(bpf_tc_hook, inv_hook, .attach_point = BPF_TC_INGRESS);
72 DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts, .handle = 1, .priority = 1);
73 int ret;
74
75 ret = bpf_tc_hook_create(NULL);
76 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook = NULL"))
77 return -EINVAL;
78
79 /* hook ifindex = 0 */
80 ret = bpf_tc_hook_create(&inv_hook);
81 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook ifindex == 0"))
82 return -EINVAL;
83
84 ret = bpf_tc_hook_destroy(&inv_hook);
85 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_destroy invalid hook ifindex == 0"))
86 return -EINVAL;
87
88 ret = bpf_tc_attach(&inv_hook, &attach_opts);
89 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook ifindex == 0"))
90 return -EINVAL;
91 attach_opts.prog_id = 0;
92
93 ret = bpf_tc_detach(&inv_hook, &opts);
94 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook ifindex == 0"))
95 return -EINVAL;
96
97 ret = bpf_tc_query(&inv_hook, &opts);
98 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook ifindex == 0"))
99 return -EINVAL;
100
101 /* hook ifindex < 0 */
102 inv_hook.ifindex = -1;
103
104 ret = bpf_tc_hook_create(&inv_hook);
105 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook ifindex < 0"))
106 return -EINVAL;
107
108 ret = bpf_tc_hook_destroy(&inv_hook);
109 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_destroy invalid hook ifindex < 0"))
110 return -EINVAL;
111
112 ret = bpf_tc_attach(&inv_hook, &attach_opts);
113 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook ifindex < 0"))
114 return -EINVAL;
115 attach_opts.prog_id = 0;
116
117 ret = bpf_tc_detach(&inv_hook, &opts);
118 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook ifindex < 0"))
119 return -EINVAL;
120
121 ret = bpf_tc_query(&inv_hook, &opts);
122 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook ifindex < 0"))
123 return -EINVAL;
124
125 inv_hook.ifindex = LO_IFINDEX;
126
127 /* hook.attach_point invalid */
128 inv_hook.attach_point = 0xabcd;
129 ret = bpf_tc_hook_create(&inv_hook);
130 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook.attach_point"))
131 return -EINVAL;
132
133 ret = bpf_tc_hook_destroy(&inv_hook);
134 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_destroy invalid hook.attach_point"))
135 return -EINVAL;
136
137 ret = bpf_tc_attach(&inv_hook, &attach_opts);
138 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook.attach_point"))
139 return -EINVAL;
140
141 ret = bpf_tc_detach(&inv_hook, &opts);
142 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook.attach_point"))
143 return -EINVAL;
144
145 ret = bpf_tc_query(&inv_hook, &opts);
146 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook.attach_point"))
147 return -EINVAL;
148
149 inv_hook.attach_point = BPF_TC_INGRESS;
150
151 /* hook.attach_point valid, but parent invalid */
152 inv_hook.parent = TC_H_MAKE(1UL << 16, 10);
153 ret = bpf_tc_hook_create(&inv_hook);
154 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_create invalid hook parent"))
155 return -EINVAL;
156
157 ret = bpf_tc_hook_destroy(&inv_hook);
158 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_hook_destroy invalid hook parent"))
159 return -EINVAL;
160
161 ret = bpf_tc_attach(&inv_hook, &attach_opts);
162 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook parent"))
163 return -EINVAL;
164
165 ret = bpf_tc_detach(&inv_hook, &opts);
166 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook parent"))
167 return -EINVAL;
168
169 ret = bpf_tc_query(&inv_hook, &opts);
170 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook parent"))
171 return -EINVAL;
172
173 inv_hook.attach_point = BPF_TC_CUSTOM;
174 inv_hook.parent = 0;
175 /* These return EOPNOTSUPP instead of EINVAL as parent is checked after
176 * attach_point of the hook.
177 */
178 ret = bpf_tc_hook_create(&inv_hook);
179 if (!ASSERT_EQ(ret, -EOPNOTSUPP, "bpf_tc_hook_create invalid hook parent"))
180 return -EINVAL;
181
182 ret = bpf_tc_hook_destroy(&inv_hook);
183 if (!ASSERT_EQ(ret, -EOPNOTSUPP, "bpf_tc_hook_destroy invalid hook parent"))
184 return -EINVAL;
185
186 ret = bpf_tc_attach(&inv_hook, &attach_opts);
187 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook parent"))
188 return -EINVAL;
189
190 ret = bpf_tc_detach(&inv_hook, &opts);
191 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook parent"))
192 return -EINVAL;
193
194 ret = bpf_tc_query(&inv_hook, &opts);
195 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook parent"))
196 return -EINVAL;
197
198 inv_hook.attach_point = BPF_TC_INGRESS;
199
200 /* detach */
201 {
202 TEST_DECLARE_OPTS(fd);
203
204 ret = bpf_tc_detach(NULL, &opts_hp);
205 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid hook = NULL"))
206 return -EINVAL;
207
208 ret = bpf_tc_detach(hook, NULL);
209 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid opts = NULL"))
210 return -EINVAL;
211
212 ret = bpf_tc_detach(hook, &opts_hpr);
213 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid flags set"))
214 return -EINVAL;
215
216 ret = bpf_tc_detach(hook, &opts_hpf);
217 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid prog_fd set"))
218 return -EINVAL;
219
220 ret = bpf_tc_detach(hook, &opts_hpi);
221 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid prog_id set"))
222 return -EINVAL;
223
224 ret = bpf_tc_detach(hook, &opts_p);
225 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid handle unset"))
226 return -EINVAL;
227
228 ret = bpf_tc_detach(hook, &opts_h);
229 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid priority unset"))
230 return -EINVAL;
231
232 ret = bpf_tc_detach(hook, &opts_prio_max);
233 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_detach invalid priority > UINT16_MAX"))
234 return -EINVAL;
235 }
236
237 /* query */
238 {
239 TEST_DECLARE_OPTS(fd);
240
241 ret = bpf_tc_query(NULL, &opts);
242 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid hook = NULL"))
243 return -EINVAL;
244
245 ret = bpf_tc_query(hook, NULL);
246 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid opts = NULL"))
247 return -EINVAL;
248
249 ret = bpf_tc_query(hook, &opts_hpr);
250 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid flags set"))
251 return -EINVAL;
252
253 ret = bpf_tc_query(hook, &opts_hpf);
254 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid prog_fd set"))
255 return -EINVAL;
256
257 ret = bpf_tc_query(hook, &opts_hpi);
258 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid prog_id set"))
259 return -EINVAL;
260
261 ret = bpf_tc_query(hook, &opts_p);
262 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid handle unset"))
263 return -EINVAL;
264
265 ret = bpf_tc_query(hook, &opts_h);
266 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid priority unset"))
267 return -EINVAL;
268
269 ret = bpf_tc_query(hook, &opts_prio_max);
270 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query invalid priority > UINT16_MAX"))
271 return -EINVAL;
272
273 /* when chain is not present, kernel returns -EINVAL */
274 ret = bpf_tc_query(hook, &opts_hp);
275 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_query valid handle, priority set"))
276 return -EINVAL;
277 }
278
279 /* attach */
280 {
281 TEST_DECLARE_OPTS(fd);
282
283 ret = bpf_tc_attach(NULL, &opts_hp);
284 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid hook = NULL"))
285 return -EINVAL;
286
287 ret = bpf_tc_attach(hook, NULL);
288 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid opts = NULL"))
289 return -EINVAL;
290
291 opts_hp.flags = 42;
292 ret = bpf_tc_attach(hook, &opts_hp);
293 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid flags"))
294 return -EINVAL;
295
296 ret = bpf_tc_attach(hook, NULL);
297 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid prog_fd unset"))
298 return -EINVAL;
299
300 ret = bpf_tc_attach(hook, &opts_hpi);
301 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid prog_id set"))
302 return -EINVAL;
303
304 ret = bpf_tc_attach(hook, &opts_pf);
305 if (!ASSERT_OK(ret, "bpf_tc_attach valid handle unset"))
306 return -EINVAL;
307 opts_pf.prog_fd = opts_pf.prog_id = 0;
308 ASSERT_OK(bpf_tc_detach(hook, &opts_pf), "bpf_tc_detach");
309
310 ret = bpf_tc_attach(hook, &opts_hf);
311 if (!ASSERT_OK(ret, "bpf_tc_attach valid priority unset"))
312 return -EINVAL;
313 opts_hf.prog_fd = opts_hf.prog_id = 0;
314 ASSERT_OK(bpf_tc_detach(hook, &opts_hf), "bpf_tc_detach");
315
316 ret = bpf_tc_attach(hook, &opts_prio_max);
317 if (!ASSERT_EQ(ret, -EINVAL, "bpf_tc_attach invalid priority > UINT16_MAX"))
318 return -EINVAL;
319
320 ret = bpf_tc_attach(hook, &opts_f);
321 if (!ASSERT_OK(ret, "bpf_tc_attach valid both handle and priority unset"))
322 return -EINVAL;
323 opts_f.prog_fd = opts_f.prog_id = 0;
324 ASSERT_OK(bpf_tc_detach(hook, &opts_f), "bpf_tc_detach");
325 }
326
327 return 0;
328 }
329
test_tc_bpf(void)330 void test_tc_bpf(void)
331 {
332 DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook, .ifindex = LO_IFINDEX,
333 .attach_point = BPF_TC_INGRESS);
334 struct test_tc_bpf *skel = NULL;
335 bool hook_created = false;
336 int cls_fd, ret;
337
338 skel = test_tc_bpf__open_and_load();
339 if (!ASSERT_OK_PTR(skel, "test_tc_bpf__open_and_load"))
340 return;
341
342 cls_fd = bpf_program__fd(skel->progs.cls);
343
344 ret = bpf_tc_hook_create(&hook);
345 if (ret == 0)
346 hook_created = true;
347
348 ret = ret == -EEXIST ? 0 : ret;
349 if (!ASSERT_OK(ret, "bpf_tc_hook_create(BPF_TC_INGRESS)"))
350 goto end;
351
352 hook.attach_point = BPF_TC_CUSTOM;
353 hook.parent = TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_INGRESS);
354 ret = bpf_tc_hook_create(&hook);
355 if (!ASSERT_EQ(ret, -EOPNOTSUPP, "bpf_tc_hook_create invalid hook.attach_point"))
356 goto end;
357
358 ret = test_tc_bpf_basic(&hook, cls_fd);
359 if (!ASSERT_OK(ret, "test_tc_internal ingress"))
360 goto end;
361
362 ret = bpf_tc_hook_destroy(&hook);
363 if (!ASSERT_EQ(ret, -EOPNOTSUPP, "bpf_tc_hook_destroy invalid hook.attach_point"))
364 goto end;
365
366 hook.attach_point = BPF_TC_INGRESS;
367 hook.parent = 0;
368 bpf_tc_hook_destroy(&hook);
369
370 ret = test_tc_bpf_basic(&hook, cls_fd);
371 if (!ASSERT_OK(ret, "test_tc_internal ingress"))
372 goto end;
373
374 bpf_tc_hook_destroy(&hook);
375
376 hook.attach_point = BPF_TC_EGRESS;
377 ret = test_tc_bpf_basic(&hook, cls_fd);
378 if (!ASSERT_OK(ret, "test_tc_internal egress"))
379 goto end;
380
381 bpf_tc_hook_destroy(&hook);
382
383 ret = test_tc_bpf_api(&hook, cls_fd);
384 if (!ASSERT_OK(ret, "test_tc_bpf_api"))
385 goto end;
386
387 bpf_tc_hook_destroy(&hook);
388
389 end:
390 if (hook_created) {
391 hook.attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS;
392 bpf_tc_hook_destroy(&hook);
393 }
394 test_tc_bpf__destroy(skel);
395 }
396