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, &current) && values_equal(original, &current))
> +             return true;
> +     return write_values(path, original) &&
> +            read_values(path, &current) &&
> +            values_equal(original, &current);
> +}
> +
> +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, &current) &&
> +         values_equal(&expected, &current))
> +             *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, &current) &&
> +                       values_equal(&expected, &current);
> +             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, &current) &&
> +                values_equal(original, &current);
> +     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
> 

Reply via email to