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.
322 lines
13 KiB
322 lines
13 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 (init_connections(inst_c) != 0 || init_connections(inst_a) != 0 || init_connections(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); |
|
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 (!g_failed && run_reverse() != 0) g_failed = 1; |
|
scenario_teardown(); |
|
ev_a = -1; ev_b = -1; h_a = NULL; h_b = NULL; |
|
|
|
if (!g_failed && run_indirect() != 0) g_failed = 1; |
|
scenario_teardown(); |
|
|
|
if (ua) { uasync_destroy(ua, 0); ua = NULL; } |
|
|
|
fprintf(stderr, "%s\n", g_failed ? "RESULT: FAIL" : "RESULT: PASS"); fflush(stderr); |
|
return g_failed ? 1 : 0; |
|
}
|
|
|