| // SPDX-License-Identifier: GPL-2.0 |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <sched.h> |
| #include <signal.h> |
| #include "aolib.h" |
| |
| /* |
| * Can't be included in the header: it defines static variables which |
| * will be unique to every object. Let's include it only once here. |
| */ |
| #include "../../../kselftest.h" |
| |
| /* Prevent overriding of one thread's output by another */ |
| static pthread_mutex_t ksft_print_lock = PTHREAD_MUTEX_INITIALIZER; |
| |
| void __test_msg(const char *buf) |
| { |
| pthread_mutex_lock(&ksft_print_lock); |
| ksft_print_msg("%s", buf); |
| pthread_mutex_unlock(&ksft_print_lock); |
| } |
| void __test_ok(const char *buf) |
| { |
| pthread_mutex_lock(&ksft_print_lock); |
| ksft_test_result_pass("%s", buf); |
| pthread_mutex_unlock(&ksft_print_lock); |
| } |
| void __test_fail(const char *buf) |
| { |
| pthread_mutex_lock(&ksft_print_lock); |
| ksft_test_result_fail("%s", buf); |
| pthread_mutex_unlock(&ksft_print_lock); |
| } |
| void __test_xfail(const char *buf) |
| { |
| pthread_mutex_lock(&ksft_print_lock); |
| ksft_test_result_xfail("%s", buf); |
| pthread_mutex_unlock(&ksft_print_lock); |
| } |
| void __test_error(const char *buf) |
| { |
| pthread_mutex_lock(&ksft_print_lock); |
| ksft_test_result_error("%s", buf); |
| pthread_mutex_unlock(&ksft_print_lock); |
| } |
| void __test_skip(const char *buf) |
| { |
| pthread_mutex_lock(&ksft_print_lock); |
| ksft_test_result_skip("%s", buf); |
| pthread_mutex_unlock(&ksft_print_lock); |
| } |
| |
| static volatile int failed; |
| static volatile int skipped; |
| |
| void test_failed(void) |
| { |
| failed = 1; |
| } |
| |
| static void test_exit(void) |
| { |
| if (failed) { |
| ksft_exit_fail(); |
| } else if (skipped) { |
| /* ksft_exit_skip() is different from ksft_exit_*() */ |
| ksft_print_cnts(); |
| exit(KSFT_SKIP); |
| } else { |
| ksft_exit_pass(); |
| } |
| } |
| |
| struct dlist_t { |
| void (*destruct)(void); |
| struct dlist_t *next; |
| }; |
| static struct dlist_t *destructors_list; |
| |
| void test_add_destructor(void (*d)(void)) |
| { |
| struct dlist_t *p; |
| |
| p = malloc(sizeof(struct dlist_t)); |
| if (p == NULL) |
| test_error("malloc() failed"); |
| |
| p->next = destructors_list; |
| p->destruct = d; |
| destructors_list = p; |
| } |
| |
| static void test_destructor(void) __attribute__((destructor)); |
| static void test_destructor(void) |
| { |
| while (destructors_list) { |
| struct dlist_t *p = destructors_list->next; |
| |
| destructors_list->destruct(); |
| free(destructors_list); |
| destructors_list = p; |
| } |
| test_exit(); |
| } |
| |
| static void sig_int(int signo) |
| { |
| test_error("Caught SIGINT - exiting"); |
| } |
| |
| int open_netns(void) |
| { |
| const char *netns_path = "/proc/thread-self/ns/net"; |
| int fd; |
| |
| fd = open(netns_path, O_RDONLY); |
| if (fd < 0) |
| test_error("open(%s)", netns_path); |
| return fd; |
| } |
| |
| int unshare_open_netns(void) |
| { |
| if (unshare(CLONE_NEWNET) != 0) |
| test_error("unshare()"); |
| |
| return open_netns(); |
| } |
| |
| void switch_ns(int fd) |
| { |
| if (setns(fd, CLONE_NEWNET)) |
| test_error("setns()"); |
| } |
| |
| int switch_save_ns(int new_ns) |
| { |
| int ret = open_netns(); |
| |
| switch_ns(new_ns); |
| return ret; |
| } |
| |
| void switch_close_ns(int fd) |
| { |
| if (setns(fd, CLONE_NEWNET)) |
| test_error("setns()"); |
| close(fd); |
| } |
| |
| static int nsfd_outside = -1; |
| static int nsfd_parent = -1; |
| static int nsfd_child = -1; |
| const char veth_name[] = "ktst-veth"; |
| |
| static void init_namespaces(void) |
| { |
| nsfd_outside = open_netns(); |
| nsfd_parent = unshare_open_netns(); |
| nsfd_child = unshare_open_netns(); |
| } |
| |
| static void link_init(const char *veth, int family, uint8_t prefix, |
| union tcp_addr addr, union tcp_addr dest) |
| { |
| if (link_set_up(veth)) |
| test_error("Failed to set link up"); |
| if (ip_addr_add(veth, family, addr, prefix)) |
| test_error("Failed to add ip address"); |
| if (ip_route_add(veth, family, addr, dest)) |
| test_error("Failed to add route"); |
| } |
| |
| static unsigned int nr_threads = 1; |
| |
| static pthread_mutex_t sync_lock = PTHREAD_MUTEX_INITIALIZER; |
| static pthread_cond_t sync_cond = PTHREAD_COND_INITIALIZER; |
| static volatile unsigned int stage_threads[2]; |
| static volatile unsigned int stage_nr; |
| |
| /* synchronize all threads in the same stage */ |
| void synchronize_threads(void) |
| { |
| unsigned int q = stage_nr; |
| |
| pthread_mutex_lock(&sync_lock); |
| stage_threads[q]++; |
| if (stage_threads[q] == nr_threads) { |
| stage_nr ^= 1; |
| stage_threads[stage_nr] = 0; |
| pthread_cond_signal(&sync_cond); |
| } |
| while (stage_threads[q] < nr_threads) |
| pthread_cond_wait(&sync_cond, &sync_lock); |
| pthread_mutex_unlock(&sync_lock); |
| } |
| |
| __thread union tcp_addr this_ip_addr; |
| __thread union tcp_addr this_ip_dest; |
| int test_family; |
| |
| struct new_pthread_arg { |
| thread_fn func; |
| union tcp_addr my_ip; |
| union tcp_addr dest_ip; |
| }; |
| static void *new_pthread_entry(void *arg) |
| { |
| struct new_pthread_arg *p = arg; |
| |
| this_ip_addr = p->my_ip; |
| this_ip_dest = p->dest_ip; |
| p->func(NULL); /* shouldn't return */ |
| exit(KSFT_FAIL); |
| } |
| |
| static void __test_skip_all(const char *msg) |
| { |
| ksft_set_plan(1); |
| ksft_print_header(); |
| skipped = 1; |
| test_skip("%s", msg); |
| exit(KSFT_SKIP); |
| } |
| |
| void __test_init(unsigned int ntests, int family, unsigned int prefix, |
| union tcp_addr addr1, union tcp_addr addr2, |
| thread_fn peer1, thread_fn peer2) |
| { |
| struct sigaction sa = { |
| .sa_handler = sig_int, |
| .sa_flags = SA_RESTART, |
| }; |
| time_t seed = time(NULL); |
| |
| sigemptyset(&sa.sa_mask); |
| if (sigaction(SIGINT, &sa, NULL)) |
| test_error("Can't set SIGINT handler"); |
| |
| test_family = family; |
| if (!kernel_config_has(KCONFIG_NET_NS)) |
| __test_skip_all(tests_skip_reason[KCONFIG_NET_NS]); |
| if (!kernel_config_has(KCONFIG_VETH)) |
| __test_skip_all(tests_skip_reason[KCONFIG_VETH]); |
| if (!kernel_config_has(KCONFIG_TCP_AO)) |
| __test_skip_all(tests_skip_reason[KCONFIG_TCP_AO]); |
| |
| ksft_set_plan(ntests); |
| test_print("rand seed %u", (unsigned int)seed); |
| srand(seed); |
| |
| ksft_print_header(); |
| init_namespaces(); |
| test_init_ftrace(nsfd_parent, nsfd_child); |
| |
| if (add_veth(veth_name, nsfd_parent, nsfd_child)) |
| test_error("Failed to add veth"); |
| |
| switch_ns(nsfd_child); |
| link_init(veth_name, family, prefix, addr2, addr1); |
| if (peer2) { |
| struct new_pthread_arg targ; |
| pthread_t t; |
| |
| targ.my_ip = addr2; |
| targ.dest_ip = addr1; |
| targ.func = peer2; |
| nr_threads++; |
| if (pthread_create(&t, NULL, new_pthread_entry, &targ)) |
| test_error("Failed to create pthread"); |
| } |
| switch_ns(nsfd_parent); |
| link_init(veth_name, family, prefix, addr1, addr2); |
| |
| this_ip_addr = addr1; |
| this_ip_dest = addr2; |
| peer1(NULL); |
| if (failed) |
| exit(KSFT_FAIL); |
| else |
| exit(KSFT_PASS); |
| } |
| |
| /* /proc/sys/net/core/optmem_max artifically limits the amount of memory |
| * that can be allocated with sock_kmalloc() on each socket in the system. |
| * It is not virtualized in v6.7, so it has to written outside test |
| * namespaces. To be nice a test will revert optmem back to the old value. |
| * Keeping it simple without any file lock, which means the tests that |
| * need to set/increase optmem value shouldn't run in parallel. |
| * Also, not re-entrant. |
| * Since commit f5769faeec36 ("net: Namespace-ify sysctl_optmem_max") |
| * it is per-namespace, keeping logic for non-virtualized optmem_max |
| * for v6.7, which supports TCP-AO. |
| */ |
| static const char *optmem_file = "/proc/sys/net/core/optmem_max"; |
| static size_t saved_optmem; |
| static int optmem_ns = -1; |
| |
| static bool is_optmem_namespaced(void) |
| { |
| if (optmem_ns == -1) { |
| int old_ns = switch_save_ns(nsfd_child); |
| |
| optmem_ns = !access(optmem_file, F_OK); |
| switch_close_ns(old_ns); |
| } |
| return !!optmem_ns; |
| } |
| |
| size_t test_get_optmem(void) |
| { |
| int old_ns = 0; |
| FILE *foptmem; |
| size_t ret; |
| |
| if (!is_optmem_namespaced()) |
| old_ns = switch_save_ns(nsfd_outside); |
| foptmem = fopen(optmem_file, "r"); |
| if (!foptmem) |
| test_error("failed to open %s", optmem_file); |
| |
| if (fscanf(foptmem, "%zu", &ret) != 1) |
| test_error("can't read from %s", optmem_file); |
| fclose(foptmem); |
| if (!is_optmem_namespaced()) |
| switch_close_ns(old_ns); |
| return ret; |
| } |
| |
| static void __test_set_optmem(size_t new, size_t *old) |
| { |
| int old_ns = 0; |
| FILE *foptmem; |
| |
| if (old != NULL) |
| *old = test_get_optmem(); |
| |
| if (!is_optmem_namespaced()) |
| old_ns = switch_save_ns(nsfd_outside); |
| foptmem = fopen(optmem_file, "w"); |
| if (!foptmem) |
| test_error("failed to open %s", optmem_file); |
| |
| if (fprintf(foptmem, "%zu", new) <= 0) |
| test_error("can't write %zu to %s", new, optmem_file); |
| fclose(foptmem); |
| if (!is_optmem_namespaced()) |
| switch_close_ns(old_ns); |
| } |
| |
| static void test_revert_optmem(void) |
| { |
| if (saved_optmem == 0) |
| return; |
| |
| __test_set_optmem(saved_optmem, NULL); |
| } |
| |
| void test_set_optmem(size_t value) |
| { |
| if (saved_optmem == 0) { |
| __test_set_optmem(value, &saved_optmem); |
| test_add_destructor(test_revert_optmem); |
| } else { |
| __test_set_optmem(value, NULL); |
| } |
| } |