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