mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-18 15:19:49 +00:00

Currently, tcp_ao tests have two timeouts: TEST_RETRANSMIT_SEC and
TEST_TIMEOUT_SEC [by default 1 and 5 seconds]. The first one,
TEST_RETRANSMIT_SEC is used for operations that are expected to succeed
in order for a test to pass. It is usually not consumed and exists only
to avoid indefinite test run if the operation didn't complete.
The second one, TEST_RETRANSMIT_SEC exists for the tests that checking
operations, that are expected to fail/timeout. It is shorter as it is
fully consumed, with an expectation that if operation didn't succeed
during that period, it will timeout. And the related test that expects
the timeout is passing. The actual operation failure is then
cross-verified by other means like counters checks.
The issue with TEST_RETRANSMIT_SEC timeout is that 1 second is the exact
initial TCP timeout. So, in case the initial segment gets lost (quite
unlikely on local veth interface between two net namespaces, yet happens
in slow VMs), the retransmission never happens and as a result, the test
is not actually testing the functionality. Which in the end fails
counters checks.
As I want tcp_ao selftests to be fast and finishing in a reasonable
amount of time on manual run, I didn't consider increasing
TEST_RETRANSMIT_SEC.
Rather, initially, BPF_SOCK_OPS_TIMEOUT_INIT looked promising as a lever
to make the initial TCP timeout shorter. But as it's not a socket bpf
attached thing, but sock_ops (attaches to cgroups), the selftests would
have to use libbpf, which I wanted to avoid if not absolutely required.
Instead, use a mixed select() and counters polling mode with the longer
TEST_TIMEOUT_SEC timeout to detect running-away failed tests. It
actually not only allows losing segments and succeeding after
the previous TEST_RETRANSMIT_SEC timeout was consumed, but makes
the tests expecting timeout/failure pass faster.
The only test case taking longer (TEST_TIMEOUT_SEC) now is connect-deny
"wrong snd id", which checks for no key on SYN-ACK for which there is no
counter in the kernel (see tcp_make_synack()). Yet it can be speed up
by poking skpair from the trace event (see trace_tcp_ao_synack_no_key).
Fixes: ed9d09b309
("selftests/net: Add a test for TCP-AO keys matching")
Reported-by: Jakub Kicinski <kuba@kernel.org>
Closes: https://lore.kernel.org/netdev/20241205070656.6ef344d7@kernel.org/
Signed-off-by: Dmitry Safonov <0x7f454c46@gmail.com>
Link: https://patch.msgid.link/20250319-tcp-ao-selftests-polling-v2-4-da48040153d1@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
292 lines
9.2 KiB
C
292 lines
9.2 KiB
C
// 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 volatile int sk_pair;
|
|
|
|
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_counters cnt1, cnt2;
|
|
uint64_t before_cnt = 0, after_cnt = 0; /* silence GCC */
|
|
test_cnt poll_cnt = (cnt_expected == TEST_CNT_GOOD) ? 0 : cnt_expected;
|
|
int lsk, err, sk = 0;
|
|
|
|
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_counters(lsk, &cnt1))
|
|
test_error("test_get_tcp_counters()");
|
|
|
|
synchronize_threads(); /* preparations done */
|
|
|
|
err = test_skpair_wait_poll(lsk, 0, poll_cnt, &sk_pair);
|
|
if (err == -ETIMEDOUT) {
|
|
sk_pair = err;
|
|
if (!fault(TIMEOUT))
|
|
test_fail("%s: timed out for accept()", tst_name);
|
|
} else if (err == -EKEYREJECTED) {
|
|
if (!fault(KEYREJECT))
|
|
test_fail("%s: key was rejected", tst_name);
|
|
} else if (err < 0) {
|
|
test_error("test_skpair_wait_poll()");
|
|
} else {
|
|
if (fault(TIMEOUT))
|
|
test_fail("%s: ready to accept", tst_name);
|
|
|
|
sk = accept(lsk, NULL, NULL);
|
|
if (sk < 0) {
|
|
test_error("accept()");
|
|
} else {
|
|
if (fault(TIMEOUT))
|
|
test_fail("%s: accepted", tst_name);
|
|
}
|
|
}
|
|
|
|
synchronize_threads(); /* before counter checks */
|
|
if (pwd && test_get_tcp_counters(lsk, &cnt2))
|
|
test_error("test_get_tcp_counters()");
|
|
|
|
close(lsk);
|
|
|
|
if (pwd)
|
|
test_assert_counters(tst_name, &cnt1, &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: %" PRIu64 " <= %" PRIu64,
|
|
tst_name, cnt_name, after_cnt, before_cnt);
|
|
} else {
|
|
test_ok("%s: counter %s increased %" PRIu64 " => %" PRIu64,
|
|
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", TEST_CNT_NS_KEY_NOT_FOUND, 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);
|
|
|
|
/* Key rejected by the other side, failing short through skpair */
|
|
try_accept("Client: Wrong addr", port++, NULL,
|
|
this_ip_dest, -1, 100, 100, 0, NULL, 0, FAULT_KEYREJECT);
|
|
|
|
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_counters cnt1, cnt2;
|
|
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_counters(sk, &cnt1))
|
|
test_error("test_get_tcp_counters()");
|
|
|
|
synchronize_threads(); /* preparations done */
|
|
|
|
ret = test_skpair_connect_poll(sk, this_ip_dest, port, cnt_expected, &sk_pair);
|
|
synchronize_threads(); /* before counter checks */
|
|
if (ret < 0) {
|
|
sk_pair = ret;
|
|
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_counters(sk, &cnt2))
|
|
test_error("test_get_tcp_counters()");
|
|
test_assert_counters(tst_name, &cnt1, &cnt2, cnt_expected);
|
|
} else if (pwd) {
|
|
test_tcp_counters_free(&cnt1);
|
|
}
|
|
out:
|
|
synchronize_threads(); /* close() */
|
|
|
|
if (ret > 0)
|
|
close(sk);
|
|
}
|
|
|
|
static void *client_fn(void *arg)
|
|
{
|
|
union tcp_addr wrong_addr, network_addr, addr_any = {};
|
|
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);
|
|
|
|
trace_ao_event_expect(TCP_AO_KEY_NOT_FOUND, this_ip_addr, this_ip_dest,
|
|
-1, port, 0, 0, 1, 0, 0, 0, 100, 100, -1);
|
|
try_connect("Non-AO server + AO client", port++, DEFAULT_TEST_PASSWORD,
|
|
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
|
|
|
|
trace_hash_event_expect(TCP_HASH_AO_REQUIRED, this_ip_addr, this_ip_dest,
|
|
-1, port, 0, 0, 1, 0, 0, 0);
|
|
try_connect("AO server + Non-AO client", port++, NULL,
|
|
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
|
|
|
|
trace_ao_event_expect(TCP_AO_MISMATCH, this_ip_addr, this_ip_dest,
|
|
-1, port, 0, 0, 1, 0, 0, 0, 100, 100, -1);
|
|
try_connect("Wrong password", port++, DEFAULT_TEST_PASSWORD,
|
|
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
|
|
|
|
trace_ao_event_expect(TCP_AO_KEY_NOT_FOUND, this_ip_addr, this_ip_dest,
|
|
-1, port, 0, 0, 1, 0, 0, 0, 100, 100, -1);
|
|
try_connect("Wrong rcv id", port++, DEFAULT_TEST_PASSWORD,
|
|
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
|
|
|
|
/*
|
|
* XXX: The test doesn't increase any counters, see tcp_make_synack().
|
|
* Potentially, it can be speed up by setting sk_pair = -ETIMEDOUT
|
|
* but the price would be increased complexity of the tracer thread.
|
|
*/
|
|
trace_ao_event_sk_expect(TCP_AO_SYNACK_NO_KEY, this_ip_dest, addr_any,
|
|
port, 0, 100, 100);
|
|
try_connect("Wrong snd id", port++, DEFAULT_TEST_PASSWORD,
|
|
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
|
|
|
|
trace_ao_event_expect(TCP_AO_WRONG_MACLEN, this_ip_addr, this_ip_dest,
|
|
-1, port, 0, 0, 1, 0, 0, 0, 100, 100, -1);
|
|
try_connect("Different maclen", port++, DEFAULT_TEST_PASSWORD,
|
|
this_ip_dest, -1, 100, 100, 0, FAULT_TIMEOUT);
|
|
|
|
trace_ao_event_expect(TCP_AO_KEY_NOT_FOUND, this_ip_addr, this_ip_dest,
|
|
-1, port, 0, 0, 1, 0, 0, 0, 100, 100, -1);
|
|
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(22, server_fn, client_fn);
|
|
return 0;
|
|
}
|