Commit 66fe8963 authored by David S. Miller's avatar David S. Miller

Merge branch 'tcp-ao-selftests'

Dmitry Safonov says:

====================
selftests/net: Add TCP-AO tests

An essential part of any big kernel submissions is selftests.
At the beginning of TCP-AO project, I made patches to fcnal-test.sh
and nettest.c to have the benefits of easy refactoring, early noticing
breakages, putting a moat around the code, documenting
and designing uAPI.

While tests based on fcnal-test.sh/nettest.c provided initial testing*
and were very easy to add, the pile of TCP-AO quickly grew out of
one-binary + shell-script testing.

The design of the TCP-AO testing is a bit different than one-big
selftest binary as I did previously in net/ipsec.c. I found it
beneficial to avoid implementing a tests runner/scheduler and delegate
it to the user or Makefile. The approach is very influenced
by CRIU/ZDTM testing[1]: it provides a static library with helper
functions and selftest binaries that create specific scenarios.
I also tried to utilize kselftest.h.

test_init() function does all needed preparations. To not leave
any traces after a selftest exists, it creates a network namespace
and if the test wants to establish a TCP connection, a child netns.
The parent and child netns have veth pair with proper ip addresses
and routes set up. Both peers, the client and server are different
pthreads. The treading model was chosen over forking mostly by easiness
of cleanup on a failure: no need to search for children, handle SIGCHLD,
make sure not to wait for a dead peer to perform anything, etc.
Any thread that does exit() naturally kills the tests, sweet!
The selftests are compiled currently in two variants: ipv4 and ipv6.
Ipv4-mapped-ipv6 addresses might be a third variant to add, but it's not
there in this version. As pretty much all tests are shared between two
address families, most of the code can be shared, too. To differ in code
what kind of test is running, Makefile supplies -DIPV6_TEST to compiler
and ifdeffery in tests can do things that have to be different between
address families. This is similar to TARGETS_C_BOTHBITS in x86 selftests
and also to tests code sharing in CRIU/ZDTM.

The total number of tests is 832.
From them rst_ipv{4,6} has currently one flaky subtest, that may fail:
> not ok 9 client connection was not reset: 0
I'll investigate what happens there. Also, unsigned-md5_ipv{4,6}
are flaky because of netns counter checks: it doesn't expect that
there may be retransmitted TCP segments from a previous sub-selftest.
That will be fixed. Besides, key-management_ipv{4,6} has 3 sub-tests
passing with XFAIL:
> ok 15 # XFAIL listen() after current/rnext keys set: the socket has current/rnext keys: 100:200
> ok 16 # XFAIL listen socket, delete current key from before listen(): failed to delete the key 100:100 -16
> ok 17 # XFAIL listen socket, delete rnext key from before listen(): failed to delete the key 200:200 -16
...
> # Totals: pass:117 fail:0 xfail:3 xpass:0 skip:0 error:0
Those need some more kernel work to pass instead of xfail.

The overview of selftests (see the diffstat at the bottom):
├── lib
│   ├── aolib.h
│   │   The header for all selftests to include.
│   ├── kconfig.c
│   │   Kernel kconfig detector to SKIP tests that depend on something.
│   ├── netlink.c
│   │   Netlink helper to add/modify/delete VETH/IPs/routes/VRFs
│   │   I considered just using libmnl, but this is around 400 lines
│   │   and avoids selftests dependency on out-of-tree sources/packets.
│   ├── proc.c
│   │   SNMP/netstat procfs parser and the counters comparator.
│   ├── repair.c
│   │   Heavily influenced by libsoccr and reduced to minimum TCP
│   │   socket checkpoint/repair. Shouldn't be used out of selftests,
│   │   though.
│   ├── setup.c
│   │   All the needed netns/veth/ips/etc preparations for test init.
│   ├── sock.c
│   │   Socket helpers: {s,g}etsockopt()s/connect()/listen()/etc.
│   └── utils.c
│       Random stuff (a pun intended).
├── bench-lookups.c
│   The only benchmark in selftests currently: checks how well TCP-AO
│   setsockopt()s perform, depending on the amount of keys on a socket.
├── connect.c
│   Trivial sample, can be used as a boilerplate to write a new test.
├── connect-deny.c
│   More-or-less what could be expected for TCP-AO in fcnal-test.sh
├── icmps-accept.c -> icmps-discard.c
├── icmps-discard.c
│   Verifies RFC5925 (7.8) by checking that TCP-AO connection can be
│   broken if ICMPs are accepted and survives when ::accept_icmps = 0
├── key-management.c
│   Key manipulations, rotations between randomized hashing algorithms
│   and counter checks for those scenarios.
├── restore.c
│   TCP_AO_REPAIR: verifies that a socket can be re-created without
│   TCP-AO connection being interrupted.
├── rst.c
│   As RST segments are signed on a separate code-path in kernel,
│   verifies passive/active TCP send_reset().
├── self-connect.c
│   Verifies that TCP self-connect and also simultaneous open work.
├── seq-ext.c
│   Utilizes TCP_AO_REPAIR to check that on SEQ roll-over SNE
│   increment is performed and segments with different SNEs fail to
│   pass verification.
├── setsockopt-closed.c
│   Checks that {s,g}etsockopt()s are extendable syscalls and common
│   error-paths for them.
└── unsigned-md5.c
    Checks listen() socket for (non-)matching peers with: AO/MD5/none
    keys. As well as their interaction with VRFs and AO_REQUIRED flag.

There are certainly more test scenarios that can be added, but even so,
I'm pretty happy that this much of TCP-AO functionality and uAPIs got
covered. These selftests were iteratively developed by me during TCP-AO
kernel upstreaming and the resulting kernel patches would have been
worse without having these tests. They provided the user-side
perspective but also allowed safer refactoring with less possibility
of introducing a regression. Now it's time to use them to dig
a moat around the TCP-AO code!

There are also people from other network companies that work on TCP-AO
(+testing), so sharing these selftests will allow them to contribute
and may benefit from their efforts.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 37a8997f 3c3ead55
......@@ -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 := bench-lookups
TEST_BOTH_AF += connect
TEST_BOTH_AF += connect-deny
TEST_BOTH_AF += icmps-accept icmps-discard
TEST_BOTH_AF += key-management
TEST_BOTH_AF += restore
TEST_BOTH_AF += rst
TEST_BOTH_AF += self-connect
TEST_BOTH_AF += seq-ext
TEST_BOTH_AF += setsockopt-closed
TEST_BOTH_AF += unsigned-md5
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 $@
$(OUTPUT)/icmps-accept_ipv4: CFLAGS+= -DTEST_ICMPS_ACCEPT
$(OUTPUT)/icmps-accept_ipv6: CFLAGS+= -DTEST_ICMPS_ACCEPT
$(OUTPUT)/bench-lookups_ipv4: LDFLAGS+= -lm
$(OUTPUT)/bench-lookups_ipv6: LDFLAGS+= -lm
// SPDX-License-Identifier: GPL-2.0
/* Author: Dmitry Safonov <dima@arista.com> */
#include <arpa/inet.h>
#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "../../../../include/linux/bits.h"
#include "../../../../include/linux/kernel.h"
#include "aolib.h"
#define BENCH_NR_ITERS 100 /* number of times to run gathering statistics */
static void gen_test_ips(union tcp_addr *ips, size_t ips_nr, bool use_rand)
{
union tcp_addr net = {};
size_t i, j;
if (inet_pton(TEST_FAMILY, TEST_NETWORK, &net) != 1)
test_error("Can't convert ip address %s", TEST_NETWORK);
if (!use_rand) {
for (i = 0; i < ips_nr; i++)
ips[i] = gen_tcp_addr(net, 2 * i + 1);
return;
}
for (i = 0; i < ips_nr; i++) {
size_t r = (size_t)random() | 0x1;
ips[i] = gen_tcp_addr(net, r);
for (j = i - 1; j > 0 && i > 0; j--) {
if (!memcmp(&ips[i], &ips[j], sizeof(union tcp_addr))) {
i--; /* collision */
break;
}
}
}
}
static void test_add_routes(union tcp_addr *ips, size_t ips_nr)
{
size_t i;
for (i = 0; i < ips_nr; i++) {
union tcp_addr *p = (union tcp_addr *)&ips[i];
if (ip_route_add(veth_name, TEST_FAMILY, this_ip_addr, *p))
test_error("Failed to add route");
}
}
static void server_apply_keys(int lsk, union tcp_addr *ips, size_t ips_nr)
{
size_t i;
for (i = 0; i < ips_nr; i++) {
union tcp_addr *p = (union tcp_addr *)&ips[i];
if (test_add_key(lsk, DEFAULT_TEST_PASSWORD, *p, -1, 100, 100))
test_error("setsockopt(TCP_AO)");
}
}
static const size_t nr_keys[] = { 512, 1024, 2048, 4096, 8192 };
static union tcp_addr *test_ips;
struct bench_stats {
uint64_t min;
uint64_t max;
uint64_t nr;
double mean;
double s2;
};
static struct bench_tests {
struct bench_stats delete_last_key;
struct bench_stats add_key;
struct bench_stats delete_rand_key;
struct bench_stats connect_last_key;
struct bench_stats connect_rand_key;
struct bench_stats delete_async;
} bench_results[ARRAY_SIZE(nr_keys)];
#define NSEC_PER_SEC 1000000000ULL
static void measure_call(struct bench_stats *st,
void (*f)(int, void *), int sk, void *arg)
{
struct timespec start = {}, end = {};
double delta;
uint64_t nsec;
if (clock_gettime(CLOCK_MONOTONIC, &start))
test_error("clock_gettime()");
f(sk, arg);
if (clock_gettime(CLOCK_MONOTONIC, &end))
test_error("clock_gettime()");
nsec = (end.tv_sec - start.tv_sec) * NSEC_PER_SEC;
if (end.tv_nsec >= start.tv_nsec)
nsec += end.tv_nsec - start.tv_nsec;
else
nsec -= start.tv_nsec - end.tv_nsec;
if (st->nr == 0) {
st->min = st->max = nsec;
} else {
if (st->min > nsec)
st->min = nsec;
if (st->max < nsec)
st->max = nsec;
}
/* Welford-Knuth algorithm */
st->nr++;
delta = (double)nsec - st->mean;
st->mean += delta / st->nr;
st->s2 += delta * ((double)nsec - st->mean);
}
static void delete_mkt(int sk, void *arg)
{
struct tcp_ao_del *ao = arg;
if (setsockopt(sk, IPPROTO_TCP, TCP_AO_DEL_KEY, ao, sizeof(*ao)))
test_error("setsockopt(TCP_AO_DEL_KEY)");
}
static void add_back_mkt(int sk, void *arg)
{
union tcp_addr *p = arg;
if (test_add_key(sk, DEFAULT_TEST_PASSWORD, *p, -1, 100, 100))
test_error("setsockopt(TCP_AO)");
}
static void bench_delete(int lsk, struct bench_stats *add,
struct bench_stats *del,
union tcp_addr *ips, size_t ips_nr,
bool rand_order, bool async)
{
struct tcp_ao_del ao_del = {};
union tcp_addr *p;
size_t i;
ao_del.sndid = 100;
ao_del.rcvid = 100;
ao_del.del_async = !!async;
ao_del.prefix = DEFAULT_TEST_PREFIX;
/* Remove the first added */
p = (union tcp_addr *)&ips[0];
tcp_addr_to_sockaddr_in(&ao_del.addr, p, 0);
for (i = 0; i < BENCH_NR_ITERS; i++) {
measure_call(del, delete_mkt, lsk, (void *)&ao_del);
/* Restore it back */
measure_call(add, add_back_mkt, lsk, (void *)p);
/*
* Slowest for FILO-linked-list:
* on (i) iteration removing ips[i] element. When it gets
* added to the list back - it becomes first to fetch, so
* on (i + 1) iteration go to ips[i + 1] element.
*/
if (rand_order)
p = (union tcp_addr *)&ips[rand() % ips_nr];
else
p = (union tcp_addr *)&ips[i % ips_nr];
tcp_addr_to_sockaddr_in(&ao_del.addr, p, 0);
}
}
static void bench_connect_srv(int lsk, union tcp_addr *ips, size_t ips_nr)
{
size_t i;
for (i = 0; i < BENCH_NR_ITERS; i++) {
int sk;
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()");
close(sk);
}
}
static void test_print_stats(const char *desc, size_t nr, struct bench_stats *bs)
{
test_ok("%-20s\t%zu keys: min=%" PRIu64 "ms max=%" PRIu64 "ms mean=%gms stddev=%g",
desc, nr, bs->min / 1000000, bs->max / 1000000,
bs->mean / 1000000, sqrt((bs->mean / 1000000) / bs->nr));
}
static void *server_fn(void *arg)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(nr_keys); i++) {
struct bench_tests *bt = &bench_results[i];
int lsk;
test_ips = malloc(nr_keys[i] * sizeof(union tcp_addr));
if (!test_ips)
test_error("malloc()");
lsk = test_listen_socket(this_ip_addr, test_server_port + i, 1);
gen_test_ips(test_ips, nr_keys[i], false);
test_add_routes(test_ips, nr_keys[i]);
test_set_optmem(KERNEL_TCP_AO_KEY_SZ_ROUND_UP * nr_keys[i]);
server_apply_keys(lsk, test_ips, nr_keys[i]);
synchronize_threads();
bench_connect_srv(lsk, test_ips, nr_keys[i]);
bench_connect_srv(lsk, test_ips, nr_keys[i]);
/* The worst case for FILO-list */
bench_delete(lsk, &bt->add_key, &bt->delete_last_key,
test_ips, nr_keys[i], false, false);
test_print_stats("Add a new key",
nr_keys[i], &bt->add_key);
test_print_stats("Delete: worst case",
nr_keys[i], &bt->delete_last_key);
bench_delete(lsk, &bt->add_key, &bt->delete_rand_key,
test_ips, nr_keys[i], true, false);
test_print_stats("Delete: random-search",
nr_keys[i], &bt->delete_rand_key);
bench_delete(lsk, &bt->add_key, &bt->delete_async,
test_ips, nr_keys[i], false, true);
test_print_stats("Delete: async", nr_keys[i], &bt->delete_async);
free(test_ips);
close(lsk);
}
return NULL;
}
static void connect_client(int sk, void *arg)
{
size_t *p = arg;
if (test_connect_socket(sk, this_ip_dest, test_server_port + *p) <= 0)
test_error("failed to connect()");
}
static void client_addr_setup(int sk, union tcp_addr taddr)
{
#ifdef IPV6_TEST
struct sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_port = 0,
.sin6_addr = taddr.a6,
};
#else
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = 0,
.sin_addr = taddr.a4,
};
#endif
int ret;
ret = ip_addr_add(veth_name, TEST_FAMILY, taddr, TEST_PREFIX);
if (ret && ret != -EEXIST)
test_error("Failed to add ip address");
ret = ip_route_add(veth_name, TEST_FAMILY, taddr, this_ip_dest);
if (ret && ret != -EEXIST)
test_error("Failed to add route");
if (bind(sk, &addr, sizeof(addr)))
test_error("bind()");
}
static void bench_connect_client(size_t port_off, struct bench_tests *bt,
union tcp_addr *ips, size_t ips_nr, bool rand_order)
{
struct bench_stats *con;
union tcp_addr *p;
size_t i;
if (rand_order)
con = &bt->connect_rand_key;
else
con = &bt->connect_last_key;
p = (union tcp_addr *)&ips[0];
for (i = 0; i < BENCH_NR_ITERS; i++) {
int sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
if (sk < 0)
test_error("socket()");
client_addr_setup(sk, *p);
if (test_add_key(sk, DEFAULT_TEST_PASSWORD, this_ip_dest,
-1, 100, 100))
test_error("setsockopt(TCP_AO_ADD_KEY)");
synchronize_threads();
measure_call(con, connect_client, sk, (void *)&port_off);
close(sk);
/*
* Slowest for FILO-linked-list:
* on (i) iteration removing ips[i] element. When it gets
* added to the list back - it becomes first to fetch, so
* on (i + 1) iteration go to ips[i + 1] element.
*/
if (rand_order)
p = (union tcp_addr *)&ips[rand() % ips_nr];
else
p = (union tcp_addr *)&ips[i % ips_nr];
}
}
static void *client_fn(void *arg)
{
size_t i;
for (i = 0; i < ARRAY_SIZE(nr_keys); i++) {
struct bench_tests *bt = &bench_results[i];
synchronize_threads();
bench_connect_client(i, bt, test_ips, nr_keys[i], false);
test_print_stats("Connect: worst case",
nr_keys[i], &bt->connect_last_key);
bench_connect_client(i, bt, test_ips, nr_keys[i], false);
test_print_stats("Connect: random-search",
nr_keys[i], &bt->connect_last_key);
}
synchronize_threads();
return NULL;
}
int main(int argc, char *argv[])
{
test_init(30, server_fn, client_fn);
return 0;
}
// SPDX-License-Identifier: GPL-2.0
/* Author: Dmitry Safonov <dima@arista.com> */
#include <inttypes.h>
#include "aolib.h"
#define fault(type) (inj == FAULT_ ## type)
static inline int test_add_key_maclen(int sk, const char *key, uint8_t maclen,
union tcp_addr in_addr, uint8_t prefix,
uint8_t sndid, uint8_t rcvid)
{
struct tcp_ao_add tmp = {};
int err;
if (prefix > DEFAULT_TEST_PREFIX)
prefix = DEFAULT_TEST_PREFIX;
err = test_prepare_key(&tmp, DEFAULT_TEST_ALGO, in_addr, false, false,
prefix, 0, sndid, rcvid, maclen,
0, strlen(key), key);
if (err)
return err;
err = setsockopt(sk, IPPROTO_TCP, TCP_AO_ADD_KEY, &tmp, sizeof(tmp));
if (err < 0)
return -errno;
return test_verify_socket_key(sk, &tmp);
}
static void try_accept(const char *tst_name, unsigned int port, const char *pwd,
union tcp_addr addr, uint8_t prefix,
uint8_t sndid, uint8_t rcvid, uint8_t maclen,
const char *cnt_name, test_cnt cnt_expected,
fault_t inj)
{
struct tcp_ao_counters ao_cnt1, ao_cnt2;
uint64_t before_cnt = 0, after_cnt = 0; /* silence GCC */
int lsk, err, sk = 0;
time_t timeout;
lsk = test_listen_socket(this_ip_addr, port, 1);
if (pwd && test_add_key_maclen(lsk, pwd, maclen, addr, prefix, sndid, rcvid))
test_error("setsockopt(TCP_AO_ADD_KEY)");
if (cnt_name)
before_cnt = netstat_get_one(cnt_name, NULL);
if (pwd && test_get_tcp_ao_counters(lsk, &ao_cnt1))
test_error("test_get_tcp_ao_counters()");
synchronize_threads(); /* preparations done */
timeout = fault(TIMEOUT) ? TEST_RETRANSMIT_SEC : TEST_TIMEOUT_SEC;
err = test_wait_fd(lsk, timeout, 0);
if (err == -ETIMEDOUT) {
if (!fault(TIMEOUT))
test_fail("timeouted for accept()");
} else if (err < 0) {
test_error("test_wait_fd()");
} else {
if (fault(TIMEOUT))
test_fail("ready to accept");
sk = accept(lsk, NULL, NULL);
if (sk < 0) {
test_error("accept()");
} else {
if (fault(TIMEOUT))
test_fail("%s: accepted", tst_name);
}
}
if (pwd && test_get_tcp_ao_counters(lsk, &ao_cnt2))
test_error("test_get_tcp_ao_counters()");
close(lsk);
if (pwd)
test_tcp_ao_counters_cmp(tst_name, &ao_cnt1, &ao_cnt2, cnt_expected);
if (!cnt_name)
goto out;
after_cnt = netstat_get_one(cnt_name, NULL);
if (after_cnt <= before_cnt) {
test_fail("%s: %s counter did not increase: %zu <= %zu",
tst_name, cnt_name, after_cnt, before_cnt);
} else {
test_ok("%s: counter %s increased %zu => %zu",
tst_name, cnt_name, before_cnt, after_cnt);
}
out:
synchronize_threads(); /* close() */
if (sk > 0)
close(sk);
}
static void *server_fn(void *arg)
{
union tcp_addr wrong_addr, network_addr;
unsigned int port = test_server_port;
if (inet_pton(TEST_FAMILY, TEST_WRONG_IP, &wrong_addr) != 1)
test_error("Can't convert ip address %s", TEST_WRONG_IP);
try_accept("Non-AO server + AO client", port++, NULL,
this_ip_dest, -1, 100, 100, 0,
"TCPAOKeyNotFound", 0, FAULT_TIMEOUT);
try_accept("AO server + Non-AO client", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, 0,
"TCPAORequired", TEST_CNT_AO_REQUIRED, FAULT_TIMEOUT);
try_accept("Wrong password", port++, "something that is not DEFAULT_TEST_PASSWORD",
this_ip_dest, -1, 100, 100, 0,
"TCPAOBad", TEST_CNT_BAD, FAULT_TIMEOUT);
try_accept("Wrong rcv id", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 101, 0,
"TCPAOKeyNotFound", TEST_CNT_AO_KEY_NOT_FOUND, FAULT_TIMEOUT);
try_accept("Wrong snd id", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 101, 100, 0,
"TCPAOGood", TEST_CNT_GOOD, FAULT_TIMEOUT);
try_accept("Different maclen", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, 8,
"TCPAOBad", TEST_CNT_BAD, FAULT_TIMEOUT);
try_accept("Server: Wrong addr", port++, DEFAULT_TEST_PASSWORD,
wrong_addr, -1, 100, 100, 0,
"TCPAOKeyNotFound", TEST_CNT_AO_KEY_NOT_FOUND, FAULT_TIMEOUT);
try_accept("Client: Wrong addr", port++, NULL,
this_ip_dest, -1, 100, 100, 0, NULL, 0, FAULT_TIMEOUT);
try_accept("rcv id != snd id", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 200, 100, 0,
"TCPAOGood", TEST_CNT_GOOD, 0);
if (inet_pton(TEST_FAMILY, TEST_NETWORK, &network_addr) != 1)
test_error("Can't convert ip address %s", TEST_NETWORK);
try_accept("Server: prefix match", port++, DEFAULT_TEST_PASSWORD,
network_addr, 16, 100, 100, 0,
"TCPAOGood", TEST_CNT_GOOD, 0);
try_accept("Client: prefix match", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, 0,
"TCPAOGood", TEST_CNT_GOOD, 0);
/* client exits */
synchronize_threads();
return NULL;
}
static void try_connect(const char *tst_name, unsigned int port,
const char *pwd, union tcp_addr addr, uint8_t prefix,
uint8_t sndid, uint8_t rcvid,
test_cnt cnt_expected, fault_t inj)
{
struct tcp_ao_counters ao_cnt1, ao_cnt2;
time_t timeout;
int sk, ret;
sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
if (sk < 0)
test_error("socket()");
if (pwd && test_add_key(sk, pwd, addr, prefix, sndid, rcvid))
test_error("setsockopt(TCP_AO_ADD_KEY)");
if (pwd && test_get_tcp_ao_counters(sk, &ao_cnt1))
test_error("test_get_tcp_ao_counters()");
synchronize_threads(); /* preparations done */
timeout = fault(TIMEOUT) ? TEST_RETRANSMIT_SEC : TEST_TIMEOUT_SEC;
ret = _test_connect_socket(sk, this_ip_dest, port, timeout);
if (ret < 0) {
if (fault(KEYREJECT) && ret == -EKEYREJECTED) {
test_ok("%s: connect() was prevented", tst_name);
} else if (ret == -ETIMEDOUT && fault(TIMEOUT)) {
test_ok("%s", tst_name);
} else if (ret == -ECONNREFUSED &&
(fault(TIMEOUT) || fault(KEYREJECT))) {
test_ok("%s: refused to connect", tst_name);
} else {
test_error("%s: connect() returned %d", tst_name, ret);
}
goto out;
}
if (fault(TIMEOUT) || fault(KEYREJECT))
test_fail("%s: connected", tst_name);
else
test_ok("%s: connected", tst_name);
if (pwd && ret > 0) {
if (test_get_tcp_ao_counters(sk, &ao_cnt2))
test_error("test_get_tcp_ao_counters()");
test_tcp_ao_counters_cmp(tst_name, &ao_cnt1, &ao_cnt2, cnt_expected);
}
out:
synchronize_threads(); /* close() */
if (ret > 0)
close(sk);
}
static void *client_fn(void *arg)
{
union tcp_addr wrong_addr, network_addr;
unsigned int port = test_server_port;
if (inet_pton(TEST_FAMILY, TEST_WRONG_IP, &wrong_addr) != 1)
test_error("Can't convert ip address %s", TEST_WRONG_IP);
try_connect("Non-AO server + AO client", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
try_connect("AO server + Non-AO client", port++, NULL,
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
try_connect("Wrong password", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
try_connect("Wrong rcv id", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
try_connect("Wrong snd id", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
try_connect("Different maclen", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
try_connect("Server: Wrong addr", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
try_connect("Client: Wrong addr", port++, DEFAULT_TEST_PASSWORD,
wrong_addr, -1, 100, 100, 0, FAULT_KEYREJECT);
try_connect("rcv id != snd id", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 200, TEST_CNT_GOOD, 0);
if (inet_pton(TEST_FAMILY, TEST_NETWORK, &network_addr) != 1)
test_error("Can't convert ip address %s", TEST_NETWORK);
try_connect("Server: prefix match", port++, DEFAULT_TEST_PASSWORD,
this_ip_dest, -1, 100, 100, TEST_CNT_GOOD, 0);
try_connect("Client: prefix match", port++, DEFAULT_TEST_PASSWORD,
network_addr, 16, 100, 100, TEST_CNT_GOOD, 0);
return NULL;
}
int main(int argc, char *argv[])
{
test_init(21, server_fn, client_fn);
return 0;
}
// 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;
}
icmps-discard.c
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
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,
};
// SPDX-License-Identifier: GPL-2.0
/* Author: Dmitry Safonov <dima@arista.com> */
/* 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 <inttypes.h>
#include "aolib.h"
const size_t nr_packets = 20;
const size_t msg_len = 100;
const size_t quota = nr_packets * msg_len;
#define fault(type) (inj == FAULT_ ## type)
static void try_server_run(const char *tst_name, unsigned int port,
fault_t inj, test_cnt cnt_expected)
{
const char *cnt_name = "TCPAOGood";
struct tcp_ao_counters ao1, ao2;
uint64_t before_cnt, after_cnt;
int sk, lsk;
time_t timeout;
ssize_t bytes;
if (fault(TIMEOUT))
cnt_name = "TCPAOBad";
lsk = test_listen_socket(this_ip_addr, 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(); /* 1: MKT added => connect() */
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(); /* 2: accepted => send data */
close(lsk);
bytes = test_server_run(sk, quota, TEST_TIMEOUT_SEC);
if (bytes != quota) {
test_fail("%s: server served: %zd", tst_name, bytes);
goto out;
}
before_cnt = netstat_get_one(cnt_name, NULL);
if (test_get_tcp_ao_counters(sk, &ao1))
test_error("test_get_tcp_ao_counters()");
timeout = fault(TIMEOUT) ? TEST_RETRANSMIT_SEC : TEST_TIMEOUT_SEC;
bytes = test_server_run(sk, quota, timeout);
if (fault(TIMEOUT)) {
if (bytes > 0)
test_fail("%s: server served: %zd", tst_name, bytes);
else
test_ok("%s: server couldn't serve", tst_name);
} else {
if (bytes != quota)
test_fail("%s: server served: %zd", tst_name, bytes);
else
test_ok("%s: server alive", tst_name);
}
if (test_get_tcp_ao_counters(sk, &ao2))
test_error("test_get_tcp_ao_counters()");
after_cnt = netstat_get_one(cnt_name, NULL);
test_tcp_ao_counters_cmp(tst_name, &ao1, &ao2, cnt_expected);
if (after_cnt <= before_cnt) {
test_fail("%s: %s counter did not increase: %zu <= %zu",
tst_name, cnt_name, after_cnt, before_cnt);
} else {
test_ok("%s: counter %s increased %zu => %zu",
tst_name, cnt_name, before_cnt, after_cnt);
}
/*
* Before close() as that will send FIN and move the peer in TCP_CLOSE
* and that will prevent reading AO counters from the peer's socket.
*/
synchronize_threads(); /* 3: verified => closed */
out:
close(sk);
}
static void *server_fn(void *arg)
{
unsigned int port = test_server_port;
try_server_run("TCP-AO migrate to another socket", port++,
0, TEST_CNT_GOOD);
try_server_run("TCP-AO with wrong send ISN", port++,
FAULT_TIMEOUT, TEST_CNT_BAD);
try_server_run("TCP-AO with wrong receive ISN", port++,
FAULT_TIMEOUT, TEST_CNT_BAD);
try_server_run("TCP-AO with wrong send SEQ ext number", port++,
FAULT_TIMEOUT, TEST_CNT_BAD);
try_server_run("TCP-AO with wrong receive SEQ ext number", port++,
FAULT_TIMEOUT, TEST_CNT_NS_BAD | TEST_CNT_GOOD);
synchronize_threads(); /* don't race to exit: client exits */
return NULL;
}
static void test_get_sk_checkpoint(unsigned int server_port, sockaddr_af *saddr,
struct tcp_sock_state *img,
struct tcp_ao_repair *ao_img)
{
int sk;
sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
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(); /* 1: MKT added => connect() */
if (test_connect_socket(sk, this_ip_dest, server_port) <= 0)
test_error("failed to connect()");
synchronize_threads(); /* 2: accepted => send data */
if (test_client_verify(sk, msg_len, nr_packets, TEST_TIMEOUT_SEC))
test_fail("pre-migrate verify failed");
test_enable_repair(sk);
test_sock_checkpoint(sk, img, saddr);
test_ao_checkpoint(sk, ao_img);
test_kill_sk(sk);
}
static void test_sk_restore(const char *tst_name, unsigned int server_port,
sockaddr_af *saddr, struct tcp_sock_state *img,
struct tcp_ao_repair *ao_img,
fault_t inj, test_cnt cnt_expected)
{
const char *cnt_name = "TCPAOGood";
struct tcp_ao_counters ao1, ao2;
uint64_t before_cnt, after_cnt;
time_t timeout;
int sk;
if (fault(TIMEOUT))
cnt_name = "TCPAOBad";
before_cnt = netstat_get_one(cnt_name, NULL);
sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
if (sk < 0)
test_error("socket()");
test_enable_repair(sk);
test_sock_restore(sk, img, saddr, this_ip_dest, server_port);
if (test_add_repaired_key(sk, DEFAULT_TEST_PASSWORD, 0, this_ip_dest, -1, 100, 100))
test_error("setsockopt(TCP_AO_ADD_KEY)");
test_ao_restore(sk, ao_img);
if (test_get_tcp_ao_counters(sk, &ao1))
test_error("test_get_tcp_ao_counters()");
test_disable_repair(sk);
test_sock_state_free(img);
timeout = fault(TIMEOUT) ? TEST_RETRANSMIT_SEC : TEST_TIMEOUT_SEC;
if (test_client_verify(sk, msg_len, nr_packets, timeout)) {
if (fault(TIMEOUT))
test_ok("%s: post-migrate connection is broken", tst_name);
else
test_fail("%s: post-migrate connection is working", tst_name);
} else {
if (fault(TIMEOUT))
test_fail("%s: post-migrate connection still working", tst_name);
else
test_ok("%s: post-migrate connection is alive", tst_name);
}
if (test_get_tcp_ao_counters(sk, &ao2))
test_error("test_get_tcp_ao_counters()");
after_cnt = netstat_get_one(cnt_name, NULL);
test_tcp_ao_counters_cmp(tst_name, &ao1, &ao2, cnt_expected);
if (after_cnt <= before_cnt) {
test_fail("%s: %s counter did not increase: %zu <= %zu",
tst_name, cnt_name, after_cnt, before_cnt);
} else {
test_ok("%s: counter %s increased %zu => %zu",
tst_name, cnt_name, before_cnt, after_cnt);
}
synchronize_threads(); /* 3: verified => closed */
close(sk);
}
static void *client_fn(void *arg)
{
unsigned int port = test_server_port;
struct tcp_sock_state tcp_img;
struct tcp_ao_repair ao_img;
sockaddr_af saddr;
test_get_sk_checkpoint(port, &saddr, &tcp_img, &ao_img);
test_sk_restore("TCP-AO migrate to another socket", port++,
&saddr, &tcp_img, &ao_img, 0, TEST_CNT_GOOD);
test_get_sk_checkpoint(port, &saddr, &tcp_img, &ao_img);
ao_img.snt_isn += 1;
test_sk_restore("TCP-AO with wrong send ISN", port++,
&saddr, &tcp_img, &ao_img, FAULT_TIMEOUT, TEST_CNT_BAD);
test_get_sk_checkpoint(port, &saddr, &tcp_img, &ao_img);
ao_img.rcv_isn += 1;
test_sk_restore("TCP-AO with wrong receive ISN", port++,
&saddr, &tcp_img, &ao_img, FAULT_TIMEOUT, TEST_CNT_BAD);
test_get_sk_checkpoint(port, &saddr, &tcp_img, &ao_img);
ao_img.snd_sne += 1;
test_sk_restore("TCP-AO with wrong send SEQ ext number", port++,
&saddr, &tcp_img, &ao_img, FAULT_TIMEOUT,
TEST_CNT_NS_BAD | TEST_CNT_GOOD);
test_get_sk_checkpoint(port, &saddr, &tcp_img, &ao_img);
ao_img.rcv_sne += 1;
test_sk_restore("TCP-AO with wrong receive SEQ ext number", port++,
&saddr, &tcp_img, &ao_img, FAULT_TIMEOUT,
TEST_CNT_NS_GOOD | TEST_CNT_BAD);
return NULL;
}
int main(int argc, char *argv[])
{
test_init(20, server_fn, client_fn);
return 0;
}
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0
/* Author: Dmitry Safonov <dima@arista.com> */
#include <inttypes.h>
#include "aolib.h"
static union tcp_addr local_addr;
static void __setup_lo_intf(const char *lo_intf,
const char *addr_str, uint8_t prefix)
{
if (inet_pton(TEST_FAMILY, addr_str, &local_addr) != 1)
test_error("Can't convert local ip address");
if (ip_addr_add(lo_intf, TEST_FAMILY, local_addr, prefix))
test_error("Failed to add %s ip address", lo_intf);
if (link_set_up(lo_intf))
test_error("Failed to bring %s up", lo_intf);
}
static void setup_lo_intf(const char *lo_intf)
{
#ifdef IPV6_TEST
__setup_lo_intf(lo_intf, "::1", 128);
#else
__setup_lo_intf(lo_intf, "127.0.0.1", 8);
#endif
}
static void tcp_self_connect(const char *tst, unsigned int port,
bool different_keyids, bool check_restore)
{
uint64_t before_challenge_ack, after_challenge_ack;
uint64_t before_syn_challenge, after_syn_challenge;
struct tcp_ao_counters before_ao, after_ao;
uint64_t before_aogood, after_aogood;
struct netstat *ns_before, *ns_after;
const size_t nr_packets = 20;
struct tcp_ao_repair ao_img;
struct tcp_sock_state img;
sockaddr_af addr;
int sk;
tcp_addr_to_sockaddr_in(&addr, &local_addr, htons(port));
sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
if (sk < 0)
test_error("socket()");
if (different_keyids) {
if (test_add_key(sk, DEFAULT_TEST_PASSWORD, local_addr, -1, 5, 7))
test_error("setsockopt(TCP_AO_ADD_KEY)");
if (test_add_key(sk, DEFAULT_TEST_PASSWORD, local_addr, -1, 7, 5))
test_error("setsockopt(TCP_AO_ADD_KEY)");
} else {
if (test_add_key(sk, DEFAULT_TEST_PASSWORD, local_addr, -1, 100, 100))
test_error("setsockopt(TCP_AO_ADD_KEY)");
}
if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0)
test_error("bind()");
ns_before = netstat_read();
before_aogood = netstat_get(ns_before, "TCPAOGood", NULL);
before_challenge_ack = netstat_get(ns_before, "TCPChallengeACK", NULL);
before_syn_challenge = netstat_get(ns_before, "TCPSYNChallenge", NULL);
if (test_get_tcp_ao_counters(sk, &before_ao))
test_error("test_get_tcp_ao_counters()");
if (__test_connect_socket(sk, "lo", (struct sockaddr *)&addr,
sizeof(addr), TEST_TIMEOUT_SEC) < 0) {
ns_after = netstat_read();
netstat_print_diff(ns_before, ns_after);
test_error("failed to connect()");
}
if (test_client_verify(sk, 100, nr_packets, TEST_TIMEOUT_SEC)) {
test_fail("%s: tcp connection verify failed", tst);
close(sk);
return;
}
ns_after = netstat_read();
after_aogood = netstat_get(ns_after, "TCPAOGood", NULL);
after_challenge_ack = netstat_get(ns_after, "TCPChallengeACK", NULL);
after_syn_challenge = netstat_get(ns_after, "TCPSYNChallenge", NULL);
if (test_get_tcp_ao_counters(sk, &after_ao))
test_error("test_get_tcp_ao_counters()");
if (!check_restore) {
/* to debug: netstat_print_diff(ns_before, ns_after); */
netstat_free(ns_before);
}
netstat_free(ns_after);
if (after_aogood <= before_aogood) {
test_fail("%s: TCPAOGood counter mismatch: %zu <= %zu",
tst, after_aogood, before_aogood);
close(sk);
return;
}
if (after_challenge_ack <= before_challenge_ack ||
after_syn_challenge <= before_syn_challenge) {
/*
* It's also meant to test simultaneous open, so check
* these counters as well.
*/
test_fail("%s: Didn't challenge SYN or ACK: %zu <= %zu OR %zu <= %zu",
tst, after_challenge_ack, before_challenge_ack,
after_syn_challenge, before_syn_challenge);
close(sk);
return;
}
if (test_tcp_ao_counters_cmp(tst, &before_ao, &after_ao, TEST_CNT_GOOD)) {
close(sk);
return;
}
if (!check_restore) {
test_ok("%s: connect TCPAOGood %" PRIu64 " => %" PRIu64,
tst, before_aogood, after_aogood);
close(sk);
return;
}
test_enable_repair(sk);
test_sock_checkpoint(sk, &img, &addr);
#ifdef IPV6_TEST
addr.sin6_port = htons(port + 1);
#else
addr.sin_port = htons(port + 1);
#endif
test_ao_checkpoint(sk, &ao_img);
test_kill_sk(sk);
sk = socket(test_family, SOCK_STREAM, IPPROTO_TCP);
if (sk < 0)
test_error("socket()");
test_enable_repair(sk);
__test_sock_restore(sk, "lo", &img, &addr, &addr, sizeof(addr));
if (different_keyids) {
if (test_add_repaired_key(sk, DEFAULT_TEST_PASSWORD, 0,
local_addr, -1, 7, 5))
test_error("setsockopt(TCP_AO_ADD_KEY)");
if (test_add_repaired_key(sk, DEFAULT_TEST_PASSWORD, 0,
local_addr, -1, 5, 7))
test_error("setsockopt(TCP_AO_ADD_KEY)");
} else {
if (test_add_repaired_key(sk, DEFAULT_TEST_PASSWORD, 0,
local_addr, -1, 100, 100))
test_error("setsockopt(TCP_AO_ADD_KEY)");
}
test_ao_restore(sk, &ao_img);
test_disable_repair(sk);
test_sock_state_free(&img);
if (test_client_verify(sk, 100, nr_packets, TEST_TIMEOUT_SEC)) {
test_fail("%s: tcp connection verify failed", tst);
close(sk);
return;
}
ns_after = netstat_read();
after_aogood = netstat_get(ns_after, "TCPAOGood", NULL);
/* to debug: netstat_print_diff(ns_before, ns_after); */
netstat_free(ns_before);
netstat_free(ns_after);
close(sk);
if (after_aogood <= before_aogood) {
test_fail("%s: TCPAOGood counter mismatch: %zu <= %zu",
tst, after_aogood, before_aogood);
return;
}
test_ok("%s: connect TCPAOGood %" PRIu64 " => %" PRIu64,
tst, before_aogood, after_aogood);
}
static void *client_fn(void *arg)
{
unsigned int port = test_server_port;
setup_lo_intf("lo");
tcp_self_connect("self-connect(same keyids)", port++, false, false);
tcp_self_connect("self-connect(different keyids)", port++, true, false);
tcp_self_connect("self-connect(restore)", port, false, true);
port += 2;
tcp_self_connect("self-connect(restore, different keyids)", port, true, true);
port += 2;
return NULL;
}
int main(int argc, char *argv[])
{
test_init(4, client_fn, NULL);
return 0;
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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