You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
15 KiB
314 lines
15 KiB
// test_etcp_router_reconnect.c — ETCP router test: a-b-c, b restarts |
|
// Топология: a [server] ←—[клиент b]—→ c [server], один UASYNC |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <time.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 "etcp.h" |
|
#include "etcp_connections.h" |
|
#include "etcp_router.h" |
|
#include "etcp_api.h" |
|
#include "topo_group.h" |
|
#include "topo_node.h" |
|
#include "../src/config_parser.h" |
|
#include "../src/utun_instance.h" |
|
#include "routing.h" |
|
#include "../src/tun_if.h" |
|
#include "secure_channel.h" |
|
#include "../lib/u_async.h" |
|
#include "../lib/ll_queue.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
|
|
#define TEST_SVC_ID 0x10 |
|
#define TEST_TIMEOUT_MS 60000 |
|
#define PHASE_PACKETS 500 |
|
#define MAX_PAYLOAD 1500 |
|
#define MIN_PAYLOAD 20 |
|
#define MAX_BURST 20 |
|
#define MIN_BURST 2 |
|
#define POLL_TB 50 |
|
|
|
static struct UTUN_INSTANCE* g_a = NULL; |
|
static struct UTUN_INSTANCE* g_b = NULL; |
|
static struct UTUN_INSTANCE* g_c = NULL; |
|
static struct UASYNC* ua = NULL; |
|
|
|
static char temp_dir[] = "/tmp/utun_test_XXXXXX"; |
|
static char a_conf[512], b_conf[512], c_conf[512]; |
|
static int a_port, b_port_a, b_port_c, c_port; |
|
|
|
static uint64_t node_a = 0; |
|
static uint64_t node_b = 0; |
|
static uint64_t node_c = 0; |
|
|
|
static struct SC_MYKEYS keys_a, keys_b, keys_c; |
|
static char privhex_a[65], privhex_b[65], privhex_c[65], pubhex_a[65], pubhex_b[65], pubhex_c[65]; |
|
|
|
enum { ST_INIT, ST_PHASE1_SEND, ST_PHASE1_WAIT, ST_B_KILL, ST_B_RESTART, ST_PHASE3_SEND, ST_PHASE3_WAIT, ST_DONE }; |
|
static int g_state = ST_INIT; |
|
static int g_test_ok = 0; |
|
static int g_fail_code = 0; |
|
|
|
static uint32_t g_total_sent_phase1 = 0, g_total_sent_phase3 = 0; |
|
static uint32_t g_phase_sent = 0, g_seq = 0; |
|
static int g_tick_counter = 0; |
|
static uint32_t g_rcvd_total = 0, g_expected_seq = 0; |
|
static int g_bgp_ready_mask = 0; |
|
static int g_loop_cnt = 0; |
|
|
|
static void hex_encode(const uint8_t* bin, int len, char* out) { |
|
static const char hex[] = "0123456789abcdef"; |
|
for (int i = 0; i < len; i++) { out[i*2]=hex[bin[i]>>4]; out[i*2+1]=hex[bin[i]&0xF]; } |
|
out[len*2]=0; |
|
} |
|
static void gen_payload(uint32_t seq, uint8_t* buf, int len) { |
|
for (int i=0;i<len;i++) buf[i]=(uint8_t)((i^seq^0xA5)&0xFF); |
|
} |
|
|
|
static struct TOPO_GROUP_NODE* mknode(struct UTUN_INSTANCE* inst, uint64_t nid, const uint8_t pubkey[32], |
|
uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint16_t port) { |
|
struct TOPO_GROUP_NODE* nq = u_calloc(1, sizeof(struct TOPO_GROUP_NODE)); |
|
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE)); |
|
if (!nq || !ni) { u_free(nq); u_free(ni); return NULL; } |
|
ni->group_ref_count = 1; ni->node_id = nid; ni->ver = 0; |
|
memcpy(ni->public_key, pubkey, SC_PUBKEY_SIZE); |
|
nq->ll.size = sizeof(struct TOPO_GROUP_NODE) - sizeof(struct ll_entry); |
|
nq->node_id = nid; |
|
struct TOPO_SOCKMETA4* sm = memory_pool_alloc(inst->topo_groups->v4_sock_meta_pool); |
|
if (sm) { sm->id = 0; sm->config_type = CFG_SERVER_TYPE_PUBLIC; sm->nat_type = NAT_TYPE_UNKNOWN; sm->next = ni->v4_sock_meta; ni->v4_sock_meta = sm; } |
|
struct TOPO_ADDR4* addr = memory_pool_alloc(inst->topo_groups->v4_addr_pool); |
|
if (addr) { addr->addr[0] = a; addr->addr[1] = b; addr->addr[2] = c; addr->addr[3] = d; addr->port = port; addr->type = TOPO_ADDR_INTERFACE; addr->socket_id = 0; addr->protocol = TOPO_PROTO_UDP; addr->next = ni->v4_addrs; ni->v4_addrs = addr; } |
|
if (inst && inst->topo_groups) topo_node_registry_store(inst->topo_groups, ni); |
|
return nq; |
|
} |
|
|
|
static int create_temp_configs(void) { |
|
if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "mkdtemp fail\n"); return -1; } |
|
int base = 42000+(getpid()%15000); |
|
a_port=base; b_port_a=base+1; b_port_c=base+2; c_port=base+3; |
|
if (sc_generate_keypair(&keys_a)!=SC_OK||sc_generate_keypair(&keys_b)!=SC_OK||sc_generate_keypair(&keys_c)!=SC_OK) { fprintf(stderr,"keygen fail\n"); return -1; } |
|
hex_encode(keys_a.private_key,32,privhex_a); hex_encode(keys_a.public_key,32,pubhex_a); |
|
hex_encode(keys_b.private_key,32,privhex_b); hex_encode(keys_b.public_key,32,pubhex_b); |
|
hex_encode(keys_c.private_key,32,privhex_c); hex_encode(keys_c.public_key,32,pubhex_c); |
|
|
|
snprintf(a_conf,sizeof(a_conf),"%s/a.conf",temp_dir); |
|
FILE* f=fopen(a_conf,"w"); if(!f){perror("a_conf");return -1;} |
|
fprintf(f,"[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.1/24\ntun_ifname=tun99\n\n" |
|
"[server:hub_a]\naddr=127.0.0.1:%d\ntype=public\n\n[allowed_keys]\nallow_all=1\n",privhex_a,pubhex_a,a_port); |
|
fclose(f); |
|
|
|
snprintf(b_conf,sizeof(b_conf),"%s/b.conf",temp_dir); |
|
f=fopen(b_conf,"w"); if(!f){perror("b_conf");return -1;} |
|
fprintf(f,"[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.2/24\ntun_ifname=tun98\n\n" |
|
"[server:s_a]\naddr=127.0.0.1:%d\ntype=public\n\n[server:s_c]\naddr=127.0.0.1:%d\ntype=public\n", |
|
privhex_b,pubhex_b,b_port_a,b_port_c); |
|
fclose(f); |
|
|
|
snprintf(c_conf,sizeof(c_conf),"%s/c.conf",temp_dir); |
|
f=fopen(c_conf,"w"); if(!f){perror("c_conf");return -1;} |
|
fprintf(f,"[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.99.0.3/24\ntun_ifname=tun97\n\n" |
|
"[server:hub_c]\naddr=127.0.0.1:%d\ntype=public\n\n[allowed_keys]\nallow_all=1\n",privhex_c,pubhex_c,c_port); |
|
fclose(f); |
|
return 0; |
|
} |
|
static void cleanup(void) { test_unlink(a_conf); test_unlink(b_conf); test_unlink(c_conf); test_rmdir(temp_dir); } |
|
|
|
static void recv_handler(struct ETCP_CONN* conn, struct ll_entry* entry) { |
|
if (!entry || !entry->dgram) { fprintf(stderr, "RECV: null entry\n"); g_test_ok=-1; g_fail_code=10; return; } |
|
if (entry->len == ROUTER_SVC_HDR_SIZE) { queue_dgram_free(entry); queue_entry_free(entry); return; } |
|
if (entry->len < ROUTER_SVC_PAYLOAD_OFF + 6) { fprintf(stderr, "RECV: short entry len=%u\n", (unsigned)entry->len); g_test_ok=-1; g_fail_code=10; queue_dgram_free(entry); queue_entry_free(entry); return; } |
|
uint32_t seq=0; memcpy(&seq,entry->dgram+ROUTER_SVC_PAYLOAD_OFF,4); |
|
uint16_t sz=0; memcpy(&sz, entry->dgram+ROUTER_SVC_PAYLOAD_OFF+4,2); |
|
if (entry->len != (size_t)(ROUTER_SVC_PAYLOAD_OFF+6+sz)) { fprintf(stderr, "FAIL: size mismatch seq=%u\n", seq); g_test_ok=-1; g_fail_code=11; queue_dgram_free(entry); queue_entry_free(entry); return; } |
|
if (seq != g_expected_seq) { fprintf(stderr, "FAIL: seq broken seq=%u expected=%u\n", seq, g_expected_seq); g_test_ok=-1; g_fail_code=12; queue_dgram_free(entry); queue_entry_free(entry); return; } |
|
if (sz > MAX_PAYLOAD) { g_test_ok=-1; g_fail_code=11; queue_dgram_free(entry); queue_entry_free(entry); return; } |
|
uint8_t exp[MAX_PAYLOAD]; gen_payload(seq, exp, sz); |
|
if (memcmp(entry->dgram+ROUTER_SVC_PAYLOAD_OFF+6, exp, sz) != 0) { fprintf(stderr, "FAIL: corrupt seq=%u\n", seq); g_test_ok=-1; g_fail_code=13; queue_dgram_free(entry); queue_entry_free(entry); return; } |
|
g_expected_seq++; g_rcvd_total++; |
|
queue_dgram_free(entry); queue_entry_free(entry); |
|
} |
|
|
|
static int send_one_pkt(void) { |
|
int len = MIN_PAYLOAD + (rand()%(MAX_PAYLOAD-MIN_PAYLOAD+1)); |
|
uint8_t* buf = u_malloc(7+len); if(!buf) return -1; |
|
buf[0]=TEST_SVC_ID; memcpy(buf+1,&g_seq,4); |
|
uint16_t s16=(uint16_t)len; memcpy(buf+5,&s16,2); |
|
gen_payload(g_seq, buf+7, len); |
|
struct ll_entry* e=queue_entry_new(0); if(!e){u_free(buf);return -1;} |
|
e->dgram=buf; e->len=7+len; |
|
int ret = etcp_route_send(g_a, TOPO_GROUP_UTUN, node_c, e, 0, 0); |
|
if (ret == 0) g_seq++; |
|
return ret; |
|
} |
|
|
|
static void connect_bgp_ready_cb(void* arg, struct ETCP_CONN* conn, int type) { |
|
(void)conn; |
|
if (type == ETCP_CONNECT_BGP_READY) { |
|
int id = *(int*)arg; g_bgp_ready_mask |= (1 << id); |
|
} |
|
} |
|
|
|
static const char* state_name(int s) { |
|
static const char* n[]={"INIT","P1SEND","P1WAIT","B_KILL","B_RESTART","P3SEND","P3WAIT","DONE"}; return s<8?n[s]:"?"; |
|
} |
|
|
|
static void send_burst(void) { |
|
int b = MIN_BURST+(rand()%(MAX_BURST-MIN_BURST+1)); |
|
if (b>(int)(PHASE_PACKETS-g_phase_sent)) b=PHASE_PACKETS-g_phase_sent; |
|
for (int i=0;i<b;i++) { if (send_one_pkt()==0) g_phase_sent++; else break; } |
|
} |
|
|
|
static int all_acked(void) { |
|
struct ETCP_ROUTER_CONN* c = etcp_router_conn_get(g_a, TOPO_GROUP_UTUN, node_c, TEST_SVC_ID); |
|
return c && c->tx_acked == c->tx_seq && !queue_entry_count(c->send_q) && !queue_entry_count(c->inflight_q); |
|
} |
|
|
|
static void state_step(void) { |
|
switch (g_state) { |
|
case ST_INIT: |
|
if (g_bgp_ready_mask == 3) { |
|
struct ETCP_CONN* rc = topo_group_find_conn_for_node(topo_groups_get_default(g_a->topo_groups), node_c); |
|
if (rc) g_state=ST_PHASE1_SEND; |
|
} |
|
break; |
|
case ST_PHASE1_SEND: |
|
if (g_phase_sent < PHASE_PACKETS && g_tick_counter <= 0) { |
|
send_burst(); g_tick_counter = 1; g_total_sent_phase1 = g_phase_sent; |
|
} |
|
break; |
|
case ST_PHASE1_WAIT: |
|
if (g_rcvd_total == g_total_sent_phase1 && all_acked()) g_state=ST_B_KILL; |
|
break; |
|
case ST_B_KILL: |
|
fprintf(stderr, "TRANSIT RESTART: phase1 sent=%u received=%u ACKed; a=%016llx b=%016llx c=%016llx\n", |
|
g_total_sent_phase1, g_rcvd_total, (unsigned long long)node_a, (unsigned long long)node_b, (unsigned long long)node_c); |
|
g_b->running=0; utun_instance_destroy(g_b); g_b=NULL; |
|
{ struct ll_entry* e = queue_find_data_by_index(g_a->connections, (const uint8_t*)&node_b); |
|
if (e) etcp_connection_close(((struct conn_queue_entry*)e->data)->conn); |
|
e = queue_find_data_by_index(g_c->connections, (const uint8_t*)&node_b); |
|
if (e) etcp_connection_close(((struct conn_queue_entry*)e->data)->conn); } |
|
g_phase_sent=0; g_bgp_ready_mask=0; g_state=ST_B_RESTART; |
|
break; |
|
case ST_B_RESTART: |
|
#ifdef ROUTER_TEST_RECOVERY_ONLY |
|
// Посредник остаётся выключенным: восстановление обязан выполнить штатный recovery. |
|
{ |
|
struct ETCP_CONN* direct = instance_find_conn(g_a, node_c); |
|
if (direct && direct->links_up && direct->initialized && |
|
topo_group_find_conn_for_node(topo_groups_get_default(g_a->topo_groups), node_c) == direct) { |
|
fprintf(stderr,"RECOVERY: direct a-c path established without test reconnect\n"); |
|
g_phase_sent = 0; g_state = ST_PHASE3_SEND; |
|
} |
|
} |
|
break; |
|
#else |
|
if (!g_b) { |
|
g_b=utun_instance_create(ua,b_conf); |
|
if (!g_b||utun_instance_init(g_b)<0) { fprintf(stderr,"FAIL: b restart\n"); g_test_ok=-1; g_fail_code=20; return; } |
|
g_b->etcp_connect_timeout_tb = 300000; |
|
struct TOPO_GROUP_NODE* nq_a = mknode(g_b, node_a, keys_a.public_key, 127,0,0,1, (uint16_t)a_port); |
|
struct TOPO_GROUP_NODE* nq_c = mknode(g_b, node_c, keys_c.public_key, 127,0,0,1, (uint16_t)c_port); |
|
if (!nq_a || !nq_c) { fprintf(stderr,"FAIL: mknode\n"); g_test_ok=-1; return; } |
|
static int cc_a=0,cc_c=1; |
|
etcp_connect(g_b,nq_a,connect_bgp_ready_cb,&cc_a,ETCP_CONNECT_BGP_READY); |
|
etcp_connect(g_b,nq_c,connect_bgp_ready_cb,&cc_c,ETCP_CONNECT_BGP_READY); |
|
} |
|
if (g_bgp_ready_mask == 3) { |
|
struct ETCP_CONN* rc = topo_group_find_conn_for_node(topo_groups_get_default(g_a->topo_groups), node_c); |
|
if (rc) { g_phase_sent=0; g_state=ST_PHASE3_SEND; } |
|
} |
|
break; |
|
#endif |
|
case ST_PHASE3_SEND: |
|
if (g_phase_sent < PHASE_PACKETS && g_tick_counter <= 0) { |
|
send_burst(); g_tick_counter = 1; g_total_sent_phase3 = g_phase_sent; |
|
} |
|
break; |
|
case ST_PHASE3_WAIT: |
|
if (g_rcvd_total == g_total_sent_phase1 + g_total_sent_phase3 && all_acked()) g_state=ST_DONE; |
|
break; |
|
case ST_DONE: |
|
if (g_rcvd_total == g_total_sent_phase1+g_total_sent_phase3 && g_rcvd_total>0) g_test_ok=1; |
|
else { fprintf(stderr, "FAIL: sent=%u rcvd=%u\n", g_total_sent_phase1+g_total_sent_phase3, g_rcvd_total); g_test_ok=-1; g_fail_code=30; } |
|
return; |
|
} |
|
if (g_tick_counter>0) g_tick_counter--; |
|
} |
|
|
|
static void timeout_cb(void* arg) { |
|
(void)arg; |
|
fprintf(stderr, "TIMEOUT: state=%s sent_p1=%u sent_p3=%u rcvd=%u\n", state_name(g_state), g_total_sent_phase1, g_total_sent_phase3, g_rcvd_total); |
|
g_test_ok=-1; g_fail_code=99; |
|
} |
|
|
|
int main(void) { |
|
srand(1); |
|
if (create_temp_configs()!=0) return 1; |
|
printf("=== ETCP Router Reconnect Test ===\nPorts: a=%d b_a=%d b_c=%d c=%d\n", a_port, b_port_a, b_port_c, c_port); |
|
debug_config_init(); |
|
debug_set_level(DEBUG_LEVEL_ERROR); |
|
utun_instance_set_tun_init_enabled(0); |
|
|
|
ua=uasync_create(); if(!ua){cleanup();return 1;} |
|
g_a=utun_instance_create(ua,a_conf); if(!g_a||utun_instance_init(g_a)<0){fprintf(stderr,"FAIL: a\n");goto fail;} |
|
g_c=utun_instance_create(ua,c_conf); if(!g_c||utun_instance_init(g_c)<0){fprintf(stderr,"FAIL: c\n");goto fail;} |
|
g_b=utun_instance_create(ua,b_conf); if(!g_b||utun_instance_init(g_b)<0){fprintf(stderr,"FAIL: b\n");goto fail;} |
|
|
|
const char* log = getenv("ROUTER_TEST_LOG"); |
|
if (log) { |
|
debug_enable_file_output(log, 1); |
|
debug_set_category_level(DEBUG_CATEGORY_ETCPROUTE, DEBUG_LEVEL_DEBUG); |
|
debug_set_category_level(DEBUG_CATEGORY_BGP, DEBUG_LEVEL_DEBUG); |
|
debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_INFO); |
|
} |
|
node_a=g_a->node_id; node_b=g_b->node_id; node_c=g_c->node_id; |
|
|
|
etcp_router_bind(g_c, TEST_SVC_ID, recv_handler); |
|
|
|
struct TOPO_GROUP_NODE* nq_a = mknode(g_b, node_a, keys_a.public_key, 127,0,0,1, (uint16_t)a_port); |
|
struct TOPO_GROUP_NODE* nq_c = mknode(g_b, node_c, keys_c.public_key, 127,0,0,1, (uint16_t)c_port); |
|
if (!nq_a || !nq_c) { fprintf(stderr,"FAIL: mknode\n"); goto fail; } |
|
g_b->etcp_connect_timeout_tb = 300000; |
|
{ static int cc_a=0,cc_c=1; |
|
etcp_connect(g_b,nq_a,connect_bgp_ready_cb,&cc_a,ETCP_CONNECT_BGP_READY); |
|
etcp_connect(g_b,nq_c,connect_bgp_ready_cb,&cc_c,ETCP_CONNECT_BGP_READY); } |
|
|
|
void* to_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS*10, NULL, timeout_cb, "to"); |
|
while (!g_test_ok) { |
|
uasync_poll(ua, POLL_TB); |
|
state_step(); |
|
if (g_state==ST_PHASE1_SEND && g_phase_sent>=PHASE_PACKETS) g_state=ST_PHASE1_WAIT; |
|
else if (g_state==ST_PHASE3_SEND && g_phase_sent>=PHASE_PACKETS) g_state=ST_PHASE3_WAIT; |
|
g_loop_cnt++; |
|
} |
|
|
|
uasync_cancel_timeout(ua,to_id); |
|
if(g_a){g_a->running=0;utun_instance_destroy(g_a);} |
|
if(g_b){g_b->running=0;utun_instance_destroy(g_b);} |
|
if(g_c){g_c->running=0;utun_instance_destroy(g_c);} |
|
if(ua) { uasync_poll(ua, 0); uasync_destroy(ua,0); } |
|
cleanup(); |
|
if(g_test_ok==1){printf("=== TEST PASSED: sent=%u received=%u, all ACKed before and after transit restart ===\n", g_seq, g_rcvd_total);return 0;} |
|
printf("=== TEST FAILED: code=%d ===\n",g_fail_code); |
|
return 1; |
|
fail: |
|
if(g_a){g_a->running=0;utun_instance_destroy(g_a);} |
|
if(g_b){g_b->running=0;utun_instance_destroy(g_b);} |
|
if(g_c){g_c->running=0;utun_instance_destroy(g_c);} |
|
if(ua) { uasync_poll(ua, 0); uasync_destroy(ua,0); } |
|
cleanup(); |
|
return 1; |
|
}
|
|
|