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.
 
 
 
 
 
 

281 lines
13 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 "../src/etcp.h"
#include "../src/etcp_connections.h"
#include "../src/etcp_router.h"
#include "../src/etcp_api.h"
#include "../src/topo_group.h"
#include "../src/topo_node.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 "../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 const uint64_t node_a = 0xAAAAAAAA00000001ULL;
static const uint64_t node_b = 0xBBBBBBBB00000001ULL;
static const uint64_t node_c = 0xCCCCCCCC00000001ULL;
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_NODEQ* mknode(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_NODEQ* nq = u_calloc(1, sizeof(struct TOPO_NODEQ));
struct TOPO_NODE* ni = u_calloc(1, sizeof(struct TOPO_NODE));
if (!nq || !ni) { u_free(nq); u_free(ni); return NULL; }
ni->ref_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_node_id=0x%016llX\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",(unsigned long long)node_a,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_node_id=0x%016llX\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",
(unsigned long long)node_b,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_node_id=0x%016llX\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",(unsigned long long)node_c,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, 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;i<b;i++) { if (send_one_pkt()==0) g_phase_sent++; else break; }
}
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) g_state=ST_B_KILL;
break;
case ST_B_KILL:
g_b->running=0; utun_instance_destroy(g_b); g_b=NULL;
{ struct ETCP_CONN *c,*n;
for(c=g_a->connections;c;c=n){n=c->next;if(c->peer_node_id==node_b)etcp_connection_close(c);}
for(c=g_c->connections;c;c=n){n=c->next;if(c->peer_node_id==node_b)etcp_connection_close(c);} }
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;}
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;
}