Commit cfbab37b authored by Dmitry Safonov's avatar Dmitry Safonov Committed by David S. Miller

selftests/net: Add TCP-AO library

Provide functions to create selftests dedicated to TCP-AO.
They can run in parallel, as they use temporary net namespaces.
They can be very specific to the feature being tested.
This will allow to create a lot of TCP-AO tests, without complicating
one binary with many --options and to create scenarios, that are
hard to put in bash script that uses one binary.
Signed-off-by: default avatarDmitry Safonov <dima@arista.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 37a8997f
......@@ -58,6 +58,7 @@ TARGETS += net/forwarding
TARGETS += net/hsr
TARGETS += net/mptcp
TARGETS += net/openvswitch
TARGETS += net/tcp_ao
TARGETS += netfilter
TARGETS += nsfs
TARGETS += perf_events
......
# SPDX-License-Identifier: GPL-2.0
TEST_BOTH_AF := connect
TEST_IPV4_PROGS := $(TEST_BOTH_AF:%=%_ipv4)
TEST_IPV6_PROGS := $(TEST_BOTH_AF:%=%_ipv6)
TEST_GEN_PROGS := $(TEST_IPV4_PROGS) $(TEST_IPV6_PROGS)
top_srcdir := ../../../../..
KSFT_KHDR_INSTALL := 1
include ../../lib.mk
HOSTAR ?= ar
# Drop it on port to linux/master with commit 8ce72dc32578
.DEFAULT_GOAL := all
LIBDIR := $(OUTPUT)/lib
LIB := $(LIBDIR)/libaotst.a
LDLIBS += $(LIB) -pthread
LIBDEPS := lib/aolib.h Makefile
CFLAGS := -Wall -O2 -g -D_GNU_SOURCE -fno-strict-aliasing
CFLAGS += -I ../../../../../usr/include/ -iquote $(LIBDIR)
CFLAGS += -I ../../../../include/
# Library
LIBSRC := kconfig.c netlink.c proc.c repair.c setup.c sock.c utils.c
LIBOBJ := $(LIBSRC:%.c=$(LIBDIR)/%.o)
EXTRA_CLEAN += $(LIBOBJ) $(LIB)
$(LIB): $(LIBOBJ)
$(HOSTAR) rcs $@ $^
$(LIBDIR)/%.o: ./lib/%.c $(LIBDEPS)
$(CC) $< $(CFLAGS) $(CPPFLAGS) -o $@ -c
$(TEST_GEN_PROGS): $(LIB)
$(OUTPUT)/%_ipv4: %.c
$(LINK.c) $^ $(LDLIBS) -o $@
$(OUTPUT)/%_ipv6: %.c
$(LINK.c) -DIPV6_TEST $^ $(LDLIBS) -o $@
// SPDX-License-Identifier: GPL-2.0
/* Author: Dmitry Safonov <dima@arista.com> */
#include <inttypes.h>
#include "aolib.h"
static void *server_fn(void *arg)
{
int sk, lsk;
ssize_t bytes;
lsk = test_listen_socket(this_ip_addr, test_server_port, 1);
if (test_add_key(lsk, DEFAULT_TEST_PASSWORD, this_ip_dest, -1, 100, 100))
test_error("setsockopt(TCP_AO_ADD_KEY)");
synchronize_threads();
if (test_wait_fd(lsk, TEST_TIMEOUT_SEC, 0))
test_error("test_wait_fd()");
sk = accept(lsk, NULL, NULL);
if (sk < 0)
test_error("accept()");
synchronize_threads();
bytes = test_server_run(sk, 0, 0);
test_fail("server served: %zd", bytes);
return NULL;
}
static void *client_fn(void *arg)
{
int sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
uint64_t before_aogood, after_aogood;
const size_t nr_packets = 20;
struct netstat *ns_before, *ns_after;
struct tcp_ao_counters ao1, ao2;
if (sk < 0)
test_error("socket()");
if (test_add_key(sk, DEFAULT_TEST_PASSWORD, this_ip_dest, -1, 100, 100))
test_error("setsockopt(TCP_AO_ADD_KEY)");
synchronize_threads();
if (test_connect_socket(sk, this_ip_dest, test_server_port) <= 0)
test_error("failed to connect()");
synchronize_threads();
ns_before = netstat_read();
before_aogood = netstat_get(ns_before, "TCPAOGood", NULL);
if (test_get_tcp_ao_counters(sk, &ao1))
test_error("test_get_tcp_ao_counters()");
if (test_client_verify(sk, 100, nr_packets, TEST_TIMEOUT_SEC)) {
test_fail("verify failed");
return NULL;
}
ns_after = netstat_read();
after_aogood = netstat_get(ns_after, "TCPAOGood", NULL);
if (test_get_tcp_ao_counters(sk, &ao2))
test_error("test_get_tcp_ao_counters()");
netstat_print_diff(ns_before, ns_after);
netstat_free(ns_before);
netstat_free(ns_after);
if (nr_packets > (after_aogood - before_aogood)) {
test_fail("TCPAOGood counter mismatch: %zu > (%zu - %zu)",
nr_packets, after_aogood, before_aogood);
return NULL;
}
if (test_tcp_ao_counters_cmp("connect", &ao1, &ao2, TEST_CNT_GOOD))
return NULL;
test_ok("connect TCPAOGood %" PRIu64 "/%" PRIu64 "/%" PRIu64 " => %" PRIu64 "/%" PRIu64 "/%" PRIu64 ", sent %" PRIu64,
before_aogood, ao1.ao_info_pkt_good,
ao1.key_cnts[0].pkt_good,
after_aogood, ao2.ao_info_pkt_good,
ao2.key_cnts[0].pkt_good,
nr_packets);
return NULL;
}
int main(int argc, char *argv[])
{
test_init(1, server_fn, client_fn);
return 0;
}
This diff is collapsed.
// 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 <pthread.h>
#include "aolib.h"
struct kconfig_t {
int _errno; /* the returned error 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 pthread_mutex_t kconfig_lock = PTHREAD_MUTEX_INITIALIZER;
static struct kconfig_t kconfig[__KCONFIG_LAST__] = {
{ -1, has_net_ns },
{ -1, has_veth },
{ -1, has_tcp_ao },
{ -1, has_tcp_md5 },
{ -1, has_vrfs },
};
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)",
};
bool kernel_config_has(enum test_needs_kconfig k)
{
bool ret;
pthread_mutex_lock(&kconfig_lock);
if (kconfig[k]._errno == -1) {
if (kconfig[k].check_kconfig(&kconfig[k]._errno))
test_error("Failed to initialize kconfig %u", k);
}
ret = kconfig[k]._errno == 0;
pthread_mutex_unlock(&kconfig_lock);
return ret;
}
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
#include <inttypes.h>
#include <pthread.h>
#include <stdio.h>
#include "../../../../../include/linux/compiler.h"
#include "../../../../../include/linux/kernel.h"
#include "aolib.h"
struct netstat_counter {
uint64_t val;
char *name;
};
struct netstat {
char *header_name;
struct netstat *next;
size_t counters_nr;
struct netstat_counter *counters;
};
static struct netstat *lookup_type(struct netstat *ns,
const char *type, size_t len)
{
while (ns != NULL) {
size_t cmp = max(len, strlen(ns->header_name));
if (!strncmp(ns->header_name, type, cmp))
return ns;
ns = ns->next;
}
return NULL;
}
static struct netstat *lookup_get(struct netstat *ns,
const char *type, const size_t len)
{
struct netstat *ret;
ret = lookup_type(ns, type, len);
if (ret != NULL)
return ret;
ret = malloc(sizeof(struct netstat));
if (!ret)
test_error("malloc()");
ret->header_name = strndup(type, len);
if (ret->header_name == NULL)
test_error("strndup()");
ret->next = ns;
ret->counters_nr = 0;
ret->counters = NULL;
return ret;
}
static struct netstat *lookup_get_column(struct netstat *ns, const char *line)
{
char *column;
column = strchr(line, ':');
if (!column)
test_error("can't parse netstat file");
return lookup_get(ns, line, column - line);
}
static void netstat_read_type(FILE *fnetstat, struct netstat **dest, char *line)
{
struct netstat *type = lookup_get_column(*dest, line);
const char *pos = line;
size_t i, nr_elems = 0;
char tmp;
while ((pos = strchr(pos, ' '))) {
nr_elems++;
pos++;
}
*dest = type;
type->counters = reallocarray(type->counters,
type->counters_nr + nr_elems,
sizeof(struct netstat_counter));
if (!type->counters)
test_error("reallocarray()");
pos = strchr(line, ' ') + 1;
if (fscanf(fnetstat, type->header_name) == EOF)
test_error("fscanf(%s)", type->header_name);
if (fread(&tmp, 1, 1, fnetstat) != 1 || tmp != ':')
test_error("Unexpected netstat format (%c)", tmp);
for (i = type->counters_nr; i < type->counters_nr + nr_elems; i++) {
struct netstat_counter *nc = &type->counters[i];
const char *new_pos = strchr(pos, ' ');
const char *fmt = " %" PRIu64;
if (new_pos == NULL)
new_pos = strchr(pos, '\n');
nc->name = strndup(pos, new_pos - pos);
if (nc->name == NULL)
test_error("strndup()");
if (unlikely(!strcmp(nc->name, "MaxConn")))
fmt = " %" PRId64; /* MaxConn is signed, RFC 2012 */
if (fscanf(fnetstat, fmt, &nc->val) != 1)
test_error("fscanf(%s)", nc->name);
pos = new_pos + 1;
}
type->counters_nr += nr_elems;
if (fread(&tmp, 1, 1, fnetstat) != 1 || tmp != '\n')
test_error("Unexpected netstat format");
}
static const char *snmp6_name = "Snmp6";
static void snmp6_read(FILE *fnetstat, struct netstat **dest)
{
struct netstat *type = lookup_get(*dest, snmp6_name, strlen(snmp6_name));
char *counter_name;
size_t i;
for (i = type->counters_nr;; i++) {
struct netstat_counter *nc;
uint64_t counter;
if (fscanf(fnetstat, "%ms", &counter_name) == EOF)
break;
if (fscanf(fnetstat, "%" PRIu64, &counter) == EOF)
test_error("Unexpected snmp6 format");
type->counters = reallocarray(type->counters, i + 1,
sizeof(struct netstat_counter));
if (!type->counters)
test_error("reallocarray()");
nc = &type->counters[i];
nc->name = counter_name;
nc->val = counter;
}
type->counters_nr = i;
*dest = type;
}
struct netstat *netstat_read(void)
{
struct netstat *ret = 0;
size_t line_sz = 0;
char *line = NULL;
FILE *fnetstat;
/*
* Opening thread-self instead of /proc/net/... as the latter
* points to /proc/self/net/ which instantiates thread-leader's
* net-ns, see:
* commit 155134fef2b6 ("Revert "proc: Point /proc/{mounts,net} at..")
*/
errno = 0;
fnetstat = fopen("/proc/thread-self/net/netstat", "r");
if (fnetstat == NULL)
test_error("failed to open /proc/net/netstat");
while (getline(&line, &line_sz, fnetstat) != -1)
netstat_read_type(fnetstat, &ret, line);
fclose(fnetstat);
errno = 0;
fnetstat = fopen("/proc/thread-self/net/snmp", "r");
if (fnetstat == NULL)
test_error("failed to open /proc/net/snmp");
while (getline(&line, &line_sz, fnetstat) != -1)
netstat_read_type(fnetstat, &ret, line);
fclose(fnetstat);
errno = 0;
fnetstat = fopen("/proc/thread-self/net/snmp6", "r");
if (fnetstat == NULL)
test_error("failed to open /proc/net/snmp6");
snmp6_read(fnetstat, &ret);
fclose(fnetstat);
free(line);
return ret;
}
void netstat_free(struct netstat *ns)
{
while (ns != NULL) {
struct netstat *prev = ns;
size_t i;
free(ns->header_name);
for (i = 0; i < ns->counters_nr; i++)
free(ns->counters[i].name);
free(ns->counters);
ns = ns->next;
free(prev);
}
}
static inline void
__netstat_print_diff(uint64_t a, struct netstat *nsb, size_t i)
{
if (unlikely(!strcmp(nsb->header_name, "MaxConn"))) {
test_print("%8s %25s: %" PRId64 " => %" PRId64,
nsb->header_name, nsb->counters[i].name,
a, nsb->counters[i].val);
return;
}
test_print("%8s %25s: %" PRIu64 " => %" PRIu64, nsb->header_name,
nsb->counters[i].name, a, nsb->counters[i].val);
}
void netstat_print_diff(struct netstat *nsa, struct netstat *nsb)
{
size_t i, j;
while (nsb != NULL) {
if (unlikely(strcmp(nsb->header_name, nsa->header_name))) {
for (i = 0; i < nsb->counters_nr; i++)
__netstat_print_diff(0, nsb, i);
nsb = nsb->next;
continue;
}
if (nsb->counters_nr < nsa->counters_nr)
test_error("Unexpected: some counters dissapeared!");
for (j = 0, i = 0; i < nsb->counters_nr; i++) {
if (strcmp(nsb->counters[i].name, nsa->counters[j].name)) {
__netstat_print_diff(0, nsb, i);
continue;
}
if (nsa->counters[j].val == nsb->counters[i].val) {
j++;
continue;
}
__netstat_print_diff(nsa->counters[j].val, nsb, i);
j++;
}
if (j != nsa->counters_nr)
test_error("Unexpected: some counters dissapeared!");
nsb = nsb->next;
nsa = nsa->next;
}
}
uint64_t netstat_get(struct netstat *ns, const char *name, bool *not_found)
{
if (not_found)
*not_found = false;
while (ns != NULL) {
size_t i;
for (i = 0; i < ns->counters_nr; i++) {
if (!strcmp(name, ns->counters[i].name))
return ns->counters[i].val;
}
ns = ns->next;
}
if (not_found)
*not_found = true;
return 0;
}
// SPDX-License-Identifier: GPL-2.0
/* This is over-simplified TCP_REPAIR for TCP_ESTABLISHED sockets
* It tests that TCP-AO enabled connection can be restored.
* For the proper socket repair see:
* https://github.com/checkpoint-restore/criu/blob/criu-dev/soccr/soccr.h
*/
#include <fcntl.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include "aolib.h"
#ifndef TCPOPT_MAXSEG
# define TCPOPT_MAXSEG 2
#endif
#ifndef TCPOPT_WINDOW
# define TCPOPT_WINDOW 3
#endif
#ifndef TCPOPT_SACK_PERMITTED
# define TCPOPT_SACK_PERMITTED 4
#endif
#ifndef TCPOPT_TIMESTAMP
# define TCPOPT_TIMESTAMP 8
#endif
enum {
TCP_ESTABLISHED = 1,
TCP_SYN_SENT,
TCP_SYN_RECV,
TCP_FIN_WAIT1,
TCP_FIN_WAIT2,
TCP_TIME_WAIT,
TCP_CLOSE,
TCP_CLOSE_WAIT,
TCP_LAST_ACK,
TCP_LISTEN,
TCP_CLOSING, /* Now a valid state */
TCP_NEW_SYN_RECV,
TCP_MAX_STATES /* Leave at the end! */
};
static void test_sock_checkpoint_queue(int sk, int queue, int qlen,
struct tcp_sock_queue *q)
{
socklen_t len;
int ret;
if (setsockopt(sk, SOL_TCP, TCP_REPAIR_QUEUE, &queue, sizeof(queue)))
test_error("setsockopt(TCP_REPAIR_QUEUE)");
len = sizeof(q->seq);
ret = getsockopt(sk, SOL_TCP, TCP_QUEUE_SEQ, &q->seq, &len);
if (ret || len != sizeof(q->seq))
test_error("getsockopt(TCP_QUEUE_SEQ): %d", (int)len);
if (!qlen) {
q->buf = NULL;
return;
}
q->buf = malloc(qlen);
if (q->buf == NULL)
test_error("malloc()");
ret = recv(sk, q->buf, qlen, MSG_PEEK | MSG_DONTWAIT);
if (ret != qlen)
test_error("recv(%d): %d", qlen, ret);
}
void __test_sock_checkpoint(int sk, struct tcp_sock_state *state,
void *addr, size_t addr_size)
{
socklen_t len = sizeof(state->info);
int ret;
memset(state, 0, sizeof(*state));
ret = getsockopt(sk, SOL_TCP, TCP_INFO, &state->info, &len);
if (ret || len != sizeof(state->info))
test_error("getsockopt(TCP_INFO): %d", (int)len);
len = addr_size;
if (getsockname(sk, addr, &len) || len != addr_size)
test_error("getsockname(): %d", (int)len);
len = sizeof(state->trw);
ret = getsockopt(sk, SOL_TCP, TCP_REPAIR_WINDOW, &state->trw, &len);
if (ret || len != sizeof(state->trw))
test_error("getsockopt(TCP_REPAIR_WINDOW): %d", (int)len);
if (ioctl(sk, SIOCOUTQ, &state->outq_len))
test_error("ioctl(SIOCOUTQ)");
if (ioctl(sk, SIOCOUTQNSD, &state->outq_nsd_len))
test_error("ioctl(SIOCOUTQNSD)");
test_sock_checkpoint_queue(sk, TCP_SEND_QUEUE, state->outq_len, &state->out);
if (ioctl(sk, SIOCINQ, &state->inq_len))
test_error("ioctl(SIOCINQ)");
test_sock_checkpoint_queue(sk, TCP_RECV_QUEUE, state->inq_len, &state->in);
if (state->info.tcpi_state == TCP_CLOSE)
state->outq_len = state->outq_nsd_len = 0;
len = sizeof(state->mss);
ret = getsockopt(sk, SOL_TCP, TCP_MAXSEG, &state->mss, &len);
if (ret || len != sizeof(state->mss))
test_error("getsockopt(TCP_MAXSEG): %d", (int)len);
len = sizeof(state->timestamp);
ret = getsockopt(sk, SOL_TCP, TCP_TIMESTAMP, &state->timestamp, &len);
if (ret || len != sizeof(state->timestamp))
test_error("getsockopt(TCP_TIMESTAMP): %d", (int)len);
}
void test_ao_checkpoint(int sk, struct tcp_ao_repair *state)
{
socklen_t len = sizeof(*state);
int ret;
memset(state, 0, sizeof(*state));
ret = getsockopt(sk, SOL_TCP, TCP_AO_REPAIR, state, &len);
if (ret || len != sizeof(*state))
test_error("getsockopt(TCP_AO_REPAIR): %d", (int)len);
}
static void test_sock_restore_seq(int sk, int queue, uint32_t seq)
{
if (setsockopt(sk, SOL_TCP, TCP_REPAIR_QUEUE, &queue, sizeof(queue)))
test_error("setsockopt(TCP_REPAIR_QUEUE)");
if (setsockopt(sk, SOL_TCP, TCP_QUEUE_SEQ, &seq, sizeof(seq)))
test_error("setsockopt(TCP_QUEUE_SEQ)");
}
static void test_sock_restore_queue(int sk, int queue, void *buf, int len)
{
int chunk = len;
size_t off = 0;
if (len == 0)
return;
if (setsockopt(sk, SOL_TCP, TCP_REPAIR_QUEUE, &queue, sizeof(queue)))
test_error("setsockopt(TCP_REPAIR_QUEUE)");
do {
int ret;
ret = send(sk, buf + off, chunk, 0);
if (ret <= 0) {
if (chunk > 1024) {
chunk >>= 1;
continue;
}
test_error("send()");
}
off += ret;
len -= ret;
} while (len > 0);
}
void __test_sock_restore(int sk, const char *device,
struct tcp_sock_state *state,
void *saddr, void *daddr, size_t addr_size)
{
struct tcp_repair_opt opts[4];
unsigned int opt_nr = 0;
long flags;
if (bind(sk, saddr, addr_size))
test_error("bind()");
flags = fcntl(sk, F_GETFL);
if ((flags < 0) || (fcntl(sk, F_SETFL, flags | O_NONBLOCK) < 0))
test_error("fcntl()");
test_sock_restore_seq(sk, TCP_RECV_QUEUE, state->in.seq - state->inq_len);
test_sock_restore_seq(sk, TCP_SEND_QUEUE, state->out.seq - state->outq_len);
if (device != NULL && setsockopt(sk, SOL_SOCKET, SO_BINDTODEVICE,
device, strlen(device) + 1))
test_error("setsockopt(SO_BINDTODEVICE, %s)", device);
if (connect(sk, daddr, addr_size))
test_error("connect()");
if (state->info.tcpi_options & TCPI_OPT_SACK) {
opts[opt_nr].opt_code = TCPOPT_SACK_PERMITTED;
opts[opt_nr].opt_val = 0;
opt_nr++;
}
if (state->info.tcpi_options & TCPI_OPT_WSCALE) {
opts[opt_nr].opt_code = TCPOPT_WINDOW;
opts[opt_nr].opt_val = state->info.tcpi_snd_wscale +
(state->info.tcpi_rcv_wscale << 16);
opt_nr++;
}
if (state->info.tcpi_options & TCPI_OPT_TIMESTAMPS) {
opts[opt_nr].opt_code = TCPOPT_TIMESTAMP;
opts[opt_nr].opt_val = 0;
opt_nr++;
}
opts[opt_nr].opt_code = TCPOPT_MAXSEG;
opts[opt_nr].opt_val = state->mss;
opt_nr++;
if (setsockopt(sk, SOL_TCP, TCP_REPAIR_OPTIONS, opts, opt_nr * sizeof(opts[0])))
test_error("setsockopt(TCP_REPAIR_OPTIONS)");
if (state->info.tcpi_options & TCPI_OPT_TIMESTAMPS) {
if (setsockopt(sk, SOL_TCP, TCP_TIMESTAMP,
&state->timestamp, opt_nr * sizeof(opts[0])))
test_error("setsockopt(TCP_TIMESTAMP)");
}
test_sock_restore_queue(sk, TCP_RECV_QUEUE, state->in.buf, state->inq_len);
test_sock_restore_queue(sk, TCP_SEND_QUEUE, state->out.buf, state->outq_len);
if (setsockopt(sk, SOL_TCP, TCP_REPAIR_WINDOW, &state->trw, sizeof(state->trw)))
test_error("setsockopt(TCP_REPAIR_WINDOW)");
}
void test_ao_restore(int sk, struct tcp_ao_repair *state)
{
if (setsockopt(sk, SOL_TCP, TCP_AO_REPAIR, state, sizeof(*state)))
test_error("setsockopt(TCP_AO_REPAIR)");
}
void test_sock_state_free(struct tcp_sock_state *state)
{
free(state->out.buf);
free(state->in.buf);
}
void test_enable_repair(int sk)
{
int val = TCP_REPAIR_ON;
if (setsockopt(sk, SOL_TCP, TCP_REPAIR, &val, sizeof(val)))
test_error("setsockopt(TCP_REPAIR)");
}
void test_disable_repair(int sk)
{
int val = TCP_REPAIR_OFF_NO_WP;
if (setsockopt(sk, SOL_TCP, TCP_REPAIR, &val, sizeof(val)))
test_error("setsockopt(TCP_REPAIR)");
}
void test_kill_sk(int sk)
{
test_enable_repair(sk);
close(sk);
}
// 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(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, 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.
*/
static const char *optmem_file = "/proc/sys/net/core/optmem_max";
static size_t saved_optmem;
size_t test_get_optmem(void)
{
FILE *foptmem;
int old_ns;
size_t ret;
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);
switch_ns(old_ns);
return ret;
}
static void __test_set_optmem(size_t new, size_t *old)
{
FILE *foptmem;
int old_ns;
if (old != NULL)
*old = test_get_optmem();
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);
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);
}
}
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
#include "aolib.h"
#include <string.h>
void randomize_buffer(void *buf, size_t buflen)
{
int *p = (int *)buf;
size_t words = buflen / sizeof(int);
size_t leftover = buflen % sizeof(int);
if (!buflen)
return;
while (words--)
*p++ = rand();
if (leftover) {
int tmp = rand();
memcpy(buf + buflen - leftover, &tmp, leftover);
}
}
const struct sockaddr_in6 addr_any6 = {
.sin6_family = AF_INET6,
};
const struct sockaddr_in addr_any4 = {
.sin_family = AF_INET,
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment