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
20static int duration;
21
22static 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
40static 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
57static 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
82out_clean:
83	close(client_fd);
84	close(server_fd);
85	return res;
86}
87
88static 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
151close_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
158static 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 egree 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
260close_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
271static 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
359close_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
370void 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
390close_cgroup_fd:
391	close(child_cgroup_fd);
392	close(parent_cgroup_fd);
393}
394