| // SPDX-License-Identifier: GPL-2.0 |
| // Copyright (c) 2020 Cloudflare |
| /* |
| * Test suite for SOCKMAP/SOCKHASH holding listening sockets. |
| * Covers: |
| * 1. BPF map operations - bpf_map_{update,lookup delete}_elem |
| * 2. BPF redirect helpers - bpf_{sk,msg}_redirect_map |
| * 3. BPF reuseport helper - bpf_sk_select_reuseport |
| */ |
| |
| #include <linux/compiler.h> |
| #include <errno.h> |
| #include <error.h> |
| #include <limits.h> |
| #include <netinet/in.h> |
| #include <pthread.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/select.h> |
| #include <unistd.h> |
| |
| #include <bpf/bpf.h> |
| #include <bpf/libbpf.h> |
| |
| #include "bpf_util.h" |
| #include "test_progs.h" |
| #include "test_sockmap_listen.skel.h" |
| |
| #define IO_TIMEOUT_SEC 30 |
| #define MAX_STRERR_LEN 256 |
| #define MAX_TEST_NAME 80 |
| |
| #define _FAIL(errnum, fmt...) \ |
| ({ \ |
| error_at_line(0, (errnum), __func__, __LINE__, fmt); \ |
| CHECK_FAIL(true); \ |
| }) |
| #define FAIL(fmt...) _FAIL(0, fmt) |
| #define FAIL_ERRNO(fmt...) _FAIL(errno, fmt) |
| #define FAIL_LIBBPF(err, msg) \ |
| ({ \ |
| char __buf[MAX_STRERR_LEN]; \ |
| libbpf_strerror((err), __buf, sizeof(__buf)); \ |
| FAIL("%s: %s", (msg), __buf); \ |
| }) |
| |
| /* Wrappers that fail the test on error and report it. */ |
| |
| #define xaccept_nonblock(fd, addr, len) \ |
| ({ \ |
| int __ret = \ |
| accept_timeout((fd), (addr), (len), IO_TIMEOUT_SEC); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("accept"); \ |
| __ret; \ |
| }) |
| |
| #define xbind(fd, addr, len) \ |
| ({ \ |
| int __ret = bind((fd), (addr), (len)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("bind"); \ |
| __ret; \ |
| }) |
| |
| #define xclose(fd) \ |
| ({ \ |
| int __ret = close((fd)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("close"); \ |
| __ret; \ |
| }) |
| |
| #define xconnect(fd, addr, len) \ |
| ({ \ |
| int __ret = connect((fd), (addr), (len)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("connect"); \ |
| __ret; \ |
| }) |
| |
| #define xgetsockname(fd, addr, len) \ |
| ({ \ |
| int __ret = getsockname((fd), (addr), (len)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("getsockname"); \ |
| __ret; \ |
| }) |
| |
| #define xgetsockopt(fd, level, name, val, len) \ |
| ({ \ |
| int __ret = getsockopt((fd), (level), (name), (val), (len)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("getsockopt(" #name ")"); \ |
| __ret; \ |
| }) |
| |
| #define xlisten(fd, backlog) \ |
| ({ \ |
| int __ret = listen((fd), (backlog)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("listen"); \ |
| __ret; \ |
| }) |
| |
| #define xsetsockopt(fd, level, name, val, len) \ |
| ({ \ |
| int __ret = setsockopt((fd), (level), (name), (val), (len)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("setsockopt(" #name ")"); \ |
| __ret; \ |
| }) |
| |
| #define xsend(fd, buf, len, flags) \ |
| ({ \ |
| ssize_t __ret = send((fd), (buf), (len), (flags)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("send"); \ |
| __ret; \ |
| }) |
| |
| #define xrecv_nonblock(fd, buf, len, flags) \ |
| ({ \ |
| ssize_t __ret = recv_timeout((fd), (buf), (len), (flags), \ |
| IO_TIMEOUT_SEC); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("recv"); \ |
| __ret; \ |
| }) |
| |
| #define xsocket(family, sotype, flags) \ |
| ({ \ |
| int __ret = socket(family, sotype, flags); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("socket"); \ |
| __ret; \ |
| }) |
| |
| #define xbpf_map_delete_elem(fd, key) \ |
| ({ \ |
| int __ret = bpf_map_delete_elem((fd), (key)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("map_delete"); \ |
| __ret; \ |
| }) |
| |
| #define xbpf_map_lookup_elem(fd, key, val) \ |
| ({ \ |
| int __ret = bpf_map_lookup_elem((fd), (key), (val)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("map_lookup"); \ |
| __ret; \ |
| }) |
| |
| #define xbpf_map_update_elem(fd, key, val, flags) \ |
| ({ \ |
| int __ret = bpf_map_update_elem((fd), (key), (val), (flags)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("map_update"); \ |
| __ret; \ |
| }) |
| |
| #define xbpf_prog_attach(prog, target, type, flags) \ |
| ({ \ |
| int __ret = \ |
| bpf_prog_attach((prog), (target), (type), (flags)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("prog_attach(" #type ")"); \ |
| __ret; \ |
| }) |
| |
| #define xbpf_prog_detach2(prog, target, type) \ |
| ({ \ |
| int __ret = bpf_prog_detach2((prog), (target), (type)); \ |
| if (__ret == -1) \ |
| FAIL_ERRNO("prog_detach2(" #type ")"); \ |
| __ret; \ |
| }) |
| |
| #define xpthread_create(thread, attr, func, arg) \ |
| ({ \ |
| int __ret = pthread_create((thread), (attr), (func), (arg)); \ |
| errno = __ret; \ |
| if (__ret) \ |
| FAIL_ERRNO("pthread_create"); \ |
| __ret; \ |
| }) |
| |
| #define xpthread_join(thread, retval) \ |
| ({ \ |
| int __ret = pthread_join((thread), (retval)); \ |
| errno = __ret; \ |
| if (__ret) \ |
| FAIL_ERRNO("pthread_join"); \ |
| __ret; \ |
| }) |
| |
| static int poll_read(int fd, unsigned int timeout_sec) |
| { |
| struct timeval timeout = { .tv_sec = timeout_sec }; |
| fd_set rfds; |
| int r; |
| |
| FD_ZERO(&rfds); |
| FD_SET(fd, &rfds); |
| |
| r = select(fd + 1, &rfds, NULL, NULL, &timeout); |
| if (r == 0) |
| errno = ETIME; |
| |
| return r == 1 ? 0 : -1; |
| } |
| |
| static int accept_timeout(int fd, struct sockaddr *addr, socklen_t *len, |
| unsigned int timeout_sec) |
| { |
| if (poll_read(fd, timeout_sec)) |
| return -1; |
| |
| return accept(fd, addr, len); |
| } |
| |
| static int recv_timeout(int fd, void *buf, size_t len, int flags, |
| unsigned int timeout_sec) |
| { |
| if (poll_read(fd, timeout_sec)) |
| return -1; |
| |
| return recv(fd, buf, len, flags); |
| } |
| |
| static void init_addr_loopback4(struct sockaddr_storage *ss, socklen_t *len) |
| { |
| struct sockaddr_in *addr4 = memset(ss, 0, sizeof(*ss)); |
| |
| addr4->sin_family = AF_INET; |
| addr4->sin_port = 0; |
| addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| *len = sizeof(*addr4); |
| } |
| |
| static void init_addr_loopback6(struct sockaddr_storage *ss, socklen_t *len) |
| { |
| struct sockaddr_in6 *addr6 = memset(ss, 0, sizeof(*ss)); |
| |
| addr6->sin6_family = AF_INET6; |
| addr6->sin6_port = 0; |
| addr6->sin6_addr = in6addr_loopback; |
| *len = sizeof(*addr6); |
| } |
| |
| static void init_addr_loopback(int family, struct sockaddr_storage *ss, |
| socklen_t *len) |
| { |
| switch (family) { |
| case AF_INET: |
| init_addr_loopback4(ss, len); |
| return; |
| case AF_INET6: |
| init_addr_loopback6(ss, len); |
| return; |
| default: |
| FAIL("unsupported address family %d", family); |
| } |
| } |
| |
| static inline struct sockaddr *sockaddr(struct sockaddr_storage *ss) |
| { |
| return (struct sockaddr *)ss; |
| } |
| |
| static int enable_reuseport(int s, int progfd) |
| { |
| int err, one = 1; |
| |
| err = xsetsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); |
| if (err) |
| return -1; |
| err = xsetsockopt(s, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, &progfd, |
| sizeof(progfd)); |
| if (err) |
| return -1; |
| |
| return 0; |
| } |
| |
| static int socket_loopback_reuseport(int family, int sotype, int progfd) |
| { |
| struct sockaddr_storage addr; |
| socklen_t len; |
| int err, s; |
| |
| init_addr_loopback(family, &addr, &len); |
| |
| s = xsocket(family, sotype, 0); |
| if (s == -1) |
| return -1; |
| |
| if (progfd >= 0) |
| enable_reuseport(s, progfd); |
| |
| err = xbind(s, sockaddr(&addr), len); |
| if (err) |
| goto close; |
| |
| if (sotype & SOCK_DGRAM) |
| return s; |
| |
| err = xlisten(s, SOMAXCONN); |
| if (err) |
| goto close; |
| |
| return s; |
| close: |
| xclose(s); |
| return -1; |
| } |
| |
| static int socket_loopback(int family, int sotype) |
| { |
| return socket_loopback_reuseport(family, sotype, -1); |
| } |
| |
| static void test_insert_invalid(int family, int sotype, int mapfd) |
| { |
| u32 key = 0; |
| u64 value; |
| int err; |
| |
| value = -1; |
| err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| if (!err || errno != EINVAL) |
| FAIL_ERRNO("map_update: expected EINVAL"); |
| |
| value = INT_MAX; |
| err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| if (!err || errno != EBADF) |
| FAIL_ERRNO("map_update: expected EBADF"); |
| } |
| |
| static void test_insert_opened(int family, int sotype, int mapfd) |
| { |
| u32 key = 0; |
| u64 value; |
| int err, s; |
| |
| s = xsocket(family, sotype, 0); |
| if (s == -1) |
| return; |
| |
| errno = 0; |
| value = s; |
| err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| if (!err || errno != EOPNOTSUPP) |
| FAIL_ERRNO("map_update: expected EOPNOTSUPP"); |
| |
| xclose(s); |
| } |
| |
| static void test_insert_bound(int family, int sotype, int mapfd) |
| { |
| struct sockaddr_storage addr; |
| socklen_t len; |
| u32 key = 0; |
| u64 value; |
| int err, s; |
| |
| init_addr_loopback(family, &addr, &len); |
| |
| s = xsocket(family, sotype, 0); |
| if (s == -1) |
| return; |
| |
| err = xbind(s, sockaddr(&addr), len); |
| if (err) |
| goto close; |
| |
| errno = 0; |
| value = s; |
| err = bpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| if (!err || errno != EOPNOTSUPP) |
| FAIL_ERRNO("map_update: expected EOPNOTSUPP"); |
| close: |
| xclose(s); |
| } |
| |
| static void test_insert(int family, int sotype, int mapfd) |
| { |
| u64 value; |
| u32 key; |
| int s; |
| |
| s = socket_loopback(family, sotype); |
| if (s < 0) |
| return; |
| |
| key = 0; |
| value = s; |
| xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| xclose(s); |
| } |
| |
| static void test_delete_after_insert(int family, int sotype, int mapfd) |
| { |
| u64 value; |
| u32 key; |
| int s; |
| |
| s = socket_loopback(family, sotype); |
| if (s < 0) |
| return; |
| |
| key = 0; |
| value = s; |
| xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| xbpf_map_delete_elem(mapfd, &key); |
| xclose(s); |
| } |
| |
| static void test_delete_after_close(int family, int sotype, int mapfd) |
| { |
| int err, s; |
| u64 value; |
| u32 key; |
| |
| s = socket_loopback(family, sotype); |
| if (s < 0) |
| return; |
| |
| key = 0; |
| value = s; |
| xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| |
| xclose(s); |
| |
| errno = 0; |
| err = bpf_map_delete_elem(mapfd, &key); |
| if (!err || (errno != EINVAL && errno != ENOENT)) |
| /* SOCKMAP and SOCKHASH return different error codes */ |
| FAIL_ERRNO("map_delete: expected EINVAL/EINVAL"); |
| } |
| |
| static void test_lookup_after_insert(int family, int sotype, int mapfd) |
| { |
| u64 cookie, value; |
| socklen_t len; |
| u32 key; |
| int s; |
| |
| s = socket_loopback(family, sotype); |
| if (s < 0) |
| return; |
| |
| key = 0; |
| value = s; |
| xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| |
| len = sizeof(cookie); |
| xgetsockopt(s, SOL_SOCKET, SO_COOKIE, &cookie, &len); |
| |
| xbpf_map_lookup_elem(mapfd, &key, &value); |
| |
| if (value != cookie) { |
| FAIL("map_lookup: have %#llx, want %#llx", |
| (unsigned long long)value, (unsigned long long)cookie); |
| } |
| |
| xclose(s); |
| } |
| |
| static void test_lookup_after_delete(int family, int sotype, int mapfd) |
| { |
| int err, s; |
| u64 value; |
| u32 key; |
| |
| s = socket_loopback(family, sotype); |
| if (s < 0) |
| return; |
| |
| key = 0; |
| value = s; |
| xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| xbpf_map_delete_elem(mapfd, &key); |
| |
| errno = 0; |
| err = bpf_map_lookup_elem(mapfd, &key, &value); |
| if (!err || errno != ENOENT) |
| FAIL_ERRNO("map_lookup: expected ENOENT"); |
| |
| xclose(s); |
| } |
| |
| static void test_lookup_32_bit_value(int family, int sotype, int mapfd) |
| { |
| u32 key, value32; |
| int err, s; |
| |
| s = socket_loopback(family, sotype); |
| if (s < 0) |
| return; |
| |
| mapfd = bpf_create_map(BPF_MAP_TYPE_SOCKMAP, sizeof(key), |
| sizeof(value32), 1, 0); |
| if (mapfd < 0) { |
| FAIL_ERRNO("map_create"); |
| goto close; |
| } |
| |
| key = 0; |
| value32 = s; |
| xbpf_map_update_elem(mapfd, &key, &value32, BPF_NOEXIST); |
| |
| errno = 0; |
| err = bpf_map_lookup_elem(mapfd, &key, &value32); |
| if (!err || errno != ENOSPC) |
| FAIL_ERRNO("map_lookup: expected ENOSPC"); |
| |
| xclose(mapfd); |
| close: |
| xclose(s); |
| } |
| |
| static void test_update_existing(int family, int sotype, int mapfd) |
| { |
| int s1, s2; |
| u64 value; |
| u32 key; |
| |
| s1 = socket_loopback(family, sotype); |
| if (s1 < 0) |
| return; |
| |
| s2 = socket_loopback(family, sotype); |
| if (s2 < 0) |
| goto close_s1; |
| |
| key = 0; |
| value = s1; |
| xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| |
| value = s2; |
| xbpf_map_update_elem(mapfd, &key, &value, BPF_EXIST); |
| xclose(s2); |
| close_s1: |
| xclose(s1); |
| } |
| |
| /* Exercise the code path where we destroy child sockets that never |
| * got accept()'ed, aka orphans, when parent socket gets closed. |
| */ |
| static void test_destroy_orphan_child(int family, int sotype, int mapfd) |
| { |
| struct sockaddr_storage addr; |
| socklen_t len; |
| int err, s, c; |
| u64 value; |
| u32 key; |
| |
| s = socket_loopback(family, sotype); |
| if (s < 0) |
| return; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv; |
| |
| key = 0; |
| value = s; |
| xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| |
| c = xsocket(family, sotype, 0); |
| if (c == -1) |
| goto close_srv; |
| |
| xconnect(c, sockaddr(&addr), len); |
| xclose(c); |
| close_srv: |
| xclose(s); |
| } |
| |
| /* Perform a passive open after removing listening socket from SOCKMAP |
| * to ensure that callbacks get restored properly. |
| */ |
| static void test_clone_after_delete(int family, int sotype, int mapfd) |
| { |
| struct sockaddr_storage addr; |
| socklen_t len; |
| int err, s, c; |
| u64 value; |
| u32 key; |
| |
| s = socket_loopback(family, sotype); |
| if (s < 0) |
| return; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv; |
| |
| key = 0; |
| value = s; |
| xbpf_map_update_elem(mapfd, &key, &value, BPF_NOEXIST); |
| xbpf_map_delete_elem(mapfd, &key); |
| |
| c = xsocket(family, sotype, 0); |
| if (c < 0) |
| goto close_srv; |
| |
| xconnect(c, sockaddr(&addr), len); |
| xclose(c); |
| close_srv: |
| xclose(s); |
| } |
| |
| /* Check that child socket that got created while parent was in a |
| * SOCKMAP, but got accept()'ed only after the parent has been removed |
| * from SOCKMAP, gets cloned without parent psock state or callbacks. |
| */ |
| static void test_accept_after_delete(int family, int sotype, int mapfd) |
| { |
| struct sockaddr_storage addr; |
| const u32 zero = 0; |
| int err, s, c, p; |
| socklen_t len; |
| u64 value; |
| |
| s = socket_loopback(family, sotype | SOCK_NONBLOCK); |
| if (s == -1) |
| return; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv; |
| |
| value = s; |
| err = xbpf_map_update_elem(mapfd, &zero, &value, BPF_NOEXIST); |
| if (err) |
| goto close_srv; |
| |
| c = xsocket(family, sotype, 0); |
| if (c == -1) |
| goto close_srv; |
| |
| /* Create child while parent is in sockmap */ |
| err = xconnect(c, sockaddr(&addr), len); |
| if (err) |
| goto close_cli; |
| |
| /* Remove parent from sockmap */ |
| err = xbpf_map_delete_elem(mapfd, &zero); |
| if (err) |
| goto close_cli; |
| |
| p = xaccept_nonblock(s, NULL, NULL); |
| if (p == -1) |
| goto close_cli; |
| |
| /* Check that child sk_user_data is not set */ |
| value = p; |
| xbpf_map_update_elem(mapfd, &zero, &value, BPF_NOEXIST); |
| |
| xclose(p); |
| close_cli: |
| xclose(c); |
| close_srv: |
| xclose(s); |
| } |
| |
| /* Check that child socket that got created and accepted while parent |
| * was in a SOCKMAP is cloned without parent psock state or callbacks. |
| */ |
| static void test_accept_before_delete(int family, int sotype, int mapfd) |
| { |
| struct sockaddr_storage addr; |
| const u32 zero = 0, one = 1; |
| int err, s, c, p; |
| socklen_t len; |
| u64 value; |
| |
| s = socket_loopback(family, sotype | SOCK_NONBLOCK); |
| if (s == -1) |
| return; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv; |
| |
| value = s; |
| err = xbpf_map_update_elem(mapfd, &zero, &value, BPF_NOEXIST); |
| if (err) |
| goto close_srv; |
| |
| c = xsocket(family, sotype, 0); |
| if (c == -1) |
| goto close_srv; |
| |
| /* Create & accept child while parent is in sockmap */ |
| err = xconnect(c, sockaddr(&addr), len); |
| if (err) |
| goto close_cli; |
| |
| p = xaccept_nonblock(s, NULL, NULL); |
| if (p == -1) |
| goto close_cli; |
| |
| /* Check that child sk_user_data is not set */ |
| value = p; |
| xbpf_map_update_elem(mapfd, &one, &value, BPF_NOEXIST); |
| |
| xclose(p); |
| close_cli: |
| xclose(c); |
| close_srv: |
| xclose(s); |
| } |
| |
| struct connect_accept_ctx { |
| int sockfd; |
| unsigned int done; |
| unsigned int nr_iter; |
| }; |
| |
| static bool is_thread_done(struct connect_accept_ctx *ctx) |
| { |
| return READ_ONCE(ctx->done); |
| } |
| |
| static void *connect_accept_thread(void *arg) |
| { |
| struct connect_accept_ctx *ctx = arg; |
| struct sockaddr_storage addr; |
| int family, socktype; |
| socklen_t len; |
| int err, i, s; |
| |
| s = ctx->sockfd; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto done; |
| |
| len = sizeof(family); |
| err = xgetsockopt(s, SOL_SOCKET, SO_DOMAIN, &family, &len); |
| if (err) |
| goto done; |
| |
| len = sizeof(socktype); |
| err = xgetsockopt(s, SOL_SOCKET, SO_TYPE, &socktype, &len); |
| if (err) |
| goto done; |
| |
| for (i = 0; i < ctx->nr_iter; i++) { |
| int c, p; |
| |
| c = xsocket(family, socktype, 0); |
| if (c < 0) |
| break; |
| |
| err = xconnect(c, (struct sockaddr *)&addr, sizeof(addr)); |
| if (err) { |
| xclose(c); |
| break; |
| } |
| |
| p = xaccept_nonblock(s, NULL, NULL); |
| if (p < 0) { |
| xclose(c); |
| break; |
| } |
| |
| xclose(p); |
| xclose(c); |
| } |
| done: |
| WRITE_ONCE(ctx->done, 1); |
| return NULL; |
| } |
| |
| static void test_syn_recv_insert_delete(int family, int sotype, int mapfd) |
| { |
| struct connect_accept_ctx ctx = { 0 }; |
| struct sockaddr_storage addr; |
| socklen_t len; |
| u32 zero = 0; |
| pthread_t t; |
| int err, s; |
| u64 value; |
| |
| s = socket_loopback(family, sotype | SOCK_NONBLOCK); |
| if (s < 0) |
| return; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close; |
| |
| ctx.sockfd = s; |
| ctx.nr_iter = 1000; |
| |
| err = xpthread_create(&t, NULL, connect_accept_thread, &ctx); |
| if (err) |
| goto close; |
| |
| value = s; |
| while (!is_thread_done(&ctx)) { |
| err = xbpf_map_update_elem(mapfd, &zero, &value, BPF_NOEXIST); |
| if (err) |
| break; |
| |
| err = xbpf_map_delete_elem(mapfd, &zero); |
| if (err) |
| break; |
| } |
| |
| xpthread_join(t, NULL); |
| close: |
| xclose(s); |
| } |
| |
| static void *listen_thread(void *arg) |
| { |
| struct sockaddr unspec = { AF_UNSPEC }; |
| struct connect_accept_ctx *ctx = arg; |
| int err, i, s; |
| |
| s = ctx->sockfd; |
| |
| for (i = 0; i < ctx->nr_iter; i++) { |
| err = xlisten(s, 1); |
| if (err) |
| break; |
| err = xconnect(s, &unspec, sizeof(unspec)); |
| if (err) |
| break; |
| } |
| |
| WRITE_ONCE(ctx->done, 1); |
| return NULL; |
| } |
| |
| static void test_race_insert_listen(int family, int socktype, int mapfd) |
| { |
| struct connect_accept_ctx ctx = { 0 }; |
| const u32 zero = 0; |
| const int one = 1; |
| pthread_t t; |
| int err, s; |
| u64 value; |
| |
| s = xsocket(family, socktype, 0); |
| if (s < 0) |
| return; |
| |
| err = xsetsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); |
| if (err) |
| goto close; |
| |
| ctx.sockfd = s; |
| ctx.nr_iter = 10000; |
| |
| err = pthread_create(&t, NULL, listen_thread, &ctx); |
| if (err) |
| goto close; |
| |
| value = s; |
| while (!is_thread_done(&ctx)) { |
| err = bpf_map_update_elem(mapfd, &zero, &value, BPF_NOEXIST); |
| /* Expecting EOPNOTSUPP before listen() */ |
| if (err && errno != EOPNOTSUPP) { |
| FAIL_ERRNO("map_update"); |
| break; |
| } |
| |
| err = bpf_map_delete_elem(mapfd, &zero); |
| /* Expecting no entry after unhash on connect(AF_UNSPEC) */ |
| if (err && errno != EINVAL && errno != ENOENT) { |
| FAIL_ERRNO("map_delete"); |
| break; |
| } |
| } |
| |
| xpthread_join(t, NULL); |
| close: |
| xclose(s); |
| } |
| |
| static void zero_verdict_count(int mapfd) |
| { |
| unsigned int zero = 0; |
| int key; |
| |
| key = SK_DROP; |
| xbpf_map_update_elem(mapfd, &key, &zero, BPF_ANY); |
| key = SK_PASS; |
| xbpf_map_update_elem(mapfd, &key, &zero, BPF_ANY); |
| } |
| |
| enum redir_mode { |
| REDIR_INGRESS, |
| REDIR_EGRESS, |
| }; |
| |
| static const char *redir_mode_str(enum redir_mode mode) |
| { |
| switch (mode) { |
| case REDIR_INGRESS: |
| return "ingress"; |
| case REDIR_EGRESS: |
| return "egress"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| static void redir_to_connected(int family, int sotype, int sock_mapfd, |
| int verd_mapfd, enum redir_mode mode) |
| { |
| const char *log_prefix = redir_mode_str(mode); |
| struct sockaddr_storage addr; |
| int s, c0, c1, p0, p1; |
| unsigned int pass; |
| socklen_t len; |
| int err, n; |
| u64 value; |
| u32 key; |
| char b; |
| |
| zero_verdict_count(verd_mapfd); |
| |
| s = socket_loopback(family, sotype | SOCK_NONBLOCK); |
| if (s < 0) |
| return; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv; |
| |
| c0 = xsocket(family, sotype, 0); |
| if (c0 < 0) |
| goto close_srv; |
| err = xconnect(c0, sockaddr(&addr), len); |
| if (err) |
| goto close_cli0; |
| |
| p0 = xaccept_nonblock(s, NULL, NULL); |
| if (p0 < 0) |
| goto close_cli0; |
| |
| c1 = xsocket(family, sotype, 0); |
| if (c1 < 0) |
| goto close_peer0; |
| err = xconnect(c1, sockaddr(&addr), len); |
| if (err) |
| goto close_cli1; |
| |
| p1 = xaccept_nonblock(s, NULL, NULL); |
| if (p1 < 0) |
| goto close_cli1; |
| |
| key = 0; |
| value = p0; |
| err = xbpf_map_update_elem(sock_mapfd, &key, &value, BPF_NOEXIST); |
| if (err) |
| goto close_peer1; |
| |
| key = 1; |
| value = p1; |
| err = xbpf_map_update_elem(sock_mapfd, &key, &value, BPF_NOEXIST); |
| if (err) |
| goto close_peer1; |
| |
| n = write(mode == REDIR_INGRESS ? c1 : p1, "a", 1); |
| if (n < 0) |
| FAIL_ERRNO("%s: write", log_prefix); |
| if (n == 0) |
| FAIL("%s: incomplete write", log_prefix); |
| if (n < 1) |
| goto close_peer1; |
| |
| key = SK_PASS; |
| err = xbpf_map_lookup_elem(verd_mapfd, &key, &pass); |
| if (err) |
| goto close_peer1; |
| if (pass != 1) |
| FAIL("%s: want pass count 1, have %d", log_prefix, pass); |
| |
| n = read(c0, &b, 1); |
| if (n < 0) |
| FAIL_ERRNO("%s: read", log_prefix); |
| if (n == 0) |
| FAIL("%s: incomplete read", log_prefix); |
| |
| close_peer1: |
| xclose(p1); |
| close_cli1: |
| xclose(c1); |
| close_peer0: |
| xclose(p0); |
| close_cli0: |
| xclose(c0); |
| close_srv: |
| xclose(s); |
| } |
| |
| static void test_skb_redir_to_connected(struct test_sockmap_listen *skel, |
| struct bpf_map *inner_map, int family, |
| int sotype) |
| { |
| int verdict = bpf_program__fd(skel->progs.prog_skb_verdict); |
| int parser = bpf_program__fd(skel->progs.prog_skb_parser); |
| int verdict_map = bpf_map__fd(skel->maps.verdict_map); |
| int sock_map = bpf_map__fd(inner_map); |
| int err; |
| |
| err = xbpf_prog_attach(parser, sock_map, BPF_SK_SKB_STREAM_PARSER, 0); |
| if (err) |
| return; |
| err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_STREAM_VERDICT, 0); |
| if (err) |
| goto detach; |
| |
| redir_to_connected(family, sotype, sock_map, verdict_map, |
| REDIR_INGRESS); |
| |
| xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_STREAM_VERDICT); |
| detach: |
| xbpf_prog_detach2(parser, sock_map, BPF_SK_SKB_STREAM_PARSER); |
| } |
| |
| static void test_msg_redir_to_connected(struct test_sockmap_listen *skel, |
| struct bpf_map *inner_map, int family, |
| int sotype) |
| { |
| int verdict = bpf_program__fd(skel->progs.prog_msg_verdict); |
| int verdict_map = bpf_map__fd(skel->maps.verdict_map); |
| int sock_map = bpf_map__fd(inner_map); |
| int err; |
| |
| err = xbpf_prog_attach(verdict, sock_map, BPF_SK_MSG_VERDICT, 0); |
| if (err) |
| return; |
| |
| redir_to_connected(family, sotype, sock_map, verdict_map, REDIR_EGRESS); |
| |
| xbpf_prog_detach2(verdict, sock_map, BPF_SK_MSG_VERDICT); |
| } |
| |
| static void redir_to_listening(int family, int sotype, int sock_mapfd, |
| int verd_mapfd, enum redir_mode mode) |
| { |
| const char *log_prefix = redir_mode_str(mode); |
| struct sockaddr_storage addr; |
| int s, c, p, err, n; |
| unsigned int drop; |
| socklen_t len; |
| u64 value; |
| u32 key; |
| |
| zero_verdict_count(verd_mapfd); |
| |
| s = socket_loopback(family, sotype | SOCK_NONBLOCK); |
| if (s < 0) |
| return; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv; |
| |
| c = xsocket(family, sotype, 0); |
| if (c < 0) |
| goto close_srv; |
| err = xconnect(c, sockaddr(&addr), len); |
| if (err) |
| goto close_cli; |
| |
| p = xaccept_nonblock(s, NULL, NULL); |
| if (p < 0) |
| goto close_cli; |
| |
| key = 0; |
| value = s; |
| err = xbpf_map_update_elem(sock_mapfd, &key, &value, BPF_NOEXIST); |
| if (err) |
| goto close_peer; |
| |
| key = 1; |
| value = p; |
| err = xbpf_map_update_elem(sock_mapfd, &key, &value, BPF_NOEXIST); |
| if (err) |
| goto close_peer; |
| |
| n = write(mode == REDIR_INGRESS ? c : p, "a", 1); |
| if (n < 0 && errno != EACCES) |
| FAIL_ERRNO("%s: write", log_prefix); |
| if (n == 0) |
| FAIL("%s: incomplete write", log_prefix); |
| if (n < 1) |
| goto close_peer; |
| |
| key = SK_DROP; |
| err = xbpf_map_lookup_elem(verd_mapfd, &key, &drop); |
| if (err) |
| goto close_peer; |
| if (drop != 1) |
| FAIL("%s: want drop count 1, have %d", log_prefix, drop); |
| |
| close_peer: |
| xclose(p); |
| close_cli: |
| xclose(c); |
| close_srv: |
| xclose(s); |
| } |
| |
| static void test_skb_redir_to_listening(struct test_sockmap_listen *skel, |
| struct bpf_map *inner_map, int family, |
| int sotype) |
| { |
| int verdict = bpf_program__fd(skel->progs.prog_skb_verdict); |
| int parser = bpf_program__fd(skel->progs.prog_skb_parser); |
| int verdict_map = bpf_map__fd(skel->maps.verdict_map); |
| int sock_map = bpf_map__fd(inner_map); |
| int err; |
| |
| err = xbpf_prog_attach(parser, sock_map, BPF_SK_SKB_STREAM_PARSER, 0); |
| if (err) |
| return; |
| err = xbpf_prog_attach(verdict, sock_map, BPF_SK_SKB_STREAM_VERDICT, 0); |
| if (err) |
| goto detach; |
| |
| redir_to_listening(family, sotype, sock_map, verdict_map, |
| REDIR_INGRESS); |
| |
| xbpf_prog_detach2(verdict, sock_map, BPF_SK_SKB_STREAM_VERDICT); |
| detach: |
| xbpf_prog_detach2(parser, sock_map, BPF_SK_SKB_STREAM_PARSER); |
| } |
| |
| static void test_msg_redir_to_listening(struct test_sockmap_listen *skel, |
| struct bpf_map *inner_map, int family, |
| int sotype) |
| { |
| int verdict = bpf_program__fd(skel->progs.prog_msg_verdict); |
| int verdict_map = bpf_map__fd(skel->maps.verdict_map); |
| int sock_map = bpf_map__fd(inner_map); |
| int err; |
| |
| err = xbpf_prog_attach(verdict, sock_map, BPF_SK_MSG_VERDICT, 0); |
| if (err) |
| return; |
| |
| redir_to_listening(family, sotype, sock_map, verdict_map, REDIR_EGRESS); |
| |
| xbpf_prog_detach2(verdict, sock_map, BPF_SK_MSG_VERDICT); |
| } |
| |
| static void test_reuseport_select_listening(int family, int sotype, |
| int sock_map, int verd_map, |
| int reuseport_prog) |
| { |
| struct sockaddr_storage addr; |
| unsigned int pass; |
| int s, c, err; |
| socklen_t len; |
| u64 value; |
| u32 key; |
| |
| zero_verdict_count(verd_map); |
| |
| s = socket_loopback_reuseport(family, sotype | SOCK_NONBLOCK, |
| reuseport_prog); |
| if (s < 0) |
| return; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv; |
| |
| key = 0; |
| value = s; |
| err = xbpf_map_update_elem(sock_map, &key, &value, BPF_NOEXIST); |
| if (err) |
| goto close_srv; |
| |
| c = xsocket(family, sotype, 0); |
| if (c < 0) |
| goto close_srv; |
| err = xconnect(c, sockaddr(&addr), len); |
| if (err) |
| goto close_cli; |
| |
| if (sotype == SOCK_STREAM) { |
| int p; |
| |
| p = xaccept_nonblock(s, NULL, NULL); |
| if (p < 0) |
| goto close_cli; |
| xclose(p); |
| } else { |
| char b = 'a'; |
| ssize_t n; |
| |
| n = xsend(c, &b, sizeof(b), 0); |
| if (n == -1) |
| goto close_cli; |
| |
| n = xrecv_nonblock(s, &b, sizeof(b), 0); |
| if (n == -1) |
| goto close_cli; |
| } |
| |
| key = SK_PASS; |
| err = xbpf_map_lookup_elem(verd_map, &key, &pass); |
| if (err) |
| goto close_cli; |
| if (pass != 1) |
| FAIL("want pass count 1, have %d", pass); |
| |
| close_cli: |
| xclose(c); |
| close_srv: |
| xclose(s); |
| } |
| |
| static void test_reuseport_select_connected(int family, int sotype, |
| int sock_map, int verd_map, |
| int reuseport_prog) |
| { |
| struct sockaddr_storage addr; |
| int s, c0, c1, p0, err; |
| unsigned int drop; |
| socklen_t len; |
| u64 value; |
| u32 key; |
| |
| zero_verdict_count(verd_map); |
| |
| s = socket_loopback_reuseport(family, sotype, reuseport_prog); |
| if (s < 0) |
| return; |
| |
| /* Populate sock_map[0] to avoid ENOENT on first connection */ |
| key = 0; |
| value = s; |
| err = xbpf_map_update_elem(sock_map, &key, &value, BPF_NOEXIST); |
| if (err) |
| goto close_srv; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv; |
| |
| c0 = xsocket(family, sotype, 0); |
| if (c0 < 0) |
| goto close_srv; |
| |
| err = xconnect(c0, sockaddr(&addr), len); |
| if (err) |
| goto close_cli0; |
| |
| if (sotype == SOCK_STREAM) { |
| p0 = xaccept_nonblock(s, NULL, NULL); |
| if (p0 < 0) |
| goto close_cli0; |
| } else { |
| p0 = xsocket(family, sotype, 0); |
| if (p0 < 0) |
| goto close_cli0; |
| |
| len = sizeof(addr); |
| err = xgetsockname(c0, sockaddr(&addr), &len); |
| if (err) |
| goto close_cli0; |
| |
| err = xconnect(p0, sockaddr(&addr), len); |
| if (err) |
| goto close_cli0; |
| } |
| |
| /* Update sock_map[0] to redirect to a connected socket */ |
| key = 0; |
| value = p0; |
| err = xbpf_map_update_elem(sock_map, &key, &value, BPF_EXIST); |
| if (err) |
| goto close_peer0; |
| |
| c1 = xsocket(family, sotype, 0); |
| if (c1 < 0) |
| goto close_peer0; |
| |
| len = sizeof(addr); |
| err = xgetsockname(s, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv; |
| |
| errno = 0; |
| err = connect(c1, sockaddr(&addr), len); |
| if (sotype == SOCK_DGRAM) { |
| char b = 'a'; |
| ssize_t n; |
| |
| n = xsend(c1, &b, sizeof(b), 0); |
| if (n == -1) |
| goto close_cli1; |
| |
| n = recv_timeout(c1, &b, sizeof(b), 0, IO_TIMEOUT_SEC); |
| err = n == -1; |
| } |
| if (!err || errno != ECONNREFUSED) |
| FAIL_ERRNO("connect: expected ECONNREFUSED"); |
| |
| key = SK_DROP; |
| err = xbpf_map_lookup_elem(verd_map, &key, &drop); |
| if (err) |
| goto close_cli1; |
| if (drop != 1) |
| FAIL("want drop count 1, have %d", drop); |
| |
| close_cli1: |
| xclose(c1); |
| close_peer0: |
| xclose(p0); |
| close_cli0: |
| xclose(c0); |
| close_srv: |
| xclose(s); |
| } |
| |
| /* Check that redirecting across reuseport groups is not allowed. */ |
| static void test_reuseport_mixed_groups(int family, int sotype, int sock_map, |
| int verd_map, int reuseport_prog) |
| { |
| struct sockaddr_storage addr; |
| int s1, s2, c, err; |
| unsigned int drop; |
| socklen_t len; |
| u64 value; |
| u32 key; |
| |
| zero_verdict_count(verd_map); |
| |
| /* Create two listeners, each in its own reuseport group */ |
| s1 = socket_loopback_reuseport(family, sotype, reuseport_prog); |
| if (s1 < 0) |
| return; |
| |
| s2 = socket_loopback_reuseport(family, sotype, reuseport_prog); |
| if (s2 < 0) |
| goto close_srv1; |
| |
| key = 0; |
| value = s1; |
| err = xbpf_map_update_elem(sock_map, &key, &value, BPF_NOEXIST); |
| if (err) |
| goto close_srv2; |
| |
| key = 1; |
| value = s2; |
| err = xbpf_map_update_elem(sock_map, &key, &value, BPF_NOEXIST); |
| |
| /* Connect to s2, reuseport BPF selects s1 via sock_map[0] */ |
| len = sizeof(addr); |
| err = xgetsockname(s2, sockaddr(&addr), &len); |
| if (err) |
| goto close_srv2; |
| |
| c = xsocket(family, sotype, 0); |
| if (c < 0) |
| goto close_srv2; |
| |
| err = connect(c, sockaddr(&addr), len); |
| if (sotype == SOCK_DGRAM) { |
| char b = 'a'; |
| ssize_t n; |
| |
| n = xsend(c, &b, sizeof(b), 0); |
| if (n == -1) |
| goto close_cli; |
| |
| n = recv_timeout(c, &b, sizeof(b), 0, IO_TIMEOUT_SEC); |
| err = n == -1; |
| } |
| if (!err || errno != ECONNREFUSED) { |
| FAIL_ERRNO("connect: expected ECONNREFUSED"); |
| goto close_cli; |
| } |
| |
| /* Expect drop, can't redirect outside of reuseport group */ |
| key = SK_DROP; |
| err = xbpf_map_lookup_elem(verd_map, &key, &drop); |
| if (err) |
| goto close_cli; |
| if (drop != 1) |
| FAIL("want drop count 1, have %d", drop); |
| |
| close_cli: |
| xclose(c); |
| close_srv2: |
| xclose(s2); |
| close_srv1: |
| xclose(s1); |
| } |
| |
| #define TEST(fn, ...) \ |
| { \ |
| fn, #fn, __VA_ARGS__ \ |
| } |
| |
| static void test_ops_cleanup(const struct bpf_map *map) |
| { |
| const struct bpf_map_def *def; |
| int err, mapfd; |
| u32 key; |
| |
| def = bpf_map__def(map); |
| mapfd = bpf_map__fd(map); |
| |
| for (key = 0; key < def->max_entries; key++) { |
| err = bpf_map_delete_elem(mapfd, &key); |
| if (err && errno != EINVAL && errno != ENOENT) |
| FAIL_ERRNO("map_delete: expected EINVAL/ENOENT"); |
| } |
| } |
| |
| static const char *family_str(sa_family_t family) |
| { |
| switch (family) { |
| case AF_INET: |
| return "IPv4"; |
| case AF_INET6: |
| return "IPv6"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| static const char *map_type_str(const struct bpf_map *map) |
| { |
| const struct bpf_map_def *def; |
| |
| def = bpf_map__def(map); |
| if (IS_ERR(def)) |
| return "invalid"; |
| |
| switch (def->type) { |
| case BPF_MAP_TYPE_SOCKMAP: |
| return "sockmap"; |
| case BPF_MAP_TYPE_SOCKHASH: |
| return "sockhash"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| static const char *sotype_str(int sotype) |
| { |
| switch (sotype) { |
| case SOCK_DGRAM: |
| return "UDP"; |
| case SOCK_STREAM: |
| return "TCP"; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| static void test_ops(struct test_sockmap_listen *skel, struct bpf_map *map, |
| int family, int sotype) |
| { |
| const struct op_test { |
| void (*fn)(int family, int sotype, int mapfd); |
| const char *name; |
| int sotype; |
| } tests[] = { |
| /* insert */ |
| TEST(test_insert_invalid), |
| TEST(test_insert_opened), |
| TEST(test_insert_bound, SOCK_STREAM), |
| TEST(test_insert), |
| /* delete */ |
| TEST(test_delete_after_insert), |
| TEST(test_delete_after_close), |
| /* lookup */ |
| TEST(test_lookup_after_insert), |
| TEST(test_lookup_after_delete), |
| TEST(test_lookup_32_bit_value), |
| /* update */ |
| TEST(test_update_existing), |
| /* races with insert/delete */ |
| TEST(test_destroy_orphan_child, SOCK_STREAM), |
| TEST(test_syn_recv_insert_delete, SOCK_STREAM), |
| TEST(test_race_insert_listen, SOCK_STREAM), |
| /* child clone */ |
| TEST(test_clone_after_delete, SOCK_STREAM), |
| TEST(test_accept_after_delete, SOCK_STREAM), |
| TEST(test_accept_before_delete, SOCK_STREAM), |
| }; |
| const char *family_name, *map_name, *sotype_name; |
| const struct op_test *t; |
| char s[MAX_TEST_NAME]; |
| int map_fd; |
| |
| family_name = family_str(family); |
| map_name = map_type_str(map); |
| sotype_name = sotype_str(sotype); |
| map_fd = bpf_map__fd(map); |
| |
| for (t = tests; t < tests + ARRAY_SIZE(tests); t++) { |
| snprintf(s, sizeof(s), "%s %s %s %s", map_name, family_name, |
| sotype_name, t->name); |
| |
| if (t->sotype != 0 && t->sotype != sotype) |
| continue; |
| |
| if (!test__start_subtest(s)) |
| continue; |
| |
| t->fn(family, sotype, map_fd); |
| test_ops_cleanup(map); |
| } |
| } |
| |
| static void test_redir(struct test_sockmap_listen *skel, struct bpf_map *map, |
| int family, int sotype) |
| { |
| const struct redir_test { |
| void (*fn)(struct test_sockmap_listen *skel, |
| struct bpf_map *map, int family, int sotype); |
| const char *name; |
| } tests[] = { |
| TEST(test_skb_redir_to_connected), |
| TEST(test_skb_redir_to_listening), |
| TEST(test_msg_redir_to_connected), |
| TEST(test_msg_redir_to_listening), |
| }; |
| const char *family_name, *map_name; |
| const struct redir_test *t; |
| char s[MAX_TEST_NAME]; |
| |
| family_name = family_str(family); |
| map_name = map_type_str(map); |
| |
| for (t = tests; t < tests + ARRAY_SIZE(tests); t++) { |
| snprintf(s, sizeof(s), "%s %s %s", map_name, family_name, |
| t->name); |
| |
| if (!test__start_subtest(s)) |
| continue; |
| |
| t->fn(skel, map, family, sotype); |
| } |
| } |
| |
| static void test_reuseport(struct test_sockmap_listen *skel, |
| struct bpf_map *map, int family, int sotype) |
| { |
| const struct reuseport_test { |
| void (*fn)(int family, int sotype, int socket_map, |
| int verdict_map, int reuseport_prog); |
| const char *name; |
| int sotype; |
| } tests[] = { |
| TEST(test_reuseport_select_listening), |
| TEST(test_reuseport_select_connected), |
| TEST(test_reuseport_mixed_groups), |
| }; |
| int socket_map, verdict_map, reuseport_prog; |
| const char *family_name, *map_name, *sotype_name; |
| const struct reuseport_test *t; |
| char s[MAX_TEST_NAME]; |
| |
| family_name = family_str(family); |
| map_name = map_type_str(map); |
| sotype_name = sotype_str(sotype); |
| |
| socket_map = bpf_map__fd(map); |
| verdict_map = bpf_map__fd(skel->maps.verdict_map); |
| reuseport_prog = bpf_program__fd(skel->progs.prog_reuseport); |
| |
| for (t = tests; t < tests + ARRAY_SIZE(tests); t++) { |
| snprintf(s, sizeof(s), "%s %s %s %s", map_name, family_name, |
| sotype_name, t->name); |
| |
| if (t->sotype != 0 && t->sotype != sotype) |
| continue; |
| |
| if (!test__start_subtest(s)) |
| continue; |
| |
| t->fn(family, sotype, socket_map, verdict_map, reuseport_prog); |
| } |
| } |
| |
| static void run_tests(struct test_sockmap_listen *skel, struct bpf_map *map, |
| int family) |
| { |
| test_ops(skel, map, family, SOCK_STREAM); |
| test_ops(skel, map, family, SOCK_DGRAM); |
| test_redir(skel, map, family, SOCK_STREAM); |
| test_reuseport(skel, map, family, SOCK_STREAM); |
| test_reuseport(skel, map, family, SOCK_DGRAM); |
| } |
| |
| void test_sockmap_listen(void) |
| { |
| struct test_sockmap_listen *skel; |
| |
| skel = test_sockmap_listen__open_and_load(); |
| if (!skel) { |
| FAIL("skeleton open/load failed"); |
| return; |
| } |
| |
| skel->bss->test_sockmap = true; |
| run_tests(skel, skel->maps.sock_map, AF_INET); |
| run_tests(skel, skel->maps.sock_map, AF_INET6); |
| |
| skel->bss->test_sockmap = false; |
| run_tests(skel, skel->maps.sock_hash, AF_INET); |
| run_tests(skel, skel->maps.sock_hash, AF_INET6); |
| |
| test_sockmap_listen__destroy(skel); |
| } |