xref: /linux/tools/testing/selftests/bpf/prog_tests/cgroup_iter.c (revision 6e7fd890f1d6ac83805409e9c346240de2705584)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2022 Google */
3 
4 #include <test_progs.h>
5 #include <bpf/libbpf.h>
6 #include <bpf/btf.h>
7 #include "iters_css_task.skel.h"
8 #include "cgroup_iter.skel.h"
9 #include "cgroup_helpers.h"
10 
11 #define ROOT           0
12 #define PARENT         1
13 #define CHILD1         2
14 #define CHILD2         3
15 #define NUM_CGROUPS    4
16 
17 #define PROLOGUE       "prologue\n"
18 #define EPILOGUE       "epilogue\n"
19 
20 static const char *cg_path[] = {
21 	"/", "/parent", "/parent/child1", "/parent/child2"
22 };
23 
24 static int cg_fd[] = {-1, -1, -1, -1};
25 static unsigned long long cg_id[] = {0, 0, 0, 0};
26 static char expected_output[64];
27 
28 static int setup_cgroups(void)
29 {
30 	int fd, i = 0;
31 
32 	for (i = 0; i < NUM_CGROUPS; i++) {
33 		fd = create_and_get_cgroup(cg_path[i]);
34 		if (fd < 0)
35 			return fd;
36 
37 		cg_fd[i] = fd;
38 		cg_id[i] = get_cgroup_id(cg_path[i]);
39 	}
40 	return 0;
41 }
42 
43 static void cleanup_cgroups(void)
44 {
45 	int i;
46 
47 	for (i = 0; i < NUM_CGROUPS; i++)
48 		close(cg_fd[i]);
49 }
50 
51 static void read_from_cgroup_iter(struct bpf_program *prog, int cgroup_fd,
52 				  int order, const char *testname)
53 {
54 	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
55 	union bpf_iter_link_info linfo;
56 	struct bpf_link *link;
57 	int len, iter_fd;
58 	static char buf[128];
59 	size_t left;
60 	char *p;
61 
62 	memset(&linfo, 0, sizeof(linfo));
63 	linfo.cgroup.cgroup_fd = cgroup_fd;
64 	linfo.cgroup.order = order;
65 	opts.link_info = &linfo;
66 	opts.link_info_len = sizeof(linfo);
67 
68 	link = bpf_program__attach_iter(prog, &opts);
69 	if (!ASSERT_OK_PTR(link, "attach_iter"))
70 		return;
71 
72 	iter_fd = bpf_iter_create(bpf_link__fd(link));
73 	if (iter_fd < 0)
74 		goto free_link;
75 
76 	memset(buf, 0, sizeof(buf));
77 	left = ARRAY_SIZE(buf);
78 	p = buf;
79 	while ((len = read(iter_fd, p, left)) > 0) {
80 		p += len;
81 		left -= len;
82 	}
83 
84 	ASSERT_STREQ(buf, expected_output, testname);
85 
86 	/* read() after iter finishes should be ok. */
87 	if (len == 0)
88 		ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read");
89 
90 	close(iter_fd);
91 free_link:
92 	bpf_link__destroy(link);
93 }
94 
95 /* Invalid cgroup. */
96 static void test_invalid_cgroup(struct cgroup_iter *skel)
97 {
98 	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
99 	union bpf_iter_link_info linfo;
100 	struct bpf_link *link;
101 
102 	memset(&linfo, 0, sizeof(linfo));
103 	linfo.cgroup.cgroup_fd = (__u32)-1;
104 	opts.link_info = &linfo;
105 	opts.link_info_len = sizeof(linfo);
106 
107 	link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
108 	ASSERT_ERR_PTR(link, "attach_iter");
109 	bpf_link__destroy(link);
110 }
111 
112 /* Specifying both cgroup_fd and cgroup_id is invalid. */
113 static void test_invalid_cgroup_spec(struct cgroup_iter *skel)
114 {
115 	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
116 	union bpf_iter_link_info linfo;
117 	struct bpf_link *link;
118 
119 	memset(&linfo, 0, sizeof(linfo));
120 	linfo.cgroup.cgroup_fd = (__u32)cg_fd[PARENT];
121 	linfo.cgroup.cgroup_id = (__u64)cg_id[PARENT];
122 	opts.link_info = &linfo;
123 	opts.link_info_len = sizeof(linfo);
124 
125 	link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
126 	ASSERT_ERR_PTR(link, "attach_iter");
127 	bpf_link__destroy(link);
128 }
129 
130 /* Preorder walk prints parent and child in order. */
131 static void test_walk_preorder(struct cgroup_iter *skel)
132 {
133 	snprintf(expected_output, sizeof(expected_output),
134 		 PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE,
135 		 cg_id[PARENT], cg_id[CHILD1], cg_id[CHILD2]);
136 
137 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
138 			      BPF_CGROUP_ITER_DESCENDANTS_PRE, "preorder");
139 }
140 
141 /* Postorder walk prints child and parent in order. */
142 static void test_walk_postorder(struct cgroup_iter *skel)
143 {
144 	snprintf(expected_output, sizeof(expected_output),
145 		 PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE,
146 		 cg_id[CHILD1], cg_id[CHILD2], cg_id[PARENT]);
147 
148 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
149 			      BPF_CGROUP_ITER_DESCENDANTS_POST, "postorder");
150 }
151 
152 /* Walking parents prints parent and then root. */
153 static void test_walk_ancestors_up(struct cgroup_iter *skel)
154 {
155 	/* terminate the walk when ROOT is met. */
156 	skel->bss->terminal_cgroup = cg_id[ROOT];
157 
158 	snprintf(expected_output, sizeof(expected_output),
159 		 PROLOGUE "%8llu\n%8llu\n" EPILOGUE,
160 		 cg_id[PARENT], cg_id[ROOT]);
161 
162 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
163 			      BPF_CGROUP_ITER_ANCESTORS_UP, "ancestors_up");
164 
165 	skel->bss->terminal_cgroup = 0;
166 }
167 
168 /* Early termination prints parent only. */
169 static void test_early_termination(struct cgroup_iter *skel)
170 {
171 	/* terminate the walk after the first element is processed. */
172 	skel->bss->terminate_early = 1;
173 
174 	snprintf(expected_output, sizeof(expected_output),
175 		 PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]);
176 
177 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
178 			      BPF_CGROUP_ITER_DESCENDANTS_PRE, "early_termination");
179 
180 	skel->bss->terminate_early = 0;
181 }
182 
183 /* Waling self prints self only. */
184 static void test_walk_self_only(struct cgroup_iter *skel)
185 {
186 	snprintf(expected_output, sizeof(expected_output),
187 		 PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]);
188 
189 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
190 			      BPF_CGROUP_ITER_SELF_ONLY, "self_only");
191 }
192 
193 static void test_walk_dead_self_only(struct cgroup_iter *skel)
194 {
195 	DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
196 	char expected_output[128], buf[128];
197 	const char *cgrp_name = "/dead";
198 	union bpf_iter_link_info linfo;
199 	int len, cgrp_fd, iter_fd;
200 	struct bpf_link *link;
201 	size_t left;
202 	char *p;
203 
204 	cgrp_fd = create_and_get_cgroup(cgrp_name);
205 	if (!ASSERT_GE(cgrp_fd, 0, "create cgrp"))
206 		return;
207 
208 	/* The cgroup will be dead during read() iteration, so it only has
209 	 * epilogue in the output
210 	 */
211 	snprintf(expected_output, sizeof(expected_output), EPILOGUE);
212 
213 	memset(&linfo, 0, sizeof(linfo));
214 	linfo.cgroup.cgroup_fd = cgrp_fd;
215 	linfo.cgroup.order = BPF_CGROUP_ITER_SELF_ONLY;
216 	opts.link_info = &linfo;
217 	opts.link_info_len = sizeof(linfo);
218 
219 	link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
220 	if (!ASSERT_OK_PTR(link, "attach_iter"))
221 		goto close_cgrp;
222 
223 	iter_fd = bpf_iter_create(bpf_link__fd(link));
224 	if (!ASSERT_GE(iter_fd, 0, "iter_create"))
225 		goto free_link;
226 
227 	/* Close link fd and cgroup fd */
228 	bpf_link__destroy(link);
229 	close(cgrp_fd);
230 
231 	/* Remove cgroup to mark it as dead */
232 	remove_cgroup(cgrp_name);
233 
234 	/* Two kern_sync_rcu() and usleep() pairs are used to wait for the
235 	 * releases of cgroup css, and the last kern_sync_rcu() and usleep()
236 	 * pair is used to wait for the free of cgroup itself.
237 	 */
238 	kern_sync_rcu();
239 	usleep(8000);
240 	kern_sync_rcu();
241 	usleep(8000);
242 	kern_sync_rcu();
243 	usleep(1000);
244 
245 	memset(buf, 0, sizeof(buf));
246 	left = ARRAY_SIZE(buf);
247 	p = buf;
248 	while ((len = read(iter_fd, p, left)) > 0) {
249 		p += len;
250 		left -= len;
251 	}
252 
253 	ASSERT_STREQ(buf, expected_output, "dead cgroup output");
254 
255 	/* read() after iter finishes should be ok. */
256 	if (len == 0)
257 		ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read");
258 
259 	close(iter_fd);
260 	return;
261 free_link:
262 	bpf_link__destroy(link);
263 close_cgrp:
264 	close(cgrp_fd);
265 }
266 
267 static void test_walk_self_only_css_task(void)
268 {
269 	struct iters_css_task *skel;
270 	int err;
271 
272 	skel = iters_css_task__open();
273 	if (!ASSERT_OK_PTR(skel, "skel_open"))
274 		return;
275 
276 	bpf_program__set_autoload(skel->progs.cgroup_id_printer, true);
277 
278 	err = iters_css_task__load(skel);
279 	if (!ASSERT_OK(err, "skel_load"))
280 		goto cleanup;
281 
282 	err = join_cgroup(cg_path[CHILD2]);
283 	if (!ASSERT_OK(err, "join_cgroup"))
284 		goto cleanup;
285 
286 	skel->bss->target_pid = getpid();
287 	snprintf(expected_output, sizeof(expected_output),
288 		PROLOGUE "%8llu\n" EPILOGUE, cg_id[CHILD2]);
289 	read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[CHILD2],
290 		BPF_CGROUP_ITER_SELF_ONLY, "test_walk_self_only_css_task");
291 	ASSERT_EQ(skel->bss->css_task_cnt, 1, "css_task_cnt");
292 cleanup:
293 	iters_css_task__destroy(skel);
294 }
295 
296 void test_cgroup_iter(void)
297 {
298 	struct cgroup_iter *skel = NULL;
299 
300 	if (setup_cgroup_environment())
301 		return;
302 
303 	if (setup_cgroups())
304 		goto out;
305 
306 	skel = cgroup_iter__open_and_load();
307 	if (!ASSERT_OK_PTR(skel, "cgroup_iter__open_and_load"))
308 		goto out;
309 
310 	if (test__start_subtest("cgroup_iter__invalid_cgroup"))
311 		test_invalid_cgroup(skel);
312 	if (test__start_subtest("cgroup_iter__invalid_cgroup_spec"))
313 		test_invalid_cgroup_spec(skel);
314 	if (test__start_subtest("cgroup_iter__preorder"))
315 		test_walk_preorder(skel);
316 	if (test__start_subtest("cgroup_iter__postorder"))
317 		test_walk_postorder(skel);
318 	if (test__start_subtest("cgroup_iter__ancestors_up_walk"))
319 		test_walk_ancestors_up(skel);
320 	if (test__start_subtest("cgroup_iter__early_termination"))
321 		test_early_termination(skel);
322 	if (test__start_subtest("cgroup_iter__self_only"))
323 		test_walk_self_only(skel);
324 	if (test__start_subtest("cgroup_iter__dead_self_only"))
325 		test_walk_dead_self_only(skel);
326 	if (test__start_subtest("cgroup_iter__self_only_css_task"))
327 		test_walk_self_only_css_task();
328 
329 out:
330 	cgroup_iter__destroy(skel);
331 	cleanup_cgroups();
332 	cleanup_cgroup_environment();
333 }
334