1 // SPDX-License-Identifier: GPL-2.0-only
2 
3 /*
4  * Copyright 2020 Google LLC.
5  */
6 
7 #include <test_progs.h>
8 #include <cgroup_helpers.h>
9 #include <network_helpers.h>
10 
11 #include "progs/cg_storage_multi.h"
12 
13 #include "cg_storage_multi_egress_only.skel.h"
14 #include "cg_storage_multi_isolated.skel.h"
15 #include "cg_storage_multi_shared.skel.h"
16 
17 #define PARENT_CGROUP "/cgroup_storage"
18 #define CHILD_CGROUP "/cgroup_storage/child"
19 
20 static int duration;
21 
22 static bool assert_storage(struct bpf_map *map, const void *key,
23 			   struct cgroup_value *expected)
24 {
25 	struct cgroup_value value;
26 	int map_fd;
27 
28 	map_fd = bpf_map__fd(map);
29 
30 	if (CHECK(bpf_map_lookup_elem(map_fd, key, &value) < 0,
31 		  "map-lookup", "errno %d", errno))
32 		return true;
33 	if (CHECK(memcmp(&value, expected, sizeof(struct cgroup_value)),
34 		  "assert-storage", "storages differ"))
35 		return true;
36 
37 	return false;
38 }
39 
40 static bool assert_storage_noexist(struct bpf_map *map, const void *key)
41 {
42 	struct cgroup_value value;
43 	int map_fd;
44 
45 	map_fd = bpf_map__fd(map);
46 
47 	if (CHECK(bpf_map_lookup_elem(map_fd, key, &value) == 0,
48 		  "map-lookup", "succeeded, expected ENOENT"))
49 		return true;
50 	if (CHECK(errno != ENOENT,
51 		  "map-lookup", "errno %d, expected ENOENT", errno))
52 		return true;
53 
54 	return false;
55 }
56 
57 static bool connect_send(const char *cgroup_path)
58 {
59 	bool res = true;
60 	int server_fd = -1, client_fd = -1;
61 
62 	if (join_cgroup(cgroup_path))
63 		goto out_clean;
64 
65 	server_fd = start_server(AF_INET, SOCK_DGRAM, NULL, 0, 0);
66 	if (server_fd < 0)
67 		goto out_clean;
68 
69 	client_fd = connect_to_fd(server_fd, 0);
70 	if (client_fd < 0)
71 		goto out_clean;
72 
73 	if (send(client_fd, "message", strlen("message"), 0) < 0)
74 		goto out_clean;
75 
76 	res = false;
77 
78 out_clean:
79 	close(client_fd);
80 	close(server_fd);
81 	return res;
82 }
83 
84 static void test_egress_only(int parent_cgroup_fd, int child_cgroup_fd)
85 {
86 	struct cg_storage_multi_egress_only *obj;
87 	struct cgroup_value expected_cgroup_value;
88 	struct bpf_cgroup_storage_key key;
89 	struct bpf_link *parent_link = NULL, *child_link = NULL;
90 	bool err;
91 
92 	key.attach_type = BPF_CGROUP_INET_EGRESS;
93 
94 	obj = cg_storage_multi_egress_only__open_and_load();
95 	if (CHECK(!obj, "skel-load", "errno %d", errno))
96 		return;
97 
98 	/* Attach to parent cgroup, trigger packet from child.
99 	 * Assert that there is only one run and in that run the storage is
100 	 * parent cgroup's storage.
101 	 * Also assert that child cgroup's storage does not exist
102 	 */
103 	parent_link = bpf_program__attach_cgroup(obj->progs.egress,
104 						 parent_cgroup_fd);
105 	if (CHECK(IS_ERR(parent_link), "parent-cg-attach",
106 		  "err %ld", PTR_ERR(parent_link)))
107 		goto close_bpf_object;
108 	err = connect_send(CHILD_CGROUP);
109 	if (CHECK(err, "first-connect-send", "errno %d", errno))
110 		goto close_bpf_object;
111 	if (CHECK(obj->bss->invocations != 1,
112 		  "first-invoke", "invocations=%d", obj->bss->invocations))
113 		goto close_bpf_object;
114 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
115 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 1 };
116 	if (assert_storage(obj->maps.cgroup_storage,
117 			   &key, &expected_cgroup_value))
118 		goto close_bpf_object;
119 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
120 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
121 		goto close_bpf_object;
122 
123 	/* Attach to parent and child cgroup, trigger packet from child.
124 	 * Assert that there are two additional runs, one that run with parent
125 	 * cgroup's storage and one with child cgroup's storage.
126 	 */
127 	child_link = bpf_program__attach_cgroup(obj->progs.egress,
128 						child_cgroup_fd);
129 	if (CHECK(IS_ERR(child_link), "child-cg-attach",
130 		  "err %ld", PTR_ERR(child_link)))
131 		goto close_bpf_object;
132 	err = connect_send(CHILD_CGROUP);
133 	if (CHECK(err, "second-connect-send", "errno %d", errno))
134 		goto close_bpf_object;
135 	if (CHECK(obj->bss->invocations != 3,
136 		  "second-invoke", "invocations=%d", obj->bss->invocations))
137 		goto close_bpf_object;
138 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
139 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 2 };
140 	if (assert_storage(obj->maps.cgroup_storage,
141 			   &key, &expected_cgroup_value))
142 		goto close_bpf_object;
143 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
144 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 1 };
145 	if (assert_storage(obj->maps.cgroup_storage,
146 			   &key, &expected_cgroup_value))
147 		goto close_bpf_object;
148 
149 close_bpf_object:
150 	if (!IS_ERR(parent_link))
151 		bpf_link__destroy(parent_link);
152 	if (!IS_ERR(child_link))
153 		bpf_link__destroy(child_link);
154 
155 	cg_storage_multi_egress_only__destroy(obj);
156 }
157 
158 static void test_isolated(int parent_cgroup_fd, int child_cgroup_fd)
159 {
160 	struct cg_storage_multi_isolated *obj;
161 	struct cgroup_value expected_cgroup_value;
162 	struct bpf_cgroup_storage_key key;
163 	struct bpf_link *parent_egress1_link = NULL, *parent_egress2_link = NULL;
164 	struct bpf_link *child_egress1_link = NULL, *child_egress2_link = NULL;
165 	struct bpf_link *parent_ingress_link = NULL, *child_ingress_link = NULL;
166 	bool err;
167 
168 	obj = cg_storage_multi_isolated__open_and_load();
169 	if (CHECK(!obj, "skel-load", "errno %d", errno))
170 		return;
171 
172 	/* Attach to parent cgroup, trigger packet from child.
173 	 * Assert that there is three runs, two with parent cgroup egress and
174 	 * one with parent cgroup ingress, stored in separate parent storages.
175 	 * Also assert that child cgroup's storages does not exist
176 	 */
177 	parent_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
178 							 parent_cgroup_fd);
179 	if (CHECK(IS_ERR(parent_egress1_link), "parent-egress1-cg-attach",
180 		  "err %ld", PTR_ERR(parent_egress1_link)))
181 		goto close_bpf_object;
182 	parent_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
183 							 parent_cgroup_fd);
184 	if (CHECK(IS_ERR(parent_egress2_link), "parent-egress2-cg-attach",
185 		  "err %ld", PTR_ERR(parent_egress2_link)))
186 		goto close_bpf_object;
187 	parent_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
188 							 parent_cgroup_fd);
189 	if (CHECK(IS_ERR(parent_ingress_link), "parent-ingress-cg-attach",
190 		  "err %ld", PTR_ERR(parent_ingress_link)))
191 		goto close_bpf_object;
192 	err = connect_send(CHILD_CGROUP);
193 	if (CHECK(err, "first-connect-send", "errno %d", errno))
194 		goto close_bpf_object;
195 	if (CHECK(obj->bss->invocations != 3,
196 		  "first-invoke", "invocations=%d", obj->bss->invocations))
197 		goto close_bpf_object;
198 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
199 	key.attach_type = BPF_CGROUP_INET_EGRESS;
200 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 2 };
201 	if (assert_storage(obj->maps.cgroup_storage,
202 			   &key, &expected_cgroup_value))
203 		goto close_bpf_object;
204 	key.attach_type = BPF_CGROUP_INET_INGRESS;
205 	expected_cgroup_value = (struct cgroup_value) { .ingress_pkts = 1 };
206 	if (assert_storage(obj->maps.cgroup_storage,
207 			   &key, &expected_cgroup_value))
208 		goto close_bpf_object;
209 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
210 	key.attach_type = BPF_CGROUP_INET_EGRESS;
211 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
212 		goto close_bpf_object;
213 	key.attach_type = BPF_CGROUP_INET_INGRESS;
214 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
215 		goto close_bpf_object;
216 
217 	/* Attach to parent and child cgroup, trigger packet from child.
218 	 * Assert that there is six additional runs, parent cgroup egresses and
219 	 * ingress, child cgroup egresses and ingress.
220 	 * Assert that egree and ingress storages are separate.
221 	 */
222 	child_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
223 							child_cgroup_fd);
224 	if (CHECK(IS_ERR(child_egress1_link), "child-egress1-cg-attach",
225 		  "err %ld", PTR_ERR(child_egress1_link)))
226 		goto close_bpf_object;
227 	child_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
228 							child_cgroup_fd);
229 	if (CHECK(IS_ERR(child_egress2_link), "child-egress2-cg-attach",
230 		  "err %ld", PTR_ERR(child_egress2_link)))
231 		goto close_bpf_object;
232 	child_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
233 							child_cgroup_fd);
234 	if (CHECK(IS_ERR(child_ingress_link), "child-ingress-cg-attach",
235 		  "err %ld", PTR_ERR(child_ingress_link)))
236 		goto close_bpf_object;
237 	err = connect_send(CHILD_CGROUP);
238 	if (CHECK(err, "second-connect-send", "errno %d", errno))
239 		goto close_bpf_object;
240 	if (CHECK(obj->bss->invocations != 9,
241 		  "second-invoke", "invocations=%d", obj->bss->invocations))
242 		goto close_bpf_object;
243 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
244 	key.attach_type = BPF_CGROUP_INET_EGRESS;
245 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 4 };
246 	if (assert_storage(obj->maps.cgroup_storage,
247 			   &key, &expected_cgroup_value))
248 		goto close_bpf_object;
249 	key.attach_type = BPF_CGROUP_INET_INGRESS;
250 	expected_cgroup_value = (struct cgroup_value) { .ingress_pkts = 2 };
251 	if (assert_storage(obj->maps.cgroup_storage,
252 			   &key, &expected_cgroup_value))
253 		goto close_bpf_object;
254 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
255 	key.attach_type = BPF_CGROUP_INET_EGRESS;
256 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 2 };
257 	if (assert_storage(obj->maps.cgroup_storage,
258 			   &key, &expected_cgroup_value))
259 		goto close_bpf_object;
260 	key.attach_type = BPF_CGROUP_INET_INGRESS;
261 	expected_cgroup_value = (struct cgroup_value) { .ingress_pkts = 1 };
262 	if (assert_storage(obj->maps.cgroup_storage,
263 			   &key, &expected_cgroup_value))
264 		goto close_bpf_object;
265 
266 close_bpf_object:
267 	if (!IS_ERR(parent_egress1_link))
268 		bpf_link__destroy(parent_egress1_link);
269 	if (!IS_ERR(parent_egress2_link))
270 		bpf_link__destroy(parent_egress2_link);
271 	if (!IS_ERR(parent_ingress_link))
272 		bpf_link__destroy(parent_ingress_link);
273 	if (!IS_ERR(child_egress1_link))
274 		bpf_link__destroy(child_egress1_link);
275 	if (!IS_ERR(child_egress2_link))
276 		bpf_link__destroy(child_egress2_link);
277 	if (!IS_ERR(child_ingress_link))
278 		bpf_link__destroy(child_ingress_link);
279 
280 	cg_storage_multi_isolated__destroy(obj);
281 }
282 
283 static void test_shared(int parent_cgroup_fd, int child_cgroup_fd)
284 {
285 	struct cg_storage_multi_shared *obj;
286 	struct cgroup_value expected_cgroup_value;
287 	__u64 key;
288 	struct bpf_link *parent_egress1_link = NULL, *parent_egress2_link = NULL;
289 	struct bpf_link *child_egress1_link = NULL, *child_egress2_link = NULL;
290 	struct bpf_link *parent_ingress_link = NULL, *child_ingress_link = NULL;
291 	bool err;
292 
293 	obj = cg_storage_multi_shared__open_and_load();
294 	if (CHECK(!obj, "skel-load", "errno %d", errno))
295 		return;
296 
297 	/* Attach to parent cgroup, trigger packet from child.
298 	 * Assert that there is three runs, two with parent cgroup egress and
299 	 * one with parent cgroup ingress.
300 	 * Also assert that child cgroup's storage does not exist
301 	 */
302 	parent_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
303 							 parent_cgroup_fd);
304 	if (CHECK(IS_ERR(parent_egress1_link), "parent-egress1-cg-attach",
305 		  "err %ld", PTR_ERR(parent_egress1_link)))
306 		goto close_bpf_object;
307 	parent_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
308 							 parent_cgroup_fd);
309 	if (CHECK(IS_ERR(parent_egress2_link), "parent-egress2-cg-attach",
310 		  "err %ld", PTR_ERR(parent_egress2_link)))
311 		goto close_bpf_object;
312 	parent_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
313 							 parent_cgroup_fd);
314 	if (CHECK(IS_ERR(parent_ingress_link), "parent-ingress-cg-attach",
315 		  "err %ld", PTR_ERR(parent_ingress_link)))
316 		goto close_bpf_object;
317 	err = connect_send(CHILD_CGROUP);
318 	if (CHECK(err, "first-connect-send", "errno %d", errno))
319 		goto close_bpf_object;
320 	if (CHECK(obj->bss->invocations != 3,
321 		  "first-invoke", "invocations=%d", obj->bss->invocations))
322 		goto close_bpf_object;
323 	key = get_cgroup_id(PARENT_CGROUP);
324 	expected_cgroup_value = (struct cgroup_value) {
325 		.egress_pkts = 2,
326 		.ingress_pkts = 1,
327 	};
328 	if (assert_storage(obj->maps.cgroup_storage,
329 			   &key, &expected_cgroup_value))
330 		goto close_bpf_object;
331 	key = get_cgroup_id(CHILD_CGROUP);
332 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
333 		goto close_bpf_object;
334 
335 	/* Attach to parent and child cgroup, trigger packet from child.
336 	 * Assert that there is six additional runs, parent cgroup egresses and
337 	 * ingress, child cgroup egresses and ingress.
338 	 */
339 	child_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
340 							child_cgroup_fd);
341 	if (CHECK(IS_ERR(child_egress1_link), "child-egress1-cg-attach",
342 		  "err %ld", PTR_ERR(child_egress1_link)))
343 		goto close_bpf_object;
344 	child_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
345 							child_cgroup_fd);
346 	if (CHECK(IS_ERR(child_egress2_link), "child-egress2-cg-attach",
347 		  "err %ld", PTR_ERR(child_egress2_link)))
348 		goto close_bpf_object;
349 	child_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
350 							child_cgroup_fd);
351 	if (CHECK(IS_ERR(child_ingress_link), "child-ingress-cg-attach",
352 		  "err %ld", PTR_ERR(child_ingress_link)))
353 		goto close_bpf_object;
354 	err = connect_send(CHILD_CGROUP);
355 	if (CHECK(err, "second-connect-send", "errno %d", errno))
356 		goto close_bpf_object;
357 	if (CHECK(obj->bss->invocations != 9,
358 		  "second-invoke", "invocations=%d", obj->bss->invocations))
359 		goto close_bpf_object;
360 	key = get_cgroup_id(PARENT_CGROUP);
361 	expected_cgroup_value = (struct cgroup_value) {
362 		.egress_pkts = 4,
363 		.ingress_pkts = 2,
364 	};
365 	if (assert_storage(obj->maps.cgroup_storage,
366 			   &key, &expected_cgroup_value))
367 		goto close_bpf_object;
368 	key = get_cgroup_id(CHILD_CGROUP);
369 	expected_cgroup_value = (struct cgroup_value) {
370 		.egress_pkts = 2,
371 		.ingress_pkts = 1,
372 	};
373 	if (assert_storage(obj->maps.cgroup_storage,
374 			   &key, &expected_cgroup_value))
375 		goto close_bpf_object;
376 
377 close_bpf_object:
378 	if (!IS_ERR(parent_egress1_link))
379 		bpf_link__destroy(parent_egress1_link);
380 	if (!IS_ERR(parent_egress2_link))
381 		bpf_link__destroy(parent_egress2_link);
382 	if (!IS_ERR(parent_ingress_link))
383 		bpf_link__destroy(parent_ingress_link);
384 	if (!IS_ERR(child_egress1_link))
385 		bpf_link__destroy(child_egress1_link);
386 	if (!IS_ERR(child_egress2_link))
387 		bpf_link__destroy(child_egress2_link);
388 	if (!IS_ERR(child_ingress_link))
389 		bpf_link__destroy(child_ingress_link);
390 
391 	cg_storage_multi_shared__destroy(obj);
392 }
393 
394 void test_cg_storage_multi(void)
395 {
396 	int parent_cgroup_fd = -1, child_cgroup_fd = -1;
397 
398 	parent_cgroup_fd = test__join_cgroup(PARENT_CGROUP);
399 	if (CHECK(parent_cgroup_fd < 0, "cg-create-parent", "errno %d", errno))
400 		goto close_cgroup_fd;
401 	child_cgroup_fd = create_and_get_cgroup(CHILD_CGROUP);
402 	if (CHECK(child_cgroup_fd < 0, "cg-create-child", "errno %d", errno))
403 		goto close_cgroup_fd;
404 
405 	if (test__start_subtest("egress_only"))
406 		test_egress_only(parent_cgroup_fd, child_cgroup_fd);
407 
408 	if (test__start_subtest("isolated"))
409 		test_isolated(parent_cgroup_fd, child_cgroup_fd);
410 
411 	if (test__start_subtest("shared"))
412 		test_shared(parent_cgroup_fd, child_cgroup_fd);
413 
414 close_cgroup_fd:
415 	close(child_cgroup_fd);
416 	close(parent_cgroup_fd);
417 }
418