]>
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) { | |
b57162aa JK |
136 | STRV_FOREACH(i, paths) |
137 | (void) unlink(*i); | |
138 | ||
139 | return strv_free(paths); | |
140 | } | |
141 | ||
142 | DEFINE_TRIVIAL_CLEANUP_FUNC(char **, unlink_paths_and_free); | |
143 | ||
144 | static int pin_programs(Unit *u, CGroupContext *cc, const Test *test_suite, size_t test_suite_size, char ***paths_ret) { | |
145 | _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL; | |
146 | static const struct bpf_insn trivial[] = { | |
147 | BPF_MOV64_IMM(BPF_REG_0, 0), | |
148 | BPF_EXIT_INSN() | |
149 | }; | |
150 | char log_buf[0xffff]; | |
151 | int r; | |
152 | ||
153 | assert_se(paths_ret); | |
154 | ||
155 | for (size_t i = 0; i < test_suite_size; i++) { | |
76dc1725 | 156 | _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; |
b57162aa JK |
157 | _cleanup_free_ char *str = NULL; |
158 | ||
159 | r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &str); | |
160 | if (r < 0) | |
161 | return log_error_errno(r, "Failed to convert program to string"); | |
162 | ||
8fe9dbb9 | 163 | r = bpf_program_new(test_suite[i].prog_type, "sd_trivial", &prog); |
b57162aa JK |
164 | if (r < 0) |
165 | return log_error_errno(r, "Failed to create program '%s'", str); | |
166 | ||
167 | r = bpf_program_add_instructions(prog, trivial, ELEMENTSOF(trivial)); | |
168 | if (r < 0) | |
169 | return log_error_errno(r, "Failed to add trivial instructions for '%s'", str); | |
170 | ||
171 | r = bpf_program_load_kernel(prog, log_buf, ELEMENTSOF(log_buf)); | |
172 | if (r < 0) | |
173 | return log_error_errno(r, "Failed to load BPF program '%s'", str); | |
174 | ||
175 | if (strv_contains(bpffs_paths, test_suite[i].bpffs_path)) | |
176 | continue; | |
177 | ||
178 | r = strv_extend(&bpffs_paths, test_suite[i].bpffs_path); | |
179 | if (r < 0) | |
180 | return log_error_errno(r, "Failed to put path into a vector: %m"); | |
181 | ||
182 | r = bpf_program_pin(prog->kernel_fd, test_suite[i].bpffs_path); | |
183 | if (r < 0) | |
184 | return log_error_errno(r, "Failed to pin BPF program '%s'", str); | |
185 | } | |
186 | ||
187 | *paths_ret = TAKE_PTR(bpffs_paths); | |
188 | return 0; | |
189 | } | |
190 | ||
191 | static int test_bpf_cgroup_programs(Manager *m, const char *unit_name, const Test *test_suite, size_t test_suite_size) { | |
192 | _cleanup_(unlink_paths_and_freep) char **bpffs_paths = NULL; | |
193 | _cleanup_(unit_freep) Unit *u = NULL; | |
194 | CGroupContext *cc = NULL; | |
195 | int cld_code, r; | |
196 | ||
197 | assert_se(u = unit_new(m, sizeof(Service))); | |
198 | assert_se(unit_add_name(u, unit_name) == 0); | |
199 | assert_se(cc = unit_get_cgroup_context(u)); | |
200 | ||
201 | r = pin_programs(u, cc, test_suite, test_suite_size, &bpffs_paths); | |
202 | if (r < 0) | |
203 | return log_error_errno(r, "Failed to pin programs: %m"); | |
204 | ||
205 | for (size_t i = 0; i < test_suite_size; i++) { | |
206 | if (streq(test_suite[i].option_name, "BPFProgram")) { | |
207 | _cleanup_free_ char *option = NULL; | |
208 | r = bpf_foreign_test_to_string(test_suite[i].attach_type, test_suite[i].bpffs_path, &option); | |
209 | if (r < 0) | |
210 | return log_error_errno(r, "Failed to compose option string: %m"); | |
211 | r = config_parse_bpf_foreign_program( | |
212 | u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, cc, u); | |
213 | ||
214 | if (r < 0) | |
215 | return log_error_errno(r, "Failed to parse option string '%s': %m", option); | |
216 | } else if (STR_IN_SET(test_suite[i].option_name, "IPIngressFilterPath", "IPEgressFilterPath")) { | |
217 | const char *option = test_suite[i].bpffs_path; | |
218 | void *paths = NULL; | |
219 | ||
220 | if (streq(test_suite[i].option_name, "IPIngressFilterPath")) | |
221 | paths = &cc->ip_filters_ingress; | |
222 | else | |
223 | paths = &cc->ip_filters_egress; | |
224 | ||
225 | r = config_parse_ip_filter_bpf_progs( | |
226 | u->id, "filename", 1, "Service", 1, test_suite[i].option_name, 0, option, paths, u); | |
227 | if (r < 0) | |
228 | return log_error_errno(r, "Failed to parse option string '%s': %m", option); | |
229 | } | |
230 | } | |
231 | ||
232 | r = config_parse_exec( | |
233 | u->id, | |
234 | "filename", | |
235 | 1, | |
236 | "Service", | |
237 | 1, | |
238 | "ExecStart", | |
239 | SERVICE_EXEC_START, | |
240 | "-/bin/ping -c 5 127.0.0.1 -W 1", | |
241 | SERVICE(u)->exec_command, | |
242 | u); | |
243 | if (r < 0) | |
244 | return log_error_errno(r, "Failed to parse ExecStart"); | |
245 | ||
246 | SERVICE(u)->type = SERVICE_ONESHOT; | |
247 | u->load_state = UNIT_LOADED; | |
248 | ||
249 | r = unit_start(u); | |
250 | if (r < 0) | |
251 | return log_error_errno(r, "Unit start failed %m"); | |
252 | ||
253 | while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) { | |
254 | r = sd_event_run(m->event, UINT64_MAX); | |
255 | if (r < 0) | |
256 | return log_error_errno(errno, "Event run failed %m"); | |
257 | } | |
258 | ||
259 | cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code; | |
260 | if (cld_code != CLD_EXITED) | |
261 | return log_error_errno(SYNTHETIC_ERRNO(EBUSY), | |
6f350e05 | 262 | "Child didn't exit normally, code='%s'", sigchld_code_to_string(cld_code)); |
b57162aa JK |
263 | |
264 | if (SERVICE(u)->state != SERVICE_DEAD) | |
265 | return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Service is not dead"); | |
266 | ||
267 | return r; | |
268 | } | |
269 | ||
270 | int main(int argc, char *argv[]) { | |
271 | _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; | |
272 | _cleanup_(manager_freep) Manager *m = NULL; | |
273 | _cleanup_free_ char *unit_dir = NULL; | |
274 | struct rlimit rl; | |
275 | int r; | |
276 | ||
277 | test_setup_logging(LOG_DEBUG); | |
278 | ||
279 | if (detect_container() > 0) | |
280 | return log_tests_skipped("test-bpf fails inside LXC and Docker containers: https://github.com/systemd/systemd/issues/9666"); | |
281 | ||
282 | if (getuid() != 0) | |
283 | return log_tests_skipped("not running as root"); | |
284 | ||
285 | assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); | |
286 | rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); | |
287 | (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl); | |
288 | ||
289 | if (!can_memlock()) | |
4a55ce8f | 290 | return log_tests_skipped("Can't use mlock()"); |
b57162aa JK |
291 | |
292 | r = cg_all_unified(); | |
293 | if (r <= 0) | |
4a55ce8f | 294 | return log_tests_skipped("Unified hierarchy is required"); |
b57162aa JK |
295 | |
296 | r = enter_cgroup_subroot(NULL); | |
297 | if (r == -ENOMEDIUM) | |
298 | return log_tests_skipped("cgroupfs not available"); | |
299 | ||
300 | assert_se(get_testdata_dir("units", &unit_dir) >= 0); | |
301 | assert_se(set_unit_path(unit_dir) >= 0); | |
302 | assert_se(runtime_dir = setup_fake_runtime_dir()); | |
303 | ||
304 | assert_se(manager_new(UNIT_FILE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); | |
2a7cf953 | 305 | assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); |
b57162aa JK |
306 | |
307 | assert_se(test_bpf_cgroup_programs(m, | |
308 | "single_prog.service", single_prog, ELEMENTSOF(single_prog)) >= 0); | |
309 | assert_se(test_bpf_cgroup_programs(m, | |
310 | "multi_prog_same_hook.service", | |
311 | multi_prog_same_hook, ELEMENTSOF(multi_prog_same_hook)) >= 0); | |
312 | assert_se(test_bpf_cgroup_programs(m, | |
313 | "same_prog_multi_hook.service", | |
314 | same_prog_multi_hook, ELEMENTSOF(same_prog_multi_hook)) >= 0); | |
315 | assert_se(test_bpf_cgroup_programs(m, | |
316 | "same_prog_multi_option_0.service", | |
317 | same_prog_multi_option_0, ELEMENTSOF(same_prog_multi_option_0)) >= 0); | |
318 | assert_se(test_bpf_cgroup_programs(m, | |
319 | "same_prog_multi_option_1.service", | |
320 | same_prog_multi_option_1, ELEMENTSOF(same_prog_multi_option_1)) >= 0); | |
321 | assert_se(test_bpf_cgroup_programs(m, | |
322 | "same_prog_same_hook.service", | |
323 | same_prog_same_hook, | |
324 | ELEMENTSOF(same_prog_same_hook)) >= 0); | |
325 | assert_se(test_bpf_cgroup_programs(m, | |
326 | "path_split_test.service", | |
327 | path_split_test, | |
328 | ELEMENTSOF(path_split_test)) >= 0); | |
329 | return 0; | |
330 | } |