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_prog_get_info_by_fd(fd, &info, &info_len);
33 	if (!ASSERT_OK(ret, "bpf_prog_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 }
396