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
26static 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
62end:
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
69static 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
331void 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
390end:
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
398void 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
418restore_cap:
419	if (caps)
420		cap_enable_effective(caps, NULL);
421}
422
423void 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