/** * @file test_route_ping.c * @brief Интеграционный тест route_ping через три инстанса * * Топология: A -> B -> C * - A отправляет запрос route_ping на B для узла C * - B пингует C через ETCP ping * - C отвечает PONG * - B отправляет BGP_PING_RESPONSE на A * - A получает ответ через route_ping_send_req callback */ #include #include #include #include #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 "routing.h" #include "topo_group.h" #include "route_ping.h" #include "../src/tun_if.h" #include "secure_channel.h" #include "../lib/u_async.h" #include "../lib/debug_config.h" #define TEST_TIMEOUT_MS 10000 static uint64_t g_node_id_a = 0; static uint64_t g_node_id_b = 0; static uint64_t g_node_id_c = 0; static struct UTUN_INSTANCE* inst_a = NULL; static struct UTUN_INSTANCE* inst_b = NULL; static struct UTUN_INSTANCE* inst_c = NULL; static struct UASYNC* ua = NULL; static int test_timed_out = 0; static void* test_timeout_id = NULL; static char temp_dir[] = "/tmp/utun_route_ping_test_XXXXXX"; static char config_a[256]; static char config_b[256]; static char config_c[256]; static struct { int done; int success; uint16_t avg_rtt; uint8_t count_sent; uint8_t count_ok; } ping_result; static int write_config(const char* path, const char* content) { FILE* f = fopen(path, "w"); if (!f) return -1; fprintf(f, "%s", content); fclose(f); return 0; } static char* get_pubkey_from_config(const char* path) { struct utun_config* cfg = parse_config(path); if (!cfg) return NULL; char* pub = strdup(cfg->global.my_public_key_hex); free_config(cfg); return pub; } static int create_temp_configs(void) { if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "Failed to create temp directory\n"); return -1; } snprintf(config_a, sizeof(config_a), "%s/a.conf", temp_dir); snprintf(config_b, sizeof(config_b), "%s/b.conf", temp_dir); snprintf(config_c, sizeof(config_c), "%s/c.conf", temp_dir); // 1. Config C (no keys) const char* tpl_c = "[global]\n" "tun_ip=10.100.0.3/24\n" "tun_ifname=tun102\n" "\n" "[server:test_c]\n" "addr=127.0.0.1:39013\n" "type=public\n" "\n" "[allowed_keys]\n" "allow_all=1\n"; if (write_config(config_c, tpl_c) != 0) return -1; if (config_ensure_keys_and_node_id(config_c) != 0) return -1; char* pub_c = get_pubkey_from_config(config_c); if (!pub_c) return -1; // 2. Config B (no own keys, but needs peer_public_key of C) char tpl_b[4096]; snprintf(tpl_b, sizeof(tpl_b), "[global]\n" "tun_ip=10.100.0.2/24\n" "tun_ifname=tun101\n" "\n" "[server:test_b]\n" "addr=127.0.0.1:39012\n" "type=public\n" "\n" "[allowed_keys]\n" "allow_all=1\n" "\n" "[client:to_c]\n" "keepalive=1\n" "peer_public_key=%s\n" "link=test_b:127.0.0.1:39013\n", pub_c); free(pub_c); if (write_config(config_b, tpl_b) != 0) return -1; if (config_ensure_keys_and_node_id(config_b) != 0) return -1; char* pub_b = get_pubkey_from_config(config_b); if (!pub_b) return -1; // 3. Config A (no own keys, but needs peer_public_key of B) char tpl_a[4096]; snprintf(tpl_a, sizeof(tpl_a), "[global]\n" "tun_ip=10.100.0.1/24\n" "tun_ifname=tun100\n" "\n" "[server:test_a]\n" "addr=127.0.0.1:39011\n" "type=public\n" "\n" "[allowed_keys]\n" "allow_all=1\n" "\n" "[client:to_b]\n" "keepalive=1\n" "peer_public_key=%s\n" "link=test_a:127.0.0.1:39012\n", pub_b); free(pub_b); if (write_config(config_a, tpl_a) != 0) return -1; if (config_ensure_keys_and_node_id(config_a) != 0) return -1; return 0; } static void cleanup_temp_configs(void) { test_unlink(config_a); test_unlink(config_b); test_unlink(config_c); test_rmdir(temp_dir); } static int has_initialized_link(struct UTUN_INSTANCE* inst) { if (!inst || !inst->connections) return 0; struct ll_entry* entry = inst->connections->head; while (entry) { struct conn_queue_entry* ce = (struct conn_queue_entry*)entry->data; struct ETCP_CONN* conn = ce->conn; struct ETCP_LINK* link = conn->links; while (link) { if (link->initialized) return 1; link = link->next; } entry = entry->next; } return 0; } static struct ETCP_CONN* find_conn_to_peer(struct UTUN_INSTANCE* inst, uint64_t peer_node_id) { if (!inst || !inst->connections) return NULL; struct ll_entry* entry = queue_find_data_by_index(inst->connections, (const uint8_t*)&peer_node_id); return entry ? ((struct conn_queue_entry*)entry->data)->conn : NULL; } static void test_timeout_cb(void* arg) { (void)arg; test_timed_out = 1; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "test_route_ping: overall test timeout"); } static void ping_resp_cb(int success, uint16_t avg_rtt, uint8_t count_sent, uint8_t count_ok, void* arg) { (void)arg; ping_result.done = 1; ping_result.success = success; ping_result.avg_rtt = avg_rtt; ping_result.count_sent = count_sent; ping_result.count_ok = count_ok; DEBUG_INFO(DEBUG_CATEGORY_BGP, "ping_resp_cb: success=%d avg_rtt=%u sent=%u ok=%u", success, (unsigned)avg_rtt, (unsigned)count_sent, (unsigned)count_ok); } int main(void) { int test_result = 1; debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); debug_set_categories(DEBUG_CATEGORY_ETCP | DEBUG_CATEGORY_BGP | DEBUG_CATEGORY_ROUTING); utun_instance_set_tun_init_enabled(0); if (create_temp_configs() != 0) { fprintf(stderr, "Failed to create temp configs\n"); return 1; } ua = uasync_create(); if (!ua) { cleanup_temp_configs(); return 1; } inst_a = utun_instance_create(ua, config_a); inst_b = utun_instance_create(ua, config_b); inst_c = utun_instance_create(ua, config_c); if (!inst_a || !inst_b || !inst_c) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to create instances"); goto cleanup; } if (utun_instance_init(inst_a) != 0 || utun_instance_init(inst_b) != 0 || utun_instance_init(inst_c) != 0) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to init connections"); goto cleanup; } g_node_id_a = inst_a->node_id; g_node_id_b = inst_b->node_id; g_node_id_c = inst_c->node_id; test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout_cb, "test_route_ping"); // 1. Wait for links A->B and B->C to initialize DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Waiting for ETCP links to initialize..."); while (!test_timed_out) { if (has_initialized_link(inst_a) && has_initialized_link(inst_b)) { DEBUG_INFO(DEBUG_CATEGORY_ETCP, "Links initialized"); break; } uasync_poll(ua, 10); } if (test_timed_out) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Timeout waiting for links"); goto cleanup; } // 2. Wait for BGP exchange so B learns C (registry has C's nodeinfo + addresses) DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for BGP exchange (B learns C)..."); int bgp_wait_cycles = 0; while (!test_timed_out && bgp_wait_cycles < 500) { struct TOPO_NODE* ni_c = topo_node_registry_find(inst_b->topo_groups, g_node_id_c); if (ni_c && ni_c->v4_addrs) { DEBUG_INFO(DEBUG_CATEGORY_BGP, "B learned about C"); break; } uasync_poll(ua, 10); bgp_wait_cycles++; } if (!topo_node_registry_find(inst_b->topo_groups, g_node_id_c)) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "B did not learn about C in time"); goto cleanup; } // 3. Get target info for C and send route ping request from A to B struct ETCP_CONN* conn_ab = find_conn_to_peer(inst_a, g_node_id_b); if (!conn_ab) { DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "A has no connection to B"); goto cleanup; } /* Получаем target IP:port из nodeinfo узла C */ /* addr[] в network byte order, port в host byte order */ uint32_t target_ip = 0; uint16_t target_port = 0; struct TOPO_GROUP_NODE* nq = topo_groups_get_default(inst_b->topo_groups) ? topo_node_find_by_id(topo_groups_get_default(inst_b->topo_groups), g_node_id_c) : NULL; struct TOPO_NODE* ni = nq ? topo_node_registry_find(inst_b->topo_groups, nq->node_id) : NULL; if (nq && ni) { const struct TOPO_ADDR4* a = ni->v4_addrs; if (a) { memcpy(&target_ip, a->addr, 4); target_port = a->port; } } if (target_ip == 0 || target_port == 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Cannot resolve target for node C"); goto cleanup; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending route ping request A->B for C (%s:%u)", ip_to_str(&target_ip, AF_INET).str, (unsigned)target_port); memset(&ping_result, 0, sizeof(ping_result)); int ret = route_ping_send_req_addr(inst_a->nat_det, topo_groups_get_default(inst_a->topo_groups), conn_ab, target_ip, target_port, 3, // count 10, // interval_ms 200, // timeout_ms 5000,// wait_timeout_ms ping_resp_cb, NULL, ni ? ni->public_key : NULL); if (ret != 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req_addr failed: %d", ret); goto cleanup; } // 4. Wait for response DEBUG_INFO(DEBUG_CATEGORY_BGP, "Waiting for route ping response..."); while (!test_timed_out && !ping_result.done) { uasync_poll(ua, 10); } if (test_timed_out) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Timeout waiting for ping response"); goto cleanup; } // 5. Verify results if (!ping_result.success) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Ping reported failure (success=0)"); goto cleanup; } if (ping_result.count_ok == 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Ping count_ok is zero"); goto cleanup; } if (ping_result.avg_rtt == 0) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "Ping avg_rtt is zero"); goto cleanup; } DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route ping test PASSED: sent=%u ok=%u avg_rtt=%u", (unsigned)ping_result.count_sent, (unsigned)ping_result.count_ok, (unsigned)ping_result.avg_rtt); test_result = 0; cleanup: if (test_timeout_id) uasync_cancel_timeout(ua, test_timeout_id); if (inst_a) utun_instance_destroy(inst_a); if (inst_b) utun_instance_destroy(inst_b); if (inst_c) utun_instance_destroy(inst_c); if (ua) uasync_destroy(ua, 0); cleanup_temp_configs(); return test_result; }