|
|
|
|
@ -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; } |
|
|
|
|
|
|
|
|
|
|