From 0ee431b49042dfb972584bf838309b44fa0b4a68 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 12 Oct 2026 18:13:29 +0200 Subject: [PATCH] Prefer untouched helper IPs and exercise NAT through real groups --- src/routing_layer/nat_detection.c | 12 +++++++++--- tests/nat_fixture.h | 20 ++++++++++++++------ tests/test_route_ping.c | 13 +++++++------ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/routing_layer/nat_detection.c b/src/routing_layer/nat_detection.c index 5f364b1a..c981b04b 100644 --- a/src/routing_layer/nat_detection.c +++ b/src/routing_layer/nat_detection.c @@ -63,7 +63,7 @@ struct nat_socket { uint8_t contacts[NAT_MAX_CONTACTS][17]; unsigned contact_count, phase, reason, retries; uint8_t history_loaded, history_complete, history_incomplete, filter_clean, pre_received, post_received, pre_healthy; - uint8_t helper_socket, active_ping, forced, cycling, using_fallback; + uint8_t helper_socket, active_ping, forced, cycling, using_fallback, prefer_fresh; uint32_t rounds, helper_reselections; }; @@ -104,6 +104,7 @@ static void owner_finish(struct nat_socket *, uint8_t); static void helper_remove(struct nat_probe *); static int helper_send(struct nat_probe *); static void owner_start(struct nat_socket *); +static int contacted(struct nat_socket *, const struct sockaddr_storage *); static uint64_t read64(const uint8_t *data) { uint64_t value = 0; @@ -231,6 +232,7 @@ static int select_pair(struct nat_socket *state) { const struct TOPO_NODE *cn = group_node(detector, group->group_id, helper); struct sockaddr_storage ca = {0}; uint8_t cs = 0; if (!state->socket->is_tcp && !node_udp_endpoint(detector, cn, state->local.ss_family, 1, &ca, &cs)) continue; + if (!state->socket->is_tcp && state->prefer_fresh && !state->using_fallback && contacted(state, &ca)) continue; struct nat_pair candidate = {group->group_id, observer, helper}; if (state->using_fallback) { if (memcmp(&candidate, &state->fallback, sizeof(candidate))) continue; @@ -242,6 +244,9 @@ static int select_pair(struct nat_socket *state) { } } if (!best.group) { + if (state->prefer_fresh && !state->using_fallback) { + state->prefer_fresh = 0; state->cursor = (struct nat_pair){0}; return select_pair(state); + } if (!state->using_fallback && state->fallback.group) { state->using_fallback = 1; return select_pair(state); } state->reason = observers ? NAT_REASON_NO_HELPER : NAT_REASON_NO_OBSERVER; DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT candidate cycle exhausted socket=%s groups=%u observers=%u reason=%s", @@ -1051,11 +1056,12 @@ static void owner_start(struct nat_socket *state) { state->socket->name, sockaddr_storage_to_str(&state->local).str); return; } if (!state->cycling) { - state->cycling = 1; state->cursor = (struct nat_pair){0}; state->fallback = (struct nat_pair){0}; state->using_fallback = 0; + state->cycling = 1; state->cursor = (struct nat_pair){0}; state->fallback = (struct nat_pair){0}; + state->using_fallback = 0; state->prefer_fresh = 1; } + history_load(state); if (select_pair(state)) { owner_finish(state, state->reason); return; } if (random_id(&state->round)) { owner_finish(state, NAT_REASON_SEND_ERROR); return; } - history_load(state); state->rounds++; memset(&state->helper_seen, 0, sizeof(state->helper_seen)); memset(&state->alternate_seen, 0, sizeof(state->alternate_seen)); state->retries = 0; state->pre_received = 0; state->post_received = 0; state->filter_clean = 0; state->pre_healthy = 0; diff --git a/tests/nat_fixture.h b/tests/nat_fixture.h index bf8b7ac6..aee97fa3 100644 --- a/tests/nat_fixture.h +++ b/tests/nat_fixture.h @@ -44,11 +44,11 @@ static inline struct UTUN_INSTANCE *nat_fixture_node(struct nat_fixture *fixture struct UTUN_INSTANCE *node = utun_instance_create_from_str(fixture->ua, config); assert(node && fixture->node_count < 8 && utun_core_start(node) == 0); nat_detection_set_allow_local(node->nat_det, 1); + assert(topo_groups_create_group(node->topo_groups, TOPO_GROUP_UTUN, TOPO_GROUP_TYPE_UTUN, "")); fixture->nodes[fixture->node_count++] = node; return node; } -/* Без групповой автосвязности: A контактирует только с явно указанным B. - * NCD переносит настоящую подписанную запись, handshake идёт по UDP. */ +/* Без автосвязности: заданный UDP-транспорт присоединяется к группе; остальные узлы доступны через BGP. */ static inline struct ETCP_CONN *nat_fixture_connect(struct nat_fixture *fixture, struct UTUN_INSTANCE *from, const char *source, struct UTUN_INSTANCE *to) { assert(fixture->handle_count < 24); @@ -60,12 +60,20 @@ static inline struct ETCP_CONN *nat_fixture_connect(struct nat_fixture *fixture, struct ETCP_CONN *connection = NULL; while (get_time_tb() < end) { connection = node_conn_direct_get_conn(*handle); - if (connection && connection->links_up) return connection; + if (connection && connection->links_up) break; uasync_poll(fixture->ua, 10); } - DEBUG_ERROR(DEBUG_CATEGORY_NAT, "fixture connection timed out from=%016llx to=%016llx", - (unsigned long long)from->node_id, (unsigned long long)to->node_id); - abort(); + assert(connection && connection->links_up); + struct TOPO_GROUP *group = topo_groups_find(from->topo_groups, TOPO_GROUP_UTUN); + struct TOPO_GROUP *remote = topo_groups_find(to->topo_groups, TOPO_GROUP_UTUN); + assert(group && remote && !topo_group_new_conn(group, connection)); + end = get_time_tb() + 50000; + while (get_time_tb() < end) { + if (topo_group_peer_ready(group, to->node_id) && topo_group_peer_ready(remote, from->node_id)) return connection; + uasync_poll(fixture->ua, 10); + } + DEBUG_ERROR(DEBUG_CATEGORY_NAT, "fixture group negotiation timed out from=%016llx to=%016llx", + (unsigned long long)from->node_id, (unsigned long long)to->node_id); abort(); } static inline void nat_fixture_cleanup(struct nat_fixture *fixture) { diff --git a/tests/test_route_ping.c b/tests/test_route_ping.c index 989c02da..036169b2 100644 --- a/tests/test_route_ping.c +++ b/tests/test_route_ping.c @@ -65,30 +65,31 @@ int main(void) { struct ETCP_SOCKET *peer = test_etcp_bound_socket(observer, "udp"); const uint64_t round = UINT64_C(0x0102030405060708); struct result result = {0}; - assert(route_ping_observe(socket, observer->my_keys.public_key, &peer->local_addr, round, 500, ping_done, &result) == 0); + assert(route_ping_observe(socket, observer->my_keys.public_key, &peer->local_addr, TOPO_GROUP_UTUN, round, 500, ping_done, &result) == 0); wait_result(&fixture, &result); struct sockaddr_storage observed; - assert(result.success && route_ping_observation(result.data, result.length, socket, round, &observed) == 0); + assert(result.success && route_ping_observation(result.data, result.length, socket, TOPO_GROUP_UTUN, round, &observed) == 0); assert(route_ping_addr_equal(&observed, &socket->local_addr, 1)); - assert(route_ping_observation(result.data, result.length, socket, round + 1, &observed) < 0); + assert(route_ping_observation(result.data, result.length, socket, TOPO_GROUP_UTUN, round + 1, &observed) < 0); + assert(route_ping_observation(result.data, result.length, socket, TOPO_GROUP_UTUN + 1, round, &observed) < 0); assert(socket->address_profile.mapping == ADDRESS_MAP_UNKNOWN); /* один ответ не назначает NAT-тип */ memset(&result, 0, sizeof(result)); struct ETCP_SOCKET *untrusted = test_etcp_bound_socket(foreign, "udp"); assert(route_ping_observe(socket, observer->my_keys.public_key, &untrusted->local_addr, - round + 1, 300, ping_done, &result) == 0); + TOPO_GROUP_UTUN, round + 1, 300, ping_done, &result) == 0); assert(owner->pending_pings); send_foreign_pong(foreign, socket, owner->pending_pings->nonce); wait_result(&fixture, &result); assert(!result.success && !owner->pending_pings); memset(&result, 0, sizeof(result)); - assert(route_ping_observe(socket, observer->my_keys.public_key, &peer->local_addr, round + 2, 300, ping_done, &result) == 0); + assert(route_ping_observe(socket, observer->my_keys.public_key, &peer->local_addr, TOPO_GROUP_UTUN, round + 2, 300, ping_done, &result) == 0); uint64_t generation = socket->generation; socket->generation = generation == UINT64_MAX ? generation - 1 : generation + 1; wait_result(&fixture, &result); assert(!result.success); socket->generation = generation; memset(&result, 0, sizeof(result)); - assert(route_ping_observe(socket, observer->my_keys.public_key, &peer->local_addr, round + 3, 500, ping_done, &result) == 0); + assert(route_ping_observe(socket, observer->my_keys.public_key, &peer->local_addr, TOPO_GROUP_UTUN, round + 3, 500, ping_done, &result) == 0); etcp_cancel_pings(owner, ping_done, &result); assert(!owner->pending_pings); uint64_t end = get_time_tb() + 6000; while (get_time_tb() < end) uasync_poll(fixture.ua, 10);