You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

329 lines
14 KiB

/**
* @file test_conn_mgr_phases.c
* @brief conn_mgr: все три варианта подключения (DIRECT / REVERSE / INDIRECT)
*
* Топология каждого сценария — три узла на loopback: C (публичный релей),
* A и B подключаются к C как клиенты, через BGP узнают друг друга.
*
* Сценарий 1 DIRECT: A=public, B=public → A подключается к B напрямую.
* Сценарий 2 REVERSE: A=public, B=nat → A не пробивает B (адрес испорчен),
* шлёт DIRECT_REQ, B подключается к A (обратное подключение).
* Сценарий 3 INDIRECT: A=nat, B=nat → оба без прямого адреса, выбирают
* посредника C через обмен кандидатами.
*
* Проверка честная: фазы выбираются реальным кодом (адреса цели испорчены на
* «неверный» порт, чтобы DIRECT провалился), RTT кандидата зреет настоящими
* probe-циклами (таймаут probe уменьшен до 10мс для скорости).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.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 "topo_node.h"
#include "conn_mgr.h"
#include "conn_mgr_priv.h"
#include "route_connectivity.h"
#include "../lib/u_async.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#define POLL_MS 5
#define LINK_TIMEOUT_MS 8000
#define BGP_TIMEOUT_MS 8000
#define CONN_TIMEOUT_MS 15000
static struct UASYNC* ua = NULL;
static struct UTUN_INSTANCE *inst_a = NULL, *inst_b = NULL, *inst_c = NULL;
static uint64_t nid_a = 0, nid_b = 0, nid_c = 0;
static volatile int ev_a = -1, ev_b = -1;
static struct CONN_MGR_HANDLE *h_a = NULL, *h_b = NULL;
static int g_failed = 0;
static char temp_dir[] = "/tmp/utun_cm_phases_XXXXXX";
#define TEMP_DIR_TEMPLATE "/tmp/utun_cm_phases_XXXXXX"
static char cfg_a[256], cfg_b[256], cfg_c[256];
static int base_port = 0;
/* ─── утилиты ─── */
static int write_file(const char* path, const char* fmt, ...) {
va_list ap; FILE* f = fopen(path, "w");
if (!f) return -1;
va_start(ap, fmt); vfprintf(f, fmt, ap); va_end(ap); fclose(f);
return 0;
}
static char* get_pubkey(const char* path) {
struct utun_config* cfg = parse_config(path);
if (!cfg) return NULL;
char* pub = u_strdup(cfg->global.my_public_key_hex);
free_config(cfg);
return pub;
}
static void fail(const char* msg) { fprintf(stderr, "FAIL: %s\n", msg); fflush(stderr); g_failed = 1; }
static void conn_cb_a(struct CONN_MGR_HANDLE* h, uint64_t node, uint64_t grp, enum conn_mgr_event ev, void* arg) {
(void)h; (void)node; (void)grp; (void)arg; ev_a = (int)ev;
}
static void conn_cb_b(struct CONN_MGR_HANDLE* h, uint64_t node, uint64_t grp, enum conn_mgr_event ev, void* arg) {
(void)h; (void)node; (void)grp; (void)arg; ev_b = (int)ev;
}
static int poll_until(int (*cond)(void), uint32_t timeout_ms) {
uint64_t start = get_time_tb();
while (!cond() && (get_time_tb() - start) < (uint64_t)timeout_ms * 10) uasync_poll(ua, POLL_MS);
return cond();
}
/* forward-декларации условий сценариев */
static int ev_a_up(void);
static int ev_ab_up(void);
static int candidates_a_and_b(void);
static int has_initialized_link(struct UTUN_INSTANCE* inst) {
if (!inst || !inst->connections) return 0;
struct ll_entry* e = inst->connections->head;
while (e) {
struct conn_queue_entry* ce = (struct conn_queue_entry*)e->data;
struct ETCP_LINK* l = ce->conn->links;
while (l) { if (l->initialized) return 1; l = l->next; }
e = e->next;
}
return 0;
}
static struct TOPO_GROUP* defgrp(struct UTUN_INSTANCE* inst) { return topo_groups_get_default(inst->topo_groups); }
static int learned(struct UTUN_INSTANCE* inst, uint64_t node_id) {
return defgrp(inst) && topo_node_find_by_id(defgrp(inst), node_id) != NULL;
}
/* Меняем порт адресов цели на «неверный» (закрытый) — DIRECT создаст линк,
* но handshake не получит ответа и фаза провалится по таймауту NCD. */
static void mangle_addrs(struct UTUN_INSTANCE* inst, uint64_t node_id, uint16_t wrong_port) {
struct TOPO_NODE* ni = topo_node_registry_find(inst->topo_groups, node_id);
if (!ni) return;
for (struct TOPO_ADDR4* a = ni->v4_addrs; a; a = a->next) a->port = wrong_port;
for (struct TOPO_ADDR6* a6 = ni->v6_addrs; a6; a6 = a6->next) a6->port = wrong_port;
}
static int best_candidates_ready(struct UTUN_INSTANCE* inst) {
struct TOPO_GROUP* g = defgrp(inst);
return g && g->conn_mgr && g->conn_mgr->best_candidate_count > 0;
}
static uint8_t conn_type_of(struct UTUN_INSTANCE* inst, uint64_t node_id) {
struct TOPO_GROUP_NODE* nq = defgrp(inst) ? topo_node_find_by_id(defgrp(inst), node_id) : NULL;
return nq ? nq->conn_mgr_type : CONN_TYPE_NONE;
}
/* ─── конфиги ─── */
static int write_node_config(const char* path, const char* tun_ip, const char* tun_if,
int srv_port, const char* type, const char* peer_pub, int peer_port) {
if (peer_pub) {
return write_file(path,
"[global]\n"
"tun_ip=%s/24\n"
"tun_ifname=%s\n"
"[server: srv]\n"
"addr=127.0.0.1:%d\n"
"type=%s\n"
"[client: to_relay]\n"
"keepalive=1\n"
"peer_public_key=%s\n"
"link=srv:127.0.0.1:%d\n"
"[allowed_keys]\n"
"allow_all=1\n",
tun_ip, tun_if, srv_port, type, peer_pub, peer_port);
}
return write_file(path,
"[global]\n"
"tun_ip=%s/24\n"
"tun_ifname=%s\n"
"[server: srv]\n"
"addr=127.0.0.1:%d\n"
"type=%s\n"
"[allowed_keys]\n"
"allow_all=1\n",
tun_ip, tun_if, srv_port, type);
}
/* Создаёт конфиги C (public relay), A и B с заданными типами сокетов. */
static int create_configs(const char* type_a, const char* type_b) {
strcpy(temp_dir, TEMP_DIR_TEMPLATE);
if (test_mkdtemp(temp_dir) != 0) { fprintf(stderr, "mkdtemp failed\n"); return -1; }
base_port = 43000 + (getpid() % 12000);
int pc = base_port, pa = base_port + 1, pb = base_port + 2;
snprintf(cfg_c, sizeof(cfg_c), "%s/c.conf", temp_dir);
snprintf(cfg_a, sizeof(cfg_a), "%s/a.conf", temp_dir);
snprintf(cfg_b, sizeof(cfg_b), "%s/b.conf", temp_dir);
if (write_node_config(cfg_c, "10.90.0.3", "tun_c", pc, "public", NULL, 0) != 0) return -1;
if (config_ensure_keys_and_node_id(cfg_c) != 0) return -1;
char* pub_c = get_pubkey(cfg_c);
if (!pub_c) return -1;
if (write_node_config(cfg_a, "10.90.0.1", "tun_a", pa, type_a, pub_c, pc) != 0) { u_free(pub_c); return -1; }
if (write_node_config(cfg_b, "10.90.0.2", "tun_b", pb, type_b, pub_c, pc) != 0) { u_free(pub_c); return -1; }
u_free(pub_c);
if (config_ensure_keys_and_node_id(cfg_a) != 0) return -1;
if (config_ensure_keys_and_node_id(cfg_b) != 0) return -1;
return 0;
}
static void cleanup_configs(void) {
test_unlink(cfg_a); test_unlink(cfg_b); test_unlink(cfg_c);
test_rmdir(temp_dir);
}
/* ─── общий каркас сценария ─── */
static int has_initialized_link_all(void) {
return has_initialized_link(inst_a) && has_initialized_link(inst_b) && has_initialized_link(inst_c);
}
static int learned_ab(void) { return learned(inst_a, nid_b) && learned(inst_b, nid_a); }
/* Инициализирует инстансы и ждёт линков + BGP (A↔B узнают друг друга). */
static int scenario_setup(void) {
inst_c = utun_instance_create(ua, cfg_c);
inst_a = utun_instance_create(ua, cfg_a);
inst_b = utun_instance_create(ua, cfg_b);
if (!inst_c || !inst_a || !inst_b) { fail("instance create"); return -1; }
if (utun_instance_init(inst_c) != 0 || utun_instance_init(inst_a) != 0 || utun_instance_init(inst_b) != 0) {
fail("init_connections"); return -1;
}
nid_a = inst_a->node_id; nid_b = inst_b->node_id; nid_c = inst_c->node_id;
if (!poll_until(has_initialized_link_all, LINK_TIMEOUT_MS)) { fail("links not initialized"); return -1; }
if (!poll_until(learned_ab, BGP_TIMEOUT_MS)) { fail("BGP: A/B did not learn each other"); return -1; }
return 0;
}
static void scenario_teardown(void) {
if (h_a) { conn_mgr_close(h_a); h_a = NULL; }
if (h_b) { conn_mgr_close(h_b); h_b = NULL; }
if (inst_a) { inst_a->running = 0; utun_instance_destroy(inst_a); inst_a = NULL; }
if (inst_b) { inst_b->running = 0; utun_instance_destroy(inst_b); inst_b = NULL; }
if (inst_c) { inst_c->running = 0; utun_instance_destroy(inst_c); inst_c = NULL; }
cleanup_configs();
}
/* ─── Сценарий 1: DIRECT ─── */
static int run_direct(void) {
fprintf(stderr, "\n=== Scenario 1: DIRECT ===\n"); fflush(stderr);
if (create_configs("public", "public") != 0) { fail("configs"); return -1; }
if (scenario_setup() != 0) return -1;
ev_a = -1;
conn_mgr_open(inst_a, TOPO_GROUP_UTUN, nid_b, conn_cb_a, NULL, &h_a);
if (!poll_until(ev_a_up, CONN_TIMEOUT_MS)) { fail("DIRECT: no CONN_EVENT_UP"); return -1; }
if (conn_type_of(inst_a, nid_b) != CONN_TYPE_DIRECT) { fail("DIRECT: conn_type != DIRECT"); return -1; }
fprintf(stderr, " OK: DIRECT\n"); fflush(stderr);
return 0;
}
static int ev_a_up(void) { return ev_a == CONN_EVENT_UP; }
/* ─── Сценарий 2: REVERSE ─── */
static int run_reverse(void) {
fprintf(stderr, "\n=== Scenario 2: REVERSE ===\n"); fflush(stderr);
if (create_configs("public", "nat") != 0) { fail("configs"); return -1; }
if (scenario_setup() != 0) return -1;
/* Испортим адреса B в реестре A — DIRECT провалится, A уйдёт в REVERSE. */
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 1000));
inst_a->etcp_connect_timeout_tb = 3000; /* 300мс — быстрый провал DIRECT */
ev_a = -1;
conn_mgr_open(inst_a, TOPO_GROUP_UTUN, nid_b, conn_cb_a, NULL, &h_a);
if (!poll_until(ev_a_up, CONN_TIMEOUT_MS)) { fail("REVERSE: no CONN_EVENT_UP"); return -1; }
if (conn_type_of(inst_a, nid_b) != CONN_TYPE_REVERSE) { fail("REVERSE: conn_type != REVERSE"); return -1; }
fprintf(stderr, " OK: REVERSE\n"); fflush(stderr);
return 0;
}
/* ─── Сценарий 3: INDIRECT ─── */
static int run_indirect(void) {
fprintf(stderr, "\n=== Scenario 3: INDIRECT ===\n"); fflush(stderr);
if (create_configs("nat", "nat") != 0) { fail("configs"); return -1; }
if (scenario_setup() != 0) return -1;
/* Ждём, пока candidate_ping реально заполнит список посредников (C). */
if (!poll_until(candidates_a_and_b, 8000)) { fail("INDIRECT: no candidates (C)"); return -1; }
/* Испортим адреса цели с обеих сторон — DIRECT провалится у обоих. */
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 1000));
mangle_addrs(inst_b, nid_a, (uint16_t)(base_port + 1000));
inst_a->etcp_connect_timeout_tb = 3000;
inst_b->etcp_connect_timeout_tb = 3000;
ev_a = -1; ev_b = -1;
conn_mgr_open(inst_a, TOPO_GROUP_UTUN, nid_b, conn_cb_a, NULL, &h_a);
conn_mgr_open(inst_b, TOPO_GROUP_UTUN, nid_a, conn_cb_b, NULL, &h_b);
if (!poll_until(ev_ab_up, CONN_TIMEOUT_MS)) { fail("INDIRECT: no CONN_EVENT_UP on both sides"); return -1; }
if (conn_type_of(inst_a, nid_b) != CONN_TYPE_INDIRECT) { fail("INDIRECT: A conn_type != INDIRECT"); return -1; }
if (conn_type_of(inst_b, nid_a) != CONN_TYPE_INDIRECT) { fail("INDIRECT: B conn_type != INDIRECT"); return -1; }
/* Посредником должен быть C. */
struct TOPO_GROUP_NODE* nq_b = topo_node_find_by_id(defgrp(inst_a), nid_b);
if (!nq_b || nq_b->conn_mgr_intermediariy_count < 1 || nq_b->conn_mgr_intermediaries[0] != nid_c) {
fail("INDIRECT: intermediary != C"); return -1;
}
fprintf(stderr, " OK: INDIRECT via C\n"); fflush(stderr);
return 0;
}
static int candidates_a_and_b(void) { return best_candidates_ready(inst_a) && best_candidates_ready(inst_b); }
static int ev_ab_up(void) { return ev_a == CONN_EVENT_UP && ev_b == CONN_EVENT_UP; }
/* ─── main ─── */
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_ERROR);
if (getenv("UTUN_TEST_DEBUG")) {
debug_set_category_level(DEBUG_CATEGORY_BGP, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_ETCP, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_CONNECTION, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_ETCPROUTE, DEBUG_LEVEL_DEBUG);
debug_set_category_level(DEBUG_CATEGORY_GENERAL, DEBUG_LEVEL_DEBUG);
}
utun_instance_set_tun_init_enabled(0);
route_connectivity_set_probe_timeout_ms(10);
ua = uasync_create();
if (!ua) { fprintf(stderr, "uasync_create failed\n"); return 1; }
if (run_direct() != 0) g_failed = 1;
scenario_teardown();
ev_a = -1; ev_b = -1; h_a = NULL; h_b = NULL;
if (run_reverse() != 0) g_failed = 1;
scenario_teardown();
ev_a = -1; ev_b = -1; h_a = NULL; h_b = NULL;
if (run_indirect() != 0) g_failed = 1;
scenario_teardown();
if (ua) { uasync_poll(ua, 0); uasync_destroy(ua, 0); ua = NULL; }
fprintf(stderr, "%s\n", g_failed ? "RESULT: FAIL" : "RESULT: PASS"); fflush(stderr);
return g_failed ? 1 : 0;
}