// SPDX-License-Identifier: GPL-2.0 /* Check what features does the kernel support (where the selftest is running). * Somewhat inspired by CRIU kerndat/kdat kernel features detector. */ #include #include "aolib.h" struct kconfig_t { int _error; /* negative errno if not supported */ int (*check_kconfig)(int *error); }; static int has_net_ns(int *err) { if (access("/proc/self/ns/net", F_OK) < 0) { *err = errno; if (errno == ENOENT) return 0; test_print("Unable to access /proc/self/ns/net: %m"); return -errno; } return *err = errno = 0; } static int has_veth(int *err) { int orig_netns, ns_a, ns_b; orig_netns = open_netns(); ns_a = unshare_open_netns(); ns_b = unshare_open_netns(); *err = add_veth("check_veth", ns_a, ns_b); switch_ns(orig_netns); close(orig_netns); close(ns_a); close(ns_b); return 0; } static int has_tcp_ao(int *err) { struct sockaddr_in addr = { .sin_family = test_family, }; struct tcp_ao_add tmp = {}; const char *password = DEFAULT_TEST_PASSWORD; int sk, ret = 0; sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP); if (sk < 0) { test_print("socket(): %m"); return -errno; } tmp.sndid = 100; tmp.rcvid = 100; tmp.keylen = strlen(password); memcpy(tmp.key, password, strlen(password)); strcpy(tmp.alg_name, "hmac(sha1)"); memcpy(&tmp.addr, &addr, sizeof(addr)); *err = 0; if (setsockopt(sk, IPPROTO_TCP, TCP_AO_ADD_KEY, &tmp, sizeof(tmp)) < 0) { *err = -errno; if (errno != ENOPROTOOPT) ret = -errno; } close(sk); return ret; } static int has_tcp_md5(int *err) { union tcp_addr addr_any = {}; int sk, ret = 0; sk = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sk < 0) { test_print("socket(): %m"); return -errno; } /* * Under CONFIG_CRYPTO_FIPS=y it fails with ENOMEM, rather with * anything more descriptive. Oh well. */ *err = 0; if (test_set_md5(sk, addr_any, 0, -1, DEFAULT_TEST_PASSWORD)) { *err = -errno; if (errno != ENOPROTOOPT && errno == ENOMEM) { test_print("setsockopt(TCP_MD5SIG_EXT): %m"); ret = -errno; } } close(sk); return ret; } static int has_vrfs(int *err) { int orig_netns, ns_test, ret = 0; orig_netns = open_netns(); ns_test = unshare_open_netns(); *err = add_vrf("ksft-check", 55, 101, ns_test); if (*err && *err != -EOPNOTSUPP) { test_print("Failed to add a VRF: %d", *err); ret = *err; } switch_ns(orig_netns); close(orig_netns); close(ns_test); return ret; } static int has_ftrace(int *err) { *err = test_setup_tracing(); return 0; } #define KCONFIG_UNKNOWN 1 static pthread_mutex_t kconfig_lock = PTHREAD_MUTEX_INITIALIZER; static struct kconfig_t kconfig[__KCONFIG_LAST__] = { { KCONFIG_UNKNOWN, has_net_ns }, { KCONFIG_UNKNOWN, has_veth }, { KCONFIG_UNKNOWN, has_tcp_ao }, { KCONFIG_UNKNOWN, has_tcp_md5 }, { KCONFIG_UNKNOWN, has_vrfs }, { KCONFIG_UNKNOWN, has_ftrace }, }; const char *tests_skip_reason[__KCONFIG_LAST__] = { "Tests require network namespaces support (CONFIG_NET_NS)", "Tests require veth support (CONFIG_VETH)", "Tests require TCP-AO support (CONFIG_TCP_AO)", "setsockopt(TCP_MD5SIG_EXT) is not supported (CONFIG_TCP_MD5)", "VRFs are not supported (CONFIG_NET_VRF)", "Ftrace points are not supported (CONFIG_TRACEPOINTS)", }; bool kernel_config_has(enum test_needs_kconfig k) { bool ret; pthread_mutex_lock(&kconfig_lock); if (kconfig[k]._error == KCONFIG_UNKNOWN) { if (kconfig[k].check_kconfig(&kconfig[k]._error)) test_error("Failed to initialize kconfig %u", k); } ret = kconfig[k]._error == 0; pthread_mutex_unlock(&kconfig_lock); return ret; }