bpf: test BPF_MAP_TYPE_REUSEPORT_SOCKARRAY
authorMartin KaFai Lau <kafai@fb.com>
Wed, 8 Aug 2018 08:01:30 +0000 (01:01 -0700)
committerDaniel Borkmann <daniel@iogearbox.net>
Fri, 10 Aug 2018 23:58:46 +0000 (01:58 +0200)
This patch adds tests for the new BPF_MAP_TYPE_REUSEPORT_SOCKARRAY.

Signed-off-by: Martin KaFai Lau <kafai@fb.com>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
tools/lib/bpf/libbpf.c
tools/testing/selftests/bpf/test_maps.c

index 40211b51427a1a734f4559465343a8debd6d353e..2abd0f112627607a3f6d295e688a2f31b20417e2 100644 (file)
@@ -1501,6 +1501,7 @@ static bool bpf_prog_type__needs_kver(enum bpf_prog_type type)
        case BPF_PROG_TYPE_SK_MSG:
        case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
        case BPF_PROG_TYPE_LIRC_MODE2:
+       case BPF_PROG_TYPE_SK_REUSEPORT:
                return false;
        case BPF_PROG_TYPE_UNSPEC:
        case BPF_PROG_TYPE_KPROBE:
index 6c253343a6f96e3d5eb9fbd67d0ad8a4eb7f2bf6..4b7c74f5faa7cb82c6604689f6469f2b71f53f75 100644 (file)
@@ -17,7 +17,8 @@
 #include <stdlib.h>
 
 #include <sys/wait.h>
-
+#include <sys/socket.h>
+#include <netinet/in.h>
 #include <linux/bpf.h>
 
 #include <bpf/bpf.h>
 #include "bpf_util.h"
 #include "bpf_rlimit.h"
 
+#ifndef ENOTSUPP
+#define ENOTSUPP 524
+#endif
+
 static int map_flags;
 
+#define CHECK(condition, tag, format...) ({                            \
+       int __ret = !!(condition);                                      \
+       if (__ret) {                                                    \
+               printf("%s(%d):FAIL:%s ", __func__, __LINE__, tag);     \
+               printf(format);                                         \
+               exit(-1);                                               \
+       }                                                               \
+})
+
 static void test_hashmap(int task, void *data)
 {
        long long key, next_key, first_key, value;
@@ -1150,6 +1164,250 @@ static void test_map_wronly(void)
        assert(bpf_map_get_next_key(fd, &key, &value) == -1 && errno == EPERM);
 }
 
+static void prepare_reuseport_grp(int type, int map_fd,
+                                 __s64 *fds64, __u64 *sk_cookies,
+                                 unsigned int n)
+{
+       socklen_t optlen, addrlen;
+       struct sockaddr_in6 s6;
+       const __u32 index0 = 0;
+       const int optval = 1;
+       unsigned int i;
+       u64 sk_cookie;
+       __s64 fd64;
+       int err;
+
+       s6.sin6_family = AF_INET6;
+       s6.sin6_addr = in6addr_any;
+       s6.sin6_port = 0;
+       addrlen = sizeof(s6);
+       optlen = sizeof(sk_cookie);
+
+       for (i = 0; i < n; i++) {
+               fd64 = socket(AF_INET6, type, 0);
+               CHECK(fd64 == -1, "socket()",
+                     "sock_type:%d fd64:%lld errno:%d\n",
+                     type, fd64, errno);
+
+               err = setsockopt(fd64, SOL_SOCKET, SO_REUSEPORT,
+                                &optval, sizeof(optval));
+               CHECK(err == -1, "setsockopt(SO_REUSEEPORT)",
+                     "err:%d errno:%d\n", err, errno);
+
+               /* reuseport_array does not allow unbound sk */
+               err = bpf_map_update_elem(map_fd, &index0, &fd64,
+                                         BPF_ANY);
+               CHECK(err != -1 || errno != EINVAL,
+                     "reuseport array update unbound sk",
+                     "sock_type:%d err:%d errno:%d\n",
+                     type, err, errno);
+
+               err = bind(fd64, (struct sockaddr *)&s6, sizeof(s6));
+               CHECK(err == -1, "bind()",
+                     "sock_type:%d err:%d errno:%d\n", type, err, errno);
+
+               if (i == 0) {
+                       err = getsockname(fd64, (struct sockaddr *)&s6,
+                                         &addrlen);
+                       CHECK(err == -1, "getsockname()",
+                             "sock_type:%d err:%d errno:%d\n",
+                             type, err, errno);
+               }
+
+               err = getsockopt(fd64, SOL_SOCKET, SO_COOKIE, &sk_cookie,
+                                &optlen);
+               CHECK(err == -1, "getsockopt(SO_COOKIE)",
+                     "sock_type:%d err:%d errno:%d\n", type, err, errno);
+
+               if (type == SOCK_STREAM) {
+                       /*
+                        * reuseport_array does not allow
+                        * non-listening tcp sk.
+                        */
+                       err = bpf_map_update_elem(map_fd, &index0, &fd64,
+                                                 BPF_ANY);
+                       CHECK(err != -1 || errno != EINVAL,
+                             "reuseport array update non-listening sk",
+                             "sock_type:%d err:%d errno:%d\n",
+                             type, err, errno);
+                       err = listen(fd64, 0);
+                       CHECK(err == -1, "listen()",
+                             "sock_type:%d, err:%d errno:%d\n",
+                             type, err, errno);
+               }
+
+               fds64[i] = fd64;
+               sk_cookies[i] = sk_cookie;
+       }
+}
+
+static void test_reuseport_array(void)
+{
+#define REUSEPORT_FD_IDX(err, last) ({ (err) ? last : !last; })
+
+       const __u32 array_size = 4, index0 = 0, index3 = 3;
+       int types[2] = { SOCK_STREAM, SOCK_DGRAM }, type;
+       __u64 grpa_cookies[2], sk_cookie, map_cookie;
+       __s64 grpa_fds64[2] = { -1, -1 }, fd64 = -1;
+       const __u32 bad_index = array_size;
+       int map_fd, err, t, f;
+       __u32 fds_idx = 0;
+       int fd;
+
+       map_fd = bpf_create_map(BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
+                               sizeof(__u32), sizeof(__u64), array_size, 0);
+       CHECK(map_fd == -1, "reuseport array create",
+             "map_fd:%d, errno:%d\n", map_fd, errno);
+
+       /* Test lookup/update/delete with invalid index */
+       err = bpf_map_delete_elem(map_fd, &bad_index);
+       CHECK(err != -1 || errno != E2BIG, "reuseport array del >=max_entries",
+             "err:%d errno:%d\n", err, errno);
+
+       err = bpf_map_update_elem(map_fd, &bad_index, &fd64, BPF_ANY);
+       CHECK(err != -1 || errno != E2BIG,
+             "reuseport array update >=max_entries",
+             "err:%d errno:%d\n", err, errno);
+
+       err = bpf_map_lookup_elem(map_fd, &bad_index, &map_cookie);
+       CHECK(err != -1 || errno != ENOENT,
+             "reuseport array update >=max_entries",
+             "err:%d errno:%d\n", err, errno);
+
+       /* Test lookup/delete non existence elem */
+       err = bpf_map_lookup_elem(map_fd, &index3, &map_cookie);
+       CHECK(err != -1 || errno != ENOENT,
+             "reuseport array lookup not-exist elem",
+             "err:%d errno:%d\n", err, errno);
+       err = bpf_map_delete_elem(map_fd, &index3);
+       CHECK(err != -1 || errno != ENOENT,
+             "reuseport array del not-exist elem",
+             "err:%d errno:%d\n", err, errno);
+
+       for (t = 0; t < ARRAY_SIZE(types); t++) {
+               type = types[t];
+
+               prepare_reuseport_grp(type, map_fd, grpa_fds64,
+                                     grpa_cookies, ARRAY_SIZE(grpa_fds64));
+
+               /* Test BPF_* update flags */
+               /* BPF_EXIST failure case */
+               err = bpf_map_update_elem(map_fd, &index3, &grpa_fds64[fds_idx],
+                                         BPF_EXIST);
+               CHECK(err != -1 || errno != ENOENT,
+                     "reuseport array update empty elem BPF_EXIST",
+                     "sock_type:%d err:%d errno:%d\n",
+                     type, err, errno);
+               fds_idx = REUSEPORT_FD_IDX(err, fds_idx);
+
+               /* BPF_NOEXIST success case */
+               err = bpf_map_update_elem(map_fd, &index3, &grpa_fds64[fds_idx],
+                                         BPF_NOEXIST);
+               CHECK(err == -1,
+                     "reuseport array update empty elem BPF_NOEXIST",
+                     "sock_type:%d err:%d errno:%d\n",
+                     type, err, errno);
+               fds_idx = REUSEPORT_FD_IDX(err, fds_idx);
+
+               /* BPF_EXIST success case. */
+               err = bpf_map_update_elem(map_fd, &index3, &grpa_fds64[fds_idx],
+                                         BPF_EXIST);
+               CHECK(err == -1,
+                     "reuseport array update same elem BPF_EXIST",
+                     "sock_type:%d err:%d errno:%d\n", type, err, errno);
+               fds_idx = REUSEPORT_FD_IDX(err, fds_idx);
+
+               /* BPF_NOEXIST failure case */
+               err = bpf_map_update_elem(map_fd, &index3, &grpa_fds64[fds_idx],
+                                         BPF_NOEXIST);
+               CHECK(err != -1 || errno != EEXIST,
+                     "reuseport array update non-empty elem BPF_NOEXIST",
+                     "sock_type:%d err:%d errno:%d\n",
+                     type, err, errno);
+               fds_idx = REUSEPORT_FD_IDX(err, fds_idx);
+
+               /* BPF_ANY case (always succeed) */
+               err = bpf_map_update_elem(map_fd, &index3, &grpa_fds64[fds_idx],
+                                         BPF_ANY);
+               CHECK(err == -1,
+                     "reuseport array update same sk with BPF_ANY",
+                     "sock_type:%d err:%d errno:%d\n", type, err, errno);
+
+               fd64 = grpa_fds64[fds_idx];
+               sk_cookie = grpa_cookies[fds_idx];
+
+               /* The same sk cannot be added to reuseport_array twice */
+               err = bpf_map_update_elem(map_fd, &index3, &fd64, BPF_ANY);
+               CHECK(err != -1 || errno != EBUSY,
+                     "reuseport array update same sk with same index",
+                     "sock_type:%d err:%d errno:%d\n",
+                     type, err, errno);
+
+               err = bpf_map_update_elem(map_fd, &index0, &fd64, BPF_ANY);
+               CHECK(err != -1 || errno != EBUSY,
+                     "reuseport array update same sk with different index",
+                     "sock_type:%d err:%d errno:%d\n",
+                     type, err, errno);
+
+               /* Test delete elem */
+               err = bpf_map_delete_elem(map_fd, &index3);
+               CHECK(err == -1, "reuseport array delete sk",
+                     "sock_type:%d err:%d errno:%d\n",
+                     type, err, errno);
+
+               /* Add it back with BPF_NOEXIST */
+               err = bpf_map_update_elem(map_fd, &index3, &fd64, BPF_NOEXIST);
+               CHECK(err == -1,
+                     "reuseport array re-add with BPF_NOEXIST after del",
+                     "sock_type:%d err:%d errno:%d\n", type, err, errno);
+
+               /* Test cookie */
+               err = bpf_map_lookup_elem(map_fd, &index3, &map_cookie);
+               CHECK(err == -1 || sk_cookie != map_cookie,
+                     "reuseport array lookup re-added sk",
+                     "sock_type:%d err:%d errno:%d sk_cookie:0x%llx map_cookie:0x%llxn",
+                     type, err, errno, sk_cookie, map_cookie);
+
+               /* Test elem removed by close() */
+               for (f = 0; f < ARRAY_SIZE(grpa_fds64); f++)
+                       close(grpa_fds64[f]);
+               err = bpf_map_lookup_elem(map_fd, &index3, &map_cookie);
+               CHECK(err != -1 || errno != ENOENT,
+                     "reuseport array lookup after close()",
+                     "sock_type:%d err:%d errno:%d\n",
+                     type, err, errno);
+       }
+
+       /* Test SOCK_RAW */
+       fd64 = socket(AF_INET6, SOCK_RAW, IPPROTO_UDP);
+       CHECK(fd64 == -1, "socket(SOCK_RAW)", "err:%d errno:%d\n",
+             err, errno);
+       err = bpf_map_update_elem(map_fd, &index3, &fd64, BPF_NOEXIST);
+       CHECK(err != -1 || errno != ENOTSUPP, "reuseport array update SOCK_RAW",
+             "err:%d errno:%d\n", err, errno);
+       close(fd64);
+
+       /* Close the 64 bit value map */
+       close(map_fd);
+
+       /* Test 32 bit fd */
+       map_fd = bpf_create_map(BPF_MAP_TYPE_REUSEPORT_SOCKARRAY,
+                               sizeof(__u32), sizeof(__u32), array_size, 0);
+       CHECK(map_fd == -1, "reuseport array create",
+             "map_fd:%d, errno:%d\n", map_fd, errno);
+       prepare_reuseport_grp(SOCK_STREAM, map_fd, &fd64, &sk_cookie, 1);
+       fd = fd64;
+       err = bpf_map_update_elem(map_fd, &index3, &fd, BPF_NOEXIST);
+       CHECK(err == -1, "reuseport array update 32 bit fd",
+             "err:%d errno:%d\n", err, errno);
+       err = bpf_map_lookup_elem(map_fd, &index3, &map_cookie);
+       CHECK(err != -1 || errno != ENOSPC,
+             "reuseport array lookup 32 bit fd",
+             "err:%d errno:%d\n", err, errno);
+       close(fd);
+       close(map_fd);
+}
+
 static void run_all_tests(void)
 {
        test_hashmap(0, NULL);
@@ -1170,6 +1428,8 @@ static void run_all_tests(void)
 
        test_map_rdonly();
        test_map_wronly();
+
+       test_reuseport_array();
 }
 
 int main(void)