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