Files
utun2/tests/test_etcp_stcp.c
T
evgeny 90d3206071 Confirm new TCP sessions and bound reciprocal challenge retries
Negotiate new TCP sessions even when the peer epoch is known, and let the retry timer send reciprocal challenges to avoid feedback loops when confirmations are lost.

Cover peer restarts, simultaneous connects, REALITY, confirmation loss, second links, stale confirmations and deadlines. Remove forced client resets and count only validated test payloads in the UDP reconnect test.

Validation: 149 tests passed; proxy, burst and load integration tests passed; Android assembleDebug succeeded.
2026-10-12 21:29:41 +02:00

268 lines
13 KiB
C

// Реальные STCP/REALITY-линки: встречное подключение и рестарт пира без reset выжившего узла.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "etcp.h"
#include "etcp_api.h"
#include "etcp_connections.h"
#include "etcp_session.h"
#include "node_conn_direct.h"
#include "topo_group.h"
#include "topo_node.h"
#include "utun_instance.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "test_clock.h"
#define CHECK(x) do { if (!(x)) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "FAIL %s:%d: %s", __func__, __LINE__, #x); exit(1); } } while (0)
#define TEST_CMD 253
#define MESSAGE_COUNT 16
#define FULL_MASK ((1u << MESSAGE_COUNT) - 1)
struct endpoint {
struct UTUN_INSTANCE* inst;
struct NODE_CONN_DIRECT* owner;
struct ETCP_CONN* conn;
uint64_t id;
uint16_t port;
unsigned received, reinit, deleted;
};
struct message { uint8_t cmd, from, phase, seq, body[64]; };
static struct endpoint endpoints[2];
static struct UASYNC* ua;
static unsigned phase;
static int failed;
static uint64_t stream_packets, session_packets;
static const char* public_keys[2] = {
"b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01",
"ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a"
};
static const char* private_keys[2] = {
"704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f",
"38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68"
};
#ifdef __linux__
// Считаем реальные отправки потока/control: тест ловит шторм независимо от скорости CPU.
int __real_etcp_encrypt_send(struct ETCP_DGRAM* packet);
int __wrap_etcp_encrypt_send(struct ETCP_DGRAM* packet) {
if (packet && packet->data_len) {
if (packet->data[0] <= ETCP_SECTION_ACK) stream_packets++;
if (packet->data[0] >= ETCP_SESSION_HELLO && packet->data[0] <= ETCP_SESSION_CONFIRM) session_packets++;
}
return __real_etcp_encrypt_send(packet);
}
#endif
static void fill_message(struct message* message, int from, unsigned seq) {
message->cmd = TEST_CMD; message->from = from; message->phase = phase; message->seq = seq;
for (unsigned i = 0; i < sizeof(message->body); i++) message->body[i] = (phase * 17 + seq * 3 + i) ^ from;
}
static void receive_message(struct ETCP_CONN* conn, struct ll_entry* entry) {
int to = conn->instance == endpoints[0].inst ? 0 : 1;
struct message expected;
int valid = entry->len == sizeof(expected) && entry->dgram;
const struct message* received = valid ? (const struct message*)entry->dgram : NULL;
if (valid) valid = received->phase == phase && received->from == 1 - to && received->seq < MESSAGE_COUNT;
if (valid) {
fill_message(&expected, 1 - to, received->seq);
valid = !memcmp(received, &expected, sizeof(expected)) && !(endpoints[to].received & (1u << received->seq));
}
if (valid) endpoints[to].received |= 1u << received->seq;
else { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "Invalid/duplicate message: to=%d phase=%u bytes=%u", to, phase, entry->len); failed = 1; }
queue_dgram_free(entry); queue_entry_free(entry);
}
static void connection_event(struct ETCP_CONN* conn, int event, void* arg) {
(void)conn;
struct endpoint* endpoint = arg;
if (event == ETCP_CBK_EVENT_REINIT) endpoint->reinit++;
if (event == ETCP_CBK_EVENT_DELETE) endpoint->deleted++;
}
static void connect_done(void* arg, struct ETCP_CONN* conn, int type) {
struct endpoint* endpoint = arg;
if (!conn) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "Connection failed: local=%016llx", (unsigned long long)endpoint->id); failed = 1; }
else if (type & ETCP_CONNECT_EARLY) endpoint->conn = conn;
}
static void start_endpoint(int i, int reality) {
struct endpoint* endpoint = &endpoints[i];
struct SC_MYKEYS keys;
CHECK(sc_init_local_keys(&keys, public_keys[i], private_keys[i]) == SC_OK);
uint64_t id = sc_derive_node_id_from_pubkey(keys.public_key);
char config[2048], reality_config[512] = "";
if (reality) snprintf(reality_config, sizeof(reality_config),
"[reality]\nenabled=1\nprivate_key=%s\nshort_ids=0102030405060708\nserver_name=localhost.test\n"
"dest=127.0.0.1:1\nfingerprint=openssl\n", private_keys[i]);
snprintf(config, sizeof(config),
"[global]\nmy_node_id=0x%016llx\nmy_node_name=transport-%d\nmy_public_key=%s\nmy_private_key=%s\n"
"[server:stcp]\naddr=127.0.0.1:%u\ntype=public\ntransport=tcp\nreality=%d\n%s",
(unsigned long long)id, i, public_keys[i], private_keys[i], endpoint->port, reality, reality_config);
endpoint->inst = utun_instance_create_from_str(ua, config); CHECK(endpoint->inst);
CHECK(utun_instance_init(endpoint->inst) >= 0);
CHECK(etcp_bind(endpoint->inst, TEST_CMD, receive_message) == 0);
endpoint->id = endpoint->inst->node_id;
struct ETCP_SOCKET* socket = endpoint->inst->etcp_sockets;
while (socket && !socket->is_tcp) socket = socket->next;
CHECK(socket && socket->local_addr.ss_family == AF_INET);
endpoint->port = ntohs(((struct sockaddr_in*)&socket->local_addr)->sin_port); CHECK(endpoint->port);
endpoint->conn = NULL; endpoint->owner = NULL; endpoint->received = endpoint->reinit = endpoint->deleted = 0;
}
static void stop_endpoint(int i) {
struct endpoint* endpoint = &endpoints[i];
if (endpoint->owner) { node_conn_direct_close(endpoint->owner); endpoint->owner = NULL; }
endpoint->inst->running = 0;
utun_instance_destroy(endpoint->inst); endpoint->inst = NULL; endpoint->conn = NULL;
}
static struct TOPO_NODE* import_peer(int to) {
struct UTUN_INSTANCE* from = endpoints[1-to].inst;
struct TOPO_GROUP_NODE* local = topo_groups_get_default(from->topo_groups)->local_node;
struct TOPO_NODE* info = topo_node_registry_find(from->topo_groups, from->node_id); CHECK(info);
uint8_t wire[4096]; int len = topo_node_serialize(info, local, 0, 0, wire, sizeof(wire), 0); CHECK(len > 0);
struct TOPO_NODE* imported = NULL;
CHECK(topo_node_deserialize(topo_groups_get_default(endpoints[to].inst->topo_groups), wire, len,
&imported, NULL, NULL, NULL, NULL) == 0);
CHECK(topo_node_registry_store(endpoints[to].inst->topo_groups, imported));
return imported;
}
static int pair_ready(void) {
for (int i = 0; i < 2; i++) {
endpoints[i].conn = instance_find_conn(endpoints[i].inst, endpoints[1-i].id);
struct ETCP_CONN* conn = endpoints[i].conn;
if (!conn || !conn->initialized || !conn->links_up || conn->session_required || conn->reinit_pending) return 0;
}
return endpoints[0].conn->peer_reset_id == endpoints[1].conn->reset_id &&
endpoints[1].conn->peer_reset_id == endpoints[0].conn->reset_id;
}
static void wait_pair(void) {
uint64_t end = get_time_tb() + 50000;
while (!failed && get_time_tb() < end) {
test_clock_poll(ua, 100);
if (pair_ready()) return;
}
DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "Session recovery timed out: phase=%u stream=%llu control=%llu", phase,
(unsigned long long)stream_packets, (unsigned long long)session_packets);
CHECK(0);
}
static void check_unique(int i) {
unsigned count = 0;
for (struct ll_entry* e = endpoints[i].inst->connections->head; e; e = e->next)
if (((struct conn_queue_entry*)e->data)->peer_node_id == endpoints[1-i].id) count++;
CHECK(count == 1);
}
static void hold_connection(int i) {
struct endpoint* endpoint = &endpoints[i];
struct TOPO_NODE* peer = topo_node_registry_find(endpoint->inst->topo_groups, endpoints[1-i].id); CHECK(peer);
CHECK(node_conn_direct_open_node(endpoint->inst, peer->node_id, NULL, NULL, &endpoint->owner, peer, NULL) == NCD_REUSED);
CHECK(node_conn_direct_get_conn(endpoint->owner) == endpoint->conn);
etcp_conn_add_cbk(endpoint->conn, connection_event, endpoint, ETCP_CBK_EVENT_REINIT | ETCP_CBK_EVENT_DELETE);
}
static void exchange_messages(void) {
for (int i = 0; i < 2; i++) endpoints[i].received = 0;
for (int i = 0; i < 2; i++) for (unsigned seq = 0; seq < MESSAGE_COUNT; seq++) {
struct ll_entry* entry = ll_alloc_lldgram(sizeof(struct message)); CHECK(entry);
fill_message((struct message*)entry->dgram, i, seq); entry->len = sizeof(struct message);
CHECK(etcp_send(endpoints[i].conn, entry) == 0);
}
uint64_t end = get_time_tb() + 30000;
while (!failed && get_time_tb() < end) {
test_clock_poll(ua, 100);
if (endpoints[0].received != FULL_MASK || endpoints[1].received != FULL_MASK) continue;
int drained = 1;
for (int i = 0; i < 2; i++) {
struct ETCP_CONN* conn = endpoints[i].conn;
if (conn->input_wait_ack->count || conn->input_send_q->count || conn->input_queue->count || conn->send_input_q->count) drained = 0;
}
if (drained) break;
}
CHECK(!failed && endpoints[0].received == FULL_MASK && endpoints[1].received == FULL_MASK);
for (int i = 0; i < 2; i++) CHECK(!endpoints[i].conn->input_wait_ack->count && !endpoints[i].conn->input_send_q->count);
}
static void run_case(int reality, int simultaneous, int restarted) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Transport case: reality=%d simultaneous=%d restart=%d", reality, simultaneous, restarted);
memset(endpoints, 0, sizeof(endpoints)); phase = 0; failed = 0; stream_packets = session_packets = 0;
ua = uasync_create(); CHECK(ua);
start_endpoint(0, reality); start_endpoint(1, reality);
struct TOPO_NODE* peers[2] = {import_peer(0), import_peer(1)};
for (int i = 0; i <= simultaneous; i++) {
struct TOPO_GROUP_NODE node = {0}; node.node_id = peers[i]->node_id;
CHECK(etcp_connect(endpoints[i].inst, &node, connect_done, &endpoints[i], ETCP_CONNECT_EARLY | ETCP_CONNECT_LATE) == 0);
}
wait_pair();
for (int i = 0; i < 2; i++) { check_unique(i); hold_connection(i); }
// Повторные владельцы и обновление адресов не создают дополнительные линки.
unsigned links = 0; for (struct ETCP_LINK* l = endpoints[0].conn->links; l; l = l->next) links++;
struct NODE_CONN_DIRECT* extra = NULL;
CHECK(node_conn_direct_open_node(endpoints[0].inst, peers[0]->node_id, NULL, NULL, &extra, peers[0], NULL) == NCD_REUSED);
CHECK(node_conn_direct_update_node(endpoints[0].inst, peers[0]->node_id) == 0);
unsigned updated = 0; for (struct ETCP_LINK* l = endpoints[0].conn->links; l; l = l->next) updated++;
CHECK(updated == links); node_conn_direct_close(extra);
exchange_messages();
if (restarted < 2) {
int survivor = 1 - restarted;
struct ETCP_CONN* retained = endpoints[survivor].conn;
CHECK(etcp_conn_ref_take(retained) == 0);
uint64_t local_epoch = retained->reset_id, peer_epoch = retained->peer_reset_id;
unsigned before = endpoints[survivor].reinit;
uint64_t packets_before = stream_packets;
stop_endpoint(restarted); start_endpoint(restarted, reality);
phase++; wait_pair();
CHECK(!endpoints[survivor].deleted && endpoints[survivor].conn == retained);
CHECK(retained->reset_id == local_epoch && retained->peer_reset_id != peer_epoch);
CHECK(endpoints[survivor].reinit == before + 1);
CHECK(node_conn_direct_get_conn(endpoints[survivor].owner) == retained);
#ifdef __linux__
CHECK(stream_packets - packets_before < 256);
#endif
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "Recovery complete: stream=%llu control=%llu REINIT=1 survivor_epoch=%016llx",
(unsigned long long)(stream_packets - packets_before), (unsigned long long)session_packets,
(unsigned long long)local_epoch);
etcp_conn_ref_free(retained);
check_unique(0); check_unique(1); exchange_messages();
} else {
uint64_t old_epochs[2] = {endpoints[0].conn->reset_id, endpoints[1].conn->reset_id};
stop_endpoint(0); stop_endpoint(1); start_endpoint(0, reality); start_endpoint(1, reality);
peers[0] = import_peer(0); peers[1] = import_peer(1);
for (int i = 0; i < 2; i++) {
struct TOPO_GROUP_NODE node = {0}; node.node_id = peers[i]->node_id;
CHECK(etcp_connect(endpoints[i].inst, &node, connect_done, &endpoints[i], ETCP_CONNECT_EARLY) == 0);
}
phase++; wait_pair();
for (int i = 0; i < 2; i++) { CHECK(endpoints[i].conn->reset_id != old_epochs[i]); check_unique(i); }
exchange_messages();
}
stop_endpoint(0); stop_endpoint(1);
uasync_poll(ua, 0);
CHECK(ua->timer_alloc_count == ua->timer_free_count && ua->socket_alloc_count == ua->socket_free_count);
uasync_destroy(ua, 0);
printf("PASS transport reality=%d simultaneous=%d restart=%d: epochs, bidirectional delivery and resource balance\n",
reality, simultaneous, restarted);
}
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
if (getenv("UTUN_TEST_DEBUG")) {
debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_INFO);
}
test_clock_init(100); utun_instance_set_tun_init_enabled(0);
for (int reality = 0; reality < 2; reality++) {
for (int simultaneous = 0; simultaneous < 2; simultaneous++)
for (int restarted = 0; restarted < 2; restarted++) run_case(reality, simultaneous, restarted);
run_case(reality, 1, 2);
}
test_clock_finish();
return 0;
}