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 
assert_storage(struct bpf_map * map,const void * key,struct cgroup_value * expected)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 
assert_storage_noexist(struct bpf_map * map,const void * key)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 
connect_send(const char * cgroup_path)57 static bool connect_send(const char *cgroup_path)
58 {
59 	int server_fd = -1, client_fd = -1;
60 	char message[] = "message";
61 	bool res = true;
62 
63 	if (join_cgroup(cgroup_path))
64 		goto out_clean;
65 
66 	server_fd = start_server(AF_INET, SOCK_DGRAM, NULL, 0, 0);
67 	if (server_fd < 0)
68 		goto out_clean;
69 
70 	client_fd = connect_to_fd(server_fd, 0);
71 	if (client_fd < 0)
72 		goto out_clean;
73 
74 	if (send(client_fd, &message, sizeof(message), 0) < 0)
75 		goto out_clean;
76 
77 	if (read(server_fd, &message, sizeof(message)) < 0)
78 		goto out_clean;
79 
80 	res = false;
81 
82 out_clean:
83 	close(client_fd);
84 	close(server_fd);
85 	return res;
86 }
87 
test_egress_only(int parent_cgroup_fd,int child_cgroup_fd)88 static void test_egress_only(int parent_cgroup_fd, int child_cgroup_fd)
89 {
90 	struct cg_storage_multi_egress_only *obj;
91 	struct cgroup_value expected_cgroup_value;
92 	struct bpf_cgroup_storage_key key;
93 	struct bpf_link *parent_link = NULL, *child_link = NULL;
94 	bool err;
95 
96 	key.attach_type = BPF_CGROUP_INET_EGRESS;
97 
98 	obj = cg_storage_multi_egress_only__open_and_load();
99 	if (CHECK(!obj, "skel-load", "errno %d", errno))
100 		return;
101 
102 	/* Attach to parent cgroup, trigger packet from child.
103 	 * Assert that there is only one run and in that run the storage is
104 	 * parent cgroup's storage.
105 	 * Also assert that child cgroup's storage does not exist
106 	 */
107 	parent_link = bpf_program__attach_cgroup(obj->progs.egress,
108 						 parent_cgroup_fd);
109 	if (!ASSERT_OK_PTR(parent_link, "parent-cg-attach"))
110 		goto close_bpf_object;
111 	err = connect_send(CHILD_CGROUP);
112 	if (CHECK(err, "first-connect-send", "errno %d", errno))
113 		goto close_bpf_object;
114 	if (CHECK(obj->bss->invocations != 1,
115 		  "first-invoke", "invocations=%d", obj->bss->invocations))
116 		goto close_bpf_object;
117 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
118 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 1 };
119 	if (assert_storage(obj->maps.cgroup_storage,
120 			   &key, &expected_cgroup_value))
121 		goto close_bpf_object;
122 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
123 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
124 		goto close_bpf_object;
125 
126 	/* Attach to parent and child cgroup, trigger packet from child.
127 	 * Assert that there are two additional runs, one that run with parent
128 	 * cgroup's storage and one with child cgroup's storage.
129 	 */
130 	child_link = bpf_program__attach_cgroup(obj->progs.egress,
131 						child_cgroup_fd);
132 	if (!ASSERT_OK_PTR(child_link, "child-cg-attach"))
133 		goto close_bpf_object;
134 	err = connect_send(CHILD_CGROUP);
135 	if (CHECK(err, "second-connect-send", "errno %d", errno))
136 		goto close_bpf_object;
137 	if (CHECK(obj->bss->invocations != 3,
138 		  "second-invoke", "invocations=%d", obj->bss->invocations))
139 		goto close_bpf_object;
140 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
141 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 2 };
142 	if (assert_storage(obj->maps.cgroup_storage,
143 			   &key, &expected_cgroup_value))
144 		goto close_bpf_object;
145 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
146 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 1 };
147 	if (assert_storage(obj->maps.cgroup_storage,
148 			   &key, &expected_cgroup_value))
149 		goto close_bpf_object;
150 
151 close_bpf_object:
152 	bpf_link__destroy(parent_link);
153 	bpf_link__destroy(child_link);
154 
155 	cg_storage_multi_egress_only__destroy(obj);
156 }
157 
test_isolated(int parent_cgroup_fd,int child_cgroup_fd)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 (!ASSERT_OK_PTR(parent_egress1_link, "parent-egress1-cg-attach"))
180 		goto close_bpf_object;
181 	parent_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
182 							 parent_cgroup_fd);
183 	if (!ASSERT_OK_PTR(parent_egress2_link, "parent-egress2-cg-attach"))
184 		goto close_bpf_object;
185 	parent_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
186 							 parent_cgroup_fd);
187 	if (!ASSERT_OK_PTR(parent_ingress_link, "parent-ingress-cg-attach"))
188 		goto close_bpf_object;
189 	err = connect_send(CHILD_CGROUP);
190 	if (CHECK(err, "first-connect-send", "errno %d", errno))
191 		goto close_bpf_object;
192 	if (CHECK(obj->bss->invocations != 3,
193 		  "first-invoke", "invocations=%d", obj->bss->invocations))
194 		goto close_bpf_object;
195 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
196 	key.attach_type = BPF_CGROUP_INET_EGRESS;
197 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 2 };
198 	if (assert_storage(obj->maps.cgroup_storage,
199 			   &key, &expected_cgroup_value))
200 		goto close_bpf_object;
201 	key.attach_type = BPF_CGROUP_INET_INGRESS;
202 	expected_cgroup_value = (struct cgroup_value) { .ingress_pkts = 1 };
203 	if (assert_storage(obj->maps.cgroup_storage,
204 			   &key, &expected_cgroup_value))
205 		goto close_bpf_object;
206 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
207 	key.attach_type = BPF_CGROUP_INET_EGRESS;
208 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
209 		goto close_bpf_object;
210 	key.attach_type = BPF_CGROUP_INET_INGRESS;
211 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
212 		goto close_bpf_object;
213 
214 	/* Attach to parent and child cgroup, trigger packet from child.
215 	 * Assert that there is six additional runs, parent cgroup egresses and
216 	 * ingress, child cgroup egresses and ingress.
217 	 * Assert that egress and ingress storages are separate.
218 	 */
219 	child_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
220 							child_cgroup_fd);
221 	if (!ASSERT_OK_PTR(child_egress1_link, "child-egress1-cg-attach"))
222 		goto close_bpf_object;
223 	child_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
224 							child_cgroup_fd);
225 	if (!ASSERT_OK_PTR(child_egress2_link, "child-egress2-cg-attach"))
226 		goto close_bpf_object;
227 	child_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
228 							child_cgroup_fd);
229 	if (!ASSERT_OK_PTR(child_ingress_link, "child-ingress-cg-attach"))
230 		goto close_bpf_object;
231 	err = connect_send(CHILD_CGROUP);
232 	if (CHECK(err, "second-connect-send", "errno %d", errno))
233 		goto close_bpf_object;
234 	if (CHECK(obj->bss->invocations != 9,
235 		  "second-invoke", "invocations=%d", obj->bss->invocations))
236 		goto close_bpf_object;
237 	key.cgroup_inode_id = get_cgroup_id(PARENT_CGROUP);
238 	key.attach_type = BPF_CGROUP_INET_EGRESS;
239 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 4 };
240 	if (assert_storage(obj->maps.cgroup_storage,
241 			   &key, &expected_cgroup_value))
242 		goto close_bpf_object;
243 	key.attach_type = BPF_CGROUP_INET_INGRESS;
244 	expected_cgroup_value = (struct cgroup_value) { .ingress_pkts = 2 };
245 	if (assert_storage(obj->maps.cgroup_storage,
246 			   &key, &expected_cgroup_value))
247 		goto close_bpf_object;
248 	key.cgroup_inode_id = get_cgroup_id(CHILD_CGROUP);
249 	key.attach_type = BPF_CGROUP_INET_EGRESS;
250 	expected_cgroup_value = (struct cgroup_value) { .egress_pkts = 2 };
251 	if (assert_storage(obj->maps.cgroup_storage,
252 			   &key, &expected_cgroup_value))
253 		goto close_bpf_object;
254 	key.attach_type = BPF_CGROUP_INET_INGRESS;
255 	expected_cgroup_value = (struct cgroup_value) { .ingress_pkts = 1 };
256 	if (assert_storage(obj->maps.cgroup_storage,
257 			   &key, &expected_cgroup_value))
258 		goto close_bpf_object;
259 
260 close_bpf_object:
261 	bpf_link__destroy(parent_egress1_link);
262 	bpf_link__destroy(parent_egress2_link);
263 	bpf_link__destroy(parent_ingress_link);
264 	bpf_link__destroy(child_egress1_link);
265 	bpf_link__destroy(child_egress2_link);
266 	bpf_link__destroy(child_ingress_link);
267 
268 	cg_storage_multi_isolated__destroy(obj);
269 }
270 
test_shared(int parent_cgroup_fd,int child_cgroup_fd)271 static void test_shared(int parent_cgroup_fd, int child_cgroup_fd)
272 {
273 	struct cg_storage_multi_shared *obj;
274 	struct cgroup_value expected_cgroup_value;
275 	__u64 key;
276 	struct bpf_link *parent_egress1_link = NULL, *parent_egress2_link = NULL;
277 	struct bpf_link *child_egress1_link = NULL, *child_egress2_link = NULL;
278 	struct bpf_link *parent_ingress_link = NULL, *child_ingress_link = NULL;
279 	bool err;
280 
281 	obj = cg_storage_multi_shared__open_and_load();
282 	if (CHECK(!obj, "skel-load", "errno %d", errno))
283 		return;
284 
285 	/* Attach to parent cgroup, trigger packet from child.
286 	 * Assert that there is three runs, two with parent cgroup egress and
287 	 * one with parent cgroup ingress.
288 	 * Also assert that child cgroup's storage does not exist
289 	 */
290 	parent_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
291 							 parent_cgroup_fd);
292 	if (!ASSERT_OK_PTR(parent_egress1_link, "parent-egress1-cg-attach"))
293 		goto close_bpf_object;
294 	parent_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
295 							 parent_cgroup_fd);
296 	if (!ASSERT_OK_PTR(parent_egress2_link, "parent-egress2-cg-attach"))
297 		goto close_bpf_object;
298 	parent_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
299 							 parent_cgroup_fd);
300 	if (!ASSERT_OK_PTR(parent_ingress_link, "parent-ingress-cg-attach"))
301 		goto close_bpf_object;
302 	err = connect_send(CHILD_CGROUP);
303 	if (CHECK(err, "first-connect-send", "errno %d", errno))
304 		goto close_bpf_object;
305 	if (CHECK(obj->bss->invocations != 3,
306 		  "first-invoke", "invocations=%d", obj->bss->invocations))
307 		goto close_bpf_object;
308 	key = get_cgroup_id(PARENT_CGROUP);
309 	expected_cgroup_value = (struct cgroup_value) {
310 		.egress_pkts = 2,
311 		.ingress_pkts = 1,
312 	};
313 	if (assert_storage(obj->maps.cgroup_storage,
314 			   &key, &expected_cgroup_value))
315 		goto close_bpf_object;
316 	key = get_cgroup_id(CHILD_CGROUP);
317 	if (assert_storage_noexist(obj->maps.cgroup_storage, &key))
318 		goto close_bpf_object;
319 
320 	/* Attach to parent and child cgroup, trigger packet from child.
321 	 * Assert that there is six additional runs, parent cgroup egresses and
322 	 * ingress, child cgroup egresses and ingress.
323 	 */
324 	child_egress1_link = bpf_program__attach_cgroup(obj->progs.egress1,
325 							child_cgroup_fd);
326 	if (!ASSERT_OK_PTR(child_egress1_link, "child-egress1-cg-attach"))
327 		goto close_bpf_object;
328 	child_egress2_link = bpf_program__attach_cgroup(obj->progs.egress2,
329 							child_cgroup_fd);
330 	if (!ASSERT_OK_PTR(child_egress2_link, "child-egress2-cg-attach"))
331 		goto close_bpf_object;
332 	child_ingress_link = bpf_program__attach_cgroup(obj->progs.ingress,
333 							child_cgroup_fd);
334 	if (!ASSERT_OK_PTR(child_ingress_link, "child-ingress-cg-attach"))
335 		goto close_bpf_object;
336 	err = connect_send(CHILD_CGROUP);
337 	if (CHECK(err, "second-connect-send", "errno %d", errno))
338 		goto close_bpf_object;
339 	if (CHECK(obj->bss->invocations != 9,
340 		  "second-invoke", "invocations=%d", obj->bss->invocations))
341 		goto close_bpf_object;
342 	key = get_cgroup_id(PARENT_CGROUP);
343 	expected_cgroup_value = (struct cgroup_value) {
344 		.egress_pkts = 4,
345 		.ingress_pkts = 2,
346 	};
347 	if (assert_storage(obj->maps.cgroup_storage,
348 			   &key, &expected_cgroup_value))
349 		goto close_bpf_object;
350 	key = get_cgroup_id(CHILD_CGROUP);
351 	expected_cgroup_value = (struct cgroup_value) {
352 		.egress_pkts = 2,
353 		.ingress_pkts = 1,
354 	};
355 	if (assert_storage(obj->maps.cgroup_storage,
356 			   &key, &expected_cgroup_value))
357 		goto close_bpf_object;
358 
359 close_bpf_object:
360 	bpf_link__destroy(parent_egress1_link);
361 	bpf_link__destroy(parent_egress2_link);
362 	bpf_link__destroy(parent_ingress_link);
363 	bpf_link__destroy(child_egress1_link);
364 	bpf_link__destroy(child_egress2_link);
365 	bpf_link__destroy(child_ingress_link);
366 
367 	cg_storage_multi_shared__destroy(obj);
368 }
369 
serial_test_cg_storage_multi(void)370 void serial_test_cg_storage_multi(void)
371 {
372 	int parent_cgroup_fd = -1, child_cgroup_fd = -1;
373 
374 	parent_cgroup_fd = test__join_cgroup(PARENT_CGROUP);
375 	if (CHECK(parent_cgroup_fd < 0, "cg-create-parent", "errno %d", errno))
376 		goto close_cgroup_fd;
377 	child_cgroup_fd = create_and_get_cgroup(CHILD_CGROUP);
378 	if (CHECK(child_cgroup_fd < 0, "cg-create-child", "errno %d", errno))
379 		goto close_cgroup_fd;
380 
381 	if (test__start_subtest("egress_only"))
382 		test_egress_only(parent_cgroup_fd, child_cgroup_fd);
383 
384 	if (test__start_subtest("isolated"))
385 		test_isolated(parent_cgroup_fd, child_cgroup_fd);
386 
387 	if (test__start_subtest("shared"))
388 		test_shared(parent_cgroup_fd, child_cgroup_fd);
389 
390 close_cgroup_fd:
391 	close(child_cgroup_fd);
392 	close(parent_cgroup_fd);
393 }
394