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