diff --git a/tests/test_chat_join_e2e.c b/tests/test_chat_join_e2e.c index 2153238c..dc8061e5 100644 --- a/tests/test_chat_join_e2e.c +++ b/tests/test_chat_join_e2e.c @@ -35,6 +35,8 @@ #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../lib/platform_compat.h" +#include "test_net.h" +#include #include #define OPENSSL_API_COMPAT 0x10100000L @@ -72,7 +74,6 @@ struct e2e_shared { uint64_t owner; uint64_t nid[3]; uint8_t x_pub[3][32], x_priv[3][32], ed_pub[3][32]; - int port[3]; int degraded; }; @@ -149,7 +150,7 @@ static int wf(const char* p, const char* f, ...) { /* ── генерация данных сценария + конфигов (родитель) ── */ -static void fill_shared(int base_port, int degraded) { +static void fill_shared(int degraded) { memset(&g_sh, 0, sizeof(g_sh)); g_sh.degraded = degraded; snprintf(g_sh.ch_id, sizeof(g_sh.ch_id), "4242424242424242"); @@ -159,7 +160,6 @@ static void fill_shared(int base_port, int degraded) { uint8_t ed_priv[32]; x25519_to_ed25519(g_sh.x_priv[i], ed_priv, g_sh.ed_pub[i]); g_sh.nid[i] = sc_derive_node_id_from_pubkey(g_sh.x_pub[i]); - g_sh.port[i] = base_port + i * 1000; } gen_x25519(g_sh.ch_x_pub, g_sh.ch_x_priv); @@ -189,15 +189,51 @@ static void write_configs(const char* dir) { wf(path, "[global]\ntun_ip=10.98.%d.1/24\ntun_ifname=tun%d0\ndb_path=%s/db%s\n" "my_private_key=%s\nmy_public_key=%s\n" - "[server: s1]\naddr=127.0.0.1:%d\ntype=public\n" + "[server: s1]\naddr=127.0.0.1:0\ntype=public\n" "[chatserver]\nstorage_autoload=0\n[allowed_keys]\nallow_all=1\n", i, i, dir, i == IDX_A ? "a" : i == IDX_C ? "c" : "j", - priv, pub, g_sh.port[i]); + priv, pub); } } /* ── poll-хелперы (дети) ── */ +// Родитель не выбирает порты заранее: A публикует endpoint своего уже открытого слушателя. +static int publish_bootstrap_port(struct UTUN_INSTANCE* inst, const char* dir) { + char path[768], temporary[768]; + snprintf(path, sizeof(path), "%s/a.port", dir); snprintf(temporary, sizeof(temporary), "%s/a.port.tmp", dir); + uint16_t port = ss_get_port(&test_etcp_bound_socket(inst, "s1")->local_addr); + FILE* f = fopen(temporary, "w"); + if (!f) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "JOIN fixture: endpoint file open failed: %s", strerror(errno)); return 0; } + int ok = fprintf(f, "%u\n", port) > 0; + if (fclose(f)) ok = 0; + if (!ok || rename(temporary, path)) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "JOIN fixture: endpoint publication failed: %s", strerror(errno)); return 0; + } + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "JOIN fixture: bootstrap listener port=%u published", port); + return 1; +} + +static uint16_t wait_bootstrap_port(struct UTUN_INSTANCE* inst, const char* dir) { + char path[768]; snprintf(path, sizeof(path), "%s/a.port", dir); + uint64_t deadline = get_time_tb() + 50000; + while (get_time_tb() < deadline) { + FILE* f = fopen(path, "r"); + if (f) { + unsigned port = 0; int count = fscanf(f, "%u", &port), error = fclose(f); + if (count != 1 || !port || port > UINT16_MAX || error) { + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "JOIN fixture: invalid bootstrap endpoint count=%d port=%u error=%d", count, port, error); + return 0; + } + DEBUG_INFO(DEBUG_CATEGORY_SOCKET, "JOIN fixture: bootstrap listener port=%u received", port); + return (uint16_t)port; + } + if (errno != ENOENT) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "JOIN fixture: endpoint read failed: %s", strerror(errno)); return 0; } + poll_checked(inst); + } + DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "JOIN fixture: bootstrap endpoint publication timeout"); return 0; +} + static int wait_conn(struct UTUN_INSTANCE* inst, uint64_t nid, int max_iter) { for (int a = 0; a < max_iter; a++) { struct ETCP_CONN* c = instance_find_conn(inst, nid); @@ -375,9 +411,11 @@ static int check_gui_superseded(struct UTUN_INSTANCE* inst) { /* Реальный JSON control socket: два параллельных invite, затем отмена при quit. */ static int check_headless_invites(struct UTUN_INSTANCE* inst) { - int port = g_sh.port[IDX_A] + 2100, ok = 0; + int ok = 0; socket_t fd = SOCKET_INVALID; - if (chat_headless_control_init(inst->ua, inst, "127.0.0.1", port) != 0) return 0; + if (chat_headless_control_init(inst->ua, inst, "127.0.0.1", 0) != 0) return 0; + uint16_t port = chat_headless_control_get_port(inst); + if (!port) return 0; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == SOCKET_INVALID) goto out; struct sockaddr_in addr = {0}; addr.sin_family = AF_INET; addr.sin_port = htons((uint16_t)port); @@ -488,6 +526,7 @@ static int child_main(const char* role, const char* dir, int invalid_key) { utun_instance_destroy(inst); uasync_destroy(ua, 0); return 1; } chat_event_set_handler(inst, join_event_handler); + if (!strcmp(role, "a") && !publish_bootstrap_port(inst, dir)) return 1; if (strcmp(role, "c") == 0) { scenario_dir = dir; connection_join_recv = inst->router_bindings.callbacks[ETCP_RT_ID_JOIN]; @@ -497,7 +536,9 @@ static int child_main(const char* role, const char* dir, int invalid_key) { /* Fixture bootstrap between the existing members uses core NCD, without UTUN. */ struct NODE_CONN_DIRECT* bootstrap = NULL; if (!strcmp(role, "c") && !g_sh.degraded) { - struct TOPO_ADDR4 address = { .addr = {127, 0, 0, 1}, .port = g_sh.port[IDX_A], .protocol = TOPO_PROTO_UDP }; + uint16_t port = wait_bootstrap_port(inst, dir); + if (!port) return 1; + struct TOPO_ADDR4 address = { .addr = {127, 0, 0, 1}, .port = port, .protocol = TOPO_PROTO_UDP }; struct TOPO_NODE node = { .node_id = g_sh.nid[IDX_A], .v4_addrs = &address }; memcpy(node.public_key, g_sh.x_pub[IDX_A], 32); if (node_conn_direct_open_node(inst, node.node_id, NULL, NULL, &bootstrap, &node, NULL) == NCD_ERR) return 1; @@ -649,7 +690,7 @@ static int run_scenario(const char* name, int degraded, int invalid_key) { char dir[512]; snprintf(dir, sizeof(dir), "/tmp/utun_e2e_%s_XXXXXX", name); (void)!mkdtemp(dir); - fill_shared(56000 + (getpid() % 2000), degraded); + fill_shared(degraded); write_configs(dir); const char* roles[4]; diff --git a/tests/test_chat_poll_network.c b/tests/test_chat_poll_network.c index 710de69b..d35181c8 100644 --- a/tests/test_chat_poll_network.c +++ b/tests/test_chat_poll_network.c @@ -14,6 +14,7 @@ #include "../lib/platform_compat.h" #include "../lib/socket_compat.h" #include "../lib/debug_config.h" +#include "test_net.h" #include #include #include @@ -24,15 +25,6 @@ static struct UTUN_INSTANCE* nodes[3]; static char channel[21], message_hex[65]; static uint8_t message_id[32]; -/* Порт выбирается ОС, затем сразу используется новым ядром. */ -static unsigned free_port(void) { - socket_t fd = socket(AF_INET, SOCK_DGRAM, 0); assert(fd != SOCKET_INVALID); - struct sockaddr_in address = {0}; address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - assert(bind(fd, (struct sockaddr*)&address, sizeof(address)) == 0); socklen_t size = sizeof(address); - assert(getsockname(fd, (struct sockaddr*)&address, &size) == 0); unsigned port = ntohs(address.sin_port); - socket_close_wrapper(fd); return port; -} - static void hex(const uint8_t* data, size_t len, char* out) { for (size_t i = 0; i < len; i++) snprintf(out + i * 2, 3, "%02x", data[i]); } @@ -134,7 +126,10 @@ int main(void) { debug_set_category_level(DEBUG_CATEGORY_CHAT_SYNC, DEBUG_LEVEL_DEBUG); debug_set_category_level(DEBUG_CATEGORY_MEMBER_SYNC, DEBUG_LEVEL_INFO); ua = uasync_create(); assert(ua); unsigned ports[3]; - for (int i = 0; i < 3; i++) { ports[i] = free_port(); nodes[i] = create_node(ports[i]); } + for (int i = 0; i < 3; i++) { + nodes[i] = create_node(0); + ports[i] = ss_get_port(&test_etcp_bound_socket(nodes[i], "udp")->local_addr); + } chat_core_create_channel_auto(nodes[0], "Poll network"); sqlite3_stmt* st = NULL; assert(sqlite3_prepare_v2(nodes[0]->topo_sqlite_db, "SELECT channel_id FROM channels", -1, &st, NULL) == SQLITE_OK);