Loading...
Note: File does not exist in v6.8.
1// SPDX-License-Identifier: GPL-2.0
2/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
3#include <test_progs.h>
4#include <time.h>
5
6#include <sys/epoll.h>
7
8#include "struct_ops_module.skel.h"
9#include "struct_ops_nulled_out_cb.skel.h"
10#include "struct_ops_forgotten_cb.skel.h"
11#include "struct_ops_detach.skel.h"
12#include "unsupported_ops.skel.h"
13
14static void check_map_info(struct bpf_map_info *info)
15{
16 struct bpf_btf_info btf_info;
17 char btf_name[256];
18 u32 btf_info_len = sizeof(btf_info);
19 int err, fd;
20
21 fd = bpf_btf_get_fd_by_id(info->btf_vmlinux_id);
22 if (!ASSERT_GE(fd, 0, "get_value_type_btf_obj_fd"))
23 return;
24
25 memset(&btf_info, 0, sizeof(btf_info));
26 btf_info.name = ptr_to_u64(btf_name);
27 btf_info.name_len = sizeof(btf_name);
28 err = bpf_btf_get_info_by_fd(fd, &btf_info, &btf_info_len);
29 if (!ASSERT_OK(err, "get_value_type_btf_obj_info"))
30 goto cleanup;
31
32 if (!ASSERT_EQ(strcmp(btf_name, "bpf_testmod"), 0, "get_value_type_btf_obj_name"))
33 goto cleanup;
34
35cleanup:
36 close(fd);
37}
38
39static int attach_ops_and_check(struct struct_ops_module *skel,
40 struct bpf_map *map,
41 int expected_test_2_result)
42{
43 struct bpf_link *link;
44
45 link = bpf_map__attach_struct_ops(map);
46 ASSERT_OK_PTR(link, "attach_test_mod_1");
47 if (!link)
48 return -1;
49
50 /* test_{1,2}() would be called from bpf_dummy_reg() in bpf_testmod.c */
51 ASSERT_EQ(skel->bss->test_1_result, 0xdeadbeef, "test_1_result");
52 ASSERT_EQ(skel->bss->test_2_result, expected_test_2_result, "test_2_result");
53
54 bpf_link__destroy(link);
55 return 0;
56}
57
58static void test_struct_ops_load(void)
59{
60 struct struct_ops_module *skel;
61 struct bpf_map_info info = {};
62 int err;
63 u32 len;
64
65 skel = struct_ops_module__open();
66 if (!ASSERT_OK_PTR(skel, "struct_ops_module_open"))
67 return;
68
69 skel->struct_ops.testmod_1->data = 13;
70 skel->struct_ops.testmod_1->test_2 = skel->progs.test_3;
71 /* Since test_2() is not being used, it should be disabled from
72 * auto-loading, or it will fail to load.
73 */
74 bpf_program__set_autoload(skel->progs.test_2, false);
75 bpf_map__set_autocreate(skel->maps.testmod_zeroed, false);
76
77 err = struct_ops_module__load(skel);
78 if (!ASSERT_OK(err, "struct_ops_module_load"))
79 goto cleanup;
80
81 len = sizeof(info);
82 err = bpf_map_get_info_by_fd(bpf_map__fd(skel->maps.testmod_1), &info,
83 &len);
84 if (!ASSERT_OK(err, "bpf_map_get_info_by_fd"))
85 goto cleanup;
86
87 check_map_info(&info);
88 /* test_3() will be called from bpf_dummy_reg() in bpf_testmod.c
89 *
90 * In bpf_testmod.c it will pass 4 and 13 (the value of data) to
91 * .test_2. So, the value of test_2_result should be 20 (4 + 13 +
92 * 3).
93 */
94 if (!attach_ops_and_check(skel, skel->maps.testmod_1, 20))
95 goto cleanup;
96 if (!attach_ops_and_check(skel, skel->maps.testmod_2, 12))
97 goto cleanup;
98
99cleanup:
100 struct_ops_module__destroy(skel);
101}
102
103static void test_struct_ops_not_zeroed(void)
104{
105 struct struct_ops_module *skel;
106 int err;
107
108 /* zeroed is 0, and zeroed_op is null */
109 skel = struct_ops_module__open();
110 if (!ASSERT_OK_PTR(skel, "struct_ops_module_open"))
111 return;
112
113 skel->struct_ops.testmod_zeroed->zeroed = 0;
114 /* zeroed_op prog should be not loaded automatically now */
115 skel->struct_ops.testmod_zeroed->zeroed_op = NULL;
116
117 err = struct_ops_module__load(skel);
118 ASSERT_OK(err, "struct_ops_module_load");
119
120 struct_ops_module__destroy(skel);
121
122 /* zeroed is not 0 */
123 skel = struct_ops_module__open();
124 if (!ASSERT_OK_PTR(skel, "struct_ops_module_open_not_zeroed"))
125 return;
126
127 /* libbpf should reject the testmod_zeroed since struct
128 * bpf_testmod_ops in the kernel has no "zeroed" field and the
129 * value of "zeroed" is non-zero.
130 */
131 skel->struct_ops.testmod_zeroed->zeroed = 0xdeadbeef;
132 skel->struct_ops.testmod_zeroed->zeroed_op = NULL;
133 err = struct_ops_module__load(skel);
134 ASSERT_ERR(err, "struct_ops_module_load_not_zeroed");
135
136 struct_ops_module__destroy(skel);
137
138 /* zeroed_op is not null */
139 skel = struct_ops_module__open();
140 if (!ASSERT_OK_PTR(skel, "struct_ops_module_open_not_zeroed_op"))
141 return;
142
143 /* libbpf should reject the testmod_zeroed since the value of its
144 * "zeroed_op" is not null.
145 */
146 skel->struct_ops.testmod_zeroed->zeroed_op = skel->progs.test_3;
147 err = struct_ops_module__load(skel);
148 ASSERT_ERR(err, "struct_ops_module_load_not_zeroed_op");
149
150 struct_ops_module__destroy(skel);
151}
152
153/* The signature of an implementation might not match the signature of the
154 * function pointer prototype defined in the BPF program. This mismatch
155 * should be allowed as long as the behavior of the operator program
156 * adheres to the signature in the kernel. Libbpf should not enforce the
157 * signature; rather, let the kernel verifier handle the enforcement.
158 */
159static void test_struct_ops_incompatible(void)
160{
161 struct struct_ops_module *skel;
162 struct bpf_link *link;
163 int err;
164
165 skel = struct_ops_module__open();
166 if (!ASSERT_OK_PTR(skel, "struct_ops_module_open"))
167 return;
168
169 bpf_map__set_autocreate(skel->maps.testmod_zeroed, false);
170
171 err = struct_ops_module__load(skel);
172 if (!ASSERT_OK(err, "skel_load"))
173 goto cleanup;
174
175 link = bpf_map__attach_struct_ops(skel->maps.testmod_incompatible);
176 if (ASSERT_OK_PTR(link, "attach_struct_ops"))
177 bpf_link__destroy(link);
178
179cleanup:
180 struct_ops_module__destroy(skel);
181}
182
183/* validate that it's ok to "turn off" callback that kernel supports */
184static void test_struct_ops_nulled_out_cb(void)
185{
186 struct struct_ops_nulled_out_cb *skel;
187 int err;
188
189 skel = struct_ops_nulled_out_cb__open();
190 if (!ASSERT_OK_PTR(skel, "skel_open"))
191 return;
192
193 /* kernel knows about test_1, but we still null it out */
194 skel->struct_ops.ops->test_1 = NULL;
195
196 err = struct_ops_nulled_out_cb__load(skel);
197 if (!ASSERT_OK(err, "skel_load"))
198 goto cleanup;
199
200 ASSERT_FALSE(bpf_program__autoload(skel->progs.test_1_turn_off), "prog_autoload");
201 ASSERT_LT(bpf_program__fd(skel->progs.test_1_turn_off), 0, "prog_fd");
202
203cleanup:
204 struct_ops_nulled_out_cb__destroy(skel);
205}
206
207/* validate that libbpf generates reasonable error message if struct_ops is
208 * not referenced in any struct_ops map
209 */
210static void test_struct_ops_forgotten_cb(void)
211{
212 struct struct_ops_forgotten_cb *skel;
213 char *log;
214 int err;
215
216 skel = struct_ops_forgotten_cb__open();
217 if (!ASSERT_OK_PTR(skel, "skel_open"))
218 return;
219
220 start_libbpf_log_capture();
221
222 err = struct_ops_forgotten_cb__load(skel);
223 if (!ASSERT_ERR(err, "skel_load"))
224 goto cleanup;
225
226 log = stop_libbpf_log_capture();
227 ASSERT_HAS_SUBSTR(log,
228 "prog 'test_1_forgotten': SEC(\"struct_ops\") program isn't referenced anywhere, did you forget to use it?",
229 "libbpf_log");
230 free(log);
231
232 struct_ops_forgotten_cb__destroy(skel);
233
234 /* now let's programmatically use it, we should be fine now */
235 skel = struct_ops_forgotten_cb__open();
236 if (!ASSERT_OK_PTR(skel, "skel_open"))
237 return;
238
239 skel->struct_ops.ops->test_1 = skel->progs.test_1_forgotten; /* not anymore */
240
241 err = struct_ops_forgotten_cb__load(skel);
242 if (!ASSERT_OK(err, "skel_load"))
243 goto cleanup;
244
245cleanup:
246 struct_ops_forgotten_cb__destroy(skel);
247}
248
249/* Detach a link from a user space program */
250static void test_detach_link(void)
251{
252 struct epoll_event ev, events[2];
253 struct struct_ops_detach *skel;
254 struct bpf_link *link = NULL;
255 int fd, epollfd = -1, nfds;
256 int err;
257
258 skel = struct_ops_detach__open_and_load();
259 if (!ASSERT_OK_PTR(skel, "struct_ops_detach__open_and_load"))
260 return;
261
262 link = bpf_map__attach_struct_ops(skel->maps.testmod_do_detach);
263 if (!ASSERT_OK_PTR(link, "attach_struct_ops"))
264 goto cleanup;
265
266 fd = bpf_link__fd(link);
267 if (!ASSERT_GE(fd, 0, "link_fd"))
268 goto cleanup;
269
270 epollfd = epoll_create1(0);
271 if (!ASSERT_GE(epollfd, 0, "epoll_create1"))
272 goto cleanup;
273
274 ev.events = EPOLLHUP;
275 ev.data.fd = fd;
276 err = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev);
277 if (!ASSERT_OK(err, "epoll_ctl"))
278 goto cleanup;
279
280 err = bpf_link__detach(link);
281 if (!ASSERT_OK(err, "detach_link"))
282 goto cleanup;
283
284 /* Wait for EPOLLHUP */
285 nfds = epoll_wait(epollfd, events, 2, 500);
286 if (!ASSERT_EQ(nfds, 1, "epoll_wait"))
287 goto cleanup;
288
289 if (!ASSERT_EQ(events[0].data.fd, fd, "epoll_wait_fd"))
290 goto cleanup;
291 if (!ASSERT_TRUE(events[0].events & EPOLLHUP, "events[0].events"))
292 goto cleanup;
293
294cleanup:
295 if (epollfd >= 0)
296 close(epollfd);
297 bpf_link__destroy(link);
298 struct_ops_detach__destroy(skel);
299}
300
301void serial_test_struct_ops_module(void)
302{
303 if (test__start_subtest("struct_ops_load"))
304 test_struct_ops_load();
305 if (test__start_subtest("struct_ops_not_zeroed"))
306 test_struct_ops_not_zeroed();
307 if (test__start_subtest("struct_ops_incompatible"))
308 test_struct_ops_incompatible();
309 if (test__start_subtest("struct_ops_null_out_cb"))
310 test_struct_ops_nulled_out_cb();
311 if (test__start_subtest("struct_ops_forgotten_cb"))
312 test_struct_ops_forgotten_cb();
313 if (test__start_subtest("test_detach_link"))
314 test_detach_link();
315 RUN_TESTS(unsupported_ops);
316}
317