318 lines · c
1// SPDX-License-Identifier: GPL-2.02/* 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 from72 * 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.c89 *90 * In bpf_testmod.c it will pass 4 and 13 (the value of data) to91 * .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 struct128 * bpf_testmod_ops in the kernel has no "zeroed" field and the129 * 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 its144 * "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 the154 * function pointer prototype defined in the BPF program. This mismatch155 * should be allowed as long as the behavior of the operator program156 * adheres to the signature in the kernel. Libbpf should not enforce the157 * 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 is208 * not referenced in any struct_ops map209 */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 318