// SPDX-License-Identifier: GPL-2.0 #include #include "bpf/libbpf_internal.h" #include "test_global_percpu_data.skel.h" #include "test_global_percpu_data.lskel.h" void test_global_data_init(void) { const char *file = "./test_global_data.bpf.o"; int err = -ENOMEM, map_fd, zero = 0; __u8 *buff = NULL, *newval = NULL; struct bpf_object *obj; struct bpf_map *map; __u32 duration = 0; size_t sz; obj = bpf_object__open_file(file, NULL); err = libbpf_get_error(obj); if (CHECK_FAIL(err)) return; map = bpf_object__find_map_by_name(obj, ".rodata"); if (CHECK_FAIL(!map || !bpf_map__is_internal(map))) goto out; sz = bpf_map__value_size(map); newval = malloc(sz); if (CHECK_FAIL(!newval)) goto out; memset(newval, 0, sz); /* wrong size, should fail */ err = bpf_map__set_initial_value(map, newval, sz - 1); if (CHECK(!err, "reject set initial value wrong size", "err %d\n", err)) goto out; err = bpf_map__set_initial_value(map, newval, sz); if (CHECK(err, "set initial value", "err %d\n", err)) goto out; err = bpf_object__load(obj); if (CHECK_FAIL(err)) goto out; map_fd = bpf_map__fd(map); if (CHECK_FAIL(map_fd < 0)) goto out; buff = malloc(sz); if (buff) err = bpf_map_lookup_elem(map_fd, &zero, buff); if (CHECK(!buff || err || memcmp(buff, newval, sz), "compare .rodata map data override", "err %d errno %d\n", err, errno)) goto out; memset(newval, 1, sz); /* object loaded - should fail */ err = bpf_map__set_initial_value(map, newval, sz); CHECK(!err, "reject set initial value after load", "err %d\n", err); out: free(buff); free(newval); bpf_object__close(obj); } static void test_percpu_data_on_cpus(struct bpf_map *map, int map_fd, int prog_fd, int *runp) { struct test_global_percpu_data__percpu *data = NULL; int i, err, key = 0, num_online, run = 0; __u64 args[2] = {0x1234ULL, 0x5678ULL}; size_t data_sz; bool *online; LIBBPF_OPTS(bpf_test_run_opts, topts, .ctx_in = args, .ctx_size_in = sizeof(args), .flags = BPF_F_TEST_RUN_ON_CPU, ); err = parse_cpu_mask_file("/sys/devices/system/cpu/online", &online, &num_online); if (!ASSERT_OK(err, "parse_cpu_mask_file")) return; data_sz = map ? bpf_map__value_size(map) : sizeof(*data); data = calloc(1, data_sz); if (!ASSERT_OK_PTR(data, "calloc percpu data")) goto out; /* run on every online-CPU */ for (i = 0; i < num_online; i++) { __u64 flags; if (!online[i]) continue; topts.cpu = i; topts.retval = -1; err = bpf_prog_test_run_opts(prog_fd, &topts); ASSERT_OK(err, "bpf_prog_test_run_opts"); ASSERT_EQ(topts.retval, 0, "bpf_prog_test_run_opts retval"); memset(data, 0, data_sz); flags = ((__u64) i << 32) | BPF_F_CPU; if (map) err = bpf_map__lookup_elem(map, &key, sizeof(key), data, data_sz, flags); else err = bpf_map_lookup_elem_flags(map_fd, &key, data, flags); if (!ASSERT_OK(err, "lookup_elem on cpu")) break; ASSERT_EQ(*runp, ++run, "run"); ASSERT_EQ(data->cpu_id[0], i, "cpu_id"); ASSERT_EQ(data->data, 1, "data"); ASSERT_TRUE(data->set, "set"); ASSERT_EQ(data->nums[6], 0xc0de, "nums[6]"); ASSERT_EQ(data->struct_data.i, 1, "struct_data.i"); ASSERT_TRUE(data->struct_data.set, "struct_data.set"); ASSERT_EQ(data->struct_data.nums[6], 0xc0de, "struct_data.nums[6]"); } out: free(data); free(online); } static void test_global_percpu_data_init(void) { struct test_global_percpu_data__percpu init_value = {}; struct test_global_percpu_data__percpu *init_data; const __u32 desired_sz = sysconf(_SC_PAGE_SIZE); struct test_global_percpu_data *skel = NULL; size_t init_data_sz; struct bpf_map *map; int prog_fd, err; skel = test_global_percpu_data__open(); if (!ASSERT_OK_PTR(skel, "test_global_percpu_data__open")) goto out; if (!ASSERT_OK_PTR(skel->percpu, "skel->percpu")) goto out; if (!ASSERT_OK_PTR(skel->data_percpu, "skel->data_percpu")) goto out; if (!ASSERT_OK_PTR(skel->percpu_data, "skel->percpu_data")) goto out; if (!ASSERT_OK_PTR(skel->percpu_looooooooong, "skel->percpu_looooooooong")) goto out; ASSERT_STREQ(bpf_map__name(skel->maps.percpu_data), ".percpu.data", ".percpu.data map name"); ASSERT_STREQ(bpf_map__name(skel->maps.data_percpu), ".data.percpu", ".data.percpu map name"); ASSERT_STREQ(bpf_map__name(skel->maps.percpu_looooooooong), ".percpu.looooooooong", "long map name"); ASSERT_STREQ(bpf_map__name(skel->maps.percpu), ".percpu", "map name"); ASSERT_EQ(skel->percpu->data, -1, "skel->percpu->data"); ASSERT_FALSE(skel->percpu->set, "skel->percpu->set"); ASSERT_EQ(skel->percpu->nums[6], 0, "skel->percpu->nums[6]"); ASSERT_EQ(skel->percpu->struct_data.i, -1, "struct_data.i"); ASSERT_FALSE(skel->percpu->struct_data.set, "struct_data.set"); ASSERT_EQ(skel->percpu->struct_data.nums[6], 0, "struct_data.nums[6]"); map = skel->maps.percpu; if (!ASSERT_EQ(bpf_map__type(map), BPF_MAP_TYPE_PERCPU_ARRAY, "bpf_map__type")) goto out; init_value.data = 2; init_value.nums[6] = -1; init_value.struct_data.i = 2; init_value.struct_data.nums[6] = -1; err = bpf_map__set_initial_value(map, &init_value, sizeof(init_value)); if (!ASSERT_OK(err, "bpf_map__set_initial_value")) goto out; init_data = bpf_map__initial_value(map, &init_data_sz); if (!ASSERT_OK_PTR(init_data, "bpf_map__initial_value")) goto out; ASSERT_EQ(init_data->data, init_value.data, "init_value data"); ASSERT_EQ(init_data->set, init_value.set, "init_value set"); ASSERT_EQ(init_data->struct_data.i, init_value.struct_data.i, "init_value struct_data.i"); ASSERT_EQ(init_data->struct_data.nums[6], init_value.struct_data.nums[6], "init_value struct_data.nums[6]"); ASSERT_EQ(init_data_sz, sizeof(init_value), "init_value size"); ASSERT_EQ((void *) init_data, (void *) skel->percpu, "skel->percpu eq init_data"); ASSERT_EQ(skel->percpu->data, init_value.data, "skel->percpu->data"); ASSERT_EQ(skel->percpu->set, init_value.set, "skel->percpu->set"); ASSERT_EQ(skel->percpu->struct_data.i, init_value.struct_data.i, "skel->percpu->struct_data.i"); ASSERT_EQ(skel->percpu->struct_data.nums[6], init_value.struct_data.nums[6], "skel->percpu->struct_data.nums[6]"); ASSERT_GT(desired_sz, sizeof(init_value), "desired_sz"); err = bpf_map__set_value_size(map, desired_sz); if (!ASSERT_OK(err, "bpf_map__set_value_size")) goto out; if (!ASSERT_EQ(bpf_map__value_size(map), desired_sz, "percpu value size")) goto out; if (!ASSERT_NEQ(bpf_map__btf_value_type_id(map), 0, "percpu BTF value type")) goto out; init_data = bpf_map__initial_value(map, &init_data_sz); if (!ASSERT_OK_PTR(init_data, "resized bpf_map__initial_value")) goto out; if (!ASSERT_EQ(init_data_sz, desired_sz, "resized initial value size")) goto out; if (!ASSERT_EQ(init_data->data, init_value.data, "resized initial value data")) goto out; err = test_global_percpu_data__load(skel); if (!ASSERT_OK(err, "test_global_percpu_data__load")) goto out; ASSERT_OK_PTR(skel->percpu, "skel->percpu"); prog_fd = bpf_program__fd(skel->progs.update_percpu_data); test_percpu_data_on_cpus(map, bpf_map__fd(map), prog_fd, &skel->bss->run); out: test_global_percpu_data__destroy(skel); } static void test_global_percpu_data_lskel(void) { struct test_global_percpu_data_lskel *lskel = NULL; int prog_fd, map_fd; lskel = test_global_percpu_data_lskel__open_and_load(); if (!ASSERT_OK_PTR(lskel, "test_global_percpu_data_lskel__open_and_load")) goto out; map_fd = lskel->maps.percpu.map_fd; prog_fd = lskel->progs.update_percpu_data.prog_fd; test_percpu_data_on_cpus(NULL, map_fd, prog_fd, &lskel->bss->run); out: test_global_percpu_data_lskel__destroy(lskel); } static int create_rdonly_percpu_array(void) { LIBBPF_OPTS(bpf_map_create_opts, map_opts, .map_flags = BPF_F_RDONLY_PROG, ); int key = 0, map_fd, err; __u64 value = 0; map_fd = bpf_map_create(BPF_MAP_TYPE_PERCPU_ARRAY, "percpu_ro_map", sizeof(int), sizeof(__u64), 1, &map_opts); if (!ASSERT_GE(map_fd, 0, "bpf_map_create")) return -1; err = bpf_map_update_elem(map_fd, &key, &value, BPF_F_ALL_CPUS); if (!ASSERT_OK(err, "bpf_map_update_elem")) goto out; err = bpf_map_freeze(map_fd); if (!ASSERT_OK(err, "bpf_map_freeze")) goto out; return map_fd; out: close(map_fd); return -1; } static void test_global_percpu_data_rdonly_direct_read(void) { /* * Raw instructions with manually prepared rdonly percpu_array map * for testing direct-read global percpu data, because libbpf * doesn't have rdonly internal percpu_array map support for * global percpu data. */ struct bpf_insn insns[] = { BPF_LD_MAP_VALUE(BPF_REG_1, 0, 0), BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, 0), BPF_EXIT_INSN(), }; int map_fd, prog_fd; map_fd = create_rdonly_percpu_array(); if (map_fd < 0) return; insns[0].imm = map_fd; prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, "percpu_ro_prog", "GPL", insns, ARRAY_SIZE(insns), NULL); if (ASSERT_GE(prog_fd, 0, "bpf_prog_load")) close(prog_fd); close(map_fd); } static void test_global_percpu_data_rdonly_direct_write(void) { LIBBPF_OPTS(bpf_prog_load_opts, prog_opts); /* See the comment in test_global_percpu_data_rdonly_direct_read() */ struct bpf_insn insns[] = { BPF_LD_MAP_VALUE(BPF_REG_1, 0, 0), BPF_LDX_MEM(BPF_DW, BPF_REG_0, BPF_REG_1, 0), BPF_ST_MEM(BPF_DW, BPF_REG_1, 0, 0), BPF_EXIT_INSN(), }; char log_buf[256] = {}; int map_fd, prog_fd; prog_opts.log_buf = log_buf; prog_opts.log_size = sizeof(log_buf); prog_opts.log_level = 1; map_fd = create_rdonly_percpu_array(); if (map_fd < 0) return; insns[0].imm = map_fd; prog_fd = bpf_prog_load(BPF_PROG_TYPE_SOCKET_FILTER, "percpu_ro_prog", "GPL", insns, ARRAY_SIZE(insns), &prog_opts); if (!ASSERT_LT(prog_fd, 0, "bpf_prog_load")) close(prog_fd); else ASSERT_HAS_SUBSTR(log_buf, "write into map forbidden", "verifier log"); close(map_fd); } static void test_global_percpu_data_verifier_log(void) { RUN_TESTS(test_global_percpu_data); } static void test_global_percpu_data_iter(void) { DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts); struct test_global_percpu_data *skel; union bpf_iter_link_info linfo = {}; struct bpf_link *link = NULL; int fd, num_cpus, len, err; char buf[16]; num_cpus = libbpf_num_possible_cpus(); if (!ASSERT_GT(num_cpus, 0, "libbpf_num_possible_cpus")) return; skel = test_global_percpu_data__open(); if (!ASSERT_OK_PTR(skel, "test_global_percpu_data__open")) return; skel->rodata->num_cpus = num_cpus; skel->rodata->num_off = offsetof(struct test_global_percpu_data__percpu, struct_data.nums[6]); skel->rodata->elem_sz = roundup(sizeof(struct test_global_percpu_data__percpu), 8); skel->percpu->struct_data.nums[6] = 0xc0de; err = test_global_percpu_data__load(skel); if (!ASSERT_OK(err, "test_global_percpu_data__load")) goto out; linfo.map.map_fd = bpf_map__fd(skel->maps.percpu); opts.link_info = &linfo; opts.link_info_len = sizeof(linfo); link = bpf_program__attach_iter(skel->progs.dump_percpu_data, &opts); if (!ASSERT_OK_PTR(link, "bpf_program__attach_iter")) goto out; fd = bpf_iter_create(bpf_link__fd(link)); if (!ASSERT_GE(fd, 0, "bpf_iter_create")) goto out; while ((len = read(fd, buf, sizeof(buf))) > 0) do { } while (0); ASSERT_EQ(len, 0, "read iter"); ASSERT_TRUE(skel->bss->run_iter, "run_iter"); ASSERT_EQ(skel->bss->sum, 0xc0de * num_cpus, "sum"); close(fd); out: bpf_link__destroy(link); test_global_percpu_data__destroy(skel); } void test_global_percpu_data(void) { if (!feat_supported(NULL, FEAT_PERCPU_DATA)) { test__skip(); return; } if (test__start_subtest("init")) test_global_percpu_data_init(); if (test__start_subtest("lskel")) test_global_percpu_data_lskel(); if (test__start_subtest("rdonly_direct_read")) test_global_percpu_data_rdonly_direct_read(); if (test__start_subtest("rdonly_direct_write")) test_global_percpu_data_rdonly_direct_write(); test_global_percpu_data_verifier_log(); if (test__start_subtest("iter")) test_global_percpu_data_iter(); }