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

selftests/bpf: Test FD-based cgroup attachment

Add selftests to exercise FD-based cgroup BPF program attachments and their
intermixing with legacy cgroup BPF attachments. Auto-detachment and program
replacement (both unconditional and cmpxchng-like) are tested as well.

Signed-off-by: Andrii Nakryiko <andriin@fb.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20200330030001.2312810-5-andriin@fb.com

authored by

Andrii Nakryiko and committed by
Alexei Starovoitov
7cccee42 cc4f864b

+268
+244
tools/testing/selftests/bpf/prog_tests/cgroup_link.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + 3 + #include <test_progs.h> 4 + #include "cgroup_helpers.h" 5 + #include "test_cgroup_link.skel.h" 6 + 7 + static __u32 duration = 0; 8 + #define PING_CMD "ping -q -c1 -w1 127.0.0.1 > /dev/null" 9 + 10 + static struct test_cgroup_link *skel = NULL; 11 + 12 + int ping_and_check(int exp_calls, int exp_alt_calls) 13 + { 14 + skel->bss->calls = 0; 15 + skel->bss->alt_calls = 0; 16 + CHECK_FAIL(system(PING_CMD)); 17 + if (CHECK(skel->bss->calls != exp_calls, "call_cnt", 18 + "exp %d, got %d\n", exp_calls, skel->bss->calls)) 19 + return -EINVAL; 20 + if (CHECK(skel->bss->alt_calls != exp_alt_calls, "alt_call_cnt", 21 + "exp %d, got %d\n", exp_alt_calls, skel->bss->alt_calls)) 22 + return -EINVAL; 23 + return 0; 24 + } 25 + 26 + void test_cgroup_link(void) 27 + { 28 + struct { 29 + const char *path; 30 + int fd; 31 + } cgs[] = { 32 + { "/cg1" }, 33 + { "/cg1/cg2" }, 34 + { "/cg1/cg2/cg3" }, 35 + { "/cg1/cg2/cg3/cg4" }, 36 + }; 37 + int last_cg = ARRAY_SIZE(cgs) - 1, cg_nr = ARRAY_SIZE(cgs); 38 + DECLARE_LIBBPF_OPTS(bpf_link_update_opts, link_upd_opts); 39 + struct bpf_link *links[ARRAY_SIZE(cgs)] = {}, *tmp_link; 40 + __u32 prog_ids[ARRAY_SIZE(cgs)], prog_cnt = 0, attach_flags; 41 + int i = 0, err, prog_fd; 42 + bool detach_legacy = false; 43 + 44 + skel = test_cgroup_link__open_and_load(); 45 + if (CHECK(!skel, "skel_open_load", "failed to open/load skeleton\n")) 46 + return; 47 + prog_fd = bpf_program__fd(skel->progs.egress); 48 + 49 + err = setup_cgroup_environment(); 50 + if (CHECK(err, "cg_init", "failed: %d\n", err)) 51 + goto cleanup; 52 + 53 + for (i = 0; i < cg_nr; i++) { 54 + cgs[i].fd = create_and_get_cgroup(cgs[i].path); 55 + if (CHECK(cgs[i].fd < 0, "cg_create", "fail: %d\n", cgs[i].fd)) 56 + goto cleanup; 57 + } 58 + 59 + err = join_cgroup(cgs[last_cg].path); 60 + if (CHECK(err, "cg_join", "fail: %d\n", err)) 61 + goto cleanup; 62 + 63 + for (i = 0; i < cg_nr; i++) { 64 + links[i] = bpf_program__attach_cgroup(skel->progs.egress, 65 + cgs[i].fd); 66 + if (CHECK(IS_ERR(links[i]), "cg_attach", "i: %d, err: %ld\n", 67 + i, PTR_ERR(links[i]))) 68 + goto cleanup; 69 + } 70 + 71 + ping_and_check(cg_nr, 0); 72 + 73 + /* query the number of effective progs and attach flags in root cg */ 74 + err = bpf_prog_query(cgs[0].fd, BPF_CGROUP_INET_EGRESS, 75 + BPF_F_QUERY_EFFECTIVE, &attach_flags, NULL, 76 + &prog_cnt); 77 + CHECK_FAIL(err); 78 + CHECK_FAIL(attach_flags != BPF_F_ALLOW_MULTI); 79 + if (CHECK(prog_cnt != 1, "effect_cnt", "exp %d, got %d\n", 1, prog_cnt)) 80 + goto cleanup; 81 + 82 + /* query the number of effective progs in last cg */ 83 + err = bpf_prog_query(cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS, 84 + BPF_F_QUERY_EFFECTIVE, NULL, NULL, 85 + &prog_cnt); 86 + CHECK_FAIL(err); 87 + CHECK_FAIL(attach_flags != BPF_F_ALLOW_MULTI); 88 + if (CHECK(prog_cnt != cg_nr, "effect_cnt", "exp %d, got %d\n", 89 + cg_nr, prog_cnt)) 90 + goto cleanup; 91 + 92 + /* query the effective prog IDs in last cg */ 93 + err = bpf_prog_query(cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS, 94 + BPF_F_QUERY_EFFECTIVE, &attach_flags, 95 + prog_ids, &prog_cnt); 96 + CHECK_FAIL(err); 97 + CHECK_FAIL(attach_flags != BPF_F_ALLOW_MULTI); 98 + if (CHECK(prog_cnt != cg_nr, "effect_cnt", "exp %d, got %d\n", 99 + cg_nr, prog_cnt)) 100 + goto cleanup; 101 + for (i = 1; i < prog_cnt; i++) { 102 + CHECK(prog_ids[i - 1] != prog_ids[i], "prog_id_check", 103 + "idx %d, prev id %d, cur id %d\n", 104 + i, prog_ids[i - 1], prog_ids[i]); 105 + } 106 + 107 + /* detach bottom program and ping again */ 108 + bpf_link__destroy(links[last_cg]); 109 + links[last_cg] = NULL; 110 + 111 + ping_and_check(cg_nr - 1, 0); 112 + 113 + /* mix in with non link-based multi-attachments */ 114 + err = bpf_prog_attach(prog_fd, cgs[last_cg].fd, 115 + BPF_CGROUP_INET_EGRESS, BPF_F_ALLOW_MULTI); 116 + if (CHECK(err, "cg_attach_legacy", "errno=%d\n", errno)) 117 + goto cleanup; 118 + detach_legacy = true; 119 + 120 + links[last_cg] = bpf_program__attach_cgroup(skel->progs.egress, 121 + cgs[last_cg].fd); 122 + if (CHECK(IS_ERR(links[last_cg]), "cg_attach", "err: %ld\n", 123 + PTR_ERR(links[last_cg]))) 124 + goto cleanup; 125 + 126 + ping_and_check(cg_nr + 1, 0); 127 + 128 + /* detach link */ 129 + bpf_link__destroy(links[last_cg]); 130 + links[last_cg] = NULL; 131 + 132 + /* detach legacy */ 133 + err = bpf_prog_detach2(prog_fd, cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS); 134 + if (CHECK(err, "cg_detach_legacy", "errno=%d\n", errno)) 135 + goto cleanup; 136 + detach_legacy = false; 137 + 138 + /* attach legacy exclusive prog attachment */ 139 + err = bpf_prog_attach(prog_fd, cgs[last_cg].fd, 140 + BPF_CGROUP_INET_EGRESS, 0); 141 + if (CHECK(err, "cg_attach_exclusive", "errno=%d\n", errno)) 142 + goto cleanup; 143 + detach_legacy = true; 144 + 145 + /* attempt to mix in with multi-attach bpf_link */ 146 + tmp_link = bpf_program__attach_cgroup(skel->progs.egress, 147 + cgs[last_cg].fd); 148 + if (CHECK(!IS_ERR(tmp_link), "cg_attach_fail", "unexpected success!\n")) { 149 + bpf_link__destroy(tmp_link); 150 + goto cleanup; 151 + } 152 + 153 + ping_and_check(cg_nr, 0); 154 + 155 + /* detach */ 156 + err = bpf_prog_detach2(prog_fd, cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS); 157 + if (CHECK(err, "cg_detach_legacy", "errno=%d\n", errno)) 158 + goto cleanup; 159 + detach_legacy = false; 160 + 161 + ping_and_check(cg_nr - 1, 0); 162 + 163 + /* attach back link-based one */ 164 + links[last_cg] = bpf_program__attach_cgroup(skel->progs.egress, 165 + cgs[last_cg].fd); 166 + if (CHECK(IS_ERR(links[last_cg]), "cg_attach", "err: %ld\n", 167 + PTR_ERR(links[last_cg]))) 168 + goto cleanup; 169 + 170 + ping_and_check(cg_nr, 0); 171 + 172 + /* check legacy exclusive prog can't be attached */ 173 + err = bpf_prog_attach(prog_fd, cgs[last_cg].fd, 174 + BPF_CGROUP_INET_EGRESS, 0); 175 + if (CHECK(!err, "cg_attach_exclusive", "unexpected success")) { 176 + bpf_prog_detach2(prog_fd, cgs[last_cg].fd, BPF_CGROUP_INET_EGRESS); 177 + goto cleanup; 178 + } 179 + 180 + /* replace BPF programs inside their links for all but first link */ 181 + for (i = 1; i < cg_nr; i++) { 182 + err = bpf_link__update_program(links[i], skel->progs.egress_alt); 183 + if (CHECK(err, "prog_upd", "link #%d\n", i)) 184 + goto cleanup; 185 + } 186 + 187 + ping_and_check(1, cg_nr - 1); 188 + 189 + /* Attempt program update with wrong expected BPF program */ 190 + link_upd_opts.old_prog_fd = bpf_program__fd(skel->progs.egress_alt); 191 + link_upd_opts.flags = BPF_F_REPLACE; 192 + err = bpf_link_update(bpf_link__fd(links[0]), 193 + bpf_program__fd(skel->progs.egress_alt), 194 + &link_upd_opts); 195 + if (CHECK(err == 0 || errno != EPERM, "prog_cmpxchg1", 196 + "unexpectedly succeeded, err %d, errno %d\n", err, -errno)) 197 + goto cleanup; 198 + 199 + /* Compare-exchange single link program from egress to egress_alt */ 200 + link_upd_opts.old_prog_fd = bpf_program__fd(skel->progs.egress); 201 + link_upd_opts.flags = BPF_F_REPLACE; 202 + err = bpf_link_update(bpf_link__fd(links[0]), 203 + bpf_program__fd(skel->progs.egress_alt), 204 + &link_upd_opts); 205 + if (CHECK(err, "prog_cmpxchg2", "errno %d\n", -errno)) 206 + goto cleanup; 207 + 208 + /* ping */ 209 + ping_and_check(0, cg_nr); 210 + 211 + /* close cgroup FDs before detaching links */ 212 + for (i = 0; i < cg_nr; i++) { 213 + if (cgs[i].fd > 0) { 214 + close(cgs[i].fd); 215 + cgs[i].fd = -1; 216 + } 217 + } 218 + 219 + /* BPF programs should still get called */ 220 + ping_and_check(0, cg_nr); 221 + 222 + /* leave cgroup and remove them, don't detach programs */ 223 + cleanup_cgroup_environment(); 224 + 225 + /* BPF programs should have been auto-detached */ 226 + ping_and_check(0, 0); 227 + 228 + cleanup: 229 + if (detach_legacy) 230 + bpf_prog_detach2(prog_fd, cgs[last_cg].fd, 231 + BPF_CGROUP_INET_EGRESS); 232 + 233 + for (i = 0; i < cg_nr; i++) { 234 + if (!IS_ERR(links[i])) 235 + bpf_link__destroy(links[i]); 236 + } 237 + test_cgroup_link__destroy(skel); 238 + 239 + for (i = 0; i < cg_nr; i++) { 240 + if (cgs[i].fd > 0) 241 + close(cgs[i].fd); 242 + } 243 + cleanup_cgroup_environment(); 244 + }
+24
tools/testing/selftests/bpf/progs/test_cgroup_link.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + // Copyright (c) 2020 Facebook 3 + #include <linux/bpf.h> 4 + #include <bpf/bpf_helpers.h> 5 + 6 + int calls = 0; 7 + int alt_calls = 0; 8 + 9 + SEC("cgroup_skb/egress1") 10 + int egress(struct __sk_buff *skb) 11 + { 12 + __sync_fetch_and_add(&calls, 1); 13 + return 1; 14 + } 15 + 16 + SEC("cgroup_skb/egress2") 17 + int egress_alt(struct __sk_buff *skb) 18 + { 19 + __sync_fetch_and_add(&alt_calls, 1); 20 + return 1; 21 + } 22 + 23 + char _license[] SEC("license") = "GPL"; 24 +