/** * @file test_conn_mgr_handles.c * @brief conn_mgr handle lifecycle: multiple handles sharing one connection * * Test 1: Two conn_mgr_connect_node → both OK, same entry, one conn * Test 2: Release first handle → conn stays (second handle still active) * Test 3: Release last handle → conn torn down */ #include #include #include #include #include "../lib/platform_compat.h" #include "test_utils.h" #include "etcp.h" #include "etcp_connections.h" #include "../src/config_parser.h" #include "../src/config_updater.h" #include "../src/utun_instance.h" #include "topo_group.h" #include "conn_mgr.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #define TIMEOUT_TB 300000 #define POLL_MS 5 static struct UTUN_INSTANCE *g_a = NULL, *g_b = NULL; static struct UASYNC *ua = NULL; static volatile int result = 0; static void* ttimer = NULL; static char tdir[] = "/tmp/utun_cmh_XXXXXX"; static char ca[256], cb[256]; static int pa = 0, pb = 0; static uint64_t g_nid_a = 0, g_nid_b = 0; static volatile int cb1_ok = 0, cb2_ok = 0; static struct CONN_MGR_HANDLE *gh1 = NULL, *gh2 = NULL; static int wf(const char* p, const char* f, ...) { va_list ap; FILE* fp = fopen(p, "w"); if (!fp) return -1; va_start(ap, f); vfprintf(fp, f, ap); va_end(ap); fclose(fp); return 0; } static char* gv(const char* p, const char* k) { struct utun_config* c = parse_config(p); if (!c) return NULL; char* r = (strcmp(k, "pub") == 0) ? u_strdup(c->global.my_public_key_hex) : u_strdup(c->global.my_private_key_hex); free_config(c); return r; } static void fail(const char* msg) { fprintf(stderr, "FAIL: %s\n", msg); fflush(stderr); result = 2; } static void done(void) { if (result == 0) result = 1; } static void to_cb(void* arg) { (void)arg; fail("timeout"); done(); } static void ccb1(int r, uint64_t id, void* arg) { (void)arg; cb1_ok = (r == CONN_MGR_OK); fprintf(stderr, "ccb1: result=%d node=0x%llx ok=%d\n", r, (unsigned long long)id, cb1_ok); fflush(stderr); } static void ccb2(int r, uint64_t id, void* arg) { (void)arg; cb2_ok = (r == CONN_MGR_OK); fprintf(stderr, "ccb2: result=%d node=0x%llx ok=%d\n", r, (unsigned long long)id, cb2_ok); fflush(stderr); } static void test1_check(void* arg); static void test2_check(void* arg); static void test2_verify(void* arg); static void test3_check(void* arg); static void test3_verify(void* arg); static void test1(void* arg) { (void)arg; if (result) return; if (!topo_node_find_by_id(topo_groups_get_default(g_a->topo_groups), g_nid_b)) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1w"); return; } struct ETCP_CONN* c = topo_group_find_conn_for_node(g_a->conn_mgr->group, g_nid_b); if (!c) c = instance_find_conn(g_a, g_nid_b); if (!c || !c->links) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1w2"); return; } int has = 0; struct ETCP_LINK* l = c->links; while (l) { if (l->link_state == 3 && l->initialized) { has = 1; break; } l = l->next; } if (!has) { uasync_set_timeout(ua, 50, NULL, (timeout_callback_t)test1, "t1w3"); return; } fprintf(stderr, "Test 1: two conn_mgr_connect_node to same node\n"); fflush(stderr); cb1_ok = 0; cb2_ok = 0; gh1 = conn_mgr_connect_node(g_a->conn_mgr, g_nid_b, 0, ccb1, NULL); gh2 = conn_mgr_connect_node(g_a->conn_mgr, g_nid_b, 0, ccb2, NULL); if (!gh1 || !gh2) { fail("test1: handle returned NULL"); done(); return; } uasync_set_timeout(ua, 200, NULL, (timeout_callback_t)test1_check, "t1c"); } static void test1_check(void* arg) { (void)arg; if (!cb1_ok || !cb2_ok) { fail("test1: both callbacks should be OK"); done(); return; } uint8_t st, ty; conn_mgr_get_status(g_a->conn_mgr, g_nid_b, &st, &ty); if (st != CONN_MGR_STATE_CONNECTED || ty != CONN_TYPE_DIRECT) { fail("test1: status mismatch"); done(); return; } fprintf(stderr, " OK: both handles returned OK, state=%d type=%d\n", st, ty); fflush(stderr); uasync_call_soon(ua, NULL, (timeout_callback_t)test2_check); } static void test2_check(void* arg) { (void)arg; fprintf(stderr, "Test 2: release first handle — conn stays alive\n"); fflush(stderr); conn_mgr_release_handle(gh1); gh1 = NULL; uasync_set_timeout(ua, 100, NULL, (timeout_callback_t)test2_verify, "t2v"); } static void test2_verify(void* arg) { (void)arg; uint8_t st, ty; conn_mgr_get_status(g_a->conn_mgr, g_nid_b, &st, &ty); if (st != CONN_MGR_STATE_CONNECTED) { fail("test2: conn died after first handle close"); done(); return; } fprintf(stderr, " OK: conn alive after h1 close, state=%d\n", st); fflush(stderr); uasync_call_soon(ua, NULL, (timeout_callback_t)test3_check); } static void test3_check(void* arg) { (void)arg; fprintf(stderr, "Test 3: release last handle — conn torn down\n"); fflush(stderr); conn_mgr_release_handle(gh2); gh2 = NULL; uasync_set_timeout(ua, 200, NULL, (timeout_callback_t)test3_verify, "t3v"); } static void test3_verify(void* arg) { (void)arg; uint8_t st, ty; conn_mgr_get_status(g_a->conn_mgr, g_nid_b, &st, &ty); if (st != CONN_MGR_STATE_DISCONNECTED) { fail("test3: conn should be disconnected"); done(); return; } fprintf(stderr, " OK: conn DISCONNECTED after last handle close\n"); fflush(stderr); fprintf(stderr, "=== ALL DONE ===\n"); fflush(stderr); done(); } int main(void) { debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN); utun_instance_set_tun_init_enabled(0); test_mkdtemp(tdir); int base = 50000 + (getpid() % 10000); pa = base; pb = base + 1; snprintf(ca, sizeof(ca), "%s/a.conf", tdir); snprintf(cb, sizeof(cb), "%s/b.conf", tdir); wf(ca, "[global]\ntun_ip=10.96.0.1/24\ntun_ifname=tun93\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", pa); wf(cb, "[global]\ntun_ip=10.96.0.2/24\ntun_ifname=tun92\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[allowed_keys]\nallow_all=1\n", pb); config_ensure_keys_and_node_id(ca); config_ensure_keys_and_node_id(cb); { struct utun_config* cfa = parse_config(ca); struct utun_config* cfb = parse_config(cb); g_nid_a = cfa->global.my_node_id; g_nid_b = cfb->global.my_node_id; free_config(cfa); free_config(cfb); } char *p0 = gv(ca,"pub"), *r0 = gv(ca,"priv"), *p1 = gv(cb,"pub"); wf(ca, "[global]\nmy_private_key=%s\nmy_public_key=%s\ntun_ip=10.96.0.1/24\ntun_ifname=tun93\n[server: s1]\naddr=127.0.0.1:%d\ntype=public\n[client: to_b]\nkeepalive=1\npeer_public_key=%s\nlink=s1:127.0.0.1:%d\n[allowed_keys]\nallow_all=1\n", r0, p0, pa, p1, pb); u_free(p0); u_free(r0); u_free(p1); ua = uasync_create(); g_a = utun_instance_create(ua, ca); g_b = utun_instance_create(ua, cb); if (!g_a || !g_b) { fprintf(stderr,"create failed\n"); result=2; goto done; } utun_instance_init(g_a); utun_instance_init(g_b); uasync_call_soon(ua, NULL, test1); ttimer = uasync_set_timeout(ua, TIMEOUT_TB, NULL, to_cb, "to"); { int el = 0; while (!result && el < TIMEOUT_TB/10 + 500) { uasync_poll(ua, POLL_MS); el += POLL_MS; } } done: if (ttimer) { uasync_cancel_timeout(ua, ttimer); } g_a->running = 0; if (g_b) g_b->running = 0; if (ua) uasync_destroy(ua, 1); // force cleanup test_unlink(ca); test_unlink(cb); test_rmdir(tdir); return (result == 1) ? 0 : 1; }