OSDN Git Service

selftests/bpf: test for BPF_F_LOCK
authorAlexei Starovoitov <ast@kernel.org>
Thu, 31 Jan 2019 23:40:12 +0000 (15:40 -0800)
committerDaniel Borkmann <daniel@iogearbox.net>
Fri, 1 Feb 2019 19:55:39 +0000 (20:55 +0100)
Add C based test that runs 4 bpf programs in parallel
that update the same hash and array maps.
And another 2 threads that read from these two maps
via lookup(key, value, BPF_F_LOCK) api
to make sure the user space sees consistent value in both
hash and array elements while user space races with kernel bpf progs.

Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
tools/testing/selftests/bpf/Makefile
tools/testing/selftests/bpf/test_map_lock.c [new file with mode: 0644]
tools/testing/selftests/bpf/test_progs.c

index 302b8e7..9de0a1a 100644 (file)
@@ -35,7 +35,7 @@ BPF_OBJ_FILES = \
        sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
        get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
        test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o test_xdp_vlan.o \
-       xdp_dummy.o test_map_in_map.o test_spin_lock.o
+       xdp_dummy.o test_map_in_map.o test_spin_lock.o test_map_lock.o
 
 # Objects are built with default compilation flags and with sub-register
 # code-gen enabled.
diff --git a/tools/testing/selftests/bpf/test_map_lock.c b/tools/testing/selftests/bpf/test_map_lock.c
new file mode 100644 (file)
index 0000000..af8cc68
--- /dev/null
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2019 Facebook
+#include <linux/bpf.h>
+#include <linux/version.h>
+#include "bpf_helpers.h"
+
+#define VAR_NUM 16
+
+struct hmap_elem {
+       struct bpf_spin_lock lock;
+       int var[VAR_NUM];
+};
+
+struct bpf_map_def SEC("maps") hash_map = {
+       .type = BPF_MAP_TYPE_HASH,
+       .key_size = sizeof(int),
+       .value_size = sizeof(struct hmap_elem),
+       .max_entries = 1,
+};
+
+BPF_ANNOTATE_KV_PAIR(hash_map, int, struct hmap_elem);
+
+struct array_elem {
+       struct bpf_spin_lock lock;
+       int var[VAR_NUM];
+};
+
+struct bpf_map_def SEC("maps") array_map = {
+       .type = BPF_MAP_TYPE_ARRAY,
+       .key_size = sizeof(int),
+       .value_size = sizeof(struct array_elem),
+       .max_entries = 1,
+};
+
+BPF_ANNOTATE_KV_PAIR(array_map, int, struct array_elem);
+
+SEC("map_lock_demo")
+int bpf_map_lock_test(struct __sk_buff *skb)
+{
+       struct hmap_elem zero = {}, *val;
+       int rnd = bpf_get_prandom_u32();
+       int key = 0, err = 1, i;
+       struct array_elem *q;
+
+       val = bpf_map_lookup_elem(&hash_map, &key);
+       if (!val)
+               goto err;
+       /* spin_lock in hash map */
+       bpf_spin_lock(&val->lock);
+       for (i = 0; i < VAR_NUM; i++)
+               val->var[i] = rnd;
+       bpf_spin_unlock(&val->lock);
+
+       /* spin_lock in array */
+       q = bpf_map_lookup_elem(&array_map, &key);
+       if (!q)
+               goto err;
+       bpf_spin_lock(&q->lock);
+       for (i = 0; i < VAR_NUM; i++)
+               q->var[i] = rnd;
+       bpf_spin_unlock(&q->lock);
+       err = 0;
+err:
+       return err;
+}
+char _license[] SEC("license") = "GPL";
index d2e71d6..a08d026 100644 (file)
@@ -2025,6 +2025,79 @@ close_prog_noerr:
        bpf_object__close(obj);
 }
 
+static void *parallel_map_access(void *arg)
+{
+       int err, map_fd = *(u32 *) arg;
+       int vars[17], i, j, rnd, key = 0;
+
+       for (i = 0; i < 10000; i++) {
+               err = bpf_map_lookup_elem_flags(map_fd, &key, vars, BPF_F_LOCK);
+               if (err) {
+                       printf("lookup failed\n");
+                       error_cnt++;
+                       goto out;
+               }
+               if (vars[0] != 0) {
+                       printf("lookup #%d var[0]=%d\n", i, vars[0]);
+                       error_cnt++;
+                       goto out;
+               }
+               rnd = vars[1];
+               for (j = 2; j < 17; j++) {
+                       if (vars[j] == rnd)
+                               continue;
+                       printf("lookup #%d var[1]=%d var[%d]=%d\n",
+                              i, rnd, j, vars[j]);
+                       error_cnt++;
+                       goto out;
+               }
+       }
+out:
+       pthread_exit(arg);
+}
+
+static void test_map_lock(void)
+{
+       const char *file = "./test_map_lock.o";
+       int prog_fd, map_fd[2], vars[17] = {};
+       pthread_t thread_id[6];
+       struct bpf_object *obj;
+       int err = 0, key = 0, i;
+       void *ret;
+
+       err = bpf_prog_load(file, BPF_PROG_TYPE_CGROUP_SKB, &obj, &prog_fd);
+       if (err) {
+               printf("test_map_lock:bpf_prog_load errno %d\n", errno);
+               goto close_prog;
+       }
+       map_fd[0] = bpf_find_map(__func__, obj, "hash_map");
+       if (map_fd[0] < 0)
+               goto close_prog;
+       map_fd[1] = bpf_find_map(__func__, obj, "array_map");
+       if (map_fd[1] < 0)
+               goto close_prog;
+
+       bpf_map_update_elem(map_fd[0], &key, vars, BPF_F_LOCK);
+
+       for (i = 0; i < 4; i++)
+               assert(pthread_create(&thread_id[i], NULL,
+                                     &test_spin_lock, &prog_fd) == 0);
+       for (i = 4; i < 6; i++)
+               assert(pthread_create(&thread_id[i], NULL,
+                                     &parallel_map_access, &map_fd[i - 4]) == 0);
+       for (i = 0; i < 4; i++)
+               assert(pthread_join(thread_id[i], &ret) == 0 &&
+                      ret == (void *)&prog_fd);
+       for (i = 4; i < 6; i++)
+               assert(pthread_join(thread_id[i], &ret) == 0 &&
+                      ret == (void *)&map_fd[i - 4]);
+       goto close_prog_noerr;
+close_prog:
+       error_cnt++;
+close_prog_noerr:
+       bpf_object__close(obj);
+}
+
 int main(void)
 {
        srand(time(NULL));
@@ -2054,6 +2127,7 @@ int main(void)
        test_queue_stack_map(STACK);
        test_flow_dissector();
        test_spinlock();
+       test_map_lock();
 
        printf("Summary: %d PASSED, %d FAILED\n", pass_cnt, error_cnt);
        return error_cnt ? EXIT_FAILURE : EXIT_SUCCESS;