Hi Sahaj, On Wed, Oct 14, 2026 at 09:44:46AM +0530, Sahaj Chaudhari wrote: > Probe accepted bounds for writable network sysctls and exercise > TCP and UDP loopback traffic at each bound. Test representative > per-network-namespace TCP/UDP settings, with an option to select > from the set.
Is there any reason that you choose write this test with C? I feel a shell script should be more concise and clear. Thanks Hangbin > > Tested: Focused selftest build and CLI checks. QEMU on Linux > 7.3.0-rc7 passed (4 sysctls/8 values, TCP 16/16, UDP 16/16), also > while userspace wrote WARNING:/BUG: lines to /dev/kmsg. On an AArch64 > Linux 6.12.57 board, 4 sysctls/8 values met aggregate criteria (TCP > 15/16, UDP 16/16), with one TCP timeout at tcp_rmem[1]=1. No kernel > splats were found. > > Link: https://lore.kernel.org/r/[email protected] > Signed-off-by: Sahaj Chaudhari <[email protected]> > --- > Changes in v2: > - Remove --all and the full /proc/sys/net scan. It could enable > nf_hooks_lwtunnel, a global static key that cannot be disabled again, > so restoring the original value failed. The remaining sysctls are per > network namespace and restored by writing the original value back. > Document this constraint above the sysctl list. (Sashiko) > - The rounding issue in find_bound() applied to ms/jiffies sysctls that > only --all reached; the remaining four do not round. (Sashiko) > - Ignore /dev/kmsg records whose facility is not LOG_KERN. (Sashiko) > - Rebase on current master. > > v1: https://lore.kernel.org/r/[email protected] > > Eric, v1 was narrower than the generic network-sysctl coverage you > suggested. Is this reduced scope acceptable, or should it be folded into > an existing selftest? > tools/testing/selftests/net/.gitignore | 1 + > tools/testing/selftests/net/Makefile | 1 + > tools/testing/selftests/net/sysctl_net_test.c | 925 ++++++++++++++++++ > 3 files changed, 927 insertions(+) > create mode 100644 tools/testing/selftests/net/sysctl_net_test.c > > diff --git a/tools/testing/selftests/net/.gitignore > b/tools/testing/selftests/net/.gitignore > index dacd36ed8455..842cb359bbf6 100644 > --- a/tools/testing/selftests/net/.gitignore > +++ b/tools/testing/selftests/net/.gitignore > @@ -43,6 +43,7 @@ so_incoming_cpu > so_netns_cookie > so_rcv_listener > stress_reuseport_listen > +sysctl_net_test > tap > tcp_fastopen_backup_key > tcp_inq > diff --git a/tools/testing/selftests/net/Makefile > b/tools/testing/selftests/net/Makefile > index 590b33e16d9f..8926fcfef6e9 100644 > --- a/tools/testing/selftests/net/Makefile > +++ b/tools/testing/selftests/net/Makefile > @@ -196,6 +196,7 @@ TEST_GEN_PROGS := \ > sk_connect_zero_addr \ > sk_so_peek_off \ > so_incoming_cpu \ > + sysctl_net_test \ > tap \ > tcp_port_share \ > tls \ > diff --git a/tools/testing/selftests/net/sysctl_net_test.c > b/tools/testing/selftests/net/sysctl_net_test.c > new file mode 100644 > index 000000000000..6360616132c4 > --- /dev/null > +++ b/tools/testing/selftests/net/sysctl_net_test.c > @@ -0,0 +1,925 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#define _GNU_SOURCE > +#include <arpa/inet.h> > +#include <ctype.h> > +#include <errno.h> > +#include <fcntl.h> > +#include <getopt.h> > +#include <limits.h> > +#include <net/if.h> > +#include <netinet/in.h> > +#include <poll.h> > +#include <sched.h> > +#include <stdarg.h> > +#include <stdbool.h> > +#include <stdint.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <sys/ioctl.h> > +#include <sys/socket.h> > +#include <sys/stat.h> > +#include <sys/time.h> > +#include <unistd.h> > + > +#include "kselftest.h" > + > +#define SYSCTL_ROOT "/proc/sys/net" > +#define SYSCTL_TEXT_SIZE 4096 > +#define SYSCTL_MAX_VALUES 256 > +#define KMSG_TEXT_SIZE 4096 > +#define FIRST_TEST_PORT 20000 > +#define LAST_TEST_PORT 59998 > + > +struct sysctl_number { > + uint64_t magnitude; > + bool negative; > +}; > + > +struct sysctl_values { > + struct sysctl_number value[SYSCTL_MAX_VALUES]; > + size_t count; > +}; > + > +static int kmsg_fd = -1; > +static unsigned int next_tcp_port = FIRST_TEST_PORT; > +static unsigned int next_udp_port = FIRST_TEST_PORT; > +static size_t tested_files; > +static size_t skipped_files; > +static size_t tested_values; > +static size_t tcp_attempts; > +static size_t tcp_successes; > +static size_t udp_attempts; > +static size_t udp_successes; > +static char failure[256]; > +static char tcp_delivery_failure[256]; > +static char udp_delivery_failure[256]; > + > +/* > + * Only add sysctls that are per network namespace and are restored by > + * writing their original value back. Some sysctls change global state that > + * the private namespace does not isolate and that cannot be undone. > + */ > +static const char * const representative_sysctls[] = { > + "ipv4/tcp_rmem", > + "ipv4/tcp_wmem", > + "ipv4/udp_rmem_min", > + "ipv4/udp_wmem_min", > +}; > + > +static bool selected_sysctls[ARRAY_SIZE(representative_sysctls)]; > + > +static void set_failure(const char *fmt, ...) > +{ > + va_list args; > + > + if (failure[0]) > + return; > + > + va_start(args, fmt); > + vsnprintf(failure, sizeof(failure), fmt, args); > + va_end(args); > +} > + > +static struct sysctl_number number_from_signed(int64_t value) > +{ > + struct sysctl_number number = {}; > + > + if (value < 0) { > + number.negative = true; > + number.magnitude = (uint64_t)(-(value + 1)) + 1; > + } else { > + number.magnitude = value; > + } > + return number; > +} > + > +static struct sysctl_number number_from_unsigned(uint64_t value) > +{ > + struct sysctl_number number = { > + .magnitude = value, > + }; > + > + return number; > +} > + > +static int64_t number_to_signed(struct sysctl_number number) > +{ > + if (!number.negative) > + return (int64_t)number.magnitude; > + if (number.magnitude == (UINT64_C(1) << 63)) > + return INT64_MIN; > + return -(int64_t)number.magnitude; > +} > + > +static bool numbers_equal(struct sysctl_number a, struct sysctl_number b) > +{ > + return a.magnitude == b.magnitude && > + (a.magnitude == 0 || a.negative == b.negative); > +} > + > +static bool format_number(struct sysctl_number number, char *text, > + size_t text_size) > +{ > + int n = snprintf(text, text_size, number.negative ? "-%llu" : "%llu", > + (unsigned long long)number.magnitude); > + > + return n >= 0 && (size_t)n < text_size; > +} > + > +static bool parse_values(char *text, struct sysctl_values *values) > +{ > + char *pos = text; > + > + values->count = 0; > + while (*pos) { > + char *end; > + struct sysctl_number value; > + > + while (isspace((unsigned char)*pos)) > + pos++; > + if (!*pos) > + break; > + if (values->count == SYSCTL_MAX_VALUES) > + return false; > + > + errno = 0; > + if (*pos == '-') { > + long long signed_value = strtoll(pos, &end, 10); > + > + if (end == pos || errno == ERANGE) > + return false; > + value = number_from_signed(signed_value); > + } else { > + unsigned long long unsigned_value = strtoull(pos, &end, > 10); > + > + if (end == pos || errno == ERANGE) > + return false; > + value = number_from_unsigned(unsigned_value); > + } > + if (*end && !isspace((unsigned char)*end)) > + return false; > + > + values->value[values->count++] = value; > + pos = end; > + } > + > + return values->count > 0; > +} > + > +static bool read_values(const char *path, struct sysctl_values *values) > +{ > + char text[SYSCTL_TEXT_SIZE]; > + size_t len = 0; > + int fd; > + bool ok = false; > + > + fd = open(path, O_RDONLY | O_CLOEXEC); > + if (fd < 0) > + return false; > + > + while (len < sizeof(text) - 1) { > + ssize_t n = read(fd, text + len, sizeof(text) - 1 - len); > + > + if (n < 0 && errno == EINTR) > + continue; > + if (n < 0) > + goto out; > + if (!n) > + break; > + len += n; > + } > + if (len == sizeof(text) - 1) > + goto out; > + > + text[len] = '\0'; > + ok = parse_values(text, values); > +out: > + close(fd); > + return ok; > +} > + > +static bool values_equal(const struct sysctl_values *a, > + const struct sysctl_values *b) > +{ > + size_t i; > + > + if (a->count != b->count) > + return false; > + for (i = 0; i < a->count; i++) { > + if (!numbers_equal(a->value[i], b->value[i])) > + return false; > + } > + return true; > +} > + > +static bool format_values(const struct sysctl_values *values, char *text, > + size_t text_size) > +{ > + size_t i, len = 0; > + > + for (i = 0; i < values->count; i++) { > + char number[32]; > + int n; > + > + if (!format_number(values->value[i], number, sizeof(number))) > + return false; > + n = snprintf(text + len, text_size - len, "%s%c", number, > + i + 1 == values->count ? '\n' : ' '); > + if (n < 0 || (size_t)n >= text_size - len) > + return false; > + len += n; > + } > + return len > 0; > +} > + > +static bool write_values(const char *path, > + const struct sysctl_values *values) > +{ > + char text[SYSCTL_TEXT_SIZE]; > + size_t len; > + ssize_t n; > + int fd; > + bool ok; > + > + if (!format_values(values, text, sizeof(text))) > + return false; > + len = strlen(text); > + > + fd = open(path, O_WRONLY | O_CLOEXEC); > + if (fd < 0) > + return false; > + do { > + n = write(fd, text, len); > + } while (n < 0 && errno == EINTR); > + ok = n == (ssize_t)len; > + if (close(fd) < 0) > + ok = false; > + return ok; > +} > + > +static bool restore_values(const char *path, > + const struct sysctl_values *original) > +{ > + struct sysctl_values current; > + > + if (read_values(path, ¤t) && values_equal(original, ¤t)) > + return true; > + return write_values(path, original) && > + read_values(path, ¤t) && > + values_equal(original, ¤t); > +} > + > +static bool check_kernel_log(void) > +{ > + static const char * const splats[] = { > + "WARNING:", > + "BUG:", > + "Oops:", > + "divide error", > + "general protection fault", > + "invalid opcode", > + "kernel BUG at", > + "Kernel panic", > + "KASAN:", > + "UBSAN:", > + "soft lockup", > + "hard LOCKUP", > + "blocked for more than", > + "possible circular locking dependency", > + }; > + char record[KMSG_TEXT_SIZE + 1]; > + > + for (;;) { > + ssize_t n = read(kmsg_fd, record, sizeof(record) - 1); > + unsigned long prefix; > + char *message; > + size_t i; > + > + if (n < 0 && errno == EINTR) > + continue; > + if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) > + return true; > + if (n < 0) { > + set_failure("cannot read /dev/kmsg: %s", > strerror(errno)); > + return false; > + } > + if (!n) > + return true; > + > + record[n] = '\0'; > + message = strchr(record, ';'); > + if (!message) > + continue; > + /* Ignore messages that userspace wrote to /dev/kmsg. */ > + prefix = strtoul(record, NULL, 10); > + if (prefix >> 3 != 0) > + continue; > + message++; > + for (i = 0; i < ARRAY_SIZE(splats); i++) { > + if (!strstr(message, splats[i])) > + continue; > + set_failure("kernel splat detected: %.180s", message); > + return false; > + } > + } > +} > + > +static uint64_t ordered_value(int64_t value) > +{ > + /* Map signed integers to unsigned keys in numeric order. */ > + return (uint64_t)value ^ (UINT64_C(1) << 63); > +} > + > +static int64_t unordered_value(uint64_t key) > +{ > + uint64_t raw = key ^ (UINT64_C(1) << 63); > + > + if (raw <= INT64_MAX) > + return (int64_t)raw; > + return -1 - (int64_t)(UINT64_MAX - raw); > +} > + > +static int probe_value(const char *path, const struct sysctl_values > *original, > + size_t index, struct sysctl_number candidate, > + bool *accepted) > +{ > + struct sysctl_values expected = *original; > + struct sysctl_values current; > + char text[32]; > + > + expected.value[index] = candidate; > + *accepted = false; > + > + if (write_values(path, &expected) && > + read_values(path, ¤t) && > + values_equal(&expected, ¤t)) > + *accepted = true; > + > + if (!restore_values(path, original)) { > + format_number(candidate, text, sizeof(text)); > + set_failure("failed to restore %s after probing %s", path, > text); > + return -1; > + } > + if (!check_kernel_log()) > + return -1; > + return 0; > +} > + > +static uint64_t next_step(uint64_t step) > +{ > + if (step > UINT64_MAX / 2) > + return UINT64_MAX; > + return step * 2; > +} > + > +static int find_bound(const char *path, > + const struct sysctl_values *original, size_t index, > + bool lower, bool unsigned_domain, > + struct sysctl_number *bound) > +{ > + struct sysctl_number start = original->value[index]; > + uint64_t valid = unsigned_domain ? start.magnitude : > + ordered_value(number_to_signed(start)); > + uint64_t step = 1; > + > + for (;;) { > + uint64_t candidate, invalid; > + bool accepted; > + > + if (lower) { > + if (!valid) > + break; > + candidate = valid > step ? valid - step : 0; > + } else { > + if (valid == UINT64_MAX) > + break; > + candidate = UINT64_MAX - valid < step ? > + UINT64_MAX : valid + step; > + } > + if (candidate == valid) > + break; > + > + if (probe_value(path, original, index, > + unsigned_domain ? > number_from_unsigned(candidate) : > + number_from_signed(unordered_value(candidate)), > + &accepted)) > + return -1; > + if (accepted) { > + valid = candidate; > + step = next_step(step); > + continue; > + } > + > + invalid = candidate; > + while (lower ? valid - invalid > 1 : invalid - valid > 1) { > + uint64_t middle = lower ? > + invalid + (valid - invalid) / 2 : > + valid + (invalid - valid) / 2; > + > + if (probe_value(path, original, index, > + unsigned_domain ? > + number_from_unsigned(middle) : > + > number_from_signed(unordered_value(middle)), > + &accepted)) > + return -1; > + if (accepted) > + valid = middle; > + else > + invalid = middle; > + } > + break; > + } > + > + *bound = unsigned_domain ? number_from_unsigned(valid) : > + number_from_signed(unordered_value(valid)); > + return 0; > +} > + > +static int find_lower_bound(const char *path, > + const struct sysctl_values *original, > + size_t index, struct sysctl_number *bound) > +{ > + struct sysctl_number start = original->value[index]; > + bool unsigned_domain = !start.negative && start.magnitude > INT64_MAX; > + > + return find_bound(path, original, index, true, unsigned_domain, bound); > +} > + > +static int find_upper_bound(const char *path, > + const struct sysctl_values *original, > + size_t index, struct sysctl_number *bound) > +{ > + bool unsigned_domain = !original->value[index].negative; > + > + return find_bound(path, original, index, false, unsigned_domain, bound); > +} > + > +static int get_port_pair(bool tcp, uint16_t *server, uint16_t *client) > +{ > + unsigned int *next = tcp ? &next_tcp_port : &next_udp_port; > + > + if (*next > LAST_TEST_PORT) { > + errno = EADDRNOTAVAIL; > + return -1; > + } > + *server = *next; > + *client = *next + 1; > + *next += 2; > + return 0; > +} > + > +static int bind_loopback(int fd, uint16_t port) > +{ > + struct sockaddr_in addr = { > + .sin_family = AF_INET, > + .sin_port = htons(port), > + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), > + }; > + > + return bind(fd, (struct sockaddr *)&addr, sizeof(addr)); > +} > + > +static int wait_readable(int fd) > +{ > + struct pollfd pfd = { > + .fd = fd, > + .events = POLLIN, > + }; > + int ret; > + > + ret = poll(&pfd, 1, 1000); > + if (ret < 0) > + return -1; > + if (!ret) { > + errno = ETIMEDOUT; > + return -1; > + } > + if (!(pfd.revents & POLLIN)) { > + errno = EIO; > + return -1; > + } > + return 0; > +} > + > +static int tcp_loopback(void) > +{ > + struct timeval timeout = { .tv_sec = 1 }; > + uint16_t server_port, client_port; > + char data[64] = { 1 }; > + char received[sizeof(data)]; > + int listener = -1, client = -1, server = -1; > + int ret = -1; > + ssize_t n; > + > + if (get_port_pair(true, &server_port, &client_port)) > + return -1; > + listener = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); > + client = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); > + if (listener < 0 || client < 0 || > + setsockopt(client, SOL_SOCKET, SO_SNDTIMEO, &timeout, > + sizeof(timeout)) || > + bind_loopback(listener, server_port) || > + listen(listener, 1) || bind_loopback(client, client_port)) > + goto out; > + > + { > + struct sockaddr_in addr = { > + .sin_family = AF_INET, > + .sin_port = htons(server_port), > + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), > + }; > + > + if (connect(client, (struct sockaddr *)&addr, sizeof(addr))) > + goto out; > + } > + > + if (wait_readable(listener)) > + goto out; > + server = accept4(listener, NULL, NULL, SOCK_CLOEXEC); > + if (server < 0) > + goto out; > + n = send(client, data, sizeof(data), MSG_NOSIGNAL); > + if (n <= 0) { > + if (!n) > + errno = EPIPE; > + goto out; > + } > + if (wait_readable(server)) > + goto out; > + > + n = recv(server, received, sizeof(received), 0); > + if (n <= 0) { > + if (!n) > + errno = ECONNRESET; > + goto out; > + } > + ret = 0; > +out: > + if (server >= 0) > + close(server); > + if (client >= 0) > + close(client); > + if (listener >= 0) > + close(listener); > + return ret; > +} > + > +static int udp_loopback(void) > +{ > + uint16_t server_port, client_port; > + struct sockaddr_in addr = { > + .sin_family = AF_INET, > + .sin_addr.s_addr = htonl(INADDR_LOOPBACK), > + }; > + char data = 1, received; > + int server = -1, client = -1; > + int ret = -1; > + ssize_t n; > + > + if (get_port_pair(false, &server_port, &client_port)) > + return -1; > + server = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); > + client = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); > + if (server < 0 || client < 0 || bind_loopback(server, server_port) || > + bind_loopback(client, client_port)) > + goto out; > + > + addr.sin_port = htons(server_port); > + n = sendto(client, &data, sizeof(data), 0, (struct sockaddr *)&addr, > + sizeof(addr)); > + if (n != sizeof(data)) { > + if (n >= 0) > + errno = EIO; > + goto out; > + } > + if (wait_readable(server)) > + goto out; > + > + n = recv(server, &received, sizeof(received), 0); > + if (n != sizeof(received)) { > + if (n >= 0) > + errno = EIO; > + goto out; > + } > + ret = 0; > +out: > + if (client >= 0) > + close(client); > + if (server >= 0) > + close(server); > + return ret; > +} > + > +static void run_loopback_traffic(const char *path, size_t index, > + const char *value) > +{ > + int err; > + > + tcp_attempts++; > + if (!tcp_loopback()) { > + tcp_successes++; > + } else { > + err = errno; > + if (!tcp_delivery_failure[0]) > + snprintf(tcp_delivery_failure, > + sizeof(tcp_delivery_failure), > + "%s[%zu]=%s: %s", path, index, value, > + strerror(err)); > + } > + > + udp_attempts++; > + if (!udp_loopback()) { > + udp_successes++; > + } else { > + err = errno; > + if (!udp_delivery_failure[0]) > + snprintf(udp_delivery_failure, > + sizeof(udp_delivery_failure), > + "%s[%zu]=%s: %s", path, index, value, > + strerror(err)); > + } > +} > + > +static int exercise_value(const char *path, > + const struct sysctl_values *original, > + size_t index, struct sysctl_number value) > +{ > + struct sysctl_values expected = *original; > + char text[32]; > + bool restore = !numbers_equal(value, original->value[index]); > + bool applied = false; > + > + if (!format_number(value, text, sizeof(text))) { > + set_failure("cannot format boundary for %s", path); > + return -1; > + } > + if (restore) { > + struct sysctl_values current; > + > + expected.value[index] = value; > + applied = write_values(path, &expected) && > + read_values(path, ¤t) && > + values_equal(&expected, ¤t); > + if (!applied) { > + if (!restore_values(path, original)) { > + set_failure("failed to restore %s after setting > %s", > + path, text); > + return -1; > + } > + set_failure("%s no longer accepts boundary %s", path, > text); > + return -1; > + } > + } > + > + check_kernel_log(); > + if (!failure[0]) > + run_loopback_traffic(path, index, text); > + > + if (restore && !restore_values(path, original)) { > + format_number(value, text, sizeof(text)); > + set_failure("failed to restore %s after traffic at %s", path, > + text); > + } > + check_kernel_log(); > + return failure[0] ? -1 : 0; > +} > + > +static bool prepare_sysctl(const char *path, > + const struct sysctl_values *original) > +{ > + struct sysctl_values current; > + bool writable; > + > + writable = write_values(path, original) && > + read_values(path, ¤t) && > + values_equal(original, ¤t); > + if (!writable && !restore_values(path, original)) { > + set_failure("failed to restore %s after initial write", path); > + return false; > + } > + if (!check_kernel_log()) > + return false; > + return writable; > +} > + > +static int test_sysctl(const char *path, const struct sysctl_values > *original) > +{ > + size_t i; > + > + if (!prepare_sysctl(path, original)) { > + if (!failure[0]) { > + skipped_files++; > + printf("# SKIP %s: sysctl is not writable\n", path); > + } > + return failure[0] ? -1 : 0; > + } > + > + tested_files++; > + for (i = 0; i < original->count; i++) { > + struct sysctl_number lower, upper; > + > + if (find_lower_bound(path, original, i, &lower) || > + find_upper_bound(path, original, i, &upper)) > + return -1; > + if (exercise_value(path, original, i, lower) || > + (!numbers_equal(upper, lower) && > + exercise_value(path, original, i, upper))) > + return -1; > + tested_values++; > + } > + return 0; > +} > + > +static void print_usage(FILE *stream, const char *program) > +{ > + size_t i; > + > + fprintf(stream, "Usage: %s [-s SYSCTL ...]\n", program); > + fprintf(stream, " -s, --sysctl SYSCTL Test a representative sysctl > (repeatable)\n"); > + fprintf(stream, " -h, --help Show this help\n"); > + fprintf(stream, "Default sysctls:\n"); > + for (i = 0; i < ARRAY_SIZE(representative_sysctls); i++) > + fprintf(stream, " %s\n", representative_sysctls[i]); > +} > + > +static int parse_options(int argc, char **argv) > +{ > + static const struct option options[] = { > + { "help", no_argument, NULL, 'h' }, > + { "sysctl", required_argument, NULL, 's' }, > + {} > + }; > + bool selection_given = false; > + int option; > + > + while ((option = getopt_long(argc, argv, "hs:", options, NULL)) != -1) { > + switch (option) { > + case 's': { > + size_t i; > + > + selection_given = true; > + for (i = 0; i < ARRAY_SIZE(representative_sysctls); > i++) { > + if (strcmp(optarg, representative_sysctls[i])) > + continue; > + selected_sysctls[i] = true; > + break; > + } > + if (i == ARRAY_SIZE(representative_sysctls)) { > + fprintf(stderr, "Unknown sysctl: %s\n", optarg); > + goto invalid_options; > + } > + break; > + } > + case 'h': > + print_usage(stdout, argv[0]); > + return 1; > + default: > + goto invalid_options; > + } > + } > + if (optind != argc) > + goto invalid_options; > + > + if (!selection_given) { > + size_t i; > + > + for (i = 0; i < ARRAY_SIZE(representative_sysctls); i++) > + selected_sysctls[i] = true; > + } > + return 0; > + > +invalid_options: > + print_usage(stderr, argv[0]); > + return -1; > +} > + > +static int test_selected_sysctl(const char *name) > +{ > + char path[PATH_MAX]; > + struct sysctl_values values; > + int n; > + > + n = snprintf(path, sizeof(path), "%s/%s", SYSCTL_ROOT, name); > + if (n < 0 || (size_t)n >= sizeof(path)) { > + set_failure("sysctl path is too long: %s", name); > + return -1; > + } > + > + if (access(path, F_OK)) { > + if (errno == ENOENT || errno == ENOTDIR) { > + skipped_files++; > + printf("# SKIP %s: not available\n", name); > + return 0; > + } > + set_failure("cannot inspect %s: %s", path, strerror(errno)); > + return -1; > + } > + if (access(path, R_OK)) { > + int err = errno; > + > + skipped_files++; > + printf("# SKIP %s: not readable (%s)\n", name, strerror(err)); > + return 0; > + } > + if (!read_values(path, &values)) { > + set_failure("%s is not a readable numeric sysctl", path); > + return -1; > + } > + if (access(path, W_OK)) { > + int err = errno; > + > + skipped_files++; > + printf("# SKIP %s: not writable (%s)\n", name, strerror(err)); > + return 0; > + } > + > + printf("# Testing %s\n", name); > + return test_sysctl(path, &values); > +} > + > +static int set_loopback_up(void) > +{ > + struct ifreq ifr = {}; > + int fd, ret = -1; > + > + fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); > + if (fd < 0) > + return -1; > + strncpy(ifr.ifr_name, "lo", IFNAMSIZ - 1); > + if (ioctl(fd, SIOCGIFFLAGS, &ifr)) > + goto out; > + ifr.ifr_flags |= IFF_UP; > + ret = ioctl(fd, SIOCSIFFLAGS, &ifr); > +out: > + close(fd); > + return ret; > +} > + > +int main(int argc, char **argv) > +{ > + int option_status; > + size_t i; > + > + option_status = parse_options(argc, argv); > + if (option_status > 0) > + return KSFT_PASS; > + if (option_status < 0) > + return KSFT_FAIL; > + > + ksft_print_header(); > + ksft_set_plan(1); > + > + if (unshare(CLONE_NEWNET)) > + ksft_exit_skip("cannot create network namespace: %s\n", > + strerror(errno)); > + > + kmsg_fd = open("/dev/kmsg", O_RDONLY | O_NONBLOCK | O_CLOEXEC); > + if (kmsg_fd < 0) > + ksft_exit_skip("cannot read /dev/kmsg: %s\n", strerror(errno)); > + if (lseek(kmsg_fd, 0, SEEK_END) < 0) > + ksft_exit_skip("cannot seek /dev/kmsg: %s\n", strerror(errno)); > + if (set_loopback_up()) > + ksft_exit_skip("cannot bring loopback up: %s\n", > strerror(errno)); > + if (!check_kernel_log()) > + goto fail; > + > + for (i = 0; i < ARRAY_SIZE(representative_sysctls); i++) { > + if (!selected_sysctls[i]) > + continue; > + if (test_selected_sysctl(representative_sysctls[i])) > + break; > + } > + if (!failure[0] && !tested_files) > + ksft_exit_skip("no selected writable numeric network sysctls > found\n"); > + if (!failure[0] && (!tcp_successes || !udp_successes)) > + set_failure("loopback delivery incomplete: TCP %zu/%zu, UDP > %zu/%zu", > + tcp_successes, tcp_attempts, > + udp_successes, udp_attempts); > + if (!failure[0] && !check_kernel_log()) > + goto fail; > + if (tcp_delivery_failure[0]) > + printf("# first TCP delivery failure: %s\n", > + tcp_delivery_failure); > + if (udp_delivery_failure[0]) > + printf("# first UDP delivery failure: %s\n", > + udp_delivery_failure); > + if (failure[0]) > + goto fail; > + > + printf("# tested %zu sysctls, %zu values; skipped %zu\n", > + tested_files, tested_values, skipped_files); > + ksft_test_result_pass("TCP %zu/%zu, UDP %zu/%zu deliveries succeeded\n", > + tcp_successes, tcp_attempts, > + udp_successes, udp_attempts); > + ksft_finished(); > + close(kmsg_fd); > + return KSFT_PASS; > + > +fail: > + ksft_test_result_fail("%s\n", failure); > + ksft_finished(); > + close(kmsg_fd); > + return KSFT_FAIL; > +} > -- > 2.43.0 >

