xref: /linux/tools/testing/selftests/bpf/prog_tests/bpf_tcp_ca.c (revision 94df6cbd4c4aea633a9b4bbfab835dcfee21578b)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2019 Facebook */
3 
4 #include <linux/err.h>
5 #include <netinet/tcp.h>
6 #include <test_progs.h>
7 #include "network_helpers.h"
8 #include "bpf_dctcp.skel.h"
9 #include "bpf_cubic.skel.h"
10 #include "bpf_tcp_nogpl.skel.h"
11 #include "tcp_ca_update.skel.h"
12 #include "bpf_dctcp_release.skel.h"
13 #include "tcp_ca_write_sk_pacing.skel.h"
14 #include "tcp_ca_incompl_cong_ops.skel.h"
15 #include "tcp_ca_unsupp_cong_op.skel.h"
16 #include "tcp_ca_kfunc.skel.h"
17 
18 #ifndef ENOTSUPP
19 #define ENOTSUPP 524
20 #endif
21 
22 static const unsigned int total_bytes = 10 * 1024 * 1024;
23 static int expected_stg = 0xeB9F;
24 
25 static int settcpca(int fd, const char *tcp_ca)
26 {
27 	int err;
28 
29 	err = setsockopt(fd, IPPROTO_TCP, TCP_CONGESTION, tcp_ca, strlen(tcp_ca));
30 	if (!ASSERT_NEQ(err, -1, "setsockopt"))
31 		return -1;
32 
33 	return 0;
34 }
35 
36 static void do_test(const char *tcp_ca, const struct bpf_map *sk_stg_map)
37 {
38 	int lfd = -1, fd = -1;
39 	int err;
40 
41 	lfd = start_server(AF_INET6, SOCK_STREAM, NULL, 0, 0);
42 	if (!ASSERT_NEQ(lfd, -1, "socket"))
43 		return;
44 
45 	fd = socket(AF_INET6, SOCK_STREAM, 0);
46 	if (!ASSERT_NEQ(fd, -1, "socket")) {
47 		close(lfd);
48 		return;
49 	}
50 
51 	if (settcpca(lfd, tcp_ca) || settcpca(fd, tcp_ca))
52 		goto done;
53 
54 	if (sk_stg_map) {
55 		err = bpf_map_update_elem(bpf_map__fd(sk_stg_map), &fd,
56 					  &expected_stg, BPF_NOEXIST);
57 		if (!ASSERT_OK(err, "bpf_map_update_elem(sk_stg_map)"))
58 			goto done;
59 	}
60 
61 	/* connect to server */
62 	err = connect_fd_to_fd(fd, lfd, 0);
63 	if (!ASSERT_NEQ(err, -1, "connect"))
64 		goto done;
65 
66 	if (sk_stg_map) {
67 		int tmp_stg;
68 
69 		err = bpf_map_lookup_elem(bpf_map__fd(sk_stg_map), &fd,
70 					  &tmp_stg);
71 		if (!ASSERT_ERR(err, "bpf_map_lookup_elem(sk_stg_map)") ||
72 				!ASSERT_EQ(errno, ENOENT, "bpf_map_lookup_elem(sk_stg_map)"))
73 			goto done;
74 	}
75 
76 	ASSERT_OK(send_recv_data(lfd, fd, total_bytes), "send_recv_data");
77 
78 done:
79 	close(lfd);
80 	close(fd);
81 }
82 
83 static void test_cubic(void)
84 {
85 	struct bpf_cubic *cubic_skel;
86 	struct bpf_link *link;
87 
88 	cubic_skel = bpf_cubic__open_and_load();
89 	if (!ASSERT_OK_PTR(cubic_skel, "bpf_cubic__open_and_load"))
90 		return;
91 
92 	link = bpf_map__attach_struct_ops(cubic_skel->maps.cubic);
93 	if (!ASSERT_OK_PTR(link, "bpf_map__attach_struct_ops")) {
94 		bpf_cubic__destroy(cubic_skel);
95 		return;
96 	}
97 
98 	do_test("bpf_cubic", NULL);
99 
100 	ASSERT_EQ(cubic_skel->bss->bpf_cubic_acked_called, 1, "pkts_acked called");
101 
102 	bpf_link__destroy(link);
103 	bpf_cubic__destroy(cubic_skel);
104 }
105 
106 static void test_dctcp(void)
107 {
108 	struct bpf_dctcp *dctcp_skel;
109 	struct bpf_link *link;
110 
111 	dctcp_skel = bpf_dctcp__open_and_load();
112 	if (!ASSERT_OK_PTR(dctcp_skel, "bpf_dctcp__open_and_load"))
113 		return;
114 
115 	link = bpf_map__attach_struct_ops(dctcp_skel->maps.dctcp);
116 	if (!ASSERT_OK_PTR(link, "bpf_map__attach_struct_ops")) {
117 		bpf_dctcp__destroy(dctcp_skel);
118 		return;
119 	}
120 
121 	do_test("bpf_dctcp", dctcp_skel->maps.sk_stg_map);
122 	ASSERT_EQ(dctcp_skel->bss->stg_result, expected_stg, "stg_result");
123 
124 	bpf_link__destroy(link);
125 	bpf_dctcp__destroy(dctcp_skel);
126 }
127 
128 static char *err_str;
129 static bool found;
130 
131 static int libbpf_debug_print(enum libbpf_print_level level,
132 			      const char *format, va_list args)
133 {
134 	const char *prog_name, *log_buf;
135 
136 	if (level != LIBBPF_WARN ||
137 	    !strstr(format, "-- BEGIN PROG LOAD LOG --")) {
138 		vprintf(format, args);
139 		return 0;
140 	}
141 
142 	prog_name = va_arg(args, char *);
143 	log_buf = va_arg(args, char *);
144 	if (!log_buf)
145 		goto out;
146 	if (err_str && strstr(log_buf, err_str) != NULL)
147 		found = true;
148 out:
149 	printf(format, prog_name, log_buf);
150 	return 0;
151 }
152 
153 static void test_invalid_license(void)
154 {
155 	libbpf_print_fn_t old_print_fn;
156 	struct bpf_tcp_nogpl *skel;
157 
158 	err_str = "struct ops programs must have a GPL compatible license";
159 	found = false;
160 	old_print_fn = libbpf_set_print(libbpf_debug_print);
161 
162 	skel = bpf_tcp_nogpl__open_and_load();
163 	ASSERT_NULL(skel, "bpf_tcp_nogpl");
164 	ASSERT_EQ(found, true, "expected_err_msg");
165 
166 	bpf_tcp_nogpl__destroy(skel);
167 	libbpf_set_print(old_print_fn);
168 }
169 
170 static void test_dctcp_fallback(void)
171 {
172 	int err, lfd = -1, cli_fd = -1, srv_fd = -1;
173 	struct network_helper_opts opts = {
174 		.cc = "cubic",
175 	};
176 	struct bpf_dctcp *dctcp_skel;
177 	struct bpf_link *link = NULL;
178 	char srv_cc[16];
179 	socklen_t cc_len = sizeof(srv_cc);
180 
181 	dctcp_skel = bpf_dctcp__open();
182 	if (!ASSERT_OK_PTR(dctcp_skel, "dctcp_skel"))
183 		return;
184 	strcpy(dctcp_skel->rodata->fallback, "cubic");
185 	if (!ASSERT_OK(bpf_dctcp__load(dctcp_skel), "bpf_dctcp__load"))
186 		goto done;
187 
188 	link = bpf_map__attach_struct_ops(dctcp_skel->maps.dctcp);
189 	if (!ASSERT_OK_PTR(link, "dctcp link"))
190 		goto done;
191 
192 	lfd = start_server(AF_INET6, SOCK_STREAM, "::1", 0, 0);
193 	if (!ASSERT_GE(lfd, 0, "lfd") ||
194 	    !ASSERT_OK(settcpca(lfd, "bpf_dctcp"), "lfd=>bpf_dctcp"))
195 		goto done;
196 
197 	cli_fd = connect_to_fd_opts(lfd, &opts);
198 	if (!ASSERT_GE(cli_fd, 0, "cli_fd"))
199 		goto done;
200 
201 	srv_fd = accept(lfd, NULL, 0);
202 	if (!ASSERT_GE(srv_fd, 0, "srv_fd"))
203 		goto done;
204 	ASSERT_STREQ(dctcp_skel->bss->cc_res, "cubic", "cc_res");
205 	ASSERT_EQ(dctcp_skel->bss->tcp_cdg_res, -ENOTSUPP, "tcp_cdg_res");
206 	/* All setsockopt(TCP_CONGESTION) in the recurred
207 	 * bpf_dctcp->init() should fail with -EBUSY.
208 	 */
209 	ASSERT_EQ(dctcp_skel->bss->ebusy_cnt, 3, "ebusy_cnt");
210 
211 	err = getsockopt(srv_fd, SOL_TCP, TCP_CONGESTION, srv_cc, &cc_len);
212 	if (!ASSERT_OK(err, "getsockopt(srv_fd, TCP_CONGESTION)"))
213 		goto done;
214 	ASSERT_STREQ(srv_cc, "cubic", "srv_fd cc");
215 
216 done:
217 	bpf_link__destroy(link);
218 	bpf_dctcp__destroy(dctcp_skel);
219 	if (lfd != -1)
220 		close(lfd);
221 	if (srv_fd != -1)
222 		close(srv_fd);
223 	if (cli_fd != -1)
224 		close(cli_fd);
225 }
226 
227 static void test_rel_setsockopt(void)
228 {
229 	struct bpf_dctcp_release *rel_skel;
230 	libbpf_print_fn_t old_print_fn;
231 
232 	err_str = "program of this type cannot use helper bpf_setsockopt";
233 	found = false;
234 
235 	old_print_fn = libbpf_set_print(libbpf_debug_print);
236 	rel_skel = bpf_dctcp_release__open_and_load();
237 	libbpf_set_print(old_print_fn);
238 
239 	ASSERT_ERR_PTR(rel_skel, "rel_skel");
240 	ASSERT_TRUE(found, "expected_err_msg");
241 
242 	bpf_dctcp_release__destroy(rel_skel);
243 }
244 
245 static void test_write_sk_pacing(void)
246 {
247 	struct tcp_ca_write_sk_pacing *skel;
248 	struct bpf_link *link;
249 
250 	skel = tcp_ca_write_sk_pacing__open_and_load();
251 	if (!ASSERT_OK_PTR(skel, "open_and_load"))
252 		return;
253 
254 	link = bpf_map__attach_struct_ops(skel->maps.write_sk_pacing);
255 	ASSERT_OK_PTR(link, "attach_struct_ops");
256 
257 	bpf_link__destroy(link);
258 	tcp_ca_write_sk_pacing__destroy(skel);
259 }
260 
261 static void test_incompl_cong_ops(void)
262 {
263 	struct tcp_ca_incompl_cong_ops *skel;
264 	struct bpf_link *link;
265 
266 	skel = tcp_ca_incompl_cong_ops__open_and_load();
267 	if (!ASSERT_OK_PTR(skel, "open_and_load"))
268 		return;
269 
270 	/* That cong_avoid() and cong_control() are missing is only reported at
271 	 * this point:
272 	 */
273 	link = bpf_map__attach_struct_ops(skel->maps.incompl_cong_ops);
274 	ASSERT_ERR_PTR(link, "attach_struct_ops");
275 
276 	bpf_link__destroy(link);
277 	tcp_ca_incompl_cong_ops__destroy(skel);
278 }
279 
280 static void test_unsupp_cong_op(void)
281 {
282 	libbpf_print_fn_t old_print_fn;
283 	struct tcp_ca_unsupp_cong_op *skel;
284 
285 	err_str = "attach to unsupported member get_info";
286 	found = false;
287 	old_print_fn = libbpf_set_print(libbpf_debug_print);
288 
289 	skel = tcp_ca_unsupp_cong_op__open_and_load();
290 	ASSERT_NULL(skel, "open_and_load");
291 	ASSERT_EQ(found, true, "expected_err_msg");
292 
293 	tcp_ca_unsupp_cong_op__destroy(skel);
294 	libbpf_set_print(old_print_fn);
295 }
296 
297 static void test_update_ca(void)
298 {
299 	struct tcp_ca_update *skel;
300 	struct bpf_link *link;
301 	int saved_ca1_cnt;
302 	int err;
303 
304 	skel = tcp_ca_update__open_and_load();
305 	if (!ASSERT_OK_PTR(skel, "open"))
306 		return;
307 
308 	link = bpf_map__attach_struct_ops(skel->maps.ca_update_1);
309 	ASSERT_OK_PTR(link, "attach_struct_ops");
310 
311 	do_test("tcp_ca_update", NULL);
312 	saved_ca1_cnt = skel->bss->ca1_cnt;
313 	ASSERT_GT(saved_ca1_cnt, 0, "ca1_ca1_cnt");
314 
315 	err = bpf_link__update_map(link, skel->maps.ca_update_2);
316 	ASSERT_OK(err, "update_map");
317 
318 	do_test("tcp_ca_update", NULL);
319 	ASSERT_EQ(skel->bss->ca1_cnt, saved_ca1_cnt, "ca2_ca1_cnt");
320 	ASSERT_GT(skel->bss->ca2_cnt, 0, "ca2_ca2_cnt");
321 
322 	bpf_link__destroy(link);
323 	tcp_ca_update__destroy(skel);
324 }
325 
326 static void test_update_wrong(void)
327 {
328 	struct tcp_ca_update *skel;
329 	struct bpf_link *link;
330 	int saved_ca1_cnt;
331 	int err;
332 
333 	skel = tcp_ca_update__open_and_load();
334 	if (!ASSERT_OK_PTR(skel, "open"))
335 		return;
336 
337 	link = bpf_map__attach_struct_ops(skel->maps.ca_update_1);
338 	ASSERT_OK_PTR(link, "attach_struct_ops");
339 
340 	do_test("tcp_ca_update", NULL);
341 	saved_ca1_cnt = skel->bss->ca1_cnt;
342 	ASSERT_GT(saved_ca1_cnt, 0, "ca1_ca1_cnt");
343 
344 	err = bpf_link__update_map(link, skel->maps.ca_wrong);
345 	ASSERT_ERR(err, "update_map");
346 
347 	do_test("tcp_ca_update", NULL);
348 	ASSERT_GT(skel->bss->ca1_cnt, saved_ca1_cnt, "ca2_ca1_cnt");
349 
350 	bpf_link__destroy(link);
351 	tcp_ca_update__destroy(skel);
352 }
353 
354 static void test_mixed_links(void)
355 {
356 	struct tcp_ca_update *skel;
357 	struct bpf_link *link, *link_nl;
358 	int err;
359 
360 	skel = tcp_ca_update__open_and_load();
361 	if (!ASSERT_OK_PTR(skel, "open"))
362 		return;
363 
364 	link_nl = bpf_map__attach_struct_ops(skel->maps.ca_no_link);
365 	ASSERT_OK_PTR(link_nl, "attach_struct_ops_nl");
366 
367 	link = bpf_map__attach_struct_ops(skel->maps.ca_update_1);
368 	ASSERT_OK_PTR(link, "attach_struct_ops");
369 
370 	do_test("tcp_ca_update", NULL);
371 	ASSERT_GT(skel->bss->ca1_cnt, 0, "ca1_ca1_cnt");
372 
373 	err = bpf_link__update_map(link, skel->maps.ca_no_link);
374 	ASSERT_ERR(err, "update_map");
375 
376 	bpf_link__destroy(link);
377 	bpf_link__destroy(link_nl);
378 	tcp_ca_update__destroy(skel);
379 }
380 
381 static void test_multi_links(void)
382 {
383 	struct tcp_ca_update *skel;
384 	struct bpf_link *link;
385 
386 	skel = tcp_ca_update__open_and_load();
387 	if (!ASSERT_OK_PTR(skel, "open"))
388 		return;
389 
390 	link = bpf_map__attach_struct_ops(skel->maps.ca_update_1);
391 	ASSERT_OK_PTR(link, "attach_struct_ops_1st");
392 	bpf_link__destroy(link);
393 
394 	/* A map should be able to be used to create links multiple
395 	 * times.
396 	 */
397 	link = bpf_map__attach_struct_ops(skel->maps.ca_update_1);
398 	ASSERT_OK_PTR(link, "attach_struct_ops_2nd");
399 	bpf_link__destroy(link);
400 
401 	tcp_ca_update__destroy(skel);
402 }
403 
404 static void test_link_replace(void)
405 {
406 	DECLARE_LIBBPF_OPTS(bpf_link_update_opts, opts);
407 	struct tcp_ca_update *skel;
408 	struct bpf_link *link;
409 	int err;
410 
411 	skel = tcp_ca_update__open_and_load();
412 	if (!ASSERT_OK_PTR(skel, "open"))
413 		return;
414 
415 	link = bpf_map__attach_struct_ops(skel->maps.ca_update_1);
416 	ASSERT_OK_PTR(link, "attach_struct_ops_1st");
417 	bpf_link__destroy(link);
418 
419 	link = bpf_map__attach_struct_ops(skel->maps.ca_update_2);
420 	ASSERT_OK_PTR(link, "attach_struct_ops_2nd");
421 
422 	/* BPF_F_REPLACE with a wrong old map Fd. It should fail!
423 	 *
424 	 * With BPF_F_REPLACE, the link should be updated only if the
425 	 * old map fd given here matches the map backing the link.
426 	 */
427 	opts.old_map_fd = bpf_map__fd(skel->maps.ca_update_1);
428 	opts.flags = BPF_F_REPLACE;
429 	err = bpf_link_update(bpf_link__fd(link),
430 			      bpf_map__fd(skel->maps.ca_update_1),
431 			      &opts);
432 	ASSERT_ERR(err, "bpf_link_update_fail");
433 
434 	/* BPF_F_REPLACE with a correct old map Fd. It should success! */
435 	opts.old_map_fd = bpf_map__fd(skel->maps.ca_update_2);
436 	err = bpf_link_update(bpf_link__fd(link),
437 			      bpf_map__fd(skel->maps.ca_update_1),
438 			      &opts);
439 	ASSERT_OK(err, "bpf_link_update_success");
440 
441 	bpf_link__destroy(link);
442 
443 	tcp_ca_update__destroy(skel);
444 }
445 
446 static void test_tcp_ca_kfunc(void)
447 {
448 	struct tcp_ca_kfunc *skel;
449 
450 	skel = tcp_ca_kfunc__open_and_load();
451 	ASSERT_OK_PTR(skel, "tcp_ca_kfunc__open_and_load");
452 	tcp_ca_kfunc__destroy(skel);
453 }
454 
455 void test_bpf_tcp_ca(void)
456 {
457 	if (test__start_subtest("dctcp"))
458 		test_dctcp();
459 	if (test__start_subtest("cubic"))
460 		test_cubic();
461 	if (test__start_subtest("invalid_license"))
462 		test_invalid_license();
463 	if (test__start_subtest("dctcp_fallback"))
464 		test_dctcp_fallback();
465 	if (test__start_subtest("rel_setsockopt"))
466 		test_rel_setsockopt();
467 	if (test__start_subtest("write_sk_pacing"))
468 		test_write_sk_pacing();
469 	if (test__start_subtest("incompl_cong_ops"))
470 		test_incompl_cong_ops();
471 	if (test__start_subtest("unsupp_cong_op"))
472 		test_unsupp_cong_op();
473 	if (test__start_subtest("update_ca"))
474 		test_update_ca();
475 	if (test__start_subtest("update_wrong"))
476 		test_update_wrong();
477 	if (test__start_subtest("mixed_links"))
478 		test_mixed_links();
479 	if (test__start_subtest("multi_links"))
480 		test_multi_links();
481 	if (test__start_subtest("link_replace"))
482 		test_link_replace();
483 	if (test__start_subtest("tcp_ca_kfunc"))
484 		test_tcp_ca_kfunc();
485 }
486