fix: reconnect test — cancel timers in conn_reset, add init_timer cancel on link_state=3, add reinit resilience to test
- etcp_conn_reset: cancel retrans_timer/ack_resp_timer via uasync_cancel_timeout before nulling (was leaking timer handles, confirmed by 'Timer leak: diff') - etcp_connections: cancel link->init_timer explicitly when link_state becomes 3 on both client and server INIT_RESPONSE paths (prevents spurious INIT after connection is established that could trigger cascading reinits) - etcp_connections: remove duplicate conn->session_id assignment in new conn path (session_id is set later via conn_reinit flow) - pkt_normalizer: resume output queue callback after pn_reset - Add test_etcp_reconnect with reinit detection — monitors conn->reinit_count and restarts data transfer if reinit occurs during active phase - Add etcp_dump.c/h — ETCP connection state dump utility for debugging
This commit is contained in:
1 parent
0f8df44b2b
commit
dea3a39598
8 files changed
+668
-5
No files matched your search
+7
-1
@@ -8,6 +8,7 @@ check_PROGRAMS = \
|
||||
test_ipv6_sockets \
|
||||
test_etcp_minimal \
|
||||
test_etcp_100_packets \
|
||||
test_etcp_reconnect \
|
||||
test_pkt_normalizer_etcp \
|
||||
test_pkt_normalizer_standalone \
|
||||
test_etcp_api \
|
||||
@@ -72,7 +73,8 @@ ETCP_CORE_OBJS = \
|
||||
$(top_builddir)/src/utun-etcp_loadbalancer.o \
|
||||
$(top_builddir)/src/utun-pkt_normalizer.o \
|
||||
$(top_builddir)/src/utun-etcp_api.o \
|
||||
$(top_builddir)/src/utun-etcp_debug.o
|
||||
$(top_builddir)/src/utun-etcp_debug.o \
|
||||
$(top_builddir)/src/utun-etcp_dump.o
|
||||
|
||||
# Platform-specific TUN objects
|
||||
if OS_WINDOWS
|
||||
@@ -178,6 +180,10 @@ test_etcp_100_packets_SOURCES = test_etcp_100_packets.c
|
||||
test_etcp_100_packets_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_100_packets_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_etcp_reconnect_SOURCES = test_etcp_reconnect.c
|
||||
test_etcp_reconnect_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_etcp_reconnect_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
test_pkt_normalizer_etcp_SOURCES = test_pkt_normalizer_etcp.c
|
||||
test_pkt_normalizer_etcp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tinycrypt/lib/include -I$(top_srcdir)/tinycrypt/lib/source
|
||||
test_pkt_normalizer_etcp_LDADD = $(ETCP_FULL_OBJS) $(SECURE_CHANNEL_OBJS) $(CRYPTO_LIBS) $(COMMON_LIBS)
|
||||
|
||||
@@ -0,0 +1,377 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "../lib/platform_compat.h"
|
||||
#include "test_utils.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 "../src/etcp.h"
|
||||
#include "../src/etcp_connections.h"
|
||||
#include "../src/config_parser.h"
|
||||
#include "../src/utun_instance.h"
|
||||
#include "../src/routing.h"
|
||||
#include "../src/tun_if.h"
|
||||
#include "../src/secure_channel.h"
|
||||
#include "../src/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 int create_temp_configs(void) {
|
||||
if (test_mkdtemp(temp_dir) != 0) {
|
||||
fprintf(stderr, "Failed to create temp directory\n");
|
||||
return -1;
|
||||
}
|
||||
int base_port = 40000 + (getpid() % 20000);
|
||||
server_port = base_port;
|
||||
client_port = base_port + 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);
|
||||
|
||||
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=67b705a92b41bcaae105af2d6a17743faa7b26ccebba8b3b9b0af05e9cd1d5fb\n"
|
||||
"my_public_key=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\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=4813d31d28b7e9829247f488c6be7672f2bdf61b2508333128e386d1759afed2\n"
|
||||
"my_public_key=c594f33c91f3a2222795c2c110c527bf214ad1009197ce14556cb13df3c461b3c373bed8f205a8dd1fc0c364f90bf471d7c6f5db49564c33e4235d268569ac71\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=1c55e4ccae7c4470707759086738b10681bf88b81f198cc2ab54a647d1556e17c65e6b1833e0c771e5a39382c03067c388915a4c732191bc130480f20f8e00b9\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 is_connection_established(struct UTUN_INSTANCE* inst) {
|
||||
if (!inst) return 0;
|
||||
struct ETCP_CONN* conn = inst->connections;
|
||||
while (conn) {
|
||||
struct ETCP_LINK* link = conn->links;
|
||||
while (link) {
|
||||
if (link->initialized) return 1;
|
||||
link = link->next;
|
||||
}
|
||||
conn = conn->next;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void drain_received(int count_flag) {
|
||||
if (!server_instance) return;
|
||||
struct ETCP_CONN* conn = server_instance->connections;
|
||||
while (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) {
|
||||
if (count_flag) packets_received++;
|
||||
if (pkt->ll.dgram) memory_pool_free(conn->instance->data_pool, pkt->ll.dgram);
|
||||
queue_entry_free((struct ll_entry*)pkt);
|
||||
}
|
||||
}
|
||||
conn = conn->next;
|
||||
}
|
||||
}
|
||||
|
||||
static void send_packets(void) {
|
||||
if (!client_instance || packets_sent >= TOTAL_PACKETS) return;
|
||||
struct ETCP_CONN* conn = client_instance->connections;
|
||||
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] = (uint8_t)(packets_sent & 0xFF);
|
||||
for (int i = 1; i < PACKET_SIZE; i++) packet_buffer[i] = (uint8_t)((packets_sent + i) % 256);
|
||||
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 : 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);
|
||||
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;
|
||||
drain_received(0);
|
||||
}
|
||||
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;
|
||||
drain_received(0);
|
||||
}
|
||||
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();
|
||||
debug_set_level(DEBUG_LEVEL_ERROR);
|
||||
debug_set_categories(DEBUG_CATEGORY_NONE);
|
||||
|
||||
printf("=== ETCP Reconnect Test ===\n");
|
||||
printf("Server port: %d, Client port: %d\n", server_port, client_port);
|
||||
|
||||
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 || 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);
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user