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

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
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
*_ipv4
*_ipv6
+59 −0
Original line number Diff line number Diff line
# 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
+358 −0
Original line number Diff line number Diff line
// 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;
}
+264 −0
Original line number Diff line number Diff line
// 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;
}
Loading