BPF object files are, in a way, the final artifact produced as part of the ahead-of-time compilation process. That makes them somewhat special compared to "regular" object files, which are a intermediate build artifacts that can typically be removed safely. As such, it can make sense to name them differently to make it easier to spot this difference at a glance. Among others, libbpf-bootstrap [0] has established the extension .bpf.o for BPF object files. It seems reasonable to follow this example and establish the same denomination for selftest build artifacts. To that end, this change adjusts the corresponding part of the build system and the test programs loading BPF object files to work with .bpf.o files. [0] https://github.com/libbpf/libbpf-bootstrap Suggested-by: Andrii Nakryiko <andrii@kernel.org> Signed-off-by: Daniel Müller <deso@posteo.net> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net> Link: https://lore.kernel.org/bpf/20220901222253.1199242-1-deso@posteo.net
914 lines
23 KiB
C
914 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <test_progs.h>
|
|
#include <network_helpers.h>
|
|
|
|
/* test_tailcall_1 checks basic functionality by patching multiple locations
|
|
* in a single program for a single tail call slot with nop->jmp, jmp->nop
|
|
* and jmp->jmp rewrites. Also checks for nop->nop.
|
|
*/
|
|
static void test_tailcall_1(void)
|
|
{
|
|
int err, map_fd, prog_fd, main_fd, i, j;
|
|
struct bpf_map *prog_array;
|
|
struct bpf_program *prog;
|
|
struct bpf_object *obj;
|
|
char prog_name[32];
|
|
char buff[128] = {};
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = buff,
|
|
.data_size_in = sizeof(buff),
|
|
.repeat = 1,
|
|
);
|
|
|
|
err = bpf_prog_test_load("tailcall1.bpf.o", BPF_PROG_TYPE_SCHED_CLS, &obj,
|
|
&prog_fd);
|
|
if (CHECK_FAIL(err))
|
|
return;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "entry");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
main_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(main_fd < 0))
|
|
goto out;
|
|
|
|
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
|
|
if (CHECK_FAIL(!prog_array))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(prog_array);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
goto out;
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);
|
|
|
|
prog = bpf_object__find_program_by_name(obj, prog_name);
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, i, "tailcall retval");
|
|
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 3, "tailcall retval");
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);
|
|
|
|
prog = bpf_object__find_program_by_name(obj, prog_name);
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_OK(topts.retval, "tailcall retval");
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
j = bpf_map__max_entries(prog_array) - 1 - i;
|
|
snprintf(prog_name, sizeof(prog_name), "classifier_%d", j);
|
|
|
|
prog = bpf_object__find_program_by_name(obj, prog_name);
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
j = bpf_map__max_entries(prog_array) - 1 - i;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, j, "tailcall retval");
|
|
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 3, "tailcall retval");
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err >= 0 || errno != ENOENT))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 3, "tailcall retval");
|
|
}
|
|
|
|
out:
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
/* test_tailcall_2 checks that patching multiple programs for a single
|
|
* tail call slot works. It also jumps through several programs and tests
|
|
* the tail call limit counter.
|
|
*/
|
|
static void test_tailcall_2(void)
|
|
{
|
|
int err, map_fd, prog_fd, main_fd, i;
|
|
struct bpf_map *prog_array;
|
|
struct bpf_program *prog;
|
|
struct bpf_object *obj;
|
|
char prog_name[32];
|
|
char buff[128] = {};
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = buff,
|
|
.data_size_in = sizeof(buff),
|
|
.repeat = 1,
|
|
);
|
|
|
|
err = bpf_prog_test_load("tailcall2.bpf.o", BPF_PROG_TYPE_SCHED_CLS, &obj,
|
|
&prog_fd);
|
|
if (CHECK_FAIL(err))
|
|
return;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "entry");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
main_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(main_fd < 0))
|
|
goto out;
|
|
|
|
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
|
|
if (CHECK_FAIL(!prog_array))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(prog_array);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
goto out;
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);
|
|
|
|
prog = bpf_object__find_program_by_name(obj, prog_name);
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 2, "tailcall retval");
|
|
|
|
i = 2;
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 1, "tailcall retval");
|
|
|
|
i = 0;
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 3, "tailcall retval");
|
|
out:
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
static void test_tailcall_count(const char *which)
|
|
{
|
|
int err, map_fd, prog_fd, main_fd, data_fd, i, val;
|
|
struct bpf_map *prog_array, *data_map;
|
|
struct bpf_program *prog;
|
|
struct bpf_object *obj;
|
|
char buff[128] = {};
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = buff,
|
|
.data_size_in = sizeof(buff),
|
|
.repeat = 1,
|
|
);
|
|
|
|
err = bpf_prog_test_load(which, BPF_PROG_TYPE_SCHED_CLS, &obj,
|
|
&prog_fd);
|
|
if (CHECK_FAIL(err))
|
|
return;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "entry");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
main_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(main_fd < 0))
|
|
goto out;
|
|
|
|
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
|
|
if (CHECK_FAIL(!prog_array))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(prog_array);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
goto out;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "classifier_0");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
i = 0;
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 1, "tailcall retval");
|
|
|
|
data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
|
|
if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
|
|
return;
|
|
|
|
data_fd = bpf_map__fd(data_map);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
return;
|
|
|
|
i = 0;
|
|
err = bpf_map_lookup_elem(data_fd, &i, &val);
|
|
ASSERT_OK(err, "tailcall count");
|
|
ASSERT_EQ(val, 33, "tailcall count");
|
|
|
|
i = 0;
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_OK(topts.retval, "tailcall retval");
|
|
out:
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
/* test_tailcall_3 checks that the count value of the tail call limit
|
|
* enforcement matches with expectations. JIT uses direct jump.
|
|
*/
|
|
static void test_tailcall_3(void)
|
|
{
|
|
test_tailcall_count("tailcall3.bpf.o");
|
|
}
|
|
|
|
/* test_tailcall_6 checks that the count value of the tail call limit
|
|
* enforcement matches with expectations. JIT uses indirect jump.
|
|
*/
|
|
static void test_tailcall_6(void)
|
|
{
|
|
test_tailcall_count("tailcall6.bpf.o");
|
|
}
|
|
|
|
/* test_tailcall_4 checks that the kernel properly selects indirect jump
|
|
* for the case where the key is not known. Latter is passed via global
|
|
* data to select different targets we can compare return value of.
|
|
*/
|
|
static void test_tailcall_4(void)
|
|
{
|
|
int err, map_fd, prog_fd, main_fd, data_fd, i;
|
|
struct bpf_map *prog_array, *data_map;
|
|
struct bpf_program *prog;
|
|
struct bpf_object *obj;
|
|
static const int zero = 0;
|
|
char buff[128] = {};
|
|
char prog_name[32];
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = buff,
|
|
.data_size_in = sizeof(buff),
|
|
.repeat = 1,
|
|
);
|
|
|
|
err = bpf_prog_test_load("tailcall4.bpf.o", BPF_PROG_TYPE_SCHED_CLS, &obj,
|
|
&prog_fd);
|
|
if (CHECK_FAIL(err))
|
|
return;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "entry");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
main_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(main_fd < 0))
|
|
goto out;
|
|
|
|
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
|
|
if (CHECK_FAIL(!prog_array))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(prog_array);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
goto out;
|
|
|
|
data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
|
|
if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
|
|
return;
|
|
|
|
data_fd = bpf_map__fd(data_map);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
return;
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);
|
|
|
|
prog = bpf_object__find_program_by_name(obj, prog_name);
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
err = bpf_map_update_elem(data_fd, &zero, &i, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, i, "tailcall retval");
|
|
}
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
err = bpf_map_update_elem(data_fd, &zero, &i, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 3, "tailcall retval");
|
|
}
|
|
out:
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
/* test_tailcall_5 probes similarly to test_tailcall_4 that the kernel generates
|
|
* an indirect jump when the keys are const but different from different branches.
|
|
*/
|
|
static void test_tailcall_5(void)
|
|
{
|
|
int err, map_fd, prog_fd, main_fd, data_fd, i, key[] = { 1111, 1234, 5678 };
|
|
struct bpf_map *prog_array, *data_map;
|
|
struct bpf_program *prog;
|
|
struct bpf_object *obj;
|
|
static const int zero = 0;
|
|
char buff[128] = {};
|
|
char prog_name[32];
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = buff,
|
|
.data_size_in = sizeof(buff),
|
|
.repeat = 1,
|
|
);
|
|
|
|
err = bpf_prog_test_load("tailcall5.bpf.o", BPF_PROG_TYPE_SCHED_CLS, &obj,
|
|
&prog_fd);
|
|
if (CHECK_FAIL(err))
|
|
return;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "entry");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
main_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(main_fd < 0))
|
|
goto out;
|
|
|
|
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
|
|
if (CHECK_FAIL(!prog_array))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(prog_array);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
goto out;
|
|
|
|
data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
|
|
if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
|
|
return;
|
|
|
|
data_fd = bpf_map__fd(data_map);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
return;
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);
|
|
|
|
prog = bpf_object__find_program_by_name(obj, prog_name);
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
err = bpf_map_update_elem(data_fd, &zero, &key[i], BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, i, "tailcall retval");
|
|
}
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
err = bpf_map_update_elem(data_fd, &zero, &key[i], BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 3, "tailcall retval");
|
|
}
|
|
out:
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
/* test_tailcall_bpf2bpf_1 purpose is to make sure that tailcalls are working
|
|
* correctly in correlation with BPF subprograms
|
|
*/
|
|
static void test_tailcall_bpf2bpf_1(void)
|
|
{
|
|
int err, map_fd, prog_fd, main_fd, i;
|
|
struct bpf_map *prog_array;
|
|
struct bpf_program *prog;
|
|
struct bpf_object *obj;
|
|
char prog_name[32];
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = &pkt_v4,
|
|
.data_size_in = sizeof(pkt_v4),
|
|
.repeat = 1,
|
|
);
|
|
|
|
err = bpf_prog_test_load("tailcall_bpf2bpf1.bpf.o", BPF_PROG_TYPE_SCHED_CLS,
|
|
&obj, &prog_fd);
|
|
if (CHECK_FAIL(err))
|
|
return;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "entry");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
main_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(main_fd < 0))
|
|
goto out;
|
|
|
|
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
|
|
if (CHECK_FAIL(!prog_array))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(prog_array);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
goto out;
|
|
|
|
/* nop -> jmp */
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);
|
|
|
|
prog = bpf_object__find_program_by_name(obj, prog_name);
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 1, "tailcall retval");
|
|
|
|
/* jmp -> nop, call subprog that will do tailcall */
|
|
i = 1;
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_OK(topts.retval, "tailcall retval");
|
|
|
|
/* make sure that subprog can access ctx and entry prog that
|
|
* called this subprog can properly return
|
|
*/
|
|
i = 0;
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, sizeof(pkt_v4) * 2, "tailcall retval");
|
|
out:
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
/* test_tailcall_bpf2bpf_2 checks that the count value of the tail call limit
|
|
* enforcement matches with expectations when tailcall is preceded with
|
|
* bpf2bpf call.
|
|
*/
|
|
static void test_tailcall_bpf2bpf_2(void)
|
|
{
|
|
int err, map_fd, prog_fd, main_fd, data_fd, i, val;
|
|
struct bpf_map *prog_array, *data_map;
|
|
struct bpf_program *prog;
|
|
struct bpf_object *obj;
|
|
char buff[128] = {};
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = buff,
|
|
.data_size_in = sizeof(buff),
|
|
.repeat = 1,
|
|
);
|
|
|
|
err = bpf_prog_test_load("tailcall_bpf2bpf2.bpf.o", BPF_PROG_TYPE_SCHED_CLS,
|
|
&obj, &prog_fd);
|
|
if (CHECK_FAIL(err))
|
|
return;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "entry");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
main_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(main_fd < 0))
|
|
goto out;
|
|
|
|
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
|
|
if (CHECK_FAIL(!prog_array))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(prog_array);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
goto out;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "classifier_0");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
i = 0;
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, 1, "tailcall retval");
|
|
|
|
data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
|
|
if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
|
|
return;
|
|
|
|
data_fd = bpf_map__fd(data_map);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
return;
|
|
|
|
i = 0;
|
|
err = bpf_map_lookup_elem(data_fd, &i, &val);
|
|
ASSERT_OK(err, "tailcall count");
|
|
ASSERT_EQ(val, 33, "tailcall count");
|
|
|
|
i = 0;
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_OK(topts.retval, "tailcall retval");
|
|
out:
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
/* test_tailcall_bpf2bpf_3 checks that non-trivial amount of stack (up to
|
|
* 256 bytes) can be used within bpf subprograms that have the tailcalls
|
|
* in them
|
|
*/
|
|
static void test_tailcall_bpf2bpf_3(void)
|
|
{
|
|
int err, map_fd, prog_fd, main_fd, i;
|
|
struct bpf_map *prog_array;
|
|
struct bpf_program *prog;
|
|
struct bpf_object *obj;
|
|
char prog_name[32];
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = &pkt_v4,
|
|
.data_size_in = sizeof(pkt_v4),
|
|
.repeat = 1,
|
|
);
|
|
|
|
err = bpf_prog_test_load("tailcall_bpf2bpf3.bpf.o", BPF_PROG_TYPE_SCHED_CLS,
|
|
&obj, &prog_fd);
|
|
if (CHECK_FAIL(err))
|
|
return;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "entry");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
main_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(main_fd < 0))
|
|
goto out;
|
|
|
|
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
|
|
if (CHECK_FAIL(!prog_array))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(prog_array);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
goto out;
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);
|
|
|
|
prog = bpf_object__find_program_by_name(obj, prog_name);
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, sizeof(pkt_v4) * 3, "tailcall retval");
|
|
|
|
i = 1;
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, sizeof(pkt_v4), "tailcall retval");
|
|
|
|
i = 0;
|
|
err = bpf_map_delete_elem(map_fd, &i);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, sizeof(pkt_v4) * 2, "tailcall retval");
|
|
out:
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
#include "tailcall_bpf2bpf4.skel.h"
|
|
|
|
/* test_tailcall_bpf2bpf_4 checks that tailcall counter is correctly preserved
|
|
* across tailcalls combined with bpf2bpf calls. for making sure that tailcall
|
|
* counter behaves correctly, bpf program will go through following flow:
|
|
*
|
|
* entry -> entry_subprog -> tailcall0 -> bpf_func0 -> subprog0 ->
|
|
* -> tailcall1 -> bpf_func1 -> subprog1 -> tailcall2 -> bpf_func2 ->
|
|
* subprog2 [here bump global counter] --------^
|
|
*
|
|
* We go through first two tailcalls and start counting from the subprog2 where
|
|
* the loop begins. At the end of the test make sure that the global counter is
|
|
* equal to 31, because tailcall counter includes the first two tailcalls
|
|
* whereas global counter is incremented only on loop presented on flow above.
|
|
*
|
|
* The noise parameter is used to insert bpf_map_update calls into the logic
|
|
* to force verifier to patch instructions. This allows us to ensure jump
|
|
* logic remains correct with instruction movement.
|
|
*/
|
|
static void test_tailcall_bpf2bpf_4(bool noise)
|
|
{
|
|
int err, map_fd, prog_fd, main_fd, data_fd, i;
|
|
struct tailcall_bpf2bpf4__bss val;
|
|
struct bpf_map *prog_array, *data_map;
|
|
struct bpf_program *prog;
|
|
struct bpf_object *obj;
|
|
char prog_name[32];
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = &pkt_v4,
|
|
.data_size_in = sizeof(pkt_v4),
|
|
.repeat = 1,
|
|
);
|
|
|
|
err = bpf_prog_test_load("tailcall_bpf2bpf4.bpf.o", BPF_PROG_TYPE_SCHED_CLS,
|
|
&obj, &prog_fd);
|
|
if (CHECK_FAIL(err))
|
|
return;
|
|
|
|
prog = bpf_object__find_program_by_name(obj, "entry");
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
main_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(main_fd < 0))
|
|
goto out;
|
|
|
|
prog_array = bpf_object__find_map_by_name(obj, "jmp_table");
|
|
if (CHECK_FAIL(!prog_array))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(prog_array);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
goto out;
|
|
|
|
for (i = 0; i < bpf_map__max_entries(prog_array); i++) {
|
|
snprintf(prog_name, sizeof(prog_name), "classifier_%d", i);
|
|
|
|
prog = bpf_object__find_program_by_name(obj, prog_name);
|
|
if (CHECK_FAIL(!prog))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(prog);
|
|
if (CHECK_FAIL(prog_fd < 0))
|
|
goto out;
|
|
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
}
|
|
|
|
data_map = bpf_object__find_map_by_name(obj, "tailcall.bss");
|
|
if (CHECK_FAIL(!data_map || !bpf_map__is_internal(data_map)))
|
|
return;
|
|
|
|
data_fd = bpf_map__fd(data_map);
|
|
if (CHECK_FAIL(map_fd < 0))
|
|
return;
|
|
|
|
i = 0;
|
|
val.noise = noise;
|
|
val.count = 0;
|
|
err = bpf_map_update_elem(data_fd, &i, &val, BPF_ANY);
|
|
if (CHECK_FAIL(err))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "tailcall");
|
|
ASSERT_EQ(topts.retval, sizeof(pkt_v4) * 3, "tailcall retval");
|
|
|
|
i = 0;
|
|
err = bpf_map_lookup_elem(data_fd, &i, &val);
|
|
ASSERT_OK(err, "tailcall count");
|
|
ASSERT_EQ(val.count, 31, "tailcall count");
|
|
|
|
out:
|
|
bpf_object__close(obj);
|
|
}
|
|
|
|
#include "tailcall_bpf2bpf6.skel.h"
|
|
|
|
/* Tail call counting works even when there is data on stack which is
|
|
* not aligned to 8 bytes.
|
|
*/
|
|
static void test_tailcall_bpf2bpf_6(void)
|
|
{
|
|
struct tailcall_bpf2bpf6 *obj;
|
|
int err, map_fd, prog_fd, main_fd, data_fd, i, val;
|
|
LIBBPF_OPTS(bpf_test_run_opts, topts,
|
|
.data_in = &pkt_v4,
|
|
.data_size_in = sizeof(pkt_v4),
|
|
.repeat = 1,
|
|
);
|
|
|
|
obj = tailcall_bpf2bpf6__open_and_load();
|
|
if (!ASSERT_OK_PTR(obj, "open and load"))
|
|
return;
|
|
|
|
main_fd = bpf_program__fd(obj->progs.entry);
|
|
if (!ASSERT_GE(main_fd, 0, "entry prog fd"))
|
|
goto out;
|
|
|
|
map_fd = bpf_map__fd(obj->maps.jmp_table);
|
|
if (!ASSERT_GE(map_fd, 0, "jmp_table map fd"))
|
|
goto out;
|
|
|
|
prog_fd = bpf_program__fd(obj->progs.classifier_0);
|
|
if (!ASSERT_GE(prog_fd, 0, "classifier_0 prog fd"))
|
|
goto out;
|
|
|
|
i = 0;
|
|
err = bpf_map_update_elem(map_fd, &i, &prog_fd, BPF_ANY);
|
|
if (!ASSERT_OK(err, "jmp_table map update"))
|
|
goto out;
|
|
|
|
err = bpf_prog_test_run_opts(main_fd, &topts);
|
|
ASSERT_OK(err, "entry prog test run");
|
|
ASSERT_EQ(topts.retval, 0, "tailcall retval");
|
|
|
|
data_fd = bpf_map__fd(obj->maps.bss);
|
|
if (!ASSERT_GE(map_fd, 0, "bss map fd"))
|
|
goto out;
|
|
|
|
i = 0;
|
|
err = bpf_map_lookup_elem(data_fd, &i, &val);
|
|
ASSERT_OK(err, "bss map lookup");
|
|
ASSERT_EQ(val, 1, "done flag is set");
|
|
|
|
out:
|
|
tailcall_bpf2bpf6__destroy(obj);
|
|
}
|
|
|
|
void test_tailcalls(void)
|
|
{
|
|
if (test__start_subtest("tailcall_1"))
|
|
test_tailcall_1();
|
|
if (test__start_subtest("tailcall_2"))
|
|
test_tailcall_2();
|
|
if (test__start_subtest("tailcall_3"))
|
|
test_tailcall_3();
|
|
if (test__start_subtest("tailcall_4"))
|
|
test_tailcall_4();
|
|
if (test__start_subtest("tailcall_5"))
|
|
test_tailcall_5();
|
|
if (test__start_subtest("tailcall_6"))
|
|
test_tailcall_6();
|
|
if (test__start_subtest("tailcall_bpf2bpf_1"))
|
|
test_tailcall_bpf2bpf_1();
|
|
if (test__start_subtest("tailcall_bpf2bpf_2"))
|
|
test_tailcall_bpf2bpf_2();
|
|
if (test__start_subtest("tailcall_bpf2bpf_3"))
|
|
test_tailcall_bpf2bpf_3();
|
|
if (test__start_subtest("tailcall_bpf2bpf_4"))
|
|
test_tailcall_bpf2bpf_4(false);
|
|
if (test__start_subtest("tailcall_bpf2bpf_5"))
|
|
test_tailcall_bpf2bpf_4(true);
|
|
if (test__start_subtest("tailcall_bpf2bpf_6"))
|
|
test_tailcall_bpf2bpf_6();
|
|
}
|