Files
utun2/tests/test_etcp_reconnect.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

402 lines
16 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../lib/platform_compat.h"
#include "test_utils.h"
#include "test_net.h"
#ifdef _WIN32
#include <windows.h>
#include <direct.h>
#include <process.h>
#define getpid _getpid
#else
#include <unistd.h>
#endif
#include <time.h>
#include <sys/stat.h>
#include "etcp.h"
#include "etcp_connections.h"
#include "../src/config_parser.h"
#include "../src/utun_instance.h"
#include "routing.h"
#include "../src/tun_if.h"
#include "secure_channel.h"
#include "etcp_dump.h"
#include "../lib/u_async.h"
#include "../lib/ll_queue.h"
#include "../lib/debug_config.h"
#define TEST_TIMEOUT_MS 120000
#define PACKET_SIZE 100
#define TOTAL_PACKETS 500
#define MAX_QUEUE_SIZE 5
static struct UTUN_INSTANCE* server_instance = NULL;
static struct UTUN_INSTANCE* client_instance = NULL;
static struct UASYNC* ua = NULL;
static char temp_dir[] = "/tmp/utun_test_XXXXXX";
static char server_config_path[256];
static char client_config_path[256];
static int server_port = 0;
static int client_port = 0;
static int test_completed = 0;
static void* packet_timeout_id = NULL;
static void* global_timeout_id = NULL;
static int phase = 0;
static int packets_sent = 0;
static int packets_received = 0;
static int restart_action_done = 0;
static int _dump_interval = 50;
static int _dump_timer = 0;
static uint8_t packet_buffer[PACKET_SIZE];
static uint8_t received_sequences[8][TOTAL_PACKETS];
static int write_configs(void) {
FILE* f = fopen(server_config_path, "w");
if (!f) { fprintf(stderr, "Failed to create server config file\n"); return -1; }
fprintf(f,
"[global]\n"
"my_node_id=0x1111111111111111\n"
"my_private_key=38240cb82199e504686507f11f6eaa4f740fde6f0c425c495e49a523019a5d68\n"
"my_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"tun_ip=10.99.0.1/24\n"
"tun_ifname=tun99\n"
"\n"
"[server: test]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"\n"
"[allowed_keys]\n"
"allow_all=1\n",
server_port);
fclose(f);
f = fopen(client_config_path, "w");
if (!f) { fprintf(stderr, "Failed to create client config file\n"); test_unlink(server_config_path); return -1; }
fprintf(f,
"[global]\n"
"my_node_id=0x2222222222222222\n"
"my_private_key=704f2e012c8fa8768130cb0f988a997dccb628372bc5ceccacc78dcbfec5916f\n"
"my_public_key=b3193173def895bd0fcea6f86af077c7d77216f10395275f627ac18242ec0f01\n"
"tun_ip=10.99.0.2/24\n"
"tun_ifname=tun98\n"
"\n"
"[server: test]\n"
"addr=127.0.0.1:%d\n"
"type=public\n"
"\n"
"[client: test_client]\n"
"keepalive=1\n"
"peer_public_key=ce8871f07fa056c636d297115f231b08c29cdf94e0d440fce83a07c34416d36a\n"
"link=test:127.0.0.1:%d\n",
client_port, server_port);
fclose(f);
return 0;
}
static void cleanup_temp_configs(void) {
if (server_config_path[0]) test_unlink(server_config_path);
if (client_config_path[0]) test_unlink(client_config_path);
if (temp_dir[0]) test_rmdir(temp_dir);
}
static int create_temp_configs(void) {
if (test_mkdtemp(temp_dir) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reconnect fixture: mkdtemp failed"); return -1; }
snprintf(server_config_path, sizeof(server_config_path), "%s/server.conf", temp_dir);
snprintf(client_config_path, sizeof(client_config_path), "%s/client.conf", temp_dir);
return write_configs();
}
static int is_connection_established(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->connections) return 0;
struct ll_entry* entry = inst->connections->head;
while (entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
struct ETCP_CONN* conn = ce->conn;
struct UTUN_INSTANCE* peer = inst == client_instance ? server_instance : client_instance;
struct ETCP_CONN* remote = peer ? instance_find_conn(peer, inst->node_id) : NULL;
if (remote && conn->initialized && remote->initialized && !conn->session_required && !remote->session_required &&
!conn->reinit_pending && !remote->reinit_pending && conn->peer_reset_id == remote->reset_id &&
remote->peer_reset_id == conn->reset_id) return 1;
entry = entry->next;
}
return 0;
}
static void drain_received(int count_flag) {
if (!server_instance || !server_instance->connections) return;
struct ll_entry* entry = server_instance->connections->head;
while (entry) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data;
struct ETCP_CONN* conn = ce->conn;
if (conn->output_queue) {
queue_set_callback(conn->output_queue, NULL, NULL);
struct ETCP_FRAGMENT* pkt;
while ((pkt = (struct ETCP_FRAGMENT*)queue_data_get(conn->output_queue)) != NULL) {
// output_queue содержит также служебные кодограммы; считаем только наши маркированные пакеты.
if (count_flag && pkt->ll.len >= 2 && pkt->ll.dgram[0] == 'T' && pkt->ll.dgram[1] == 'R') {
int valid = pkt->ll.len == PACKET_SIZE;
unsigned seq = valid ? pkt->ll.dgram[4] | ((unsigned)pkt->ll.dgram[5] << 8) : TOTAL_PACKETS;
if (valid) valid = pkt->ll.dgram[2] == phase && seq < TOTAL_PACKETS && !received_sequences[phase][seq];
for (int i = 6; valid && i < PACKET_SIZE; i++) valid = pkt->ll.dgram[i] == (uint8_t)(seq + i + phase);
if (valid) { received_sequences[phase][seq] = 1; packets_received++; }
else {
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "reconnect: invalid/duplicate payload phase=%d seq=%u len=%u", phase, seq, pkt->ll.len);
test_completed = 2;
}
}
if (pkt->ll.dgram) memory_pool_free(conn->instance->data_pool, pkt->ll.dgram);
queue_entry_free((struct ll_entry*)pkt);
}
}
entry = entry->next;
}
}
static void send_packets(void) {
if (!client_instance || packets_sent >= TOTAL_PACKETS) return;
struct ETCP_CONN* conn = (client_instance->connections && client_instance->connections->head) ? ((struct conn_queue_entry*)client_instance->connections->head->data)->conn : NULL;
if (!conn || !conn->input_queue) return;
while (packets_sent < TOTAL_PACKETS) {
if (queue_entry_count(conn->input_queue) >= MAX_QUEUE_SIZE) break;
packet_buffer[0] = 'T'; packet_buffer[1] = 'R'; packet_buffer[2] = phase; packet_buffer[3] = 0;
packet_buffer[4] = packets_sent; packet_buffer[5] = packets_sent >> 8;
for (int i = 6; i < PACKET_SIZE; i++) packet_buffer[i] = (uint8_t)(packets_sent + i + phase);
if (etcp_int_send(conn, packet_buffer, PACKET_SIZE) != 0) break;
packets_sent++;
}
}
static void monitor(void* arg) {
(void)arg;
if (test_completed) { packet_timeout_id = NULL; return; }
int conn_ok = is_connection_established(client_instance);
static int last_reinit_count = -1;
int cur_reinit = 0;
struct ETCP_CONN* conn = (client_instance && client_instance->connections && client_instance->connections->head) ? ((struct conn_queue_entry*)client_instance->connections->head->data)->conn : NULL;
if (conn) cur_reinit = conn->reinit_count;
switch (phase) {
case 0: // Wait for initial connection
drain_received(0);
if (conn_ok) {
printf("=== Phase 0: Connection established ===\n");
packets_sent = 0;
packets_received = 0;
phase = 1;
}
break;
case 1: // Send 500 packets client→server
send_packets();
drain_received(1);
if (packets_received >= TOTAL_PACKETS) {
printf("=== Phase 1 done: sent=%d received=%d ===\n", packets_sent, packets_received);
packets_received = 0;
phase = 2;
}
break;
case 2: // Drain and prepare for server restart
drain_received(0);
packets_received = 0;
packets_sent = 0;
restart_action_done = 0;
printf("=== Phase 2: Starting server restart ===\n");
phase = 3;
break;
case 3: // Server restart + wait for reconnect
if (!restart_action_done) {
printf("Destroying server instance...\n");
server_instance->running = 0;
utun_instance_destroy(server_instance);
server_instance = NULL;
printf("Recreating server instance...\n");
server_instance = utun_instance_create(ua, server_config_path);
if (!server_instance || utun_instance_init(server_instance) < 0) {
fprintf(stderr, "Failed to recreate server instance\n");
test_completed = 2;
return;
}
printf("Server recreated (node_id=%llx)\n", (unsigned long long)server_instance->node_id);
restart_action_done = 1;
}
drain_received(0);
conn_ok = is_connection_established(client_instance);
if (conn_ok) {
printf("=== Phase 3: Reconnected after server restart ===\n");
packets_sent = 0;
packets_received = 0;
last_reinit_count = cur_reinit;
phase = 4;
}
break;
case 4: // Send 500 packets after server restart
if (cur_reinit != last_reinit_count) {
printf("=== Phase 4: Reinit detected (%d -> %d), restarting send ===\n", last_reinit_count, cur_reinit);
last_reinit_count = cur_reinit;
packets_sent = 0;
packets_received = 0;
memset(received_sequences[phase], 0, sizeof(received_sequences[phase]));
drain_received(0);
}
if (is_connection_established(client_instance))
send_packets();
drain_received(1);
if (packets_received >= TOTAL_PACKETS) {
printf("=== Phase 4 done: sent=%d received=%d ===\n", packets_sent, packets_received);
packets_received = 0;
_dump_interval = 50;
phase = 5;
}
if (packets_received > 0 && packets_received < TOTAL_PACKETS) {
if (++_dump_timer >= _dump_interval) {
printf("--- DUMP at phase=4 (stuck, recv=%d/%d) ---\n", packets_received, TOTAL_PACKETS);
etcp_dump_all_conns(client_instance);
etcp_dump_all_conns(server_instance);
_dump_timer = 0;
if (_dump_interval < 800) _dump_interval *= 2;
}
}
break;
case 5: // Drain and prepare for client restart
drain_received(0);
packets_received = 0;
packets_sent = 0;
restart_action_done = 0;
printf("=== Phase 5: Starting client restart ===\n");
phase = 6;
break;
case 6: // Client restart + wait for reconnect
if (!restart_action_done) {
printf("Destroying client instance...\n");
client_instance->running = 0;
utun_instance_destroy(client_instance);
client_instance = NULL;
printf("Recreating client instance...\n");
client_instance = utun_instance_create(ua, client_config_path);
if (!client_instance || utun_instance_init(client_instance) < 0) {
fprintf(stderr, "Failed to recreate client instance\n");
test_completed = 2;
return;
}
printf("Client recreated (node_id=%llx)\n", (unsigned long long)client_instance->node_id);
restart_action_done = 1;
}
drain_received(0);
conn_ok = is_connection_established(client_instance);
if (conn_ok) {
printf("=== Phase 6: Reconnected after client restart ===\n");
packets_sent = 0;
packets_received = 0;
phase = 7;
}
break;
case 7: // Send 500 packets after client restart
if (cur_reinit != last_reinit_count) {
printf("=== Phase 7: Reinit detected (%d -> %d), restarting send ===\n", last_reinit_count, cur_reinit);
last_reinit_count = cur_reinit;
packets_sent = 0;
packets_received = 0;
memset(received_sequences[phase], 0, sizeof(received_sequences[phase]));
drain_received(0);
}
if (is_connection_established(client_instance))
send_packets();
drain_received(1);
if (packets_received >= TOTAL_PACKETS) {
printf("=== Phase 7 done: sent=%d received=%d ===\n", packets_sent, packets_received);
test_completed = 1;
return;
}
break;
}
if (!test_completed)
packet_timeout_id = uasync_set_timeout(ua, 10, NULL, monitor, "test_monitor");
}
static void test_timeout(void* arg) {
(void)arg;
if (!test_completed) {
printf("\n=== TEST TIMEOUT at phase %d: sent=%d recv=%d ===\n", phase, packets_sent, packets_received);
test_completed = 2;
if (packet_timeout_id) { uasync_cancel_timeout(ua, packet_timeout_id); packet_timeout_id = NULL; }
}
}
int main(void) {
if (create_temp_configs() != 0) return 1;
debug_config_init();
printf("=== ETCP Reconnect Test ===\n");
utun_instance_set_tun_init_enabled(0);
ua = uasync_create();
server_instance = utun_instance_create(ua, server_config_path);
if (!server_instance || utun_instance_init(server_instance) < 0) {
fprintf(stderr, "Failed to create server\n");
return 1;
}
printf("Server ready (node_id=%llx)\n", (unsigned long long)server_instance->node_id);
client_instance = utun_instance_create(ua, client_config_path);
if (client_instance) {
server_port = ss_get_port(&test_etcp_bound_socket(server_instance, "test")->local_addr);
client_port = ss_get_port(&test_etcp_bound_socket(client_instance, "test")->local_addr);
test_etcp_set_peer(client_instance, "test_client", server_instance, "test");
if (write_configs() != 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "reconnect fixture: cannot persist endpoints"); test_completed = 2; goto cleanup; }
printf("Server port: %d, Client port: %d (retained for restart)\n", server_port, client_port);
}
if (!client_instance || utun_instance_init(client_instance) < 0) {
fprintf(stderr, "Failed to create client\n");
utun_instance_destroy(server_instance);
return 1;
}
printf("Client ready (node_id=%llx)\n", (unsigned long long)client_instance->node_id);
packet_timeout_id = uasync_set_timeout(ua, 300, NULL, monitor, "test_monitor");
global_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout, "test_timeout");
printf("Starting main loop\n");
int elapsed = 0;
int poll_interval = 50;
int iter = 0;
while (!test_completed && elapsed < TEST_TIMEOUT_MS * 10 + 50000) {
uasync_poll(ua, poll_interval);
elapsed += poll_interval;
if (++iter % 200 == 1) printf("main_loop: iter=%d elapsed=%d phase=%d sent=%d recv=%d\n",
iter, elapsed, phase, packets_sent, packets_received);
}
printf("Main loop exit: test_completed=%d phase=%d elapsed=%d iter=%d\n",
test_completed, phase, elapsed, iter);
cleanup:
if (packet_timeout_id) uasync_cancel_timeout(ua, packet_timeout_id);
if (global_timeout_id) uasync_cancel_timeout(ua, global_timeout_id);
if (server_instance) { server_instance->running = 0; utun_instance_destroy(server_instance); server_instance = NULL; }
if (client_instance) { client_instance->running = 0; utun_instance_destroy(client_instance); client_instance = NULL; }
if (ua) { uasync_destroy(ua, 0); ua = NULL; }
cleanup_temp_configs();
if (test_completed == 1) {
printf("=== TEST PASSED: Reconnect works after server and client restart ===\n");
return 0;
}
printf("=== TEST FAILED: timeout or error at phase %d ===\n", phase);
return 1;
}