nat: prefer fresh public helpers while retaining known mapping candidates

This commit is contained in:
evgeny committed 2026-10-17 18:10:23 +02:00
1 parent 3c321ac59c
commit bbb8cff4a6
2 files changed
+24 -30

No files matched your search

+18 -29
View File
@@ -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;
+6 -1
View File
@@ -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;
}