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 (!ASSERT_OK_PTR(parent_link, "parent-cg-attach"))
106 		goto close_bpf_object;
107 	err = connect_send(CHILD_CGROUP);
108 	if (CHECK(err, "first-connect-send", "errno %d", errno))
109 		goto close_bpf_object;
110 	if (CHECK(obj->bss->invocations != 1,
111 		  "first-invoke", "invocations=%d", obj->bss->invocations))
112 		goto close_bpf_object;
113 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
114 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 1 };
115 	if (assert_storage(obj->maps.cgroup_storage,
116 			   &key, &expected_cgroup_value))
117 		goto close_bpf_object;
118 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
119 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
120 		goto close_bpf_object;
121 
122 	/* Attach to parent and child cgroup, trigger packet from child.
123 	 * Assert that there are two additional runs, one that run with parent
124 	 * cgroup's storage and one with child cgroup's storage.
125 	 */
126 	child_link = bpf_program__attach_cgroup(obj->progs.egress,
127 						child_cgroup_fd);
128 	if (!ASSERT_OK_PTR(child_link, "child-cg-attach"))
129 		goto close_bpf_object;
130 	err = connect_send(CHILD_CGROUP);
131 	if (CHECK(err, "second-connect-send", "errno %d", errno))
132 		goto close_bpf_object;
133 	if (CHECK(obj->bss->invocations != 3,
134 		  "second-invoke", "invocations=%d", obj->bss->invocations))
135 		goto close_bpf_object;
136 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
137 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 2 };
138 	if (assert_storage(obj->maps.cgroup_storage,
139 			   &key, &expected_cgroup_value))
140 		goto close_bpf_object;
141 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
142 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 1 };
143 	if (assert_storage(obj->maps.cgroup_storage,
144 			   &key, &expected_cgroup_value))
145 		goto close_bpf_object;
146 
147 close_bpf_object:
148 	bpf_link__destroy(parent_link);
149 	bpf_link__destroy(child_link);
150 
151 	cg_storage_multi_egress_only__destroy(obj);
152 }
153 
154 static void test_isolated(int parent_cgroup_fd, int child_cgroup_fd)
155 {
156 	struct cg_storage_multi_isolated *obj;
157 	struct cgroup_value expected_cgroup_value;
158 	struct bpf_cgroup_storage_key key;
159 	struct bpf_link *parent_egress1_link = NULL, *parent_egress2_link = NULL;
160 	struct bpf_link *child_egress1_link = NULL, *child_egress2_link = NULL;
161 	struct bpf_link *parent_ingress_link = NULL, *child_ingress_link = NULL;
162 	bool err;
163 
164 	obj = cg_storage_multi_isolated__open_and_load();
165 	if (CHECK(!obj, "skel-load", "errno %d", errno))
166 		return;
167 
168 	/* Attach to parent cgroup, trigger packet from child.
169 	 * Assert that there is three runs, two with parent cgroup egress and
170 	 * one with parent cgroup ingress, stored in separate parent storages.
171 	 * Also assert that child cgroup's storages does not exist
172 	 */
173 	parent_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
174 							 parent_cgroup_fd);
175 	if (!ASSERT_OK_PTR(parent_egress1_link, "parent-egress1-cg-attach"))
176 		goto close_bpf_object;
177 	parent_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
178 							 parent_cgroup_fd);
179 	if (!ASSERT_OK_PTR(parent_egress2_link, "parent-egress2-cg-attach"))
180 		goto close_bpf_object;
181 	parent_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
182 							 parent_cgroup_fd);
183 	if (!ASSERT_OK_PTR(parent_ingress_link, "parent-ingress-cg-attach"))
184 		goto close_bpf_object;
185 	err = connect_send(CHILD_CGROUP);
186 	if (CHECK(err, "first-connect-send", "errno %d", errno))
187 		goto close_bpf_object;
188 	if (CHECK(obj->bss->invocations != 3,
189 		  "first-invoke", "invocations=%d", obj->bss->invocations))
190 		goto close_bpf_object;
191 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
192 	key.attach_type = BPF_CGROUP_INET_EGRESS;
193 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 2 };
194 	if (assert_storage(obj->maps.cgroup_storage,
195 			   &key, &expected_cgroup_value))
196 		goto close_bpf_object;
197 	key.attach_type = BPF_CGROUP_INET_INGRESS;
198 	expected_cgroup_value = (struct cgroup_value) { .ingress_pkts = 1 };
199 	if (assert_storage(obj->maps.cgroup_storage,
200 			   &key, &expected_cgroup_value))
201 		goto close_bpf_object;
202 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
203 	key.attach_type = BPF_CGROUP_INET_EGRESS;
204 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
205 		goto close_bpf_object;
206 	key.attach_type = BPF_CGROUP_INET_INGRESS;
207 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
208 		goto close_bpf_object;
209 
210 	/* Attach to parent and child cgroup, trigger packet from child.
211 	 * Assert that there is six additional runs, parent cgroup egresses and
212 	 * ingress, child cgroup egresses and ingress.
213 	 * Assert that egree and ingress storages are separate.
214 	 */
215 	child_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
216 							child_cgroup_fd);
217 	if (!ASSERT_OK_PTR(child_egress1_link, "child-egress1-cg-attach"))
218 		goto close_bpf_object;
219 	child_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
220 							child_cgroup_fd);
221 	if (!ASSERT_OK_PTR(child_egress2_link, "child-egress2-cg-attach"))
222 		goto close_bpf_object;
223 	child_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
224 							child_cgroup_fd);
225 	if (!ASSERT_OK_PTR(child_ingress_link, "child-ingress-cg-attach"))
226 		goto close_bpf_object;
227 	err = connect_send(CHILD_CGROUP);
228 	if (CHECK(err, "second-connect-send", "errno %d", errno))
229 		goto close_bpf_object;
230 	if (CHECK(obj->bss->invocations != 9,
231 		  "second-invoke", "invocations=%d", obj->bss->invocations))
232 		goto close_bpf_object;
233 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
234 	key.attach_type = BPF_CGROUP_INET_EGRESS;
235 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 4 };
236 	if (assert_storage(obj->maps.cgroup_storage,
237 			   &key, &expected_cgroup_value))
238 		goto close_bpf_object;
239 	key.attach_type = BPF_CGROUP_INET_INGRESS;
240 	expected_cgroup_value = (struct cgroup_value) { .ingress_pkts = 2 };
241 	if (assert_storage(obj->maps.cgroup_storage,
242 			   &key, &expected_cgroup_value))
243 		goto close_bpf_object;
244 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
245 	key.attach_type = BPF_CGROUP_INET_EGRESS;
246 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 2 };
247 	if (assert_storage(obj->maps.cgroup_storage,
248 			   &key, &expected_cgroup_value))
249 		goto close_bpf_object;
250 	key.attach_type = BPF_CGROUP_INET_INGRESS;
251 	expected_cgroup_value = (struct cgroup_value) { .ingress_pkts = 1 };
252 	if (assert_storage(obj->maps.cgroup_storage,
253 			   &key, &expected_cgroup_value))
254 		goto close_bpf_object;
255 
256 close_bpf_object:
257 	bpf_link__destroy(parent_egress1_link);
258 	bpf_link__destroy(parent_egress2_link);
259 	bpf_link__destroy(parent_ingress_link);
260 	bpf_link__destroy(child_egress1_link);
261 	bpf_link__destroy(child_egress2_link);
262 	bpf_link__destroy(child_ingress_link);
263 
264 	cg_storage_multi_isolated__destroy(obj);
265 }
266 
267 static void test_shared(int parent_cgroup_fd, int child_cgroup_fd)
268 {
269 	struct cg_storage_multi_shared *obj;
270 	struct cgroup_value expected_cgroup_value;
271 	__u64 key;
272 	struct bpf_link *parent_egress1_link = NULL, *parent_egress2_link = NULL;
273 	struct bpf_link *child_egress1_link = NULL, *child_egress2_link = NULL;
274 	struct bpf_link *parent_ingress_link = NULL, *child_ingress_link = NULL;
275 	bool err;
276 
277 	obj = cg_storage_multi_shared__open_and_load();
278 	if (CHECK(!obj, "skel-load", "errno %d", errno))
279 		return;
280 
281 	/* Attach to parent cgroup, trigger packet from child.
282 	 * Assert that there is three runs, two with parent cgroup egress and
283 	 * one with parent cgroup ingress.
284 	 * Also assert that child cgroup's storage does not exist
285 	 */
286 	parent_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
287 							 parent_cgroup_fd);
288 	if (!ASSERT_OK_PTR(parent_egress1_link, "parent-egress1-cg-attach"))
289 		goto close_bpf_object;
290 	parent_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
291 							 parent_cgroup_fd);
292 	if (!ASSERT_OK_PTR(parent_egress2_link, "parent-egress2-cg-attach"))
293 		goto close_bpf_object;
294 	parent_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
295 							 parent_cgroup_fd);
296 	if (!ASSERT_OK_PTR(parent_ingress_link, "parent-ingress-cg-attach"))
297 		goto close_bpf_object;
298 	err = connect_send(CHILD_CGROUP);
299 	if (CHECK(err, "first-connect-send", "errno %d", errno))
300 		goto close_bpf_object;
301 	if (CHECK(obj->bss->invocations != 3,
302 		  "first-invoke", "invocations=%d", obj->bss->invocations))
303 		goto close_bpf_object;
304 	key = get_cgroup_id(PARENT_CGROUP);
305 	expected_cgroup_value = (struct cgroup_value) {
306 		.egress_pkts = 2,
307 		.ingress_pkts = 1,
308 	};
309 	if (assert_storage(obj->maps.cgroup_storage,
310 			   &key, &expected_cgroup_value))
311 		goto close_bpf_object;
312 	key = get_cgroup_id(CHILD_CGROUP);
313 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
314 		goto close_bpf_object;
315 
316 	/* Attach to parent and child cgroup, trigger packet from child.
317 	 * Assert that there is six additional runs, parent cgroup egresses and
318 	 * ingress, child cgroup egresses and ingress.
319 	 */
320 	child_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
321 							child_cgroup_fd);
322 	if (!ASSERT_OK_PTR(child_egress1_link, "child-egress1-cg-attach"))
323 		goto close_bpf_object;
324 	child_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
325 							child_cgroup_fd);
326 	if (!ASSERT_OK_PTR(child_egress2_link, "child-egress2-cg-attach"))
327 		goto close_bpf_object;
328 	child_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
329 							child_cgroup_fd);
330 	if (!ASSERT_OK_PTR(child_ingress_link, "child-ingress-cg-attach"))
331 		goto close_bpf_object;
332 	err = connect_send(CHILD_CGROUP);
333 	if (CHECK(err, "second-connect-send", "errno %d", errno))
334 		goto close_bpf_object;
335 	if (CHECK(obj->bss->invocations != 9,
336 		  "second-invoke", "invocations=%d", obj->bss->invocations))
337 		goto close_bpf_object;
338 	key = get_cgroup_id(PARENT_CGROUP);
339 	expected_cgroup_value = (struct cgroup_value) {
340 		.egress_pkts = 4,
341 		.ingress_pkts = 2,
342 	};
343 	if (assert_storage(obj->maps.cgroup_storage,
344 			   &key, &expected_cgroup_value))
345 		goto close_bpf_object;
346 	key = get_cgroup_id(CHILD_CGROUP);
347 	expected_cgroup_value = (struct cgroup_value) {
348 		.egress_pkts = 2,
349 		.ingress_pkts = 1,
350 	};
351 	if (assert_storage(obj->maps.cgroup_storage,
352 			   &key, &expected_cgroup_value))
353 		goto close_bpf_object;
354 
355 close_bpf_object:
356 	bpf_link__destroy(parent_egress1_link);
357 	bpf_link__destroy(parent_egress2_link);
358 	bpf_link__destroy(parent_ingress_link);
359 	bpf_link__destroy(child_egress1_link);
360 	bpf_link__destroy(child_egress2_link);
361 	bpf_link__destroy(child_ingress_link);
362 
363 	cg_storage_multi_shared__destroy(obj);
364 }
365 
366 void serial_test_cg_storage_multi(void)
367 {
368 	int parent_cgroup_fd = -1, child_cgroup_fd = -1;
369 
370 	parent_cgroup_fd = test__join_cgroup(PARENT_CGROUP);
371 	if (CHECK(parent_cgroup_fd < 0, "cg-create-parent", "errno %d", errno))
372 		goto close_cgroup_fd;
373 	child_cgroup_fd = create_and_get_cgroup(CHILD_CGROUP);
374 	if (CHECK(child_cgroup_fd < 0, "cg-create-child", "errno %d", errno))
375 		goto close_cgroup_fd;
376 
377 	if (test__start_subtest("egress_only"))
378 		test_egress_only(parent_cgroup_fd, child_cgroup_fd);
379 
380 	if (test__start_subtest("isolated"))
381 		test_isolated(parent_cgroup_fd, child_cgroup_fd);
382 
383 	if (test__start_subtest("shared"))
384 		test_shared(parent_cgroup_fd, child_cgroup_fd);
385 
386 close_cgroup_fd:
387 	close(child_cgroup_fd);
388 	close(parent_cgroup_fd);
389 }
390