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