From bbb8cff4a6d908a54de636893f54cefef0f821a7 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 17 Oct 2026 18:10:23 +0200 Subject: [PATCH] nat: prefer fresh public helpers while retaining known mapping candidates --- src/routing_layer/nat_detection.c | 47 ++++++++++++------------------- tests/test_android_replies.c | 7 ++++- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/routing_layer/nat_detection.c b/src/routing_layer/nat_detection.c index c13e8fa0..95c83c85 100644 --- a/src/routing_layer/nat_detection.c +++ b/src/routing_layer/nat_detection.c @@ -48,7 +48,7 @@ struct nat_wire { struct nat_pair { uint64_t group, observer, helper; - uint8_t helper_priority; /* Сначала публичный endpoint C, затем приватный, затем один наблюдатель. */ + uint8_t helper_priority; /* Публичные IP: свежий/знакомый; затем приватные: свежий/знакомый; затем один B. */ uint8_t observer_endpoint[ROUTE_ADDR_WIRE_SIZE], helper_endpoint[ROUTE_ADDR_WIRE_SIZE]; }; struct nat_recheck_wire { uint8_t cmd, command, version, socket_id, address[ROUTE_ADDR_WIRE_SIZE]; }; @@ -65,11 +65,11 @@ struct nat_socket { uint8_t observer_key[32], helper_key[32]; uint64_t group_id, round, observer, helper, token, observation_token, deadline, due, completed_tb, preferred_observer; uint64_t cycle_deadline, heartbeat_due, heartbeat_received, pre_started; - struct nat_pair cursor, fallback; + struct nat_pair cursor; uint8_t contacts[NAT_MAX_CONTACTS][17]; unsigned contact_count, contact_cutoff, 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, heartbeat_active, forced, cycling, using_fallback, prefer_fresh; + uint8_t helper_socket, active_ping, heartbeat_active, forced, cycling; uint32_t rounds, helper_reselections; }; @@ -258,7 +258,7 @@ static int select_pair(struct nat_socket *state) { node_udp_endpoint(detector, bn, state->local.ss_family, 0, previous_b, &ba, &bs)) { if (!state->socket->is_tcp && route_ping_addr_encode(previous_b, &ba)) break; observers++; - struct nat_pair observer_only = {.group = group->group_id, .observer = observer, .helper_priority = 2}; + struct nat_pair observer_only = {.group = group->group_id, .observer = observer, .helper_priority = 4}; memcpy(observer_only.observer_endpoint, previous_b, sizeof(previous_b)); if (!state->socket->is_tcp && pair_after(observer_only, state->cursor) && (!single.group || pair_after(single, observer_only))) { @@ -275,13 +275,10 @@ static int select_pair(struct nat_socket *state) { node_udp_endpoint(detector, cn, state->local.ss_family, 1, previous_c, &ca, &cs)) { if (!state->socket->is_tcp && route_ping_addr_encode(previous_c, &ca)) break; struct nat_pair candidate = {.group = group->group_id, .observer = observer, .helper = helper, - .helper_priority = !public_address(detector, &ca)}; + .helper_priority = state->socket->is_tcp ? 0 : 2 * !public_address(detector, &ca) + contacted(state, &ca)}; memcpy(candidate.observer_endpoint, previous_b, sizeof(previous_b)); memcpy(candidate.helper_endpoint, previous_c, sizeof(previous_c)); - if (state->using_fallback) { - if (pair_after(candidate, state->fallback) || pair_after(state->fallback, candidate)) continue; - } else if (!pair_after(candidate, state->cursor)) continue; - if (!state->socket->is_tcp && state->prefer_fresh && !state->using_fallback && contacted(state, &ca)) continue; + if (!pair_after(candidate, state->cursor)) continue; if (best.group && !pair_after(best, candidate)) continue; best = candidate; observer_address = ba; helper_address = ca; helper_socket = cs; memcpy(observer_key, bn->public_key, 32); memcpy(helper_key, cn->public_key, 32); @@ -290,27 +287,23 @@ static int select_pair(struct nat_socket *state) { } } } - if (!best.group && single.group && !state->using_fallback && !state->prefer_fresh) { + if (!best.group && single.group) { best = single; observer_address = single_address; memcpy(observer_key, single_key, 32); } 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", state->socket->name, groups, observers, nat_check_reason_name(state->reason)); return -1; } - if (!state->using_fallback) state->cursor = best; + state->cursor = best; state->group_id = best.group; state->observer = best.observer; state->helper = best.helper; state->observer_address = observer_address; state->helper_address = helper_address; state->helper_socket = helper_socket; memcpy(state->observer_key, observer_key, 32); memcpy(state->helper_key, helper_key, 32); - DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT candidates socket=%s group=%016llx observer=%016llx endpoint=%s helper=%016llx candidate=%s fallback=%u", + DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT candidates socket=%s group=%016llx observer=%016llx endpoint=%s helper=%016llx candidate=%s priority=%u", state->socket->name, (unsigned long long)best.group, (unsigned long long)best.observer, sockaddr_storage_to_str(&observer_address).str, (unsigned long long)best.helper, - sockaddr_storage_to_str(&helper_address).str, state->using_fallback); + sockaddr_storage_to_str(&helper_address).str, best.helper_priority); return 0; } @@ -634,7 +627,7 @@ static void owner_finish(struct nat_socket *state, uint8_t reason) { if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR); return; } - int next_pair = reason && state->phase && state->cycling && !state->using_fallback && + int next_pair = reason && state->phase && state->cycling && reason != NAT_REASON_CANCELLED && reason != NAT_REASON_ADDRESS_CHANGED; etcp_cancel_pings(state->detector->instance, owner_ping_cb, state); etcp_cancel_pings(state->detector->instance, owner_heartbeat_cb, state); @@ -647,8 +640,7 @@ static void owner_finish(struct nat_socket *state, uint8_t reason) { if (next_pair) { state->forced = 1; state->due = get_time_tb() + 1000; state->helper_reselections++; } else { - state->cycling = 0; state->using_fallback = 0; state->preferred_observer = 0; - state->cursor = (struct nat_pair){0}; state->fallback = (struct nat_pair){0}; + state->cycling = 0; state->preferred_observer = 0; state->cursor = (struct nat_pair){0}; int complete = state->measured_profile.mapping == ADDRESS_MAP_DEPENDENT || (state->measured_profile.mapping && state->measured_profile.inbound); state->due = get_time_tb() + (reason == NAT_REASON_ADDRESS_CHANGED ? 2500 : complete ? NAT_REFRESH_TB : NAT_RETRY_TB); @@ -845,8 +837,7 @@ void nat_detection_receive_ingress(struct NAT_DETECTION *detector, struct ETCP_S } if (step == NAT_STEP_PRE || step == NAT_STEP_PREPARE) { if (!state->pre_received) { - if (contacted(state, source) && !state->using_fallback) { - if (!state->fallback.group) state->fallback = state->cursor; + if (contacted(state, source)) { state->helper_address = *source; state->filter_clean = 0; DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT helper rejected by receiver socket=%s round=%016llx source=%s reason=previous_udp_contact", socket->name, (unsigned long long)state->round, sockaddr_storage_to_str(source).str); @@ -1131,7 +1122,7 @@ static void handle_result(struct nat_socket *state, const struct nat_wire *messa if (old_contact) DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT known helper used for mapping only socket=%s round=%016llx helper=%s", state->socket->name, (unsigned long long)state->round, sockaddr_storage_to_str(&primary).str); - if (state->pre_received || old_contact || state->using_fallback) { + if (state->pre_received || old_contact) { state->phase = NAT_OBSERVE_C; if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR); return; @@ -1289,8 +1280,7 @@ 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->prefer_fresh = state->measured_profile.mapping != ADDRESS_MAP_UNKNOWN; + state->cycling = 1; state->cursor = (struct nat_pair){0}; state->cycle_deadline = get_time_tb() + NAT_ROUND_TB; } history_load(state); @@ -1489,7 +1479,7 @@ static void topology_wake(void *arg) { if (!changed || !fingerprint || !detector->automatic || state->cycling) continue; if (state->measured_profile.source == ADDRESS_SOURCE_ETCP && state->measured_profile.mapping && state->measured_profile.inbound) continue; - state->cursor = (struct nat_pair){0}; state->fallback = (struct nat_pair){0}; state->using_fallback = 0; + state->cursor = (struct nat_pair){0}; state->due = get_time_tb() + 2500; DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT available candidates changed socket=%s; early measurement scheduled", state->socket->name); } @@ -1506,7 +1496,6 @@ void nat_detection_topology_changed(struct NAT_DETECTION *detector) { void nat_detection_cancel_group(struct NAT_DETECTION *detector, uint64_t group_id) { if (!detector || detector->stopping) return; for (struct nat_socket *state = detector->sockets; state; state = state->next) { - if (state->fallback.group == group_id) state->fallback = (struct nat_pair){0}; if (state->group_id != group_id || (!state->phase && !state->cycling)) continue; owner_finish(state, NAT_REASON_CANCELLED); state->forced = 0; state->group_id = 0; state->due = get_time_tb() + 2500; } @@ -1700,7 +1689,7 @@ void nat_detection_request_check_all(struct NAT_DETECTION *detector) { struct nat_socket *state = socket_state(detector, socket, 1); if (!state) continue; if (state->phase) owner_finish(state, NAT_REASON_CANCELLED); - state->cycling = 0; state->cursor = (struct nat_pair){0}; state->fallback = (struct nat_pair){0}; state->using_fallback = 0; + state->cycling = 0; state->cursor = (struct nat_pair){0}; state->due = get_time_tb(); state->forced = 1; state->preferred_observer = 0; } DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT endpoint recheck requested"); @@ -1712,7 +1701,7 @@ int nat_detection_request_check_socket(struct NAT_DETECTION *detector, struct ET DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT endpoint recheck has invalid endpoint/observer"); return -1; } if (state->phase) owner_finish(state, NAT_REASON_CANCELLED); - state->cycling = 0; state->cursor = (struct nat_pair){0}; state->fallback = (struct nat_pair){0}; state->using_fallback = 0; + state->cycling = 0; state->cursor = (struct nat_pair){0}; state->preferred_observer = observer; state->due = get_time_tb(); state->forced = 1; DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT endpoint recheck socket=%s observer=%016llx", socket->name, (unsigned long long)observer); return 0; diff --git a/tests/test_android_replies.c b/tests/test_android_replies.c index 82676dc3..f017c7cd 100644 --- a/tests/test_android_replies.c +++ b/tests/test_android_replies.c @@ -66,7 +66,7 @@ static int check_addresses(const char *channel) { char value[128]; REQUIRE(json && !extract(json, "$.adr[0].ad", value, sizeof(value)) && !strcmp(value, "194.63.142.140")); REQUIRE(!extract(json, "$.adr[0].po", value, sizeof(value)) && !strcmp(value, "1333")); - REQUIRE(!extract(json, "$.adr[0].at", value, sizeof(value)) && !strcmp(value, "4")); + REQUIRE(!extract(json, "$.adr[0].at", value, sizeof(value)) && !strcmp(value, "0")); /* адрес интерфейса, не NAT */ REQUIRE(!extract(json, "$.adr[0].pt", value, sizeof(value)) && !strcmp(value, "UDP")); REQUIRE(!extract(json, "$.adr[0].si", value, sizeof(value)) && !strcmp(value, "5")); REQUIRE(!extract(json, "$.adr[1].ad", value, sizeof(value)) && !strcmp(value, "2a04:5200::f")); @@ -74,6 +74,11 @@ static int check_addresses(const char *channel) { REQUIRE(!extract(json, "$.adr[1].pt", value, sizeof(value)) && !strcmp(value, "TCP")); REQUIRE(!extract(json, "$.adr[1].po", value, sizeof(value)) && !strcmp(value, "443")); REQUIRE(!extract(json, "$.adr[1].si", value, sizeof(value)) && !strcmp(value, "6")); + u_free(json); + addr4.type = TOPO_ADDR_NAT; meta4.address_profile.source = ADDRESS_SOURCE_ETCP; + REQUIRE(!topo_node_sqlite_addrs_put(instance->topo_sqlite_db, instance->node_id, &node)); + json = utun_bridge_get_member_detail_json(channel, instance->node_id); + REQUIRE(json && !extract(json, "$.adr[0].at", value, sizeof(value)) && !strcmp(value, "4")); /* NAT, тип неизвестен */ u_free(json); return 0; }