conn_mgr: verify route loss and document handle lifetime recovery

This commit is contained in:
evgeny committed 2026-10-01 01:06:56 +03:00
1 parent 464575ef44
commit 426fc2f4e8
2 files changed
+134 -5

No files matched your search

+97
View File
@@ -52,6 +52,7 @@ static struct CONN_MGR_HANDLE *h_a = NULL, *h_b = NULL;
static int g_failed = 0;
static int up_count, down_count, timeout_count, data_count, expected_marker;
static int close_on_timeout;
static uint64_t data_hop;
#define TEST_CM_SERVICE 200
@@ -109,6 +110,7 @@ static void conn_cb_a(struct CONN_MGR_HANDLE* h, uint64_t node, uint64_t grp, en
if (ev == CONN_EVENT_UP) up_count++;
else if (ev == CONN_EVENT_DOWN) down_count++;
else timeout_count++;
if (ev == CONN_EVENT_TIMEOUT && close_on_timeout) { conn_mgr_close(h); h_a = NULL; }
}
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;
@@ -258,6 +260,7 @@ static void scenario_teardown(void) {
if (inst_c) { inst_c->running = 0; utun_instance_destroy(inst_c); inst_c = NULL; }
cleanup_configs();
up_count = down_count = timeout_count = data_count = expected_marker = 0;
close_on_timeout = 0;
ev_a = ev_b = -1;
}
@@ -627,6 +630,96 @@ static int run_recovery(void) {
return 0;
}
/* WITHDRAW последнего маршрута убирает резерв; новый NODEINFO восстанавливает доставку. */
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));
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;
if (etcp_router_bind(inst_b, TEST_CM_SERVICE, data_recv) < 0 || send_data(1, nid_c)) return -1;
// Входящий обмен уже истёк, а собственная попытка ещё в backoff.
struct CONN_MGR_ENTRY* inbound = cm_find_entry(defgrp(inst_b)->conn_mgr, nid_a);
if (!inbound) { fail("RELAY RECOVERY: missing inbound exchange"); return -1; }
inbound->peer_exchange_deadline_tb = get_time_tb() - 1;
entry->deadline_tb = get_time_tb() + 100; cm_arm_timer(entry); poll_ms(30);
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);
conn_mgr_routes_changed(defgrp(inst_a)->conn_mgr); poll_ms(20);
if (down_count != 1 || up_count != 1 || timeout_count || entry->conn_type != CONN_TYPE_NONE ||
!entry->attempt_active || !entry->exchange_request_id || entry->exchange_request_id == old_request) {
fail("RELAY RECOVERY: lost route did not start a fresh exchange with DOWN"); return -1;
}
struct CM_INTERM_SEL stale = {0}; stale.request_id = old_request; stale.count = 1;
stale.selected[0] = (struct CONN_MGR_CANDIDATE){nid_c, 1};
cm_handle_interm_ack(defgrp(inst_a)->conn_mgr, nid_b, (const uint8_t*)&stale, sizeof(stale));
if (entry->indirect_confirmed || entry->intermediariy_count != 1 || up_count != 1) {
fail("RELAY RECOVERY: old ACK confirmed a new attempt"); return -1;
}
struct ETCP_CONN* to_a = instance_find_conn(inst_c, nid_a);
struct TOPO_GROUP_NODE* b_on_c = topo_node_find_by_id(defgrp(inst_c), nid_b);
if (!to_a || !b_on_c || topo_group_send_nodeinfo(defgrp(inst_c), b_on_c, to_a) < 0) return -1;
ev_a = -1;
if (!poll_until(ev_a_up, 3000) || up_count != 2 || down_count != 1 || timeout_count ||
entry->conn_type != CONN_TYPE_INDIRECT || inbound->peer_exchange_request_id != entry->exchange_request_id ||
inbound->peer_exchange_deadline_tb <= get_time_tb()) {
fail("RELAY RECOVERY: fresh exchange did not restore UP"); return -1;
}
if (send_data(2, nid_c)) return -1;
fprintf(stderr, " OK: one DOWN/UP, immediate exchange, stale ACK ignored and expired incoming deadline renewed\n");
return 0;
}
/* Начальный TIMEOUT не снимает потребность; callback может безопасно закрыть последний handle. */
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));
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);
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;
entry->initial_deadline_tb = entry->deadline_tb = get_time_tb() + 100;
cm_arm_timer(entry); poll_ms(30);
if (timeout_count != 1 || up_count || down_count || h_a->entry != entry || entry->attempt_active || !entry->retry_tb) {
fail("INITIAL TIMEOUT: demand was lost or wrong events"); return -1;
}
uint64_t start = entry->start_tb;
entry->retry_tb = get_time_tb() + 100; cm_arm_timer(entry); poll_ms(30);
if (!entry->attempt_active || entry->start_tb == start || timeout_count != 1) {
fail("INITIAL TIMEOUT: no subsequent retry"); return -1;
}
struct ETCP_CONN* to_a = instance_find_conn(inst_c, nid_a);
struct TOPO_GROUP_NODE* b_on_c = topo_node_find_by_id(defgrp(inst_c), nid_b);
if (!to_a || !b_on_c || topo_group_send_nodeinfo(defgrp(inst_c), b_on_c, to_a) < 0) return -1;
ev_a = -1;
if (!poll_until(ev_a_up, 3000) || up_count != 1 || timeout_count != 1 || down_count) {
fail("INITIAL TIMEOUT: late success did not deliver UP"); return -1;
}
if (etcp_router_bind(inst_b, TEST_CM_SERVICE, data_recv) < 0 || send_data(1, nid_c)) return -1;
conn_mgr_close(h_a); h_a = NULL; poll_ms(30);
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);
close_on_timeout = 1;
if (conn_mgr_open(inst_a, TOPO_GROUP_UTUN, nid_b, conn_cb_a, NULL, &h_a)) return -1;
entry = h_a->entry;
entry->initial_deadline_tb = entry->deadline_tb = get_time_tb() + 100;
cm_arm_timer(entry); poll_ms(30);
if (h_a || cm_find_entry(defgrp(inst_a)->conn_mgr, nid_b) || timeout_count != 2) {
fail("INITIAL TIMEOUT: last close inside callback did not cancel recovery"); return -1;
}
poll_ms(1100);
if (up_count != 1 || down_count || timeout_count != 2) { fail("INITIAL TIMEOUT: callback after close"); return -1; }
fprintf(stderr, " OK: one initial TIMEOUT, continuing retries, late UP and safe close inside TIMEOUT callback\n");
return 0;
}
/* ─── main ─── */
int main(void) {
@@ -664,6 +757,10 @@ int main(void) {
scenario_teardown();
if (run_recovery() != 0) g_failed = 1;
scenario_teardown();
if (run_relay_recovery() != 0) g_failed = 1;
scenario_teardown();
if (run_initial_timeout() != 0) g_failed = 1;
scenario_teardown();
if (ua) { uasync_poll(ua, 0); uasync_destroy(ua, 0); ua = NULL; }