blob: 5671c31085cd1655f1ce4615478a89f8ecd704eb [file]
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#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();
}