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 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
test_egress_only(int parent_cgroup_fd,int child_cgroup_fd)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
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 (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
test_shared(int parent_cgroup_fd,int child_cgroup_fd)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
test_cg_storage_multi(void)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