- Extract NAT detection logic (~250 lines) from topo_group.c into new nat_detection.c - NAT_DETECTION struct owns ping_pending, next_ping_req_id, allow_nat_check_local (removed from TOPO_GROUP) - route_ping.* signatures changed: TOPO_GROUP* -> NAT_DETECTION* (+ TOPO_GROUP* where needed) - topo_group.c now only dispatches BGP subcommands to nat_detection_* handlers - UTUN_INSTANCE gets struct NAT_DETECTION* nat_det field (init/destroy in topo_groups_init/destroy) - External callers (etcp_connections, etcp, control_server) call nat_detection_* directly - nat_detection_cancel_for_conn / nat_detection_send_nat_info / nat_detection_link_ready / nat_detection_request_check_all - route_ping_destroy_pending properly implemented (was dead code) - TOPO_SUBCMD_PING_REQ/RESP moved from route_ping.h to topo_group.h (all BGP subcommands in one place) - Tests updated: test_nat_detection, test_route_ping - both PASSED - Build: auto(CORE_SOURCES+libutun) and cmake(chatgui/libutun)
337 lines
11 KiB
C
337 lines
11 KiB
C
/**
|
|
* @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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#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/topo_group.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
|
|
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 (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;
|
|
}
|
|
|
|
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 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 (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) {
|
|
DEBUG_INFO(DEBUG_CATEGORY_BGP, "B learned about C");
|
|
break;
|
|
}
|
|
uasync_poll(ua, 10);
|
|
bgp_wait_cycles++;
|
|
}
|
|
if (!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) {
|
|
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_NODEQ* 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;
|
|
if (nq && nq->node) {
|
|
const struct TOPO_ADDR4* a = nq->node->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,
|
|
nq ? nq->node->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;
|
|
}
|