1 // SPDX-License-Identifier: GPL-2.0
2
3 #include <test_progs.h>
4 #include "cgroup_helpers.h"
5 #include "testing_helpers.h"
6 #include "test_cgroup_link.skel.h"
7
8 static __u32 duration = 0;
9 #define PING_CMD "ping -q -c1 -w1 127.0.0.1 > /dev/null"
10
11 static struct test_cgroup_link *skel = NULL;
12
ping_and_check(int exp_calls,int exp_alt_calls)13 int ping_and_check(int exp_calls, int exp_alt_calls)
14 {
15 skel->bss->calls = 0;
16 skel->bss->alt_calls = 0;
17 CHECK_FAIL(system(PING_CMD));
18 if (CHECK(skel->bss->calls != exp_calls, "call_cnt",
19 "exp %d, got %d\n", exp_calls, skel->bss->calls))
20 return -EINVAL;
21 if (CHECK(skel->bss->alt_calls != exp_alt_calls, "alt_call_cnt",
22 "exp %d, got %d\n", exp_alt_calls, skel->bss->alt_calls))
23 return -EINVAL;
24 return 0;
25 }
26
test_cgroup_link(void)27 void test_cgroup_link(void)
28 {
29 struct {
30 const char *path;
31 int fd;
32 } cgs[] = {
33 { "/cg1" },
34 { "/cg1/cg2" },
35 { "/cg1/cg2/cg3" },
36 { "/cg1/cg2/cg3/cg4" },
37 };
38 int last_cg = ARRAY_SIZE(cgs) - 1, cg_nr = ARRAY_SIZE(cgs);
39 DECLARE_LIBBPF_OPTS(bpf_link_update_opts, link_upd_opts);
40 struct bpf_link *links[ARRAY_SIZE(cgs)] = {}, *tmp_link;
41 __u32 prog_ids[ARRAY_SIZE(cgs)], prog_cnt = 0, attach_flags, prog_id;
42 struct bpf_link_info info;
43 int i = 0, err, prog_fd;
44 bool detach_legacy = false;
45
46 skel = test_cgroup_link__open_and_load();
47 if (CHECK(!skel, "skel_open_load", "failed to open/load skeleton\n"))
48 return;
49 prog_fd = bpf_program__fd(skel->progs.egress);
50
51 err = setup_cgroup_environment();
52 if (CHECK(err, "cg_init", "failed: %d\n", err))
53 goto cleanup;
54
55 for (i = 0; i < cg_nr; i++) {
56 cgs[i].fd = create_and_get_cgroup(cgs[i].path);
57 if (CHECK(cgs[i].fd < 0, "cg_create", "fail: %d\n", cgs[i].fd))
58 goto cleanup;
59 }
60
61 err = join_cgroup(cgs[last_cg].path);
62 if (CHECK(err, "cg_join", "fail: %d\n", err))
63 goto cleanup;
64
65 for (i = 0; i < cg_nr; i++) {
66 links[i] = bpf_program__attach_cgroup(skel->progs.egress,
67 cgs[i].fd);
68 if (CHECK(IS_ERR(links[i]), "cg_attach", "i: %d, err: %ld\n",
69 i, PTR_ERR(links[i])))
70 goto cleanup;
71 }
72
73 ping_and_check(cg_nr, 0);
74
75 /* query the number of effective progs and attach flags in root cg */
76 err = bpf_prog_query(cgs[0].fd, BPF_CGROUP_INET_EGRESS,
77 BPF_F_QUERY_EFFECTIVE, &attach_flags, NULL,
78 &prog_cnt);
79 CHECK_FAIL(err);
80 CHECK_FAIL(attach_flags != BPF_F_ALLOW_MULTI);
81 if (CHECK(prog_cnt != 1, "effect_cnt", "exp %d, got %d\n", 1, prog_cnt))
82 goto cleanup;
83
84 /* query the number of effective progs in last cg */
85 err = bpf_prog_query(cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS,
86 BPF_F_QUERY_EFFECTIVE, NULL, NULL,
87 &prog_cnt);
88 CHECK_FAIL(err);
89 CHECK_FAIL(attach_flags != BPF_F_ALLOW_MULTI);
90 if (CHECK(prog_cnt != cg_nr, "effect_cnt", "exp %d, got %d\n",
91 cg_nr, prog_cnt))
92 goto cleanup;
93
94 /* query the effective prog IDs in last cg */
95 err = bpf_prog_query(cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS,
96 BPF_F_QUERY_EFFECTIVE, &attach_flags,
97 prog_ids, &prog_cnt);
98 CHECK_FAIL(err);
99 CHECK_FAIL(attach_flags != BPF_F_ALLOW_MULTI);
100 if (CHECK(prog_cnt != cg_nr, "effect_cnt", "exp %d, got %d\n",
101 cg_nr, prog_cnt))
102 goto cleanup;
103 for (i = 1; i < prog_cnt; i++) {
104 CHECK(prog_ids[i - 1] != prog_ids[i], "prog_id_check",
105 "idx %d, prev id %d, cur id %d\n",
106 i, prog_ids[i - 1], prog_ids[i]);
107 }
108
109 /* detach bottom program and ping again */
110 bpf_link__destroy(links[last_cg]);
111 links[last_cg] = NULL;
112
113 ping_and_check(cg_nr - 1, 0);
114
115 /* mix in with non link-based multi-attachments */
116 err = bpf_prog_attach(prog_fd, cgs[last_cg].fd,
117 BPF_CGROUP_INET_EGRESS, BPF_F_ALLOW_MULTI);
118 if (CHECK(err, "cg_attach_legacy", "errno=%d\n", errno))
119 goto cleanup;
120 detach_legacy = true;
121
122 links[last_cg] = bpf_program__attach_cgroup(skel->progs.egress,
123 cgs[last_cg].fd);
124 if (CHECK(IS_ERR(links[last_cg]), "cg_attach", "err: %ld\n",
125 PTR_ERR(links[last_cg])))
126 goto cleanup;
127
128 ping_and_check(cg_nr + 1, 0);
129
130 /* detach link */
131 bpf_link__destroy(links[last_cg]);
132 links[last_cg] = NULL;
133
134 /* detach legacy */
135 err = bpf_prog_detach2(prog_fd, cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS);
136 if (CHECK(err, "cg_detach_legacy", "errno=%d\n", errno))
137 goto cleanup;
138 detach_legacy = false;
139
140 /* attach legacy exclusive prog attachment */
141 err = bpf_prog_attach(prog_fd, cgs[last_cg].fd,
142 BPF_CGROUP_INET_EGRESS, 0);
143 if (CHECK(err, "cg_attach_exclusive", "errno=%d\n", errno))
144 goto cleanup;
145 detach_legacy = true;
146
147 /* attempt to mix in with multi-attach bpf_link */
148 tmp_link = bpf_program__attach_cgroup(skel->progs.egress,
149 cgs[last_cg].fd);
150 if (CHECK(!IS_ERR(tmp_link), "cg_attach_fail", "unexpected success!\n")) {
151 bpf_link__destroy(tmp_link);
152 goto cleanup;
153 }
154
155 ping_and_check(cg_nr, 0);
156
157 /* detach */
158 err = bpf_prog_detach2(prog_fd, cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS);
159 if (CHECK(err, "cg_detach_legacy", "errno=%d\n", errno))
160 goto cleanup;
161 detach_legacy = false;
162
163 ping_and_check(cg_nr - 1, 0);
164
165 /* attach back link-based one */
166 links[last_cg] = bpf_program__attach_cgroup(skel->progs.egress,
167 cgs[last_cg].fd);
168 if (CHECK(IS_ERR(links[last_cg]), "cg_attach", "err: %ld\n",
169 PTR_ERR(links[last_cg])))
170 goto cleanup;
171
172 ping_and_check(cg_nr, 0);
173
174 /* check legacy exclusive prog can't be attached */
175 err = bpf_prog_attach(prog_fd, cgs[last_cg].fd,
176 BPF_CGROUP_INET_EGRESS, 0);
177 if (CHECK(!err, "cg_attach_exclusive", "unexpected success")) {
178 bpf_prog_detach2(prog_fd, cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS);
179 goto cleanup;
180 }
181
182 /* replace BPF programs inside their links for all but first link */
183 for (i = 1; i < cg_nr; i++) {
184 err = bpf_link__update_program(links[i], skel->progs.egress_alt);
185 if (CHECK(err, "prog_upd", "link #%d\n", i))
186 goto cleanup;
187 }
188
189 ping_and_check(1, cg_nr - 1);
190
191 /* Attempt program update with wrong expected BPF program */
192 link_upd_opts.old_prog_fd = bpf_program__fd(skel->progs.egress_alt);
193 link_upd_opts.flags = BPF_F_REPLACE;
194 err = bpf_link_update(bpf_link__fd(links[0]),
195 bpf_program__fd(skel->progs.egress_alt),
196 &link_upd_opts);
197 if (CHECK(err == 0 || errno != EPERM, "prog_cmpxchg1",
198 "unexpectedly succeeded, err %d, errno %d\n", err, -errno))
199 goto cleanup;
200
201 /* Compare-exchange single link program from egress to egress_alt */
202 link_upd_opts.old_prog_fd = bpf_program__fd(skel->progs.egress);
203 link_upd_opts.flags = BPF_F_REPLACE;
204 err = bpf_link_update(bpf_link__fd(links[0]),
205 bpf_program__fd(skel->progs.egress_alt),
206 &link_upd_opts);
207 if (CHECK(err, "prog_cmpxchg2", "errno %d\n", -errno))
208 goto cleanup;
209
210 /* ping */
211 ping_and_check(0, cg_nr);
212
213 /* close cgroup FDs before detaching links */
214 for (i = 0; i < cg_nr; i++) {
215 if (cgs[i].fd > 0) {
216 close(cgs[i].fd);
217 cgs[i].fd = -1;
218 }
219 }
220
221 /* BPF programs should still get called */
222 ping_and_check(0, cg_nr);
223
224 prog_id = link_info_prog_id(links[0], &info);
225 CHECK(prog_id == 0, "link_info", "failed\n");
226 CHECK(info.cgroup.cgroup_id == 0, "cgroup_id", "unexpected %llu\n", info.cgroup.cgroup_id);
227
228 err = bpf_link__detach(links[0]);
229 if (CHECK(err, "link_detach", "failed %d\n", err))
230 goto cleanup;
231
232 /* cgroup_id should be zero in link_info */
233 prog_id = link_info_prog_id(links[0], &info);
234 CHECK(prog_id == 0, "link_info", "failed\n");
235 CHECK(info.cgroup.cgroup_id != 0, "cgroup_id", "unexpected %llu\n", info.cgroup.cgroup_id);
236
237 /* First BPF program shouldn't be called anymore */
238 ping_and_check(0, cg_nr - 1);
239
240 /* leave cgroup and remove them, don't detach programs */
241 cleanup_cgroup_environment();
242
243 /* BPF programs should have been auto-detached */
244 ping_and_check(0, 0);
245
246 cleanup:
247 if (detach_legacy)
248 bpf_prog_detach2(prog_fd, cgs[last_cg].fd,
249 BPF_CGROUP_INET_EGRESS);
250
251 for (i = 0; i < cg_nr; i++) {
252 if (!IS_ERR(links[i]))
253 bpf_link__destroy(links[i]);
254 }
255 test_cgroup_link__destroy(skel);
256
257 for (i = 0; i < cg_nr; i++) {
258 if (cgs[i].fd > 0)
259 close(cgs[i].fd);
260 }
261 cleanup_cgroup_environment();
262 }
263