// test_etcp_router_reconnect.c — ETCP router test: a-b-c, b restarts // Топология: a [server] ←—[клиент b]—→ c [server], один UASYNC #include #include #include #include #include "../lib/platform_compat.h" #include "test_utils.h" #ifdef _WIN32 #include #include #include #define getpid _getpid #else #include #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;iref_count = 1; ni->node_id = nid; ni->ver = 0; memcpy(ni->public_key, pubkey, SC_PUBKEY_SIZE); nq->node = ni; nq->hash_node_id = nid; struct TOPO_SOCKMETA4* sm = u_calloc(1, sizeof(struct TOPO_SOCKMETA4)); 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 = u_calloc(1, sizeof(struct TOPO_ADDR4)); 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->next = ni->v4_addrs; ni->v4_addrs = addr; } 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 (!conn && entry->len == 9) { queue_dgram_free(entry); queue_entry_free(entry); return; } if (entry->len < 7) { 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+1,4); uint16_t sz=0; memcpy(&sz, entry->dgram+5,2); if (entry->len != (size_t)(7+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; } uint8_t exp[MAX_PAYLOAD]; gen_payload(seq, exp, sz); if (memcmp(entry->dgram+7, 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); 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;itopo_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) g_state=ST_B_KILL; break; case ST_B_KILL: 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: 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_NODEQ* nq_a = mknode(node_a, keys_a.public_key, 127,0,0,1, (uint16_t)a_port); struct TOPO_NODEQ* nq_c = mknode(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; 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) 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((unsigned)time(NULL)); 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;} 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_NODEQ* nq_a = mknode(node_a, keys_a.public_key, 127,0,0,1, (uint16_t)a_port); struct TOPO_NODEQ* nq_c = mknode(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"); int max_loops = TEST_TIMEOUT_MS * 10 / POLL_TB; while (!g_test_ok && g_loop_cnt < max_loops) { 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_destroy(ua,0); cleanup(); if(g_test_ok==1){printf("=== TEST PASSED ===\n");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_destroy(ua,0); cleanup(); return 1; }