mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/
synced 2025-04-19 20:58:31 +09:00

self-connect-ipv6 got slightly flaky on netdev: > # timeout set to 120 > # selftests: net/tcp_ao: self-connect_ipv6 > # 1..5 > # # 708[lib/setup.c:250] rand seed 1742872572 > # TAP version 13 > # # 708[lib/proc.c:213] Snmp6 Ip6OutNoRoutes: 0 => 1 > # not ok 1 # error 708[self-connect.c:70] failed to connect() > # ok 2 No unexpected trace events during the test run > # # Planned tests != run tests (5 != 2) > # # Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:1 > ok 1 selftests: net/tcp_ao: self-connect_ipv6 I can not reproduce it on my machines, but judging by "Ip6OutNoRoutes" there is no route to the local_addr (::1). Looking at the kernel code, I see that kernel does add link-local address automatically in init_loopback(), but that is called from ipv6 notifier block. So, in turn the userspace that brought up the loopback interface may see rtnetlink ACK earlier than addrconf_notify() does it's job (at least, on a slow VM such as netdev). Probably, for ipv4 it's the same, judging by inetdev_event(). The fix is quite simple: set the link-local route straight after bringing the loopback interface. That will make it synchronous. Signed-off-by: Dmitry Safonov <0x7f454c46@gmail.com> Link: https://patch.msgid.link/20250402-tcp-ao-selfconnect-flake-v1-1-8388d629ef3d@gmail.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
192 lines
5.6 KiB
C
192 lines
5.6 KiB
C
// 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);
|
|
|
|
if (ip_route_add(lo_intf, TEST_FAMILY, local_addr, local_addr))
|
|
test_error("Failed to add a local route %s", 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)
|
|
{
|
|
struct tcp_counters before, after;
|
|
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);
|
|
if (test_get_tcp_counters(sk, &before))
|
|
test_error("test_get_tcp_counters()");
|
|
|
|
if (__test_connect_socket(sk, "lo", (struct sockaddr *)&addr,
|
|
sizeof(addr), 0) < 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_fail("%s: tcp connection verify failed", tst);
|
|
close(sk);
|
|
return;
|
|
}
|
|
|
|
ns_after = netstat_read();
|
|
after_aogood = netstat_get(ns_after, "TCPAOGood", NULL);
|
|
if (test_get_tcp_counters(sk, &after))
|
|
test_error("test_get_tcp_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: %" PRIu64 " <= %" PRIu64,
|
|
tst, after_aogood, before_aogood);
|
|
close(sk);
|
|
return;
|
|
}
|
|
|
|
if (test_assert_counters(tst, &before, &after, 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_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: %" PRIu64 " <= %" PRIu64,
|
|
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);
|
|
|
|
/* expecting rnext to change based on the first segment RNext != Current */
|
|
trace_ao_event_expect(TCP_AO_RNEXT_REQUEST, local_addr, local_addr,
|
|
port, port, 0, -1, -1, -1, -1, -1, 7, 5, -1);
|
|
tcp_self_connect("self-connect(different keyids)", port++, true, false);
|
|
tcp_self_connect("self-connect(restore)", port, false, true);
|
|
port += 2; /* restore test restores over different port */
|
|
trace_ao_event_expect(TCP_AO_RNEXT_REQUEST, local_addr, local_addr,
|
|
port, port, 0, -1, -1, -1, -1, -1, 7, 5, -1);
|
|
/* intentionally on restore they are added to the socket in different order */
|
|
trace_ao_event_expect(TCP_AO_RNEXT_REQUEST, local_addr, local_addr,
|
|
port + 1, port + 1, 0, -1, -1, -1, -1, -1, 5, 7, -1);
|
|
tcp_self_connect("self-connect(restore, different keyids)", port, true, true);
|
|
port += 2; /* restore test restores over different port */
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
test_init(5, client_fn, NULL);
|
|
return 0;
|
|
}
|