Message ID | 20210604063116.234316-8-memxor@gmail.com (mailing list archive) |
---|---|
State | Changes Requested |
Delegated to: | BPF |
Headers | show |
Series | Add bpf_link based TC-BPF API | expand |
On 6/3/21 11:31 PM, Kumar Kartikeya Dwivedi wrote: > This covers basic attach/detach/update, and tests interaction with the > netlink API. It also exercises the bpf_link_info and fdinfo codepaths. > > Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>. > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> > --- > .../selftests/bpf/prog_tests/tc_bpf_link.c | 285 ++++++++++++++++++ > 1 file changed, 285 insertions(+) > create mode 100644 tools/testing/selftests/bpf/prog_tests/tc_bpf_link.c > > diff --git a/tools/testing/selftests/bpf/prog_tests/tc_bpf_link.c b/tools/testing/selftests/bpf/prog_tests/tc_bpf_link.c > new file mode 100644 > index 000000000000..beaf06e0557c > --- /dev/null > +++ b/tools/testing/selftests/bpf/prog_tests/tc_bpf_link.c > @@ -0,0 +1,285 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <test_progs.h> > +#include <linux/pkt_cls.h> > + > +#include "test_tc_bpf.skel.h" > + > +#define LO_IFINDEX 1 > + > +static int test_tc_bpf_link_basic(struct bpf_tc_hook *hook, > + struct bpf_program *prog) > +{ > + DECLARE_LIBBPF_OPTS(bpf_tc_link_opts, opts, .handle = 1, .priority = 1); > + DECLARE_LIBBPF_OPTS(bpf_tc_opts, qopts, .handle = 1, .priority = 1); > + struct bpf_prog_info info = {}; > + __u32 info_len = sizeof(info); > + struct bpf_link *link, *invl; > + int ret; > + > + link = bpf_program__attach_tc(prog, hook, &opts); > + if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) > + return PTR_ERR(link); If we changed the bpf_program__attach_tc return semantics such that the return "link" value can only be NULL or a valid pointer, you can do if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) return -EINVAL; The true error code should have been printed inside ASSERT_OK_PTR. The same for a few other cases below. > + > + ret = bpf_obj_get_info_by_fd(bpf_program__fd(prog), &info, &info_len); > + if (!ASSERT_OK(ret, "bpf_obj_get_info_by_fd")) > + goto end; > + > + ret = bpf_tc_query(hook, &qopts); > + if (!ASSERT_OK(ret, "bpf_tc_query")) > + goto end; > + > + if (!ASSERT_EQ(qopts.prog_id, info.id, "prog_id match")) > + goto end; > + > + opts.gen_flags = ~0u; > + invl = bpf_program__attach_tc(prog, hook, &opts); > + if (!ASSERT_ERR_PTR(invl, "bpf_program__attach_tc with invalid flags")) { > + bpf_link__destroy(invl); > + ret = -EINVAL; > + } > + > +end: > + bpf_link__destroy(link); > + return ret; > +} > + [...] > + > +static int test_tc_bpf_link_info_api(struct bpf_tc_hook *hook, > + struct bpf_program *prog) > +{ > + DECLARE_LIBBPF_OPTS(bpf_tc_link_opts, opts, .handle = 1, .priority = 1); > + __u32 ifindex, parent, handle, gen_flags, priority; > + char buf[4096], path[256], *begin; > + struct bpf_link_info info = {}; > + __u32 info_len = sizeof(info); > + struct bpf_link *link; > + int ret, fdinfo; > + > + link = bpf_program__attach_tc(prog, hook, &opts); > + if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) > + return PTR_ERR(link); > + > + ret = bpf_obj_get_info_by_fd(bpf_link__fd(link), &info, &info_len); > + if (!ASSERT_OK(ret, "bpf_obj_get_info_by_fd")) > + goto end; > + > + ret = snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", > + bpf_link__fd(link)); > + if (!ASSERT_TRUE(!ret || ret < sizeof(path), "snprintf pathname")) > + goto end; > + > + fdinfo = open(path, O_RDONLY); > + if (!ASSERT_GT(fdinfo, -1, "open fdinfo")) > + goto end; > + > + ret = read(fdinfo, buf, sizeof(buf)); > + if (!ASSERT_GT(ret, 0, "read fdinfo")) { > + ret = -EINVAL; > + goto end_file; > + } > + > + begin = strstr(buf, "ifindex"); > + if (!ASSERT_OK_PTR(begin, "find beginning of fdinfo info")) { > + ret = -EINVAL; > + goto end_file; > + } > + > + ret = sscanf(begin, "ifindex:\t%u\n" > + "parent:\t%u\n" > + "handle:\t%u\n" > + "priority:\t%u\n" > + "gen_flags:\t%u\n", > + &ifindex, &parent, &handle, &priority, &gen_flags); > + if (!ASSERT_EQ(ret, 5, "sscanf fdinfo")) { > + ret = -EINVAL; > + goto end_file; > + } > + > + ret = -EINVAL; > + > +#define X(a, b, c) (!ASSERT_EQ(a, b, #a " == " #b) || !ASSERT_EQ(b, c, #b " == " #c)) > + if (X(info.tc.ifindex, ifindex, 1) || > + X(info.tc.parent, parent, > + TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS)) || > + X(info.tc.handle, handle, 1) || > + X(info.tc.gen_flags, gen_flags, TCA_CLS_FLAGS_NOT_IN_HW) || > + X(info.tc.priority, priority, 1)) > +#undef X > + goto end_file; Maybe put "#undef X" after "goto end_file" is a little bit better? > + > + ret = 0; > + > +end_file: > + close(fdinfo); > +end: > + bpf_link__destroy(link); > + return ret; > +} > + [...]
On Thu, Jun 3, 2021 at 11:32 PM Kumar Kartikeya Dwivedi <memxor@gmail.com> wrote: > > This covers basic attach/detach/update, and tests interaction with the > netlink API. It also exercises the bpf_link_info and fdinfo codepaths. > > Reviewed-by: Toke Høiland-Jørgensen <toke@redhat.com>. > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com> > --- > .../selftests/bpf/prog_tests/tc_bpf_link.c | 285 ++++++++++++++++++ > 1 file changed, 285 insertions(+) > create mode 100644 tools/testing/selftests/bpf/prog_tests/tc_bpf_link.c > [...] > +static int test_tc_bpf_link_netlink_interaction(struct bpf_tc_hook *hook, > + struct bpf_program *prog) > +{ > + DECLARE_LIBBPF_OPTS(bpf_link_update_opts, lopts, > + .old_prog_fd = bpf_program__fd(prog)); > + DECLARE_LIBBPF_OPTS(bpf_tc_link_opts, opts, .handle = 1, .priority = 1); > + DECLARE_LIBBPF_OPTS(bpf_tc_opts, nopts, .handle = 1, .priority = 1); > + DECLARE_LIBBPF_OPTS(bpf_tc_opts, dopts, .handle = 1, .priority = 1); > + struct bpf_link *link; > + int ret; > + > + /* We need to test the following cases: > + * 1. BPF link owned filter cannot be replaced by netlink > + * 2. Netlink owned filter cannot be replaced by BPF link > + * 3. Netlink cannot do targeted delete of BPF link owned filter > + * 4. Filter is actually deleted (with chain cleanup) > + * We actually (ab)use the kernel behavior of returning EINVAL when > + * target chain doesn't exist on tc_get_tfilter (which maps to > + * bpf_tc_query) here, to know if the chain was really cleaned > + * up on tcf_proto destruction. Our setup is so that there is > + * only one reference to the chain. > + * > + * So on query, chain ? (filter ?: ENOENT) : EINVAL > + */ > + > + link = bpf_program__attach_tc(prog, hook, &opts); > + if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) > + return PTR_ERR(link); > + > + nopts.prog_fd = bpf_program__fd(prog); > + ret = bpf_tc_attach(hook, &nopts); > + if (!ASSERT_EQ(ret, -EEXIST, "bpf_tc_attach without replace")) > + goto end; > + > + nopts.flags = BPF_TC_F_REPLACE; > + ret = bpf_tc_attach(hook, &nopts); > + if (!ASSERT_EQ(ret, -EPERM, "bpf_tc_attach with replace")) > + goto end; > + > + ret = bpf_tc_detach(hook, &dopts); > + if (!ASSERT_EQ(ret, -EPERM, "bpf_tc_detach")) > + goto end; > + > + lopts.flags = BPF_F_REPLACE; > + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), > + &lopts); > + ASSERT_OK(ret, "bpf_link_update"); > + ret = ret < 0 ? -errno : ret; all selftests run in libbpf 1.0 mode, so you get actual error directly, so no need to deal with -errno here. > + > +end: > + bpf_link__destroy(link); > + if (!ret && !ASSERT_EQ(bpf_tc_query(hook, &dopts), -EINVAL, > + "chain empty delete")) > + ret = -EINVAL; > + return ret; > +} > + > +static int test_tc_bpf_link_update_ways(struct bpf_tc_hook *hook, > + struct bpf_program *prog) > +{ > + DECLARE_LIBBPF_OPTS(bpf_tc_link_opts, opts, .handle = 1, .priority = 1); > + DECLARE_LIBBPF_OPTS(bpf_link_update_opts, uopts, 0); > + struct test_tc_bpf *skel; > + struct bpf_link *link; > + int ret; > + > + skel = test_tc_bpf__open_and_load(); > + if (!ASSERT_OK_PTR(skel, "test_tc_bpf__open_and_load")) > + return PTR_ERR(skel); > + > + link = bpf_program__attach_tc(prog, hook, &opts); > + if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) { > + ret = PTR_ERR(link); > + goto end; > + } > + > + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), > + &uopts); > + if (!ASSERT_OK(ret, "bpf_link_update no old prog")) > + goto end; > + > + uopts.old_prog_fd = bpf_program__fd(prog); > + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), > + &uopts); please keep all such calls single-line, they aren't excessively long at all > + if (!ASSERT_TRUE(ret < 0 && errno == EINVAL, > + "bpf_link_update with old prog without BPF_F_REPLACE")) { same as above, ret should already be -EINVAL, so just check directly > + ret = -EINVAL; > + goto end; > + } > + > + uopts.flags = BPF_F_REPLACE; > + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), > + &uopts); > + if (!ASSERT_OK(ret, "bpf_link_update with old prog with BPF_F_REPLACE")) > + goto end; > + > + uopts.old_prog_fd = bpf_program__fd(skel->progs.cls); > + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), > + &uopts); > + if (!ASSERT_TRUE(ret < 0 && errno == EINVAL, > + "bpf_link_update with wrong old prog")) { and here > + ret = -EINVAL; > + goto end; > + } > + ret = 0; > + > +end: > + test_tc_bpf__destroy(skel); > + return ret; > +} > + > +static int test_tc_bpf_link_info_api(struct bpf_tc_hook *hook, > + struct bpf_program *prog) > +{ > + DECLARE_LIBBPF_OPTS(bpf_tc_link_opts, opts, .handle = 1, .priority = 1); > + __u32 ifindex, parent, handle, gen_flags, priority; > + char buf[4096], path[256], *begin; > + struct bpf_link_info info = {}; > + __u32 info_len = sizeof(info); > + struct bpf_link *link; > + int ret, fdinfo; > + > + link = bpf_program__attach_tc(prog, hook, &opts); > + if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) > + return PTR_ERR(link); > + > + ret = bpf_obj_get_info_by_fd(bpf_link__fd(link), &info, &info_len); > + if (!ASSERT_OK(ret, "bpf_obj_get_info_by_fd")) > + goto end; > + > + ret = snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", > + bpf_link__fd(link)); > + if (!ASSERT_TRUE(!ret || ret < sizeof(path), "snprintf pathname")) > + goto end; ASSERT_TRUE is very generic, it's better to do ASSERT_LT(ret, sizeof(path), "snprintf") here not sure why `!ret` is allowed?.. > + > + fdinfo = open(path, O_RDONLY); > + if (!ASSERT_GT(fdinfo, -1, "open fdinfo")) > + goto end; > + > + ret = read(fdinfo, buf, sizeof(buf)); > + if (!ASSERT_GT(ret, 0, "read fdinfo")) { > + ret = -EINVAL; > + goto end_file; > + } > + > + begin = strstr(buf, "ifindex"); > + if (!ASSERT_OK_PTR(begin, "find beginning of fdinfo info")) { > + ret = -EINVAL; > + goto end_file; > + } > + > + ret = sscanf(begin, "ifindex:\t%u\n" > + "parent:\t%u\n" > + "handle:\t%u\n" > + "priority:\t%u\n" > + "gen_flags:\t%u\n", > + &ifindex, &parent, &handle, &priority, &gen_flags); > + if (!ASSERT_EQ(ret, 5, "sscanf fdinfo")) { > + ret = -EINVAL; > + goto end_file; > + } > + > + ret = -EINVAL; > + > +#define X(a, b, c) (!ASSERT_EQ(a, b, #a " == " #b) || !ASSERT_EQ(b, c, #b " == " #c)) > + if (X(info.tc.ifindex, ifindex, 1) || > + X(info.tc.parent, parent, > + TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS)) || > + X(info.tc.handle, handle, 1) || > + X(info.tc.gen_flags, gen_flags, TCA_CLS_FLAGS_NOT_IN_HW) || > + X(info.tc.priority, priority, 1)) > +#undef X This seems to be a bit too convoluted and over-engineered. Just validate all the equalities unconditionally. ASSERT_EQ(info.tc.ifindex, 1, "info.tc.ifindex"); ASSERT_EQ(ifindex, 1, "fdinfo.ifindex"); ASSERT_EQ(info.tc.parent, TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS), "info.tc.parent"); and so on. Then, you don't really need to propagate errors from test_tc_bpf_link_info_api, because each ASSERT_EQ() marks the test (or subtest) as failed, so you don't have to do that below in test_tc_bpf_link. > + goto end_file; > + > + ret = 0; > + > +end_file: > + close(fdinfo); > +end: > + bpf_link__destroy(link); > + return ret; > +} > + > +void test_tc_bpf_link(void) > +{ > + DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook, .ifindex = LO_IFINDEX, > + .attach_point = BPF_TC_INGRESS); > + struct test_tc_bpf *skel = NULL; > + bool hook_created = false; > + int ret; > + > + skel = test_tc_bpf__open_and_load(); > + if (!ASSERT_OK_PTR(skel, "test_tc_bpf__open_and_load")) > + return; > + > + ret = bpf_tc_hook_create(&hook); > + if (ret == 0) > + hook_created = true; > + > + ret = ret == -EEXIST ? 0 : ret; > + if (!ASSERT_OK(ret, "bpf_tc_hook_create(BPF_TC_INGRESS)")) > + goto end; > + > + ret = test_tc_bpf_link_basic(&hook, skel->progs.cls); > + if (!ASSERT_OK(ret, "test_tc_bpf_link_basic")) > + goto end; > + > + bpf_tc_hook_destroy(&hook); > + > + hook.attach_point = BPF_TC_EGRESS; > + ret = test_tc_bpf_link_basic(&hook, skel->progs.cls); > + if (!ASSERT_OK(ret, "test_tc_bpf_link_basic")) > + goto end; > + > + bpf_tc_hook_destroy(&hook); > + > + ret = test_tc_bpf_link_netlink_interaction(&hook, skel->progs.cls); > + if (!ASSERT_OK(ret, "test_tc_bpf_link_netlink_interaction")) > + goto end; > + > + bpf_tc_hook_destroy(&hook); > + > + ret = test_tc_bpf_link_update_ways(&hook, skel->progs.cls); > + if (!ASSERT_OK(ret, "test_tc_bpf_link_update_ways")) > + goto end; > + > + bpf_tc_hook_destroy(&hook); > + > + ret = test_tc_bpf_link_info_api(&hook, skel->progs.cls); > + if (!ASSERT_OK(ret, "test_tc_bpf_link_info_api")) I was talking about this above, it's completely unnecessary and redundant. Just complicates everything. > + goto end; > + > +end: > + if (hook_created) { > + hook.attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS; > + bpf_tc_hook_destroy(&hook); > + } > + test_tc_bpf__destroy(skel); > +} > -- > 2.31.1 >
diff --git a/tools/testing/selftests/bpf/prog_tests/tc_bpf_link.c b/tools/testing/selftests/bpf/prog_tests/tc_bpf_link.c new file mode 100644 index 000000000000..beaf06e0557c --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/tc_bpf_link.c @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <test_progs.h> +#include <linux/pkt_cls.h> + +#include "test_tc_bpf.skel.h" + +#define LO_IFINDEX 1 + +static int test_tc_bpf_link_basic(struct bpf_tc_hook *hook, + struct bpf_program *prog) +{ + DECLARE_LIBBPF_OPTS(bpf_tc_link_opts, opts, .handle = 1, .priority = 1); + DECLARE_LIBBPF_OPTS(bpf_tc_opts, qopts, .handle = 1, .priority = 1); + struct bpf_prog_info info = {}; + __u32 info_len = sizeof(info); + struct bpf_link *link, *invl; + int ret; + + link = bpf_program__attach_tc(prog, hook, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) + return PTR_ERR(link); + + ret = bpf_obj_get_info_by_fd(bpf_program__fd(prog), &info, &info_len); + if (!ASSERT_OK(ret, "bpf_obj_get_info_by_fd")) + goto end; + + ret = bpf_tc_query(hook, &qopts); + if (!ASSERT_OK(ret, "bpf_tc_query")) + goto end; + + if (!ASSERT_EQ(qopts.prog_id, info.id, "prog_id match")) + goto end; + + opts.gen_flags = ~0u; + invl = bpf_program__attach_tc(prog, hook, &opts); + if (!ASSERT_ERR_PTR(invl, "bpf_program__attach_tc with invalid flags")) { + bpf_link__destroy(invl); + ret = -EINVAL; + } + +end: + bpf_link__destroy(link); + return ret; +} + +static int test_tc_bpf_link_netlink_interaction(struct bpf_tc_hook *hook, + struct bpf_program *prog) +{ + DECLARE_LIBBPF_OPTS(bpf_link_update_opts, lopts, + .old_prog_fd = bpf_program__fd(prog)); + DECLARE_LIBBPF_OPTS(bpf_tc_link_opts, opts, .handle = 1, .priority = 1); + DECLARE_LIBBPF_OPTS(bpf_tc_opts, nopts, .handle = 1, .priority = 1); + DECLARE_LIBBPF_OPTS(bpf_tc_opts, dopts, .handle = 1, .priority = 1); + struct bpf_link *link; + int ret; + + /* We need to test the following cases: + * 1. BPF link owned filter cannot be replaced by netlink + * 2. Netlink owned filter cannot be replaced by BPF link + * 3. Netlink cannot do targeted delete of BPF link owned filter + * 4. Filter is actually deleted (with chain cleanup) + * We actually (ab)use the kernel behavior of returning EINVAL when + * target chain doesn't exist on tc_get_tfilter (which maps to + * bpf_tc_query) here, to know if the chain was really cleaned + * up on tcf_proto destruction. Our setup is so that there is + * only one reference to the chain. + * + * So on query, chain ? (filter ?: ENOENT) : EINVAL + */ + + link = bpf_program__attach_tc(prog, hook, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) + return PTR_ERR(link); + + nopts.prog_fd = bpf_program__fd(prog); + ret = bpf_tc_attach(hook, &nopts); + if (!ASSERT_EQ(ret, -EEXIST, "bpf_tc_attach without replace")) + goto end; + + nopts.flags = BPF_TC_F_REPLACE; + ret = bpf_tc_attach(hook, &nopts); + if (!ASSERT_EQ(ret, -EPERM, "bpf_tc_attach with replace")) + goto end; + + ret = bpf_tc_detach(hook, &dopts); + if (!ASSERT_EQ(ret, -EPERM, "bpf_tc_detach")) + goto end; + + lopts.flags = BPF_F_REPLACE; + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), + &lopts); + ASSERT_OK(ret, "bpf_link_update"); + ret = ret < 0 ? -errno : ret; + +end: + bpf_link__destroy(link); + if (!ret && !ASSERT_EQ(bpf_tc_query(hook, &dopts), -EINVAL, + "chain empty delete")) + ret = -EINVAL; + return ret; +} + +static int test_tc_bpf_link_update_ways(struct bpf_tc_hook *hook, + struct bpf_program *prog) +{ + DECLARE_LIBBPF_OPTS(bpf_tc_link_opts, opts, .handle = 1, .priority = 1); + DECLARE_LIBBPF_OPTS(bpf_link_update_opts, uopts, 0); + struct test_tc_bpf *skel; + struct bpf_link *link; + int ret; + + skel = test_tc_bpf__open_and_load(); + if (!ASSERT_OK_PTR(skel, "test_tc_bpf__open_and_load")) + return PTR_ERR(skel); + + link = bpf_program__attach_tc(prog, hook, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) { + ret = PTR_ERR(link); + goto end; + } + + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), + &uopts); + if (!ASSERT_OK(ret, "bpf_link_update no old prog")) + goto end; + + uopts.old_prog_fd = bpf_program__fd(prog); + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), + &uopts); + if (!ASSERT_TRUE(ret < 0 && errno == EINVAL, + "bpf_link_update with old prog without BPF_F_REPLACE")) { + ret = -EINVAL; + goto end; + } + + uopts.flags = BPF_F_REPLACE; + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), + &uopts); + if (!ASSERT_OK(ret, "bpf_link_update with old prog with BPF_F_REPLACE")) + goto end; + + uopts.old_prog_fd = bpf_program__fd(skel->progs.cls); + ret = bpf_link_update(bpf_link__fd(link), bpf_program__fd(prog), + &uopts); + if (!ASSERT_TRUE(ret < 0 && errno == EINVAL, + "bpf_link_update with wrong old prog")) { + ret = -EINVAL; + goto end; + } + ret = 0; + +end: + test_tc_bpf__destroy(skel); + return ret; +} + +static int test_tc_bpf_link_info_api(struct bpf_tc_hook *hook, + struct bpf_program *prog) +{ + DECLARE_LIBBPF_OPTS(bpf_tc_link_opts, opts, .handle = 1, .priority = 1); + __u32 ifindex, parent, handle, gen_flags, priority; + char buf[4096], path[256], *begin; + struct bpf_link_info info = {}; + __u32 info_len = sizeof(info); + struct bpf_link *link; + int ret, fdinfo; + + link = bpf_program__attach_tc(prog, hook, &opts); + if (!ASSERT_OK_PTR(link, "bpf_program__attach_tc")) + return PTR_ERR(link); + + ret = bpf_obj_get_info_by_fd(bpf_link__fd(link), &info, &info_len); + if (!ASSERT_OK(ret, "bpf_obj_get_info_by_fd")) + goto end; + + ret = snprintf(path, sizeof(path), "/proc/self/fdinfo/%d", + bpf_link__fd(link)); + if (!ASSERT_TRUE(!ret || ret < sizeof(path), "snprintf pathname")) + goto end; + + fdinfo = open(path, O_RDONLY); + if (!ASSERT_GT(fdinfo, -1, "open fdinfo")) + goto end; + + ret = read(fdinfo, buf, sizeof(buf)); + if (!ASSERT_GT(ret, 0, "read fdinfo")) { + ret = -EINVAL; + goto end_file; + } + + begin = strstr(buf, "ifindex"); + if (!ASSERT_OK_PTR(begin, "find beginning of fdinfo info")) { + ret = -EINVAL; + goto end_file; + } + + ret = sscanf(begin, "ifindex:\t%u\n" + "parent:\t%u\n" + "handle:\t%u\n" + "priority:\t%u\n" + "gen_flags:\t%u\n", + &ifindex, &parent, &handle, &priority, &gen_flags); + if (!ASSERT_EQ(ret, 5, "sscanf fdinfo")) { + ret = -EINVAL; + goto end_file; + } + + ret = -EINVAL; + +#define X(a, b, c) (!ASSERT_EQ(a, b, #a " == " #b) || !ASSERT_EQ(b, c, #b " == " #c)) + if (X(info.tc.ifindex, ifindex, 1) || + X(info.tc.parent, parent, + TC_H_MAKE(TC_H_CLSACT, TC_H_MIN_EGRESS)) || + X(info.tc.handle, handle, 1) || + X(info.tc.gen_flags, gen_flags, TCA_CLS_FLAGS_NOT_IN_HW) || + X(info.tc.priority, priority, 1)) +#undef X + goto end_file; + + ret = 0; + +end_file: + close(fdinfo); +end: + bpf_link__destroy(link); + return ret; +} + +void test_tc_bpf_link(void) +{ + DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook, .ifindex = LO_IFINDEX, + .attach_point = BPF_TC_INGRESS); + struct test_tc_bpf *skel = NULL; + bool hook_created = false; + int ret; + + skel = test_tc_bpf__open_and_load(); + if (!ASSERT_OK_PTR(skel, "test_tc_bpf__open_and_load")) + return; + + ret = bpf_tc_hook_create(&hook); + if (ret == 0) + hook_created = true; + + ret = ret == -EEXIST ? 0 : ret; + if (!ASSERT_OK(ret, "bpf_tc_hook_create(BPF_TC_INGRESS)")) + goto end; + + ret = test_tc_bpf_link_basic(&hook, skel->progs.cls); + if (!ASSERT_OK(ret, "test_tc_bpf_link_basic")) + goto end; + + bpf_tc_hook_destroy(&hook); + + hook.attach_point = BPF_TC_EGRESS; + ret = test_tc_bpf_link_basic(&hook, skel->progs.cls); + if (!ASSERT_OK(ret, "test_tc_bpf_link_basic")) + goto end; + + bpf_tc_hook_destroy(&hook); + + ret = test_tc_bpf_link_netlink_interaction(&hook, skel->progs.cls); + if (!ASSERT_OK(ret, "test_tc_bpf_link_netlink_interaction")) + goto end; + + bpf_tc_hook_destroy(&hook); + + ret = test_tc_bpf_link_update_ways(&hook, skel->progs.cls); + if (!ASSERT_OK(ret, "test_tc_bpf_link_update_ways")) + goto end; + + bpf_tc_hook_destroy(&hook); + + ret = test_tc_bpf_link_info_api(&hook, skel->progs.cls); + if (!ASSERT_OK(ret, "test_tc_bpf_link_info_api")) + goto end; + +end: + if (hook_created) { + hook.attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS; + bpf_tc_hook_destroy(&hook); + } + test_tc_bpf__destroy(skel); +}