Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Hao Luo and committed by
Alexei Starovoitov
fe0dd9d4 d4ccaf58

+271 -1
+1 -1
tools/testing/selftests/bpf/prog_tests/btf_dump.c
··· 764 764 765 765 /* union with nested struct */ 766 766 TEST_BTF_DUMP_DATA(btf, d, "union", str, union bpf_iter_link_info, BTF_F_COMPACT, 767 - "(union bpf_iter_link_info){.map = (struct){.map_fd = (__u32)1,},.cgroup = (struct){.order = (__u32)1,.cgroup_fd = (__u32)1,},}", 767 + "(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,},}", 768 768 { .cgroup = { .order = 1, .cgroup_fd = 1, }}); 769 769 770 770 /* struct skb with nested structs/unions; because type output is so
+224
tools/testing/selftests/bpf/prog_tests/cgroup_iter.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (c) 2022 Google */ 3 + 4 + #include <test_progs.h> 5 + #include <bpf/libbpf.h> 6 + #include <bpf/btf.h> 7 + #include "cgroup_iter.skel.h" 8 + #include "cgroup_helpers.h" 9 + 10 + #define ROOT 0 11 + #define PARENT 1 12 + #define CHILD1 2 13 + #define CHILD2 3 14 + #define NUM_CGROUPS 4 15 + 16 + #define PROLOGUE "prologue\n" 17 + #define EPILOGUE "epilogue\n" 18 + 19 + static const char *cg_path[] = { 20 + "/", "/parent", "/parent/child1", "/parent/child2" 21 + }; 22 + 23 + static int cg_fd[] = {-1, -1, -1, -1}; 24 + static unsigned long long cg_id[] = {0, 0, 0, 0}; 25 + static char expected_output[64]; 26 + 27 + static int setup_cgroups(void) 28 + { 29 + int fd, i = 0; 30 + 31 + for (i = 0; i < NUM_CGROUPS; i++) { 32 + fd = create_and_get_cgroup(cg_path[i]); 33 + if (fd < 0) 34 + return fd; 35 + 36 + cg_fd[i] = fd; 37 + cg_id[i] = get_cgroup_id(cg_path[i]); 38 + } 39 + return 0; 40 + } 41 + 42 + static void cleanup_cgroups(void) 43 + { 44 + int i; 45 + 46 + for (i = 0; i < NUM_CGROUPS; i++) 47 + close(cg_fd[i]); 48 + } 49 + 50 + static void read_from_cgroup_iter(struct bpf_program *prog, int cgroup_fd, 51 + int order, const char *testname) 52 + { 53 + DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts); 54 + union bpf_iter_link_info linfo; 55 + struct bpf_link *link; 56 + int len, iter_fd; 57 + static char buf[128]; 58 + size_t left; 59 + char *p; 60 + 61 + memset(&linfo, 0, sizeof(linfo)); 62 + linfo.cgroup.cgroup_fd = cgroup_fd; 63 + linfo.cgroup.order = order; 64 + opts.link_info = &linfo; 65 + opts.link_info_len = sizeof(linfo); 66 + 67 + link = bpf_program__attach_iter(prog, &opts); 68 + if (!ASSERT_OK_PTR(link, "attach_iter")) 69 + return; 70 + 71 + iter_fd = bpf_iter_create(bpf_link__fd(link)); 72 + if (iter_fd < 0) 73 + goto free_link; 74 + 75 + memset(buf, 0, sizeof(buf)); 76 + left = ARRAY_SIZE(buf); 77 + p = buf; 78 + while ((len = read(iter_fd, p, left)) > 0) { 79 + p += len; 80 + left -= len; 81 + } 82 + 83 + ASSERT_STREQ(buf, expected_output, testname); 84 + 85 + /* read() after iter finishes should be ok. */ 86 + if (len == 0) 87 + ASSERT_OK(read(iter_fd, buf, sizeof(buf)), "second_read"); 88 + 89 + close(iter_fd); 90 + free_link: 91 + bpf_link__destroy(link); 92 + } 93 + 94 + /* Invalid cgroup. */ 95 + static void test_invalid_cgroup(struct cgroup_iter *skel) 96 + { 97 + DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts); 98 + union bpf_iter_link_info linfo; 99 + struct bpf_link *link; 100 + 101 + memset(&linfo, 0, sizeof(linfo)); 102 + linfo.cgroup.cgroup_fd = (__u32)-1; 103 + opts.link_info = &linfo; 104 + opts.link_info_len = sizeof(linfo); 105 + 106 + link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts); 107 + ASSERT_ERR_PTR(link, "attach_iter"); 108 + bpf_link__destroy(link); 109 + } 110 + 111 + /* Specifying both cgroup_fd and cgroup_id is invalid. */ 112 + static void test_invalid_cgroup_spec(struct cgroup_iter *skel) 113 + { 114 + DECLARE_LIBBPF_OPTS(bpf_iter_attach_opts, opts); 115 + union bpf_iter_link_info linfo; 116 + struct bpf_link *link; 117 + 118 + memset(&linfo, 0, sizeof(linfo)); 119 + linfo.cgroup.cgroup_fd = (__u32)cg_fd[PARENT]; 120 + linfo.cgroup.cgroup_id = (__u64)cg_id[PARENT]; 121 + opts.link_info = &linfo; 122 + opts.link_info_len = sizeof(linfo); 123 + 124 + link = bpf_program__attach_iter(skel->progs.cgroup_id_printer, &opts); 125 + ASSERT_ERR_PTR(link, "attach_iter"); 126 + bpf_link__destroy(link); 127 + } 128 + 129 + /* Preorder walk prints parent and child in order. */ 130 + static void test_walk_preorder(struct cgroup_iter *skel) 131 + { 132 + snprintf(expected_output, sizeof(expected_output), 133 + PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE, 134 + cg_id[PARENT], cg_id[CHILD1], cg_id[CHILD2]); 135 + 136 + read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT], 137 + BPF_ITER_DESCENDANTS_PRE, "preorder"); 138 + } 139 + 140 + /* Postorder walk prints child and parent in order. */ 141 + static void test_walk_postorder(struct cgroup_iter *skel) 142 + { 143 + snprintf(expected_output, sizeof(expected_output), 144 + PROLOGUE "%8llu\n%8llu\n%8llu\n" EPILOGUE, 145 + cg_id[CHILD1], cg_id[CHILD2], cg_id[PARENT]); 146 + 147 + read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT], 148 + BPF_ITER_DESCENDANTS_POST, "postorder"); 149 + } 150 + 151 + /* Walking parents prints parent and then root. */ 152 + static void test_walk_ancestors_up(struct cgroup_iter *skel) 153 + { 154 + /* terminate the walk when ROOT is met. */ 155 + skel->bss->terminal_cgroup = cg_id[ROOT]; 156 + 157 + snprintf(expected_output, sizeof(expected_output), 158 + PROLOGUE "%8llu\n%8llu\n" EPILOGUE, 159 + cg_id[PARENT], cg_id[ROOT]); 160 + 161 + read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT], 162 + BPF_ITER_ANCESTORS_UP, "ancestors_up"); 163 + 164 + skel->bss->terminal_cgroup = 0; 165 + } 166 + 167 + /* Early termination prints parent only. */ 168 + static void test_early_termination(struct cgroup_iter *skel) 169 + { 170 + /* terminate the walk after the first element is processed. */ 171 + skel->bss->terminate_early = 1; 172 + 173 + snprintf(expected_output, sizeof(expected_output), 174 + PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]); 175 + 176 + read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT], 177 + BPF_ITER_DESCENDANTS_PRE, "early_termination"); 178 + 179 + skel->bss->terminate_early = 0; 180 + } 181 + 182 + /* Waling self prints self only. */ 183 + static void test_walk_self_only(struct cgroup_iter *skel) 184 + { 185 + snprintf(expected_output, sizeof(expected_output), 186 + PROLOGUE "%8llu\n" EPILOGUE, cg_id[PARENT]); 187 + 188 + read_from_cgroup_iter(skel->progs.cgroup_id_printer, cg_fd[PARENT], 189 + BPF_ITER_SELF_ONLY, "self_only"); 190 + } 191 + 192 + void test_cgroup_iter(void) 193 + { 194 + struct cgroup_iter *skel = NULL; 195 + 196 + if (setup_cgroup_environment()) 197 + return; 198 + 199 + if (setup_cgroups()) 200 + goto out; 201 + 202 + skel = cgroup_iter__open_and_load(); 203 + if (!ASSERT_OK_PTR(skel, "cgroup_iter__open_and_load")) 204 + goto out; 205 + 206 + if (test__start_subtest("cgroup_iter__invalid_cgroup")) 207 + test_invalid_cgroup(skel); 208 + if (test__start_subtest("cgroup_iter__invalid_cgroup_spec")) 209 + test_invalid_cgroup_spec(skel); 210 + if (test__start_subtest("cgroup_iter__preorder")) 211 + test_walk_preorder(skel); 212 + if (test__start_subtest("cgroup_iter__postorder")) 213 + test_walk_postorder(skel); 214 + if (test__start_subtest("cgroup_iter__ancestors_up_walk")) 215 + test_walk_ancestors_up(skel); 216 + if (test__start_subtest("cgroup_iter__early_termination")) 217 + test_early_termination(skel); 218 + if (test__start_subtest("cgroup_iter__self_only")) 219 + test_walk_self_only(skel); 220 + out: 221 + cgroup_iter__destroy(skel); 222 + cleanup_cgroups(); 223 + cleanup_cgroup_environment(); 224 + }
+7
tools/testing/selftests/bpf/progs/bpf_iter.h
··· 17 17 #define bpf_iter__bpf_sk_storage_map bpf_iter__bpf_sk_storage_map___not_used 18 18 #define bpf_iter__sockmap bpf_iter__sockmap___not_used 19 19 #define bpf_iter__bpf_link bpf_iter__bpf_link___not_used 20 + #define bpf_iter__cgroup bpf_iter__cgroup___not_used 20 21 #define btf_ptr btf_ptr___not_used 21 22 #define BTF_F_COMPACT BTF_F_COMPACT___not_used 22 23 #define BTF_F_NONAME BTF_F_NONAME___not_used ··· 41 40 #undef bpf_iter__bpf_sk_storage_map 42 41 #undef bpf_iter__sockmap 43 42 #undef bpf_iter__bpf_link 43 + #undef bpf_iter__cgroup 44 44 #undef btf_ptr 45 45 #undef BTF_F_COMPACT 46 46 #undef BTF_F_NONAME ··· 142 140 struct bpf_iter_meta *meta; 143 141 struct bpf_link *link; 144 142 }; 143 + 144 + struct bpf_iter__cgroup { 145 + struct bpf_iter_meta *meta; 146 + struct cgroup *cgroup; 147 + } __attribute__((preserve_access_index)); 145 148 146 149 struct btf_ptr { 147 150 void *ptr;
+39
tools/testing/selftests/bpf/progs/cgroup_iter.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (c) 2022 Google */ 3 + 4 + #include "bpf_iter.h" 5 + #include <bpf/bpf_helpers.h> 6 + #include <bpf/bpf_tracing.h> 7 + 8 + char _license[] SEC("license") = "GPL"; 9 + int terminate_early = 0; 10 + u64 terminal_cgroup = 0; 11 + 12 + static inline u64 cgroup_id(struct cgroup *cgrp) 13 + { 14 + return cgrp->kn->id; 15 + } 16 + 17 + SEC("iter/cgroup") 18 + int cgroup_id_printer(struct bpf_iter__cgroup *ctx) 19 + { 20 + struct seq_file *seq = ctx->meta->seq; 21 + struct cgroup *cgrp = ctx->cgroup; 22 + 23 + /* epilogue */ 24 + if (cgrp == NULL) { 25 + BPF_SEQ_PRINTF(seq, "epilogue\n"); 26 + return 0; 27 + } 28 + 29 + /* prologue */ 30 + if (ctx->meta->seq_num == 0) 31 + BPF_SEQ_PRINTF(seq, "prologue\n"); 32 + 33 + BPF_SEQ_PRINTF(seq, "%8llu\n", cgroup_id(cgrp)); 34 + 35 + if (terminal_cgroup == cgroup_id(cgrp)) 36 + return 1; 37 + 38 + return terminate_early ? 1 : 0; 39 + }