]>
Commit | Line | Data |
---|---|---|
948def4a | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
b57162aa JK |
2 | |
3 | #include <fcntl.h> | |
4 | #include <linux/bpf_insn.h> | |
5 | #include <string.h> | |
6 | #include <sys/mman.h> | |
7 | #include <unistd.h> | |
8 | ||
9 | #include "bpf-foreign.h" | |
10 | #include "load-fragment.h" | |
11 | #include "manager.h" | |
12 | #include "process-util.h" | |
13 | #include "rlimit-util.h" | |
14 | #include "rm-rf.h" | |
15 | #include "service.h" | |
16 | #include "tests.h" | |
17 | #include "unit.h" | |
18 | #include "virt.h" | |
19 | ||
20 | struct Test { | |
21 | const char *option_name; | |
22 | enum bpf_prog_type prog_type; | |
23 | enum bpf_attach_type attach_type; | |
24 | const char *bpffs_path; | |
25 | }; | |
26 | ||
27 | typedef struct Test Test; | |
28 | ||
29 | #define BPFFS_PATH(prog_suffix) ("/sys/fs/bpf/test-bpf-foreing-" # prog_suffix) | |
30 | static const Test single_prog[] = { | |
31 | { | |
32 | .option_name = "BPFProgram", | |
33 | .prog_type = BPF_PROG_TYPE_CGROUP_SKB, | |
34 | .attach_type = BPF_CGROUP_INET_INGRESS, | |
35 | .bpffs_path = BPFFS_PATH("trivial-skb"), | |
36 | }, | |
37 | }; | |
38 | static const Test path_split_test[] = { | |
39 | { | |
40 | .option_name = "BPFProgram", | |
41 | .prog_type = BPF_PROG_TYPE_CGROUP_SKB, | |
42 | .attach_type = BPF_CGROUP_INET_INGRESS, | |
43 | .bpffs_path = BPFFS_PATH("path:split:test"), | |
44 | }, | |
45 | }; | |
46 | ||
47 | static const Test same_prog_same_hook[] = { | |
48 | { | |
49 | .option_name = "BPFProgram", | |
50 | .prog_type = BPF_PROG_TYPE_CGROUP_SOCK, | |
51 | .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
52 | .bpffs_path = BPFFS_PATH("trivial-sock"), | |
53 | }, | |
54 | { | |
55 | .option_name = "BPFProgram", | |
56 | .prog_type = BPF_PROG_TYPE_CGROUP_SOCK, | |
57 | .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
58 | .bpffs_path = BPFFS_PATH("trivial-sock"), | |
59 | } | |
60 | }; | |
61 | ||
62 | static const Test multi_prog_same_hook[] = { | |
63 | { | |
64 | .option_name = "BPFProgram", | |
65 | .prog_type = BPF_PROG_TYPE_CGROUP_SOCK, | |
66 | .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
67 | .bpffs_path = BPFFS_PATH("trivial-sock-0"), | |
68 | }, | |
69 | { | |
70 | .option_name = "BPFProgram", | |
71 | .prog_type = BPF_PROG_TYPE_CGROUP_SOCK, | |
72 | .attach_type = BPF_CGROUP_INET_SOCK_CREATE, | |
73 | .bpffs_path = BPFFS_PATH("trivial-sock-1"), | |
74 | } | |
75 | }; | |
76 | ||
77 | static const Test same_prog_multi_hook[] = { | |
78 | { | |
79 | .option_name = "BPFProgram", | |
80 | .prog_type = BPF_PROG_TYPE_CGROUP_SKB, | |
81 | .attach_type = BPF_CGROUP_INET_INGRESS, | |
82 | .bpffs_path = BPFFS_PATH("trivial-skb"), | |
83 | }, | |
84 | { | |
85 | .option_name = "BPFProgram", | |
86 | .prog_type = BPF_PROG_TYPE_CGROUP_SKB, | |
87 | .attach_type = BPF_CGROUP_INET_EGRESS, | |
88 | .bpffs_path = BPFFS_PATH("trivial-skb"), | |
89 | } | |
90 | }; | |
91 | ||
92 | static const Test same_prog_multi_option_0[] = { | |
93 | { | |
94 | .option_name = "BPFProgram", | |
95 | .prog_type = BPF_PROG_TYPE_CGROUP_SKB, | |
96 | .attach_type = BPF_CGROUP_INET_INGRESS, | |
97 | .bpffs_path = BPFFS_PATH("trivial-skb"), | |
98 | }, | |
99 | { | |
100 | .option_name = "IPIngressFilterPath", | |
101 | .prog_type = BPF_PROG_TYPE_CGROUP_SKB, | |
102 | .attach_type = BPF_CGROUP_INET_INGRESS, | |
103 | .bpffs_path = BPFFS_PATH("trivial-skb"), | |
104 | } | |
105 | }; | |
106 | ||
107 | static const Test same_prog_multi_option_1[] = { | |
108 | { | |
109 | .option_name = "IPEgressFilterPath", | |
110 | .prog_type = BPF_PROG_TYPE_CGROUP_SKB, | |
111 | .attach_type = BPF_CGROUP_INET_EGRESS, | |
112 | .bpffs_path = BPFFS_PATH("trivial-skb"), | |
113 | }, | |
114 | { | |
115 | .option_name = "BPFProgram", | |
116 | .prog_type = BPF_PROG_TYPE_CGROUP_SKB, | |
117 | .attach_type = BPF_CGROUP_INET_EGRESS, | |
118 | .bpffs_path = BPFFS_PATH("trivial-skb"), | |
119 | } | |
120 | }; | |
121 | #undef BPFFS_PATH | |
122 | ||
123 | static int bpf_foreign_test_to_string(enum bpf_attach_type attach_type, const char *bpffs_path, char **ret_str) { | |
124 | const char *s = NULL; | |
125 | ||
126 | assert_se(bpffs_path); | |
127 | assert_se(ret_str); | |
128 | ||
129 | assert_se(s = bpf_cgroup_attach_type_to_string(attach_type)); | |
130 | assert_se(*ret_str = strjoin(s, ":", bpffs_path)); | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
135 | static char **unlink_paths_and_free(char **paths) { | |
136 | char **i; | |
137 | ||
138 | STRV_FOREACH(i, paths) | |
139 | (void) unlink(*i); | |
140 | ||
141 | return strv_free(paths); | |
142 | } | |
143 | ||
144 | DEFINE_TRIVIAL_CLEANUP_FUNC(char **, unlink_paths_and_free); | |
145 | ||
146 | static int pin_programs(Unit *u, CGroupContext *cc, const Test *test_suite, size_t test_suite_size, char ***paths_ret) { | |
147 | _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL; | |
148 | static const struct bpf_insn trivial[] = { | |
149 | BPF_MOV64_IMM(BPF_REG_0, 0), | |
150 | BPF_EXIT_INSN() | |
151 | }; | |
152 | char log_buf[0xffff]; | |
153 | int r; | |
154 | ||
155 | assert_se(paths_ret); | |
156 | ||
157 | for (size_t i = 0; i < test_suite_size; i++) { | |
76dc1725 | 158 | _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; |
b57162aa JK |
159 | _cleanup_free_ char *str = NULL; |
160 | ||
161 | r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &str); | |
162 | if (r < 0) | |
163 | return log_error_errno(r, "Failed to convert program to string"); | |
164 | ||
8fe9dbb9 | 165 | r = bpf_program_new(test_suite[i].prog_type, "sd_trivial", &prog); |
b57162aa JK |
166 | if (r < 0) |
167 | return log_error_errno(r, "Failed to create program '%s'", str); | |
168 | ||
169 | r = bpf_program_add_instructions(prog, trivial, ELEMENTSOF(trivial)); | |
170 | if (r < 0) | |
171 | return log_error_errno(r, "Failed to add trivial instructions for '%s'", str); | |
172 | ||
173 | r = bpf_program_load_kernel(prog, log_buf, ELEMENTSOF(log_buf)); | |
174 | if (r < 0) | |
175 | return log_error_errno(r, "Failed to load BPF program '%s'", str); | |
176 | ||
177 | if (strv_contains(bpffs_paths, test_suite[i].bpffs_path)) | |
178 | continue; | |
179 | ||
180 | r = strv_extend(&bpffs_paths, test_suite[i].bpffs_path); | |
181 | if (r < 0) | |
182 | return log_error_errno(r, "Failed to put path into a vector: %m"); | |
183 | ||
184 | r = bpf_program_pin(prog->kernel_fd, test_suite[i].bpffs_path); | |
185 | if (r < 0) | |
186 | return log_error_errno(r, "Failed to pin BPF program '%s'", str); | |
187 | } | |
188 | ||
189 | *paths_ret = TAKE_PTR(bpffs_paths); | |
190 | return 0; | |
191 | } | |
192 | ||
193 | static int test_bpf_cgroup_programs(Manager *m, const char *unit_name, const Test *test_suite, size_t test_suite_size) { | |
194 | _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL; | |
195 | _cleanup_(unit_freep) Unit *u = NULL; | |
196 | CGroupContext *cc = NULL; | |
197 | int cld_code, r; | |
198 | ||
199 | assert_se(u = unit_new(m, sizeof(Service))); | |
200 | assert_se(unit_add_name(u, unit_name) == 0); | |
201 | assert_se(cc = unit_get_cgroup_context(u)); | |
202 | ||
203 | r = pin_programs(u, cc, test_suite, test_suite_size, &bpffs_paths); | |
204 | if (r < 0) | |
205 | return log_error_errno(r, "Failed to pin programs: %m"); | |
206 | ||
207 | for (size_t i = 0; i < test_suite_size; i++) { | |
208 | if (streq(test_suite[i].option_name, "BPFProgram")) { | |
209 | _cleanup_free_ char *option = NULL; | |
210 | r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &option); | |
211 | if (r < 0) | |
212 | return log_error_errno(r, "Failed to compose option string: %m"); | |
213 | r = config_parse_bpf_foreign_program( | |
214 | u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, cc, u); | |
215 | ||
216 | if (r < 0) | |
217 | return log_error_errno(r, "Failed to parse option string '%s': %m", option); | |
218 | } else if (STR_IN_SET(test_suite[i].option_name, "IPIngressFilterPath", "IPEgressFilterPath")) { | |
219 | const char *option = test_suite[i].bpffs_path; | |
220 | void *paths = NULL; | |
221 | ||
222 | if (streq(test_suite[i].option_name, "IPIngressFilterPath")) | |
223 | paths = &cc->ip_filters_ingress; | |
224 | else | |
225 | paths = &cc->ip_filters_egress; | |
226 | ||
227 | r = config_parse_ip_filter_bpf_progs( | |
228 | u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, paths, u); | |
229 | if (r < 0) | |
230 | return log_error_errno(r, "Failed to parse option string '%s': %m", option); | |
231 | } | |
232 | } | |
233 | ||
234 | r = config_parse_exec( | |
235 | u->id, | |
236 | "filename", | |
237 | 1, | |
238 | "Service", | |
239 | 1, | |
240 | "ExecStart", | |
241 | SERVICE_EXEC_START, | |
242 | "-/bin/ping -c 5 127.0.0.1 -W 1", | |
243 | SERVICE(u)->exec_command, | |
244 | u); | |
245 | if (r < 0) | |
246 | return log_error_errno(r, "Failed to parse ExecStart"); | |
247 | ||
248 | SERVICE(u)->type = SERVICE_ONESHOT; | |
249 | u->load_state = UNIT_LOADED; | |
250 | ||
251 | r = unit_start(u); | |
252 | if (r < 0) | |
253 | return log_error_errno(r, "Unit start failed %m"); | |
254 | ||
255 | while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) { | |
256 | r = sd_event_run(m->event, UINT64_MAX); | |
257 | if (r < 0) | |
258 | return log_error_errno(errno, "Event run failed %m"); | |
259 | } | |
260 | ||
261 | cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code; | |
262 | if (cld_code != CLD_EXITED) | |
263 | return log_error_errno(SYNTHETIC_ERRNO(EBUSY), | |
6f350e05 | 264 | "Child didn't exit normally, code='%s'", sigchld_code_to_string(cld_code)); |
b57162aa JK |
265 | |
266 | if (SERVICE(u)->state != SERVICE_DEAD) | |
267 | return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Service is not dead"); | |
268 | ||
269 | return r; | |
270 | } | |
271 | ||
272 | int main(int argc, char *argv[]) { | |
273 | _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; | |
274 | _cleanup_(manager_freep) Manager *m = NULL; | |
275 | _cleanup_free_ char *unit_dir = NULL; | |
276 | struct rlimit rl; | |
277 | int r; | |
278 | ||
279 | test_setup_logging(LOG_DEBUG); | |
280 | ||
281 | if (detect_container() > 0) | |
282 | return log_tests_skipped("test-bpf fails inside LXC and Docker containers: https://github.com/systemd/systemd/issues/9666"); | |
283 | ||
284 | if (getuid() != 0) | |
285 | return log_tests_skipped("not running as root"); | |
286 | ||
287 | assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); | |
288 | rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); | |
289 | (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl); | |
290 | ||
291 | if (!can_memlock()) | |
4a55ce8f | 292 | return log_tests_skipped("Can't use mlock()"); |
b57162aa JK |
293 | |
294 | r = cg_all_unified(); | |
295 | if (r <= 0) | |
4a55ce8f | 296 | return log_tests_skipped("Unified hierarchy is required"); |
b57162aa JK |
297 | |
298 | r = enter_cgroup_subroot(NULL); | |
299 | if (r == -ENOMEDIUM) | |
300 | return log_tests_skipped("cgroupfs not available"); | |
301 | ||
302 | assert_se(get_testdata_dir("units", &unit_dir) >= 0); | |
303 | assert_se(set_unit_path(unit_dir) >= 0); | |
304 | assert_se(runtime_dir = setup_fake_runtime_dir()); | |
305 | ||
306 | assert_se(manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); | |
2a7cf953 | 307 | assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); |
b57162aa JK |
308 | |
309 | assert_se(test_bpf_cgroup_programs(m, | |
310 | "single_prog.service", single_prog, ELEMENTSOF(single_prog)) >= 0); | |
311 | assert_se(test_bpf_cgroup_programs(m, | |
312 | "multi_prog_same_hook.service", | |
313 | multi_prog_same_hook, ELEMENTSOF(multi_prog_same_hook)) >= 0); | |
314 | assert_se(test_bpf_cgroup_programs(m, | |
315 | "same_prog_multi_hook.service", | |
316 | same_prog_multi_hook, ELEMENTSOF(same_prog_multi_hook)) >= 0); | |
317 | assert_se(test_bpf_cgroup_programs(m, | |
318 | "same_prog_multi_option_0.service", | |
319 | same_prog_multi_option_0, ELEMENTSOF(same_prog_multi_option_0)) >= 0); | |
320 | assert_se(test_bpf_cgroup_programs(m, | |
321 | "same_prog_multi_option_1.service", | |
322 | same_prog_multi_option_1, ELEMENTSOF(same_prog_multi_option_1)) >= 0); | |
323 | assert_se(test_bpf_cgroup_programs(m, | |
324 | "same_prog_same_hook.service", | |
325 | same_prog_same_hook, | |
326 | ELEMENTSOF(same_prog_same_hook)) >= 0); | |
327 | assert_se(test_bpf_cgroup_programs(m, | |
328 | "path_split_test.service", | |
329 | path_split_test, | |
330 | ELEMENTSOF(path_split_test)) >= 0); | |
331 | return 0; | |
332 | } |