// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #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(buf); pthread_mutex_unlock(&ksft_print_lock); } void __test_ok(const char *buf) { pthread_mutex_lock(&ksft_print_lock); ksft_test_result_pass(buf); pthread_mutex_unlock(&ksft_print_lock); } void __test_fail(const char *buf) { pthread_mutex_lock(&ksft_print_lock); ksft_test_result_fail(buf); pthread_mutex_unlock(&ksft_print_lock); } void __test_xfail(const char *buf) { pthread_mutex_lock(&ksft_print_lock); ksft_test_result_xfail(buf); pthread_mutex_unlock(&ksft_print_lock); } void __test_error(const char *buf) { pthread_mutex_lock(&ksft_print_lock); ksft_test_result_error(buf); pthread_mutex_unlock(&ksft_print_lock); } void __test_skip(const char *buf) { pthread_mutex_lock(&ksft_print_lock); ksft_test_result_skip(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/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; } 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(); 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_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_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_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); } }