[PATCH bpf-next v1 19/19] tools/bpf: selftests: add bpf_iter selftests

Yonghong Song
2020-04-27T20:13:10Z

The test includes three subtests for the previous three verifier changes:
- new reg state btf_id_or_null
- access fields in the variable length array of the structure.
- put a btf_id ptr value in a stack and accessible to
tracing/iter programs.

The test also tested the workflow of creating and reading data
from an anonymous or file iterator. Further for file based
iterator, it tested that the link update can change the underlying
bpf program.

$ test_progs -n 2
#2/1 btf_id_or_null:OK
#2/2 ipv6_route:OK
#2/3 netlink:OK
#2/4 anon:OK
#2/5 file:OK
#2 bpf_iter:OK
Summary: 1/5 PASSED, 0 SKIPPED, 0 FAILED

Signed-off-by: Yonghong Song <yhs@fb.com>
---
.../selftests/bpf/prog_tests/bpf_iter.c | 180 ++++++++++++++++++
.../selftests/bpf/progs/bpf_iter_test_kern1.c | 4 +
.../selftests/bpf/progs/bpf_iter_test_kern2.c | 4 +
.../selftests/bpf/progs/bpf_iter_test_kern3.c | 18 ++
.../bpf/progs/bpf_iter_test_kern_common.h | 22 +++
5 files changed, 228 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/bpf_iter.c
create mode 100644 tools/testing/selftests/bpf/progs/bpf_iter_test_kern1.c
create mode 100644 tools/testing/selftests/bpf/progs/bpf_iter_test_kern2.c
create mode 100644 tools/testing/selftests/bpf/progs/bpf_iter_test_kern3.c
create mode 100644 tools/testing/selftests/bpf/progs/bpf_iter_test_kern_common.h

diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_iter.c b/tools/testing/selftests/bpf/prog_tests/bpf_iter.c
new file mode 100644
index 000000000000..d51ed0d99a75
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/bpf_iter.c
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 Facebook */
+#include <test_progs.h>
+#include "bpf_iter_ipv6_route.skel.h"
+#include "bpf_iter_netlink.skel.h"
+#include "bpf_iter_test_kern1.skel.h"
+#include "bpf_iter_test_kern2.skel.h"
+#include "bpf_iter_test_kern3.skel.h"
+
+static int duration;
+
+static void test_btf_id_or_null(void)
+{
+ struct bpf_iter_test_kern3 *skel;
+
+ skel = bpf_iter_test_kern3__open_and_load();
+ if (CHECK(skel, "skel_open_and_load",
+ "skeleton open_and_load unexpectedly succeeded\n")) {
+ bpf_iter_test_kern3__destroy(skel);
+ return;
+ }
+}
+
+static void test_load_ipv6_route(void)
+{
+ struct bpf_iter_ipv6_route *skel;
+
+ skel = bpf_iter_ipv6_route__open_and_load();
+ if (CHECK(!skel, "skel_open_and_load",
+ "skeleton open_and_load failed\n"))
+ return;
+
+ bpf_iter_ipv6_route__destroy(skel);
+}
+
+static void test_load_netlink(void)
+{
+ struct bpf_iter_netlink *skel;
+
+ skel = bpf_iter_netlink__open_and_load();
+ if (CHECK(!skel, "skel_open_and_load",
+ "skeleton open_and_load failed\n"))
+ return;
+
+ bpf_iter_netlink__destroy(skel);
+}
+
+static int do_read_with_fd(int iter_fd, const char *expected)
+{
+ int err = -1, len;
+ char buf[16] = {};
+
+ while ((len = read(iter_fd, buf, sizeof(buf))) > 0) {
+ if (CHECK(len != strlen(expected), "read",
+ "wrong read len %d\n", len))
+ return -1;
+
+ if (CHECK(err == 0, "read", "invalid additional read\n"))
+ return -1;
+
+ err = strcmp(buf, expected);
+ if (CHECK(err, "read",
+ "incorrect read result: buf %s, expected %s\n",
+ buf, expected))
+ return -1;
+ }
+
+ CHECK(err, "read", "missing read result\n");
+ return err;
+}
+
+static void test_anon_iter(void)
+{
+ struct bpf_iter_test_kern1 *skel;
+ struct bpf_link *link;
+ int iter_fd;
+
+ skel = bpf_iter_test_kern1__open_and_load();
+ if (CHECK(!skel, "skel_open_and_load",
+ "skeleton open_and_load failed\n"))
+ return;
+
+ link = bpf_program__attach_iter(skel->progs.dump_tasks, NULL);
+ if (CHECK(IS_ERR(link), "attach_iter", "attach_iter failed\n"))
+ goto out;
+
+ iter_fd = bpf_link__create_iter(link, 0);
+ if (CHECK(iter_fd < 0, "create_iter", "create_iter failed\n"))
+ goto free_link;
+
+ do_read_with_fd(iter_fd, "abcd");
+ close(iter_fd);
+
+free_link:
+ bpf_link__disconnect(link);
+ bpf_link__destroy(link);
+out:
+ bpf_iter_test_kern1__destroy(skel);
+}
+
+static int do_read(const char *path, const char *expected)
+{
+ int err, iter_fd;
+
+ iter_fd = open(path, O_RDONLY);
+ if (CHECK(iter_fd < 0, "open", "open %s failed: %s\n",
+ path, strerror(errno)))
+ return -1;
+
+ err = do_read_with_fd(iter_fd, expected);
+ close(iter_fd);
+ return err;
+}
+
+static void test_file_iter(void)
+{
+ const char *path = "/sys/fs/bpf/bpf_iter_test1";
+ struct bpf_iter_test_kern1 *skel1;
+ struct bpf_iter_test_kern2 *skel2;
+ struct bpf_link *link;
+ int err;
+
+ skel1 = bpf_iter_test_kern1__open_and_load();
+ if (CHECK(!skel1, "skel_open_and_load",
+ "skeleton open_and_load failed\n"))
+ return;
+
+ link = bpf_program__attach_iter(skel1->progs.dump_tasks, NULL);
+ if (CHECK(IS_ERR(link), "attach_iter", "attach_iter failed\n"))
+ goto out;
+
+ /* unlink this path if it exists. */
+ unlink(path);
+
+ err = bpf_link__pin(link, path);
+ if (CHECK(err, "pin_iter", "pin_iter to %s failed: %s\n", path,
+ strerror(errno)))
+ goto free_link;
+
+ err = do_read(path, "abcd");
+ if (err)
+ goto free_link;
+
+ /* file based iterator seems working fine. Let us a link update
+ * of the underlying link and `cat` the iterator again, its content
+ * should change.
+ */
+ skel2 = bpf_iter_test_kern2__open_and_load();
+ if (CHECK(!skel2, "skel_open_and_load",
+ "skeleton open_and_load failed\n"))
+ goto free_link;
+
+ err = bpf_link__update_program(link, skel2->progs.dump_tasks);
+ if (CHECK(err, "update_prog", "update_prog failed\n"))
+ goto destroy_skel2;
+
+ do_read(path, "ABCD");
+
+destroy_skel2:
+ bpf_iter_test_kern2__destroy(skel2);
+free_link:
+ bpf_link__disconnect(link);
+ bpf_link__destroy(link);
+out:
+ bpf_iter_test_kern1__destroy(skel1);
+}
+
+void test_bpf_iter(void)
+{
+ if (test__start_subtest("btf_id_or_null"))
+ test_btf_id_or_null();
+ if (test__start_subtest("ipv6_route"))
+ test_load_ipv6_route();
+ if (test__start_subtest("netlink"))
+ test_load_netlink();
+ if (test__start_subtest("anon"))
+ test_anon_iter();
+ if (test__start_subtest("file"))
+ test_file_iter();
+}
diff --git a/tools/testing/selftests/bpf/progs/bpf_iter_test_kern1.c b/tools/testing/selftests/bpf/progs/bpf_iter_test_kern1.c
new file mode 100644
index 000000000000..c71a7c283108
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_iter_test_kern1.c
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 Facebook */
+#define START_CHAR 'a'
+#include "bpf_iter_test_kern_common.h"
diff --git a/tools/testing/selftests/bpf/progs/bpf_iter_test_kern2.c b/tools/testing/selftests/bpf/progs/bpf_iter_test_kern2.c
new file mode 100644
index 000000000000..8bdc8dc07444
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_iter_test_kern2.c
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 Facebook */
+#define START_CHAR 'A'
+#include "bpf_iter_test_kern_common.h"
diff --git a/tools/testing/selftests/bpf/progs/bpf_iter_test_kern3.c b/tools/testing/selftests/bpf/progs/bpf_iter_test_kern3.c
new file mode 100644
index 000000000000..a52555ef2826
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_iter_test_kern3.c
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 Facebook */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+char _license[] SEC("license") = "GPL";
+
+SEC("iter/task")
+int dump_tasks(struct bpf_iter__task *ctx)
+{
+ struct seq_file *seq = ctx->meta->seq;
+ struct task_struct *task = ctx->task;
+ int tgid;
+
+ tgid = task->tgid;
+ bpf_seq_write(seq, &tgid, sizeof(tgid));
+ return 0;
+}
diff --git a/tools/testing/selftests/bpf/progs/bpf_iter_test_kern_common.h b/tools/testing/selftests/bpf/progs/bpf_iter_test_kern_common.h
new file mode 100644
index 000000000000..7cd9125a291f
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/bpf_iter_test_kern_common.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020 Facebook */
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+
+char _license[] SEC("license") = "GPL";
+int count = 0;
+
+SEC("iter/task")
+int dump_tasks(struct bpf_iter__task *ctx)
+{
+ struct seq_file *seq = ctx->meta->seq;
+ char c;
+
+ if (count < 4) {
+ c = START_CHAR + count;
+ bpf_seq_write(seq, &c, sizeof(c));
+ count++;
+ }
+
+ return 0;
+}
--
2.24.1