selftests/bpf: Test cgroup_iter.
Add a selftest for cgroup_iter. The selftest creates a mini cgroup tree of the following structure: ROOT (working cgroup) | PARENT / \ CHILD1 CHILD2 and tests the following scenarios: - invalid cgroup fd. - pre-order walk over descendants from PARENT. - post-order walk over descendants from PARENT. - walk of ancestors from PARENT. - process only a single object (i.e. PARENT). - early termination. Acked-by: Yonghong Song <yhs@fb.com> Acked-by: Andrii Nakryiko <andrii@kernel.org> Signed-off-by: Hao Luo <haoluo@google.com> Link: https://lore.kernel.org/r/20220824233117.1312810-3-haoluo@google.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
parent
d4ccaf58a8
commit
fe0dd9d4b7
@ -764,7 +764,7 @@ static void test_btf_dump_struct_data(struct btf *btf, struct btf_dump *d,
|
||||
|
||||
/* union with nested struct */
|
||||
TEST_BTF_DUMP_DATA(btf, d, "union", str, union bpf_iter_link_info, BTF_F_COMPACT,
|
||||
"(union bpf_iter_link_info){.map = (struct){.map_fd = (__u32)1,},.cgroup = (struct){.order = (__u32)1,.cgroup_fd = (__u32)1,},}",
|
||||
"(union bpf_iter_link_info){.map = (struct){.map_fd = (__u32)1,},.cgroup = (struct){.order = (enum bpf_cgroup_iter_order)BPF_ITER_SELF_ONLY,.cgroup_fd = (__u32)1,},}",
|
||||
{ .cgroup = { .order = 1, .cgroup_fd = 1, }});
|
||||
|
||||
/* struct skb with nested structs/unions; because type output is so
|
||||
|
224
tools/testing/selftests/bpf/prog_tests/cgroup_iter.c
Normal file
224
tools/testing/selftests/bpf/prog_tests/cgroup_iter.c
Normal file
@ -0,0 +1,224 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2022 Google */
|
||||
|
||||
#include <test_progs.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <bpf/btf.h>
|
||||
#include "cgroup_iter.skel.h"
|
||||
#include "cgroup_helpers.h"
|
||||
|
||||
#define ROOT 0
|
||||
#define PARENT 1
|
||||
#define CHILD1 2
|
||||
#define CHILD2 3
|
||||
#define NUM_CGROUPS 4
|
||||
|
||||
#define PROLOGUE "prologue\n"
|
||||
#define EPILOGUE "epilogue\n"
|
||||
|
||||
static const char *cg_path[] = {
|
||||
"/", "/parent", "/parent/child1", "/parent/child2"
|
||||
};
|
||||
|
||||
static int cg_fd[] = {-1, -1, -1, -1};
|
||||
static unsigned long long cg_id[] = {0, 0, 0, 0};
|
||||
static char expected_output[64];
|
||||
|
||||
static int setup_cgroups(void)
|
||||
{
|
||||
int fd, i = 0;
|
||||
|
||||
for (i = 0; i < NUM_CGROUPS; i++) {
|
||||
fd = create_and_get_cgroup(cg_path[i]);
|
||||
if (fd < 0)
|
||||
return fd;
|
||||
|
||||
cg_fd[i] = fd;
|
||||
cg_id[i] = get_cgroup_id(cg_path[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cleanup_cgroups(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_CGROUPS; i++)
|
||||
close(cg_fd[i]);
|
||||
}
|
||||
|
||||
static void read_from_cgroup_iter(struct bpf_program *prog, int cgroup_fd,
|
||||
int order, const char *testname)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
|
||||
union bpf_iter_link_info linfo;
|
||||
struct bpf_link *link;
|
||||
int len, iter_fd;
|
||||
static char buf[128];
|
||||
size_t left;
|
||||
char *p;
|
||||
|
||||
memset(&linfo, 0, sizeof(linfo));
|
||||
linfo.cgroup.cgroup_fd = cgroup_fd;
|
||||
linfo.cgroup.order = order;
|
||||
opts.link_info = &linfo;
|
||||
opts.link_info_len = sizeof(linfo);
|
||||
|
||||
link = bpf_program__attach_iter(prog, &opts);
|
||||
if (!ASSERT_OK_PTR(link, "attach_iter"))
|
||||
return;
|
||||
|
||||
iter_fd = bpf_iter_create(bpf_link__fd(link));
|
||||
if (iter_fd < 0)
|
||||
goto free_link;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
left = ARRAY_SIZE(buf);
|
||||
p = buf;
|
||||
while ((len = read(iter_fd, p, left)) > 0) {
|
||||
p += len;
|
||||
left -= len;
|
||||
}
|
||||
|
||||
ASSERT_STREQ(buf, expected_output, testname);
|
||||
|
||||
/* read() after iter finishes should be ok. */
|
||||
if (len == 0)
|
||||
ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read");
|
||||
|
||||
close(iter_fd);
|
||||
free_link:
|
||||
bpf_link__destroy(link);
|
||||
}
|
||||
|
||||
/* Invalid cgroup. */
|
||||
static void test_invalid_cgroup(struct cgroup_iter *skel)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
|
||||
union bpf_iter_link_info linfo;
|
||||
struct bpf_link *link;
|
||||
|
||||
memset(&linfo, 0, sizeof(linfo));
|
||||
linfo.cgroup.cgroup_fd = (__u32)-1;
|
||||
opts.link_info = &linfo;
|
||||
opts.link_info_len = sizeof(linfo);
|
||||
|
||||
link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
|
||||
ASSERT_ERR_PTR(link, "attach_iter");
|
||||
bpf_link__destroy(link);
|
||||
}
|
||||
|
||||
/* Specifying both cgroup_fd and cgroup_id is invalid. */
|
||||
static void test_invalid_cgroup_spec(struct cgroup_iter *skel)
|
||||
{
|
||||
DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts);
|
||||
union bpf_iter_link_info linfo;
|
||||
struct bpf_link *link;
|
||||
|
||||
memset(&linfo, 0, sizeof(linfo));
|
||||
linfo.cgroup.cgroup_fd = (__u32)cg_fd[PARENT];
|
||||
linfo.cgroup.cgroup_id = (__u64)cg_id[PARENT];
|
||||
opts.link_info = &linfo;
|
||||
opts.link_info_len = sizeof(linfo);
|
||||
|
||||
link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts);
|
||||
ASSERT_ERR_PTR(link, "attach_iter");
|
||||
bpf_link__destroy(link);
|
||||
}
|
||||
|
||||
/* Preorder walk prints parent and child in order. */
|
||||
static void test_walk_preorder(struct cgroup_iter *skel)
|
||||
{
|
||||
snprintf(expected_output, sizeof(expected_output),
|
||||
PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE,
|
||||
cg_id[PARENT], cg_id[CHILD1], cg_id[CHILD2]);
|
||||
|
||||
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
||||
BPF_ITER_DESCENDANTS_PRE, "preorder");
|
||||
}
|
||||
|
||||
/* Postorder walk prints child and parent in order. */
|
||||
static void test_walk_postorder(struct cgroup_iter *skel)
|
||||
{
|
||||
snprintf(expected_output, sizeof(expected_output),
|
||||
PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE,
|
||||
cg_id[CHILD1], cg_id[CHILD2], cg_id[PARENT]);
|
||||
|
||||
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
||||
BPF_ITER_DESCENDANTS_POST, "postorder");
|
||||
}
|
||||
|
||||
/* Walking parents prints parent and then root. */
|
||||
static void test_walk_ancestors_up(struct cgroup_iter *skel)
|
||||
{
|
||||
/* terminate the walk when ROOT is met. */
|
||||
skel->bss->terminal_cgroup = cg_id[ROOT];
|
||||
|
||||
snprintf(expected_output, sizeof(expected_output),
|
||||
PROLOGUE "%8llu\n%8llu\n" EPILOGUE,
|
||||
cg_id[PARENT], cg_id[ROOT]);
|
||||
|
||||
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
||||
BPF_ITER_ANCESTORS_UP, "ancestors_up");
|
||||
|
||||
skel->bss->terminal_cgroup = 0;
|
||||
}
|
||||
|
||||
/* Early termination prints parent only. */
|
||||
static void test_early_termination(struct cgroup_iter *skel)
|
||||
{
|
||||
/* terminate the walk after the first element is processed. */
|
||||
skel->bss->terminate_early = 1;
|
||||
|
||||
snprintf(expected_output, sizeof(expected_output),
|
||||
PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]);
|
||||
|
||||
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
||||
BPF_ITER_DESCENDANTS_PRE, "early_termination");
|
||||
|
||||
skel->bss->terminate_early = 0;
|
||||
}
|
||||
|
||||
/* Waling self prints self only. */
|
||||
static void test_walk_self_only(struct cgroup_iter *skel)
|
||||
{
|
||||
snprintf(expected_output, sizeof(expected_output),
|
||||
PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]);
|
||||
|
||||
read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT],
|
||||
BPF_ITER_SELF_ONLY, "self_only");
|
||||
}
|
||||
|
||||
void test_cgroup_iter(void)
|
||||
{
|
||||
struct cgroup_iter *skel = NULL;
|
||||
|
||||
if (setup_cgroup_environment())
|
||||
return;
|
||||
|
||||
if (setup_cgroups())
|
||||
goto out;
|
||||
|
||||
skel = cgroup_iter__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "cgroup_iter__open_and_load"))
|
||||
goto out;
|
||||
|
||||
if (test__start_subtest("cgroup_iter__invalid_cgroup"))
|
||||
test_invalid_cgroup(skel);
|
||||
if (test__start_subtest("cgroup_iter__invalid_cgroup_spec"))
|
||||
test_invalid_cgroup_spec(skel);
|
||||
if (test__start_subtest("cgroup_iter__preorder"))
|
||||
test_walk_preorder(skel);
|
||||
if (test__start_subtest("cgroup_iter__postorder"))
|
||||
test_walk_postorder(skel);
|
||||
if (test__start_subtest("cgroup_iter__ancestors_up_walk"))
|
||||
test_walk_ancestors_up(skel);
|
||||
if (test__start_subtest("cgroup_iter__early_termination"))
|
||||
test_early_termination(skel);
|
||||
if (test__start_subtest("cgroup_iter__self_only"))
|
||||
test_walk_self_only(skel);
|
||||
out:
|
||||
cgroup_iter__destroy(skel);
|
||||
cleanup_cgroups();
|
||||
cleanup_cgroup_environment();
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
#define bpf_iter__bpf_sk_storage_map bpf_iter__bpf_sk_storage_map___not_used
|
||||
#define bpf_iter__sockmap bpf_iter__sockmap___not_used
|
||||
#define bpf_iter__bpf_link bpf_iter__bpf_link___not_used
|
||||
#define bpf_iter__cgroup bpf_iter__cgroup___not_used
|
||||
#define btf_ptr btf_ptr___not_used
|
||||
#define BTF_F_COMPACT BTF_F_COMPACT___not_used
|
||||
#define BTF_F_NONAME BTF_F_NONAME___not_used
|
||||
@ -40,6 +41,7 @@
|
||||
#undef bpf_iter__bpf_sk_storage_map
|
||||
#undef bpf_iter__sockmap
|
||||
#undef bpf_iter__bpf_link
|
||||
#undef bpf_iter__cgroup
|
||||
#undef btf_ptr
|
||||
#undef BTF_F_COMPACT
|
||||
#undef BTF_F_NONAME
|
||||
@ -141,6 +143,11 @@ struct bpf_iter__bpf_link {
|
||||
struct bpf_link *link;
|
||||
};
|
||||
|
||||
struct bpf_iter__cgroup {
|
||||
struct bpf_iter_meta *meta;
|
||||
struct cgroup *cgroup;
|
||||
} __attribute__((preserve_access_index));
|
||||
|
||||
struct btf_ptr {
|
||||
void *ptr;
|
||||
__u32 type_id;
|
||||
|
39
tools/testing/selftests/bpf/progs/cgroup_iter.c
Normal file
39
tools/testing/selftests/bpf/progs/cgroup_iter.c
Normal file
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2022 Google */
|
||||
|
||||
#include "bpf_iter.h"
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
int terminate_early = 0;
|
||||
u64 terminal_cgroup = 0;
|
||||
|
||||
static inline u64 cgroup_id(struct cgroup *cgrp)
|
||||
{
|
||||
return cgrp->kn->id;
|
||||
}
|
||||
|
||||
SEC("iter/cgroup")
|
||||
int cgroup_id_printer(struct bpf_iter__cgroup *ctx)
|
||||
{
|
||||
struct seq_file *seq = ctx->meta->seq;
|
||||
struct cgroup *cgrp = ctx->cgroup;
|
||||
|
||||
/* epilogue */
|
||||
if (cgrp == NULL) {
|
||||
BPF_SEQ_PRINTF(seq, "epilogue\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* prologue */
|
||||
if (ctx->meta->seq_num == 0)
|
||||
BPF_SEQ_PRINTF(seq, "prologue\n");
|
||||
|
||||
BPF_SEQ_PRINTF(seq, "%8llu\n", cgroup_id(cgrp));
|
||||
|
||||
if (terminal_cgroup == cgroup_id(cgrp))
|
||||
return 1;
|
||||
|
||||
return terminate_early ? 1 : 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user