diff --git a/tests/test_route_ping.c b/tests/test_route_ping.c new file mode 100644 index 00000000..129d2fe9 --- /dev/null +++ b/tests/test_route_ping.c @@ -0,0 +1,310 @@ +/** + * @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 "../src/etcp.h" +#include "../src/etcp_connections.h" +#include "../src/config_parser.h" +#include "../src/config_updater.h" +#include "../src/utun_instance.h" +#include "../src/routing.h" +#include "../src/route_bgp.h" +#include "../src/route_ping.h" +#include "../src/tun_if.h" +#include "../src/secure_channel.h" +#include "../lib/u_async.h" +#include "../lib/debug_config.h" + +#define TEST_TIMEOUT_MS 10000 +#define NODE_ID_A 0x1111111111111111ULL +#define NODE_ID_B 0x2222222222222222ULL +#define NODE_ID_C 0x3333333333333333ULL + +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" + "my_node_id=0x3333333333333333\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"; + 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" + "my_node_id=0x2222222222222222\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" + "[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" + "my_node_id=0x1111111111111111\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" + "[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) return 0; + struct ETCP_CONN* conn = inst->connections; + while (conn) { + struct ETCP_LINK* link = conn->links; + while (link) { + if (link->initialized) return 1; + link = link->next; + } + conn = conn->next; + } + return 0; +} + +static struct ETCP_CONN* find_conn_to_peer(struct UTUN_INSTANCE* inst, uint64_t peer_node_id) { + if (!inst) return NULL; + struct ETCP_CONN* conn = inst->connections; + while (conn) { + if (conn->peer_node_id == peer_node_id) return conn; + conn = conn->next; + } + return 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 (init_connections(inst_a) != 0 || init_connections(inst_b) != 0 || init_connections(inst_c) != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "Failed to init connections"); + goto cleanup; + } + + test_timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_MS * 10, NULL, test_timeout_cb); + + // 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 about C + 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) { + if (inst_b->bgp && route_bgp_get_node(inst_b->bgp, NODE_ID_C) != NULL) { + DEBUG_INFO(DEBUG_CATEGORY_BGP, "B learned about C"); + break; + } + uasync_poll(ua, 10); + bgp_wait_cycles++; + } + if (!inst_b->bgp || route_bgp_get_node(inst_b->bgp, NODE_ID_C) == NULL) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "B did not learn about C in time"); + goto cleanup; + } + + // 3. Send route ping request from A to B for node C + struct ETCP_CONN* conn_ab = find_conn_to_peer(inst_a, NODE_ID_B); + if (!conn_ab) { + DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "A has no connection to B"); + goto cleanup; + } + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Sending route ping request A->B for C"); + memset(&ping_result, 0, sizeof(ping_result)); + int ret = route_ping_send_req(inst_a->bgp, conn_ab, NODE_ID_C, + 3, // count + 10, // interval_ms + 200, // timeout_ms + 5000,// wait_timeout_ms + ping_resp_cb, NULL); + if (ret != 0) { + DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_ping_send_req 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; +}