Message ID | 20240731-convert_cgroup_tests-v1-4-14cbc51b6947@bootlin.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | selftests/bpf: convert three other cgroup tests to test_progs | expand |
On 31/07/2024 11:38, Alexis Lothoré (eBPF Foundation) wrote: > test_skb_cgroup_id_user allows testing skb cgroup id retrieval at different > levels, but is not integrated in test_progs, so it is not run > automatically in CI. The test overlaps a bit with > cgroup_skb_sk_lookup_kern, which is integrated in test_progs and test > extensively skb cgroup helpers, but there is still one major difference > between the two tests which justifies the conversion: > cgroup_skb_sk_lookup_kern deals with a BPF_PROG_TYPE_CGROUP_SKB (attached > on a cgroup), while test_skb_cgroup_id_user deals with a > BPF_PROG_TYPE_SCHED_CLS (attached on a qdisc) > > Convert test_skb_cgroup_id_user into test_progs framework in order to run > it automatically in CI. The main differences with the original test are the > following: > - rename the test to make it shorter and more straightforward regarding > tested feature > - the wrapping shell script has been dropped since every setup step is now > handled in the main C test file > - the test has been renamed for a shorter name and reflecting the tested > API > - add dedicated assert log per level to ease test failure debugging > > Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com> Looks great! A few things below... > --- > The new test has been tested in a qemu environment: > > ./test_progs -a cgroup_ancestor > 44 cgroup_ancestor:OK > Summary: 1/0 PASSED, 0 SKIPPED, 0 FAILED > --- > tools/testing/selftests/bpf/.gitignore | 1 - > tools/testing/selftests/bpf/Makefile | 3 +- > .../selftests/bpf/prog_tests/cgroup_ancestor.c | 159 ++++++++++++++++++ > tools/testing/selftests/bpf/test_skb_cgroup_id.sh | 67 -------- > .../selftests/bpf/test_skb_cgroup_id_user.c | 183 --------------------- > 5 files changed, 160 insertions(+), 253 deletions(-) > > diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore > index a45f11f81337..c46950d4ef53 100644 > --- a/tools/testing/selftests/bpf/.gitignore > +++ b/tools/testing/selftests/bpf/.gitignore > @@ -20,7 +20,6 @@ test_sock > urandom_read > test_sockmap > test_lirc_mode2_user > -test_skb_cgroup_id_user > test_flow_dissector > flow_dissector_load > test_tcpnotify_user > diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile > index 0ac0f9dbc2f8..057e6ba003f1 100644 > --- a/tools/testing/selftests/bpf/Makefile > +++ b/tools/testing/selftests/bpf/Makefile > @@ -138,7 +138,7 @@ TEST_PROGS_EXTENDED := with_addr.sh \ > test_xdp_vlan.sh test_bpftool.py > > # Compile but not part of 'make run_tests' > -TEST_GEN_PROGS_EXTENDED = test_skb_cgroup_id_user \ > +TEST_GEN_PROGS_EXTENDED = \ > flow_dissector_load test_flow_dissector test_tcp_check_syncookie_user \ > test_lirc_mode2_user xdping test_cpp runqslower bench bpf_testmod.ko \ > xskxceiver xdp_redirect_multi xdp_synproxy veristat xdp_hw_metadata \ > @@ -292,7 +292,6 @@ CAP_HELPERS := $(OUTPUT)/cap_helpers.o > NETWORK_HELPERS := $(OUTPUT)/network_helpers.o > > $(OUTPUT)/test_dev_cgroup: $(CGROUP_HELPERS) $(TESTING_HELPERS) > -$(OUTPUT)/test_skb_cgroup_id_user: $(CGROUP_HELPERS) $(TESTING_HELPERS) > $(OUTPUT)/test_sock: $(CGROUP_HELPERS) $(TESTING_HELPERS) > $(OUTPUT)/test_sockmap: $(CGROUP_HELPERS) $(TESTING_HELPERS) > $(OUTPUT)/test_tcpnotify_user: $(CGROUP_HELPERS) $(TESTING_HELPERS) $(TRACE_HELPERS) > diff --git a/tools/testing/selftests/bpf/prog_tests/cgroup_ancestor.c b/tools/testing/selftests/bpf/prog_tests/cgroup_ancestor.c > new file mode 100644 > index 000000000000..796aa3af75e8 > --- /dev/null > +++ b/tools/testing/selftests/bpf/prog_tests/cgroup_ancestor.c > @@ -0,0 +1,159 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include "test_progs.h" > +#include "network_helpers.h" > +#include "cgroup_helpers.h" > +#include "cgroup_ancestor.skel.h" > + > +#define VETH_PREFIX "test_cgid_" > +#define VETH_1 VETH_PREFIX "1" > +#define VETH_2 VETH_PREFIX "2" > +#define CGROUP_PATH "/skb_cgroup_test" > +#define NUM_CGROUP_LEVELS 4 > +#define WAIT_AUTO_IP_MAX_ATTEMPT 10 > +#define DST_ADDR "ff02::1" > +#define DST_PORT 1234 > +#define MAX_ASSERT_NAME 32 > + > +struct test_data { > + struct cgroup_ancestor *skel; > + struct bpf_tc_hook qdisc; > + struct bpf_tc_opts tc_attach; > +}; > + > +static int send_datagram(void) > +{ > + unsigned char buf[] = "some random test data"; > + struct sockaddr_in6 addr = { .sin6_family = AF_INET6, > + .sin6_port = htons(DST_PORT), > + .sin6_scope_id = if_nametoindex(VETH_1) }; > + int sock, n; > + > + if (!ASSERT_EQ(inet_pton(AF_INET6, DST_ADDR, &addr.sin6_addr), 1, > + "inet_pton")) > + return -1; > + > + sock = socket(AF_INET6, SOCK_DGRAM, 0); > + if (!ASSERT_OK_FD(sock, "create socket")) > + return sock; > + > + n = sendto(sock, buf, sizeof(buf), 0, (const struct sockaddr *)&addr, > + sizeof(addr)); > + if (!ASSERT_EQ(n, sizeof(buf), "send data")) > + return n; > + > + return 0; > +} > + > +static int wait_local_ip(void) > +{ > + char *ping_cmd = ping_command(AF_INET6); > + int i, err; > + > + for (i = 0; i < WAIT_AUTO_IP_MAX_ATTEMPT; i++) { > + err = SYS_NOFAIL("%s -c 1 -W 1 %s%%%s", ping_cmd, DST_ADDR, > + VETH_1); > + if (!err) > + break; > + } thinking about the risks of CI flakiness, would a small sleep between checks be worth doing here? > + > + return err; > +} > + > +static int setup_network(struct test_data *t) > +{ > + int ret; > + > + SYS(fail, "ip link add dev %s type veth peer name %s", VETH_1, VETH_2); > + SYS(fail, "ip link set %s up", VETH_1); > + SYS(fail, "ip link set %s up", VETH_2); > + > + ret = wait_local_ip(); > + if (!ASSERT_EQ(ret, 0, "wait local ip")) > + goto fail; > + > + memset(&t->qdisc, 0, sizeof(t->qdisc)); > + t->qdisc.sz = sizeof(t->qdisc); > + t->qdisc.attach_point = BPF_TC_EGRESS; > + t->qdisc.ifindex = if_nametoindex(VETH_1); > + if (!ASSERT_NEQ(t->qdisc.ifindex, 0, "if_nametoindex")) > + goto cleanup_interfaces; > + if (!ASSERT_OK(bpf_tc_hook_create(&t->qdisc), "qdisc add")) > + goto cleanup_interfaces; > + > + memset(&t->tc_attach, 0, sizeof(t->tc_attach)); > + t->tc_attach.sz = sizeof(t->tc_attach); > + t->tc_attach.prog_fd = bpf_program__fd(t->skel->progs.log_cgroup_id); > + if (!ASSERT_OK(bpf_tc_attach(&t->qdisc, &t->tc_attach), "filter add")) > + goto cleanup_qdisc; > + > + return 0; > + > +cleanup_qdisc: > + bpf_tc_hook_destroy(&t->qdisc); > +cleanup_interfaces: > + SYS_NOFAIL("ip link del %s", VETH_1); > +fail: > + return 1; > +} > + > +static void cleanup_network(struct test_data *t) > +{ > + bpf_tc_detach(&t->qdisc, &t->tc_attach); > + bpf_tc_hook_destroy(&t->qdisc); > + /* Deleting first interface will also delete peer interface */ > + SYS_NOFAIL("ip link del %s", VETH_1); > +} > + > +static void check_ancestors_ids(struct test_data *t) > +{ > + __u64 actual_ids[NUM_CGROUP_LEVELS], expected_ids[NUM_CGROUP_LEVELS]; > + char assert_name[MAX_ASSERT_NAME]; > + __u32 level; > + int err; > + > + expected_ids[0] = get_cgroup_id("/.."); /* root cgroup */ > + expected_ids[1] = get_cgroup_id(""); > + expected_ids[2] = get_cgroup_id(CGROUP_PATH); > + expected_ids[3] = 0; /* non-existent cgroup */ > + > + for (level = 0; level < NUM_CGROUP_LEVELS; level++) { > + err = bpf_map__lookup_elem(t->skel->maps.cgroup_ids, &level, > + sizeof(level), &actual_ids[level], > + sizeof(__u64), 0); could probably simplify this + the BPF prog using a global array of actual_ids[], then compare it to the expected values using skel->bss->actual_ids > + if (!ASSERT_OK(err, "read map")) > + continue; > + snprintf(assert_name, MAX_ASSERT_NAME, > + "ancestor id at level %d", level); > + ASSERT_EQ(actual_ids[level], expected_ids[level], assert_name); > + } > +} > + > +void test_cgroup_ancestor(void) > +{ > + struct test_data t; > + int cgroup_fd; > + > + t.skel = cgroup_ancestor__open_and_load(); > + if (!ASSERT_OK_PTR(t.skel, "open and load")) > + return; > + > + if (setup_network(&t)) > + goto cleanup_progs; > + > + cgroup_fd = cgroup_setup_and_join(CGROUP_PATH); > + if (cgroup_fd < 0) > + goto cleanup_network; > + > + if (send_datagram()) > + goto cleanup_cgroups; > + > + check_ancestors_ids(&t); > + > +cleanup_cgroups: > + cleanup_cgroup_environment(); > +cleanup_network: > + cleanup_network(&t); > +cleanup_progs: > + cgroup_ancestor__destroy(t.skel); > +} > diff --git a/tools/testing/selftests/bpf/test_skb_cgroup_id.sh b/tools/testing/selftests/bpf/test_skb_cgroup_id.sh > deleted file mode 100755 > index d7dad49175c2..000000000000 > --- a/tools/testing/selftests/bpf/test_skb_cgroup_id.sh > +++ /dev/null > @@ -1,67 +0,0 @@ > -#!/bin/sh > -# SPDX-License-Identifier: GPL-2.0 > -# Copyright (c) 2018 Facebook > - > -set -eu > - > -wait_for_ip() > -{ > - local _i > - echo -n "Wait for testing link-local IP to become available " > - for _i in $(seq ${MAX_PING_TRIES}); do > - echo -n "." > - if $PING6 -c 1 -W 1 ff02::1%${TEST_IF} >/dev/null 2>&1; then > - echo " OK" > - return > - fi > - sleep 1 > - done > - echo 1>&2 "ERROR: Timeout waiting for test IP to become available." > - exit 1 > -} > - > -setup() > -{ > - # Create testing interfaces not to interfere with current environment. > - ip link add dev ${TEST_IF} type veth peer name ${TEST_IF_PEER} > - ip link set ${TEST_IF} up > - ip link set ${TEST_IF_PEER} up > - > - wait_for_ip > - > - tc qdisc add dev ${TEST_IF} clsact > - mkdir -p /sys/fs/bpf/${BPF_PROG_PIN} > - bpftool prog loadall ${BPF_PROG_OBJ} /sys/fs/bpf/${BPF_PROG_PIN} type tc > - tc filter add dev ${TEST_IF} egress bpf da object-pinned \ > - /sys/fs/bpf/${BPF_PROG_PIN}/${BPF_PROG_NAME} > - > - BPF_PROG_ID=$(tc filter show dev ${TEST_IF} egress | \ > - awk '/ id / {sub(/.* id /, "", $0); print($1)}') > -} > - > -cleanup() > -{ > - ip link del ${TEST_IF} 2>/dev/null || : > - ip link del ${TEST_IF_PEER} 2>/dev/null || : > - rm -rf /sys/fs/bpf/${BPF_PROG_PIN} > -} > - > -main() > -{ > - trap cleanup EXIT 2 3 6 15 > - setup > - ${PROG} ${TEST_IF} ${BPF_PROG_ID} > -} > - > -DIR=$(dirname $0) > -TEST_IF="test_cgid_1" > -TEST_IF_PEER="test_cgid_2" > -MAX_PING_TRIES=5 > -BPF_PROG_PIN="cgroup_ancestor" > -BPF_PROG_OBJ="${DIR}/${BPF_PROG_PIN}.bpf.o" > -BPF_PROG_NAME="log_cgroup_id" > -BPF_PROG_ID=0 > -PROG="${DIR}/test_skb_cgroup_id_user" > -type ping6 >/dev/null 2>&1 && PING6="ping6" || PING6="ping -6" > - > -main > diff --git a/tools/testing/selftests/bpf/test_skb_cgroup_id_user.c b/tools/testing/selftests/bpf/test_skb_cgroup_id_user.c > deleted file mode 100644 > index ed518d075d1d..000000000000 > --- a/tools/testing/selftests/bpf/test_skb_cgroup_id_user.c > +++ /dev/null > @@ -1,183 +0,0 @@ > -// SPDX-License-Identifier: GPL-2.0 > -// Copyright (c) 2018 Facebook > - > -#include <stdlib.h> > -#include <string.h> > -#include <unistd.h> > - > -#include <arpa/inet.h> > -#include <net/if.h> > -#include <netinet/in.h> > -#include <sys/socket.h> > -#include <sys/types.h> > - > - > -#include <bpf/bpf.h> > -#include <bpf/libbpf.h> > - > -#include "cgroup_helpers.h" > - > -#define CGROUP_PATH "/skb_cgroup_test" > -#define NUM_CGROUP_LEVELS 4 > - > -/* RFC 4291, Section 2.7.1 */ > -#define LINKLOCAL_MULTICAST "ff02::1" > - > -static int mk_dst_addr(const char *ip, const char *iface, > - struct sockaddr_in6 *dst) > -{ > - memset(dst, 0, sizeof(*dst)); > - > - dst->sin6_family = AF_INET6; > - dst->sin6_port = htons(1025); > - > - if (inet_pton(AF_INET6, ip, &dst->sin6_addr) != 1) { > - log_err("Invalid IPv6: %s", ip); > - return -1; > - } > - > - dst->sin6_scope_id = if_nametoindex(iface); > - if (!dst->sin6_scope_id) { > - log_err("Failed to get index of iface: %s", iface); > - return -1; > - } > - > - return 0; > -} > - > -static int send_packet(const char *iface) > -{ > - struct sockaddr_in6 dst; > - char msg[] = "msg"; > - int err = 0; > - int fd = -1; > - > - if (mk_dst_addr(LINKLOCAL_MULTICAST, iface, &dst)) > - goto err; > - > - fd = socket(AF_INET6, SOCK_DGRAM, 0); > - if (fd == -1) { > - log_err("Failed to create UDP socket"); > - goto err; > - } > - > - if (sendto(fd, &msg, sizeof(msg), 0, (const struct sockaddr *)&dst, > - sizeof(dst)) == -1) { > - log_err("Failed to send datagram"); > - goto err; > - } > - > - goto out; > -err: > - err = -1; > -out: > - if (fd >= 0) > - close(fd); > - return err; > -} > - > -int get_map_fd_by_prog_id(int prog_id) > -{ > - struct bpf_prog_info info = {}; > - __u32 info_len = sizeof(info); > - __u32 map_ids[1]; > - int prog_fd = -1; > - int map_fd = -1; > - > - prog_fd = bpf_prog_get_fd_by_id(prog_id); > - if (prog_fd < 0) { > - log_err("Failed to get fd by prog id %d", prog_id); > - goto err; > - } > - > - info.nr_map_ids = 1; > - info.map_ids = (__u64) (unsigned long) map_ids; > - > - if (bpf_prog_get_info_by_fd(prog_fd, &info, &info_len)) { > - log_err("Failed to get info by prog fd %d", prog_fd); > - goto err; > - } > - > - if (!info.nr_map_ids) { > - log_err("No maps found for prog fd %d", prog_fd); > - goto err; > - } > - > - map_fd = bpf_map_get_fd_by_id(map_ids[0]); > - if (map_fd < 0) > - log_err("Failed to get fd by map id %d", map_ids[0]); > -err: > - if (prog_fd >= 0) > - close(prog_fd); > - return map_fd; > -} > - > -int check_ancestor_cgroup_ids(int prog_id) > -{ > - __u64 actual_ids[NUM_CGROUP_LEVELS], expected_ids[NUM_CGROUP_LEVELS]; > - __u32 level; > - int err = 0; > - int map_fd; > - > - expected_ids[0] = get_cgroup_id("/.."); /* root cgroup */ > - expected_ids[1] = get_cgroup_id(""); > - expected_ids[2] = get_cgroup_id(CGROUP_PATH); > - expected_ids[3] = 0; /* non-existent cgroup */ > - > - map_fd = get_map_fd_by_prog_id(prog_id); > - if (map_fd < 0) > - goto err; > - > - for (level = 0; level < NUM_CGROUP_LEVELS; ++level) { > - if (bpf_map_lookup_elem(map_fd, &level, &actual_ids[level])) { > - log_err("Failed to lookup key %d", level); > - goto err; > - } > - if (actual_ids[level] != expected_ids[level]) { > - log_err("%llx (actual) != %llx (expected), level: %u\n", > - actual_ids[level], expected_ids[level], level); > - goto err; > - } > - } > - > - goto out; > -err: > - err = -1; > -out: > - if (map_fd >= 0) > - close(map_fd); > - return err; > -} > - > -int main(int argc, char **argv) > -{ > - int cgfd = -1; > - int err = 0; > - > - if (argc < 3) { > - fprintf(stderr, "Usage: %s iface prog_id\n", argv[0]); > - exit(EXIT_FAILURE); > - } > - > - /* Use libbpf 1.0 API mode */ > - libbpf_set_strict_mode(LIBBPF_STRICT_ALL); > - > - cgfd = cgroup_setup_and_join(CGROUP_PATH); > - if (cgfd < 0) > - goto err; > - > - if (send_packet(argv[1])) > - goto err; > - > - if (check_ancestor_cgroup_ids(atoi(argv[2]))) > - goto err; > - > - goto out; > -err: > - err = -1; > -out: > - close(cgfd); > - cleanup_cgroup_environment(); > - printf("[%s]\n", err ? "FAIL" : "PASS"); > - return err; > -} >
On 8/1/24 10:49, Alan Maguire wrote: > On 31/07/2024 11:38, Alexis Lothoré (eBPF Foundation) wrote: [...] >> +static int wait_local_ip(void) >> +{ >> + char *ping_cmd = ping_command(AF_INET6); >> + int i, err; >> + >> + for (i = 0; i < WAIT_AUTO_IP_MAX_ATTEMPT; i++) { >> + err = SYS_NOFAIL("%s -c 1 -W 1 %s%%%s", ping_cmd, DST_ADDR, >> + VETH_1); >> + if (!err) >> + break; >> + } > > > thinking about the risks of CI flakiness, would a small sleep between > checks be worth doing here? I assumed that adding -W 1 (ping timeout duration) to the command would be enough to make sure that there is a proper wait between each attempt (so currently, waiting at most 10s for network configuration between the 2 veths). Don't you think it is enough to prevent issues in CI ? >> + [...] >> + >> + expected_ids[0] = get_cgroup_id("/.."); /* root cgroup */ >> + expected_ids[1] = get_cgroup_id(""); >> + expected_ids[2] = get_cgroup_id(CGROUP_PATH); >> + expected_ids[3] = 0; /* non-existent cgroup */ >> + >> + for (level = 0; level < NUM_CGROUP_LEVELS; level++) { >> + err = bpf_map__lookup_elem(t->skel->maps.cgroup_ids, &level, >> + sizeof(level), &actual_ids[level], >> + sizeof(__u64), 0); > > could probably simplify this + the BPF prog using a global array of > actual_ids[], then compare it to the expected values using > skel->bss->actual_ids ACK, I'll update this. Thanks, Alexis
On 01/08/2024 11:12, Alexis Lothoré wrote: > On 8/1/24 10:49, Alan Maguire wrote: >> On 31/07/2024 11:38, Alexis Lothoré (eBPF Foundation) wrote: > > [...] > >>> +static int wait_local_ip(void) >>> +{ >>> + char *ping_cmd = ping_command(AF_INET6); >>> + int i, err; >>> + >>> + for (i = 0; i < WAIT_AUTO_IP_MAX_ATTEMPT; i++) { >>> + err = SYS_NOFAIL("%s -c 1 -W 1 %s%%%s", ping_cmd, DST_ADDR, >>> + VETH_1); >>> + if (!err) >>> + break; >>> + } >> >> >> thinking about the risks of CI flakiness, would a small sleep between >> checks be worth doing here? > > I assumed that adding -W 1 (ping timeout duration) to the command would be > enough to make sure that there is a proper wait between each attempt (so > currently, waiting at most 10s for network configuration between the 2 veths). > Don't you think it is enough to prevent issues in CI ? > Yep, that should be fine, I missed the wait option. >>> + > > [...] > >>> + >>> + expected_ids[0] = get_cgroup_id("/.."); /* root cgroup */ >>> + expected_ids[1] = get_cgroup_id(""); >>> + expected_ids[2] = get_cgroup_id(CGROUP_PATH); >>> + expected_ids[3] = 0; /* non-existent cgroup */ >>> + >>> + for (level = 0; level < NUM_CGROUP_LEVELS; level++) { >>> + err = bpf_map__lookup_elem(t->skel->maps.cgroup_ids, &level, >>> + sizeof(level), &actual_ids[level], >>> + sizeof(__u64), 0); >> >> could probably simplify this + the BPF prog using a global array of >> actual_ids[], then compare it to the expected values using >> skel->bss->actual_ids > > ACK, I'll update this. > Great, thanks! Alan
diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore index a45f11f81337..c46950d4ef53 100644 --- a/tools/testing/selftests/bpf/.gitignore +++ b/tools/testing/selftests/bpf/.gitignore @@ -20,7 +20,6 @@ test_sock urandom_read test_sockmap test_lirc_mode2_user -test_skb_cgroup_id_user test_flow_dissector flow_dissector_load test_tcpnotify_user diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile index 0ac0f9dbc2f8..057e6ba003f1 100644 --- a/tools/testing/selftests/bpf/Makefile +++ b/tools/testing/selftests/bpf/Makefile @@ -138,7 +138,7 @@ TEST_PROGS_EXTENDED := with_addr.sh \ test_xdp_vlan.sh test_bpftool.py # Compile but not part of 'make run_tests' -TEST_GEN_PROGS_EXTENDED = test_skb_cgroup_id_user \ +TEST_GEN_PROGS_EXTENDED = \ flow_dissector_load test_flow_dissector test_tcp_check_syncookie_user \ test_lirc_mode2_user xdping test_cpp runqslower bench bpf_testmod.ko \ xskxceiver xdp_redirect_multi xdp_synproxy veristat xdp_hw_metadata \ @@ -292,7 +292,6 @@ CAP_HELPERS := $(OUTPUT)/cap_helpers.o NETWORK_HELPERS := $(OUTPUT)/network_helpers.o $(OUTPUT)/test_dev_cgroup: $(CGROUP_HELPERS) $(TESTING_HELPERS) -$(OUTPUT)/test_skb_cgroup_id_user: $(CGROUP_HELPERS) $(TESTING_HELPERS) $(OUTPUT)/test_sock: $(CGROUP_HELPERS) $(TESTING_HELPERS) $(OUTPUT)/test_sockmap: $(CGROUP_HELPERS) $(TESTING_HELPERS) $(OUTPUT)/test_tcpnotify_user: $(CGROUP_HELPERS) $(TESTING_HELPERS) $(TRACE_HELPERS) diff --git a/tools/testing/selftests/bpf/prog_tests/cgroup_ancestor.c b/tools/testing/selftests/bpf/prog_tests/cgroup_ancestor.c new file mode 100644 index 000000000000..796aa3af75e8 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/cgroup_ancestor.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "test_progs.h" +#include "network_helpers.h" +#include "cgroup_helpers.h" +#include "cgroup_ancestor.skel.h" + +#define VETH_PREFIX "test_cgid_" +#define VETH_1 VETH_PREFIX "1" +#define VETH_2 VETH_PREFIX "2" +#define CGROUP_PATH "/skb_cgroup_test" +#define NUM_CGROUP_LEVELS 4 +#define WAIT_AUTO_IP_MAX_ATTEMPT 10 +#define DST_ADDR "ff02::1" +#define DST_PORT 1234 +#define MAX_ASSERT_NAME 32 + +struct test_data { + struct cgroup_ancestor *skel; + struct bpf_tc_hook qdisc; + struct bpf_tc_opts tc_attach; +}; + +static int send_datagram(void) +{ + unsigned char buf[] = "some random test data"; + struct sockaddr_in6 addr = { .sin6_family = AF_INET6, + .sin6_port = htons(DST_PORT), + .sin6_scope_id = if_nametoindex(VETH_1) }; + int sock, n; + + if (!ASSERT_EQ(inet_pton(AF_INET6, DST_ADDR, &addr.sin6_addr), 1, + "inet_pton")) + return -1; + + sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (!ASSERT_OK_FD(sock, "create socket")) + return sock; + + n = sendto(sock, buf, sizeof(buf), 0, (const struct sockaddr *)&addr, + sizeof(addr)); + if (!ASSERT_EQ(n, sizeof(buf), "send data")) + return n; + + return 0; +} + +static int wait_local_ip(void) +{ + char *ping_cmd = ping_command(AF_INET6); + int i, err; + + for (i = 0; i < WAIT_AUTO_IP_MAX_ATTEMPT; i++) { + err = SYS_NOFAIL("%s -c 1 -W 1 %s%%%s", ping_cmd, DST_ADDR, + VETH_1); + if (!err) + break; + } + + return err; +} + +static int setup_network(struct test_data *t) +{ + int ret; + + SYS(fail, "ip link add dev %s type veth peer name %s", VETH_1, VETH_2); + SYS(fail, "ip link set %s up", VETH_1); + SYS(fail, "ip link set %s up", VETH_2); + + ret = wait_local_ip(); + if (!ASSERT_EQ(ret, 0, "wait local ip")) + goto fail; + + memset(&t->qdisc, 0, sizeof(t->qdisc)); + t->qdisc.sz = sizeof(t->qdisc); + t->qdisc.attach_point = BPF_TC_EGRESS; + t->qdisc.ifindex = if_nametoindex(VETH_1); + if (!ASSERT_NEQ(t->qdisc.ifindex, 0, "if_nametoindex")) + goto cleanup_interfaces; + if (!ASSERT_OK(bpf_tc_hook_create(&t->qdisc), "qdisc add")) + goto cleanup_interfaces; + + memset(&t->tc_attach, 0, sizeof(t->tc_attach)); + t->tc_attach.sz = sizeof(t->tc_attach); + t->tc_attach.prog_fd = bpf_program__fd(t->skel->progs.log_cgroup_id); + if (!ASSERT_OK(bpf_tc_attach(&t->qdisc, &t->tc_attach), "filter add")) + goto cleanup_qdisc; + + return 0; + +cleanup_qdisc: + bpf_tc_hook_destroy(&t->qdisc); +cleanup_interfaces: + SYS_NOFAIL("ip link del %s", VETH_1); +fail: + return 1; +} + +static void cleanup_network(struct test_data *t) +{ + bpf_tc_detach(&t->qdisc, &t->tc_attach); + bpf_tc_hook_destroy(&t->qdisc); + /* Deleting first interface will also delete peer interface */ + SYS_NOFAIL("ip link del %s", VETH_1); +} + +static void check_ancestors_ids(struct test_data *t) +{ + __u64 actual_ids[NUM_CGROUP_LEVELS], expected_ids[NUM_CGROUP_LEVELS]; + char assert_name[MAX_ASSERT_NAME]; + __u32 level; + int err; + + expected_ids[0] = get_cgroup_id("/.."); /* root cgroup */ + expected_ids[1] = get_cgroup_id(""); + expected_ids[2] = get_cgroup_id(CGROUP_PATH); + expected_ids[3] = 0; /* non-existent cgroup */ + + for (level = 0; level < NUM_CGROUP_LEVELS; level++) { + err = bpf_map__lookup_elem(t->skel->maps.cgroup_ids, &level, + sizeof(level), &actual_ids[level], + sizeof(__u64), 0); + if (!ASSERT_OK(err, "read map")) + continue; + snprintf(assert_name, MAX_ASSERT_NAME, + "ancestor id at level %d", level); + ASSERT_EQ(actual_ids[level], expected_ids[level], assert_name); + } +} + +void test_cgroup_ancestor(void) +{ + struct test_data t; + int cgroup_fd; + + t.skel = cgroup_ancestor__open_and_load(); + if (!ASSERT_OK_PTR(t.skel, "open and load")) + return; + + if (setup_network(&t)) + goto cleanup_progs; + + cgroup_fd = cgroup_setup_and_join(CGROUP_PATH); + if (cgroup_fd < 0) + goto cleanup_network; + + if (send_datagram()) + goto cleanup_cgroups; + + check_ancestors_ids(&t); + +cleanup_cgroups: + cleanup_cgroup_environment(); +cleanup_network: + cleanup_network(&t); +cleanup_progs: + cgroup_ancestor__destroy(t.skel); +} diff --git a/tools/testing/selftests/bpf/test_skb_cgroup_id.sh b/tools/testing/selftests/bpf/test_skb_cgroup_id.sh deleted file mode 100755 index d7dad49175c2..000000000000 --- a/tools/testing/selftests/bpf/test_skb_cgroup_id.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh -# SPDX-License-Identifier: GPL-2.0 -# Copyright (c) 2018 Facebook - -set -eu - -wait_for_ip() -{ - local _i - echo -n "Wait for testing link-local IP to become available " - for _i in $(seq ${MAX_PING_TRIES}); do - echo -n "." - if $PING6 -c 1 -W 1 ff02::1%${TEST_IF} >/dev/null 2>&1; then - echo " OK" - return - fi - sleep 1 - done - echo 1>&2 "ERROR: Timeout waiting for test IP to become available." - exit 1 -} - -setup() -{ - # Create testing interfaces not to interfere with current environment. - ip link add dev ${TEST_IF} type veth peer name ${TEST_IF_PEER} - ip link set ${TEST_IF} up - ip link set ${TEST_IF_PEER} up - - wait_for_ip - - tc qdisc add dev ${TEST_IF} clsact - mkdir -p /sys/fs/bpf/${BPF_PROG_PIN} - bpftool prog loadall ${BPF_PROG_OBJ} /sys/fs/bpf/${BPF_PROG_PIN} type tc - tc filter add dev ${TEST_IF} egress bpf da object-pinned \ - /sys/fs/bpf/${BPF_PROG_PIN}/${BPF_PROG_NAME} - - BPF_PROG_ID=$(tc filter show dev ${TEST_IF} egress | \ - awk '/ id / {sub(/.* id /, "", $0); print($1)}') -} - -cleanup() -{ - ip link del ${TEST_IF} 2>/dev/null || : - ip link del ${TEST_IF_PEER} 2>/dev/null || : - rm -rf /sys/fs/bpf/${BPF_PROG_PIN} -} - -main() -{ - trap cleanup EXIT 2 3 6 15 - setup - ${PROG} ${TEST_IF} ${BPF_PROG_ID} -} - -DIR=$(dirname $0) -TEST_IF="test_cgid_1" -TEST_IF_PEER="test_cgid_2" -MAX_PING_TRIES=5 -BPF_PROG_PIN="cgroup_ancestor" -BPF_PROG_OBJ="${DIR}/${BPF_PROG_PIN}.bpf.o" -BPF_PROG_NAME="log_cgroup_id" -BPF_PROG_ID=0 -PROG="${DIR}/test_skb_cgroup_id_user" -type ping6 >/dev/null 2>&1 && PING6="ping6" || PING6="ping -6" - -main diff --git a/tools/testing/selftests/bpf/test_skb_cgroup_id_user.c b/tools/testing/selftests/bpf/test_skb_cgroup_id_user.c deleted file mode 100644 index ed518d075d1d..000000000000 --- a/tools/testing/selftests/bpf/test_skb_cgroup_id_user.c +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// Copyright (c) 2018 Facebook - -#include <stdlib.h> -#include <string.h> -#include <unistd.h> - -#include <arpa/inet.h> -#include <net/if.h> -#include <netinet/in.h> -#include <sys/socket.h> -#include <sys/types.h> - - -#include <bpf/bpf.h> -#include <bpf/libbpf.h> - -#include "cgroup_helpers.h" - -#define CGROUP_PATH "/skb_cgroup_test" -#define NUM_CGROUP_LEVELS 4 - -/* RFC 4291, Section 2.7.1 */ -#define LINKLOCAL_MULTICAST "ff02::1" - -static int mk_dst_addr(const char *ip, const char *iface, - struct sockaddr_in6 *dst) -{ - memset(dst, 0, sizeof(*dst)); - - dst->sin6_family = AF_INET6; - dst->sin6_port = htons(1025); - - if (inet_pton(AF_INET6, ip, &dst->sin6_addr) != 1) { - log_err("Invalid IPv6: %s", ip); - return -1; - } - - dst->sin6_scope_id = if_nametoindex(iface); - if (!dst->sin6_scope_id) { - log_err("Failed to get index of iface: %s", iface); - return -1; - } - - return 0; -} - -static int send_packet(const char *iface) -{ - struct sockaddr_in6 dst; - char msg[] = "msg"; - int err = 0; - int fd = -1; - - if (mk_dst_addr(LINKLOCAL_MULTICAST, iface, &dst)) - goto err; - - fd = socket(AF_INET6, SOCK_DGRAM, 0); - if (fd == -1) { - log_err("Failed to create UDP socket"); - goto err; - } - - if (sendto(fd, &msg, sizeof(msg), 0, (const struct sockaddr *)&dst, - sizeof(dst)) == -1) { - log_err("Failed to send datagram"); - goto err; - } - - goto out; -err: - err = -1; -out: - if (fd >= 0) - close(fd); - return err; -} - -int get_map_fd_by_prog_id(int prog_id) -{ - struct bpf_prog_info info = {}; - __u32 info_len = sizeof(info); - __u32 map_ids[1]; - int prog_fd = -1; - int map_fd = -1; - - prog_fd = bpf_prog_get_fd_by_id(prog_id); - if (prog_fd < 0) { - log_err("Failed to get fd by prog id %d", prog_id); - goto err; - } - - info.nr_map_ids = 1; - info.map_ids = (__u64) (unsigned long) map_ids; - - if (bpf_prog_get_info_by_fd(prog_fd, &info, &info_len)) { - log_err("Failed to get info by prog fd %d", prog_fd); - goto err; - } - - if (!info.nr_map_ids) { - log_err("No maps found for prog fd %d", prog_fd); - goto err; - } - - map_fd = bpf_map_get_fd_by_id(map_ids[0]); - if (map_fd < 0) - log_err("Failed to get fd by map id %d", map_ids[0]); -err: - if (prog_fd >= 0) - close(prog_fd); - return map_fd; -} - -int check_ancestor_cgroup_ids(int prog_id) -{ - __u64 actual_ids[NUM_CGROUP_LEVELS], expected_ids[NUM_CGROUP_LEVELS]; - __u32 level; - int err = 0; - int map_fd; - - expected_ids[0] = get_cgroup_id("/.."); /* root cgroup */ - expected_ids[1] = get_cgroup_id(""); - expected_ids[2] = get_cgroup_id(CGROUP_PATH); - expected_ids[3] = 0; /* non-existent cgroup */ - - map_fd = get_map_fd_by_prog_id(prog_id); - if (map_fd < 0) - goto err; - - for (level = 0; level < NUM_CGROUP_LEVELS; ++level) { - if (bpf_map_lookup_elem(map_fd, &level, &actual_ids[level])) { - log_err("Failed to lookup key %d", level); - goto err; - } - if (actual_ids[level] != expected_ids[level]) { - log_err("%llx (actual) != %llx (expected), level: %u\n", - actual_ids[level], expected_ids[level], level); - goto err; - } - } - - goto out; -err: - err = -1; -out: - if (map_fd >= 0) - close(map_fd); - return err; -} - -int main(int argc, char **argv) -{ - int cgfd = -1; - int err = 0; - - if (argc < 3) { - fprintf(stderr, "Usage: %s iface prog_id\n", argv[0]); - exit(EXIT_FAILURE); - } - - /* Use libbpf 1.0 API mode */ - libbpf_set_strict_mode(LIBBPF_STRICT_ALL); - - cgfd = cgroup_setup_and_join(CGROUP_PATH); - if (cgfd < 0) - goto err; - - if (send_packet(argv[1])) - goto err; - - if (check_ancestor_cgroup_ids(atoi(argv[2]))) - goto err; - - goto out; -err: - err = -1; -out: - close(cgfd); - cleanup_cgroup_environment(); - printf("[%s]\n", err ? "FAIL" : "PASS"); - return err; -}
test_skb_cgroup_id_user allows testing skb cgroup id retrieval at different levels, but is not integrated in test_progs, so it is not run automatically in CI. The test overlaps a bit with cgroup_skb_sk_lookup_kern, which is integrated in test_progs and test extensively skb cgroup helpers, but there is still one major difference between the two tests which justifies the conversion: cgroup_skb_sk_lookup_kern deals with a BPF_PROG_TYPE_CGROUP_SKB (attached on a cgroup), while test_skb_cgroup_id_user deals with a BPF_PROG_TYPE_SCHED_CLS (attached on a qdisc) Convert test_skb_cgroup_id_user into test_progs framework in order to run it automatically in CI. The main differences with the original test are the following: - rename the test to make it shorter and more straightforward regarding tested feature - the wrapping shell script has been dropped since every setup step is now handled in the main C test file - the test has been renamed for a shorter name and reflecting the tested API - add dedicated assert log per level to ease test failure debugging Signed-off-by: Alexis Lothoré (eBPF Foundation) <alexis.lothore@bootlin.com> --- The new test has been tested in a qemu environment: ./test_progs -a cgroup_ancestor 44 cgroup_ancestor:OK Summary: 1/0 PASSED, 0 SKIPPED, 0 FAILED --- tools/testing/selftests/bpf/.gitignore | 1 - tools/testing/selftests/bpf/Makefile | 3 +- .../selftests/bpf/prog_tests/cgroup_ancestor.c | 159 ++++++++++++++++++ tools/testing/selftests/bpf/test_skb_cgroup_id.sh | 67 -------- .../selftests/bpf/test_skb_cgroup_id_user.c | 183 --------------------- 5 files changed, 160 insertions(+), 253 deletions(-)