Skip to content

Commit f18ba26

Browse files
kkdwvdborkmann
authored andcommitted
libbpf: Add selftests for TC-BPF management API
This adds some basic tests for the low level bpf_tc_* API. Signed-off-by: Kumar Kartikeya Dwivedi <[email protected]> Signed-off-by: Daniel Borkmann <[email protected]> Reviewed-by: Toke Høiland-Jørgensen <[email protected]> Link: https://lore.kernel.org/bpf/[email protected]
1 parent 715c5ce commit f18ba26

File tree

2 files changed

+407
-0
lines changed

2 files changed

+407
-0
lines changed
Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
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+
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+
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+
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+
}

0 commit comments

Comments
 (0)