Browse Source

tests: assign CM phase listeners and failure endpoints dynamically

master
evgeny 4 days ago
parent
commit
70b55e4626
  1. 65
      tests/test_conn_mgr_phases.c

65
tests/test_conn_mgr_phases.c

@ -38,6 +38,7 @@
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "test_clock.h"
#include "test_net.h"
#define POLL_MS 5
#define LINK_TIMEOUT_MS 8000
@ -85,7 +86,8 @@ static int call_media_delivered(void) { return call_media_frames > 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 uint16_t wrong_port;
static socket_t wrong_socket = SOCKET_INVALID;
/* ─── утилиты ─── */
@ -203,19 +205,17 @@ static int write_node_config(const char* path, const char* tun_ip, const char* t
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 (write_node_config(cfg_c, "10.90.0.3", "tun_c", 0, "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; }
if (write_node_config(cfg_a, "10.90.0.1", "tun_a", 0, type_a, pub_c, 0) != 0) { u_free(pub_c); return -1; }
if (write_node_config(cfg_b, "10.90.0.2", "tun_b", 0, type_b, pub_c, 0) != 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;
@ -240,6 +240,8 @@ static int scenario_setup(void) {
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; }
test_etcp_set_peer(inst_a, "to_relay", inst_c, "srv");
test_etcp_set_peer(inst_b, "to_relay", inst_c, "srv");
if (utun_instance_init(inst_c) != 0 || utun_instance_init(inst_a) != 0 || utun_instance_init(inst_b) != 0) {
fail("init_connections"); return -1;
@ -290,7 +292,7 @@ static int run_reverse(void) {
if (scenario_setup() != 0) return -1;
/* Испортим адреса B в реестре A — DIRECT провалится, A уйдёт в REVERSE. */
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 1000));
mangle_addrs(inst_a, nid_b, wrong_port);
inst_a->etcp_connect_timeout_tb = 3000; /* 300мс — быстрый провал DIRECT */
ev_a = -1;
@ -315,8 +317,8 @@ static int run_indirect(void) {
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));
mangle_addrs(inst_a, nid_b, wrong_port);
mangle_addrs(inst_b, nid_a, wrong_port);
inst_a->etcp_connect_timeout_tb = 3000;
inst_b->etcp_connect_timeout_tb = 3000;
@ -374,8 +376,8 @@ static int run_schedule(void) {
fprintf(stderr, "\n=== Scenario 4: 500/1000ms schedule ===\n");
if (create_configs("public", "public") || scenario_setup()) return -1;
if (!poll_until(candidates_a_and_b, 8000)) { fail("SCHEDULE: no relay candidates"); return -1; }
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 1000));
mangle_addrs(inst_b, nid_a, (uint16_t)(base_port + 1000));
mangle_addrs(inst_a, nid_b, wrong_port);
mangle_addrs(inst_b, nid_a, wrong_port);
// DIRECT остаётся pending в момент запуска обоих дополнительных вариантов.
inst_a->etcp_connect_timeout_tb = 100000;
if (conn_mgr_open(inst_a, TOPO_GROUP_UTUN, nid_b, conn_cb_a, NULL, &h_a)) return -1;
@ -406,8 +408,8 @@ static int run_upgrade(void) {
if (fallback.type != CONN_TYPE_INDIRECT || fallback.next_hop_node_id != nid_c || cm_find_entry(defgrp(inst_a)->conn_mgr, nid_b)) {
fail("UPGRADE: missing BGP fallback snapshot before CM open"); return -1;
}
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 1000));
mangle_addrs(inst_b, nid_a, (uint16_t)(base_port + 1000));
mangle_addrs(inst_a, nid_b, wrong_port);
mangle_addrs(inst_b, nid_a, wrong_port);
inst_a->etcp_connect_timeout_tb = 1000;
if (conn_mgr_open(inst_a, TOPO_GROUP_UTUN, nid_b, conn_cb_a, NULL, &h_a)) return -1;
struct CONN_MGR_ENTRY* entry = h_a->entry;
@ -455,7 +457,7 @@ static int run_upgrade(void) {
fail("CALL PATH: relay media delivery failed"); return -1;
}
// Появился пригодный адрес B. Обновление дополняет существующий NCD.
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 2));
mangle_addrs(inst_a, nid_b, ss_get_port(&test_etcp_bound_socket(inst_b, "srv")->local_addr));
if (node_conn_direct_update_node(inst_a, nid_b) < 0 || !poll_until(direct_a_ready, 4000)) {
fail("UPGRADE: no late direct READY path"); return -1;
}
@ -469,8 +471,8 @@ static int run_upgrade(void) {
poll_ms(1100);
if (call_connection_events != events) { fail("CALL PATH: duplicate unchanged status"); return -1; }
// Потеря прямого транспорта должна вернуть передачу на проверенный резерв без DOWN.
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 1000));
mangle_addrs(inst_b, nid_a, (uint16_t)(base_port + 1000));
mangle_addrs(inst_a, nid_b, wrong_port);
mangle_addrs(inst_b, nid_a, wrong_port);
struct ETCP_CONN* direct = node_conn_direct_get_conn(original);
if (etcp_conn_ref_take(direct)) { fail("UPGRADE: cannot hold direct transport"); return -1; }
while (direct->links) etcp_link_close(direct->links);
@ -513,8 +515,8 @@ static int run_invalid_candidates(void) {
fprintf(stderr, "\n=== Scenario 7: candidate validation and cleanup ===\n");
if (create_configs("nat", "nat") || scenario_setup()) return -1;
if (!poll_until(candidates_a_and_b, 8000)) return -1;
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 1000));
mangle_addrs(inst_b, nid_a, (uint16_t)(base_port + 1000));
mangle_addrs(inst_a, nid_b, wrong_port);
mangle_addrs(inst_b, nid_a, wrong_port);
if (conn_mgr_open(inst_a, TOPO_GROUP_UTUN, nid_b, conn_cb_a, NULL, &h_a)) return -1;
struct CONN_MGR_ENTRY* entry = h_a->entry;
struct CM_EXCHANGE_RESP resp = {0};
@ -555,14 +557,14 @@ static int run_recovery(void) {
fprintf(stderr, "\n=== Scenario 8: persistent recovery and address change ===\n");
if (create_configs("nat", "nat") || scenario_setup()) return -1;
if (!poll_until(candidates_a_and_b, 8000)) return -1;
uint16_t bad_port = (uint16_t)(base_port + 1000);
uint16_t bad_port = wrong_port;
mangle_addrs(inst_a, nid_b, bad_port); mangle_addrs(inst_b, nid_a, bad_port);
if (conn_mgr_open(inst_a, TOPO_GROUP_UTUN, nid_b, conn_cb_a, NULL, &h_a) || !poll_until(ev_a_up, 3000)) return -1;
struct CONN_MGR_ENTRY* entry = h_a->entry;
uint32_t stale_request = entry->exchange_request_id;
struct NODE_CONN_DIRECT* original = entry->ncd_handle;
if (etcp_router_bind(inst_b, TEST_CM_SERVICE, data_recv) < 0 || send_data(1, nid_c)) return -1;
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 2));
mangle_addrs(inst_a, nid_b, ss_get_port(&test_etcp_bound_socket(inst_b, "srv")->local_addr));
node_conn_direct_update_node(inst_a, nid_b);
if (!poll_until(direct_a_ready, 4000)) { fail("RECOVERY: no initial direct path"); return -1; }
// Истёк первоначальный бюджет. Работающее соединение не требует новых проб.
@ -599,7 +601,7 @@ static int run_recovery(void) {
}
entry->deadline_tb = get_time_tb() + 100; cm_arm_timer(entry); poll_ms(30);
uint64_t start = entry->start_tb;
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 2));
mangle_addrs(inst_a, nid_b, ss_get_port(&test_etcp_bound_socket(inst_b, "srv")->local_addr));
conn_mgr_routes_changed(defgrp(inst_a)->conn_mgr);
if (!poll_until(direct_a_ready, 2000) || entry->start_tb == start || entry->ncd_handle != original ||
up_count != 1 || down_count || timeout_count || entry->attempt_active || entry->timer) {
@ -616,7 +618,7 @@ static int run_recovery(void) {
entry->deadline_tb = get_time_tb() + 100; cm_arm_timer(entry); poll_ms(30);
entry->retry_tb = get_time_tb() + 150000; cm_arm_timer(entry);
start = entry->start_tb;
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 2));
mangle_addrs(inst_a, nid_b, ss_get_port(&test_etcp_bound_socket(inst_b, "srv")->local_addr));
entry->node_timestamp = topo_node_registry_find(inst_a->topo_groups, nid_b)->timestamp;
conn_mgr_network_changed(defgrp(inst_a)->conn_mgr);
if (!poll_until(direct_a_ready, 2000) || entry->start_tb == start) { fail("RECOVERY: local network change did not retry"); return -1; }
@ -640,8 +642,8 @@ static int run_recovery(void) {
static int run_relay_recovery(void) {
fprintf(stderr, "\n=== Scenario 9: lost relay route and new exchange ===\n");
if (create_configs("nat", "nat") || scenario_setup() || !poll_until(candidates_a_and_b, 8000)) return -1;
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 1000));
mangle_addrs(inst_b, nid_a, (uint16_t)(base_port + 1000));
mangle_addrs(inst_a, nid_b, wrong_port);
mangle_addrs(inst_b, nid_a, wrong_port);
if (conn_mgr_open(inst_a, TOPO_GROUP_UTUN, nid_b, conn_cb_a, NULL, &h_a) || !poll_until(ev_a_up, 3000)) return -1;
struct CONN_MGR_ENTRY* entry = h_a->entry;
uint32_t old_request = entry->exchange_request_id;
@ -689,8 +691,8 @@ static int run_relay_recovery(void) {
static int run_initial_timeout(void) {
fprintf(stderr, "\n=== Scenario 10: initial TIMEOUT keeps demand and callback close ===\n");
if (create_configs("nat", "nat") || scenario_setup() || !poll_until(candidates_a_and_b, 8000)) return -1;
mangle_addrs(inst_a, nid_b, (uint16_t)(base_port + 1000));
mangle_addrs(inst_b, nid_a, (uint16_t)(base_port + 1000));
mangle_addrs(inst_a, nid_b, wrong_port);
mangle_addrs(inst_b, nid_a, wrong_port);
struct TOPO_GROUP_NODE* target = topo_node_find_by_id(defgrp(inst_a), nid_b);
while (target && target->paths && target->paths->head)
topo_group_remove_path(target, ((struct TOPO_NODEPATH*)target->paths->head)->conn);
@ -753,6 +755,16 @@ int main(void) {
ua = uasync_create();
if (!ua) { fprintf(stderr, "uasync_create failed\n"); return 1; }
wrong_socket = socket_create_udp(AF_INET);
if (wrong_socket == SOCKET_INVALID) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "CM test blackhole socket failed"); uasync_destroy(ua, 0); return 1;
}
struct sockaddr_in blackhole = {.sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK)};
if (bind(wrong_socket, (struct sockaddr*)&blackhole, sizeof(blackhole)) != 0) {
DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "CM test blackhole bind failed err=%d", socket_get_error());
socket_close_wrapper(wrong_socket); uasync_destroy(ua, 0); return 1;
}
wrong_port = test_bound_port(wrong_socket); // Порт удерживается, но ответы INIT не отправляются.
if (run_direct() != 0) g_failed = 1;
scenario_teardown();
@ -781,6 +793,7 @@ int main(void) {
scenario_teardown();
if (ua) { uasync_poll(ua, 0); uasync_destroy(ua, 0); ua = NULL; }
socket_close_wrapper(wrong_socket);
test_clock_finish();
fprintf(stderr, "%s\n", g_failed ? "RESULT: FAIL" : "RESULT: PASS"); fflush(stderr);

Loading…
Cancel
Save