nat: retain observed candidates and continue mapping after ingress failures

This commit is contained in:
evgeny committed 2026-10-17 17:45:24 +02:00
1 parent b96ae1a718
commit 6037819269
3 files changed
+77 -17

No files matched your search

+1 -2
View File
@@ -45,8 +45,7 @@ static inline const char *address_profile_source_name(uint8_t value) {
static inline int address_profile_valid(const struct address_profile *profile) {
return profile && profile->mapping <= ADDRESS_MAP_DEPENDENT && profile->inbound <= ADDRESS_IN_CONTACT &&
profile->source <= ADDRESS_SOURCE_STUN &&
((!profile->mapping && !profile->inbound && profile->source == ADDRESS_SOURCE_NONE) ||
((profile->mapping || profile->inbound) && profile->source));
(profile->source || (!profile->mapping && !profile->inbound));
}
#endif
+71 -11
View File
@@ -58,7 +58,7 @@ struct nat_socket {
struct ETCP_SOCKET *socket, *helper_primary;
struct sockaddr_storage local, observer_address, helper_address;
struct sockaddr_storage baseline, helper_seen;
struct sockaddr_storage measured_address;
struct sockaddr_storage measured_address, observed_address;
struct address_profile measured_profile;
uint64_t select_due, available_fingerprint, reservation_fingerprint, cold_until, failure_recheck_tb;
uint8_t observer_key[32], helper_key[32];
@@ -234,6 +234,8 @@ static int pair_after(struct nat_pair a, struct nat_pair b) {
static int select_pair(struct nat_socket *state) {
struct NAT_DETECTION *detector = state->detector;
struct nat_pair best = {0};
struct nat_pair single = {0};
struct sockaddr_storage single_address = {0}; uint8_t single_key[32] = {0};
struct sockaddr_storage observer_address = {0}, helper_address = {0};
uint8_t observer_key[32] = {0}, helper_key[32] = {0}, helper_socket = 0;
unsigned observers = 0, groups = 0;
@@ -253,6 +255,12 @@ 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};
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))) {
single = observer_only; single_address = ba; memcpy(single_key, bn->public_key, 32);
}
for (struct ll_entry *c = state->socket->is_tcp ? b : group->nodes->head; c; c = state->socket->is_tcp ? NULL : c->next) {
uint64_t helper = ((struct TOPO_GROUP_NODE *)c)->node_id;
if (helper == detector->instance->node_id || (!state->socket->is_tcp && helper == observer) ||
@@ -278,6 +286,9 @@ static int select_pair(struct nat_socket *state) {
}
}
}
if (!best.group && single.group && !state->using_fallback) {
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);
@@ -558,6 +569,7 @@ static void clear_profile(struct nat_socket *state, uint8_t reason) {
state->cold_until = get_time_tb() + NAT_ROUND_TB + 2500;
memset(&state->measured_profile, 0, sizeof(state->measured_profile));
memset(&state->measured_address, 0, sizeof(state->measured_address));
memset(&state->observed_address, 0, sizeof(state->observed_address));
if (changed) {
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT profile invalidated socket=%s generation=%016llx reason=%s",
state->socket->name, (unsigned long long)state->socket->generation, nat_check_reason_name(reason));
@@ -574,6 +586,9 @@ static void select_profile(struct nat_socket *state) {
selected = state->measured_profile;
if (selected.mapping == ADDRESS_MAP_EIM) selected_address = state->measured_address;
}
if (!selected.mapping && state->observed_address.ss_family) {
selected.source = ADDRESS_SOURCE_ETCP; selected_address = state->observed_address;
}
int address_changed = selected_address.ss_family != state->socket->nat_addr.ss_family ||
(selected_address.ss_family && !route_ping_addr_equal(&selected_address, &state->socket->nat_addr, 1));
if (selected.mapping == current->mapping && selected.inbound == current->inbound &&
@@ -588,7 +603,33 @@ static void select_profile(struct nat_socket *state) {
publish(state->detector);
}
/* Одно наблюдение подтверждает NAT-адрес, но не его независимость от IP назначения. */
static void remember_address(struct nat_socket *state, const struct sockaddr_storage *observed) {
if (route_ping_addr_equal(&state->local, observed, 1)) return;
if (route_ping_addr_equal(&state->observed_address, observed, 1)) return;
state->observed_address = *observed;
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT address observed socket=%s local=%s external=%s mapping=%s scope=one_destination",
state->socket->name, sockaddr_storage_to_str(&state->local).str, sockaddr_storage_to_str(observed).str,
address_mapping_name(state->measured_profile.mapping));
select_profile(state);
}
static void owner_finish(struct nat_socket *state, uint8_t reason) {
/* Неудача входящего испытания не мешает сравнить mapping через уже известный публичный endpoint C. */
if ((reason == NAT_REASON_TIMEOUT || reason == NAT_REASON_CONTROL_LOST || reason == NAT_REASON_SEND_ERROR ||
reason == NAT_REASON_CONTACT_HISTORY) &&
(state->phase == NAT_PREPARE_C || state->phase == NAT_PRE || state->phase == NAT_POST) &&
get_time_tb() < state->cycle_deadline && public_address(state->detector, &state->helper_address) &&
!route_ping_addr_equal(&state->helper_address, &state->observer_address, 0) &&
!route_ping_addr_equal(&state->helper_address, &state->baseline, 0)) {
if (reason == NAT_REASON_CONTACT_HISTORY || !state->pre_received) state->filter_clean = 0;
state->phase = state->helper_seen.ss_family ? NAT_REPEAT_B : NAT_OBSERVE_C; state->retries = 0;
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT mapping continues without ingress result socket=%s round=%016llx helper=%s reason=%s",
state->socket->name, (unsigned long long)state->round, sockaddr_storage_to_str(&state->helper_address).str,
nat_check_reason_name(reason));
if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR);
return;
}
int next_pair = reason && state->phase && state->cycling && !state->using_fallback &&
reason != NAT_REASON_CANCELLED && reason != NAT_REASON_ADDRESS_CHANGED;
etcp_cancel_pings(state->detector->instance, owner_ping_cb, state);
@@ -604,8 +645,9 @@ static void owner_finish(struct nat_socket *state, uint8_t reason) {
} 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->due = get_time_tb() + (reason == NAT_REASON_ADDRESS_CHANGED ? 2500 :
state->socket->address_profile.source || !reason ? NAT_REFRESH_TB : NAT_RETRY_TB);
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);
}
if (reason) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT round incomplete socket=%s group=%016llx generation=%016llx round=%016llx reason=%s next_pair=%d",
@@ -647,6 +689,10 @@ static int owner_run(struct nat_socket *state, uint8_t step) {
message.step = step;
state->phase = step == NAT_STEP_TCP ? NAT_TCP : step == NAT_STEP_PREPARE ? NAT_PREPARE_C :
step == NAT_STEP_PRE ? NAT_PRE : NAT_POST;
uint64_t budget = (uint64_t)probe_timeout_ms(state) * 10 *
(step == NAT_STEP_PREPARE ? 2 * NAT_PROBE_COUNT + 2 : NAT_PROBE_COUNT + 2);
state->deadline = get_time_tb() + budget;
if (state->deadline > state->cycle_deadline) state->deadline = state->cycle_deadline;
if (step == NAT_STEP_PRE) state->pre_started = get_time_tb();
return send_control(state->detector, state->group_id, state->observer, &message);
}
@@ -656,6 +702,7 @@ static int owner_observe(struct nat_socket *state) {
const uint8_t *key = state->observer_key;
if (state->phase == NAT_OBSERVE_C || state->phase == NAT_REPEAT_C) { target = &state->helper_address; key = state->helper_key; }
int timeout = probe_timeout_ms(state);
state->deadline = state->cycle_deadline;
if (state->phase == NAT_OBSERVE_C && !state->retries) {
state->contact_cutoff = state->contact_count;
if (contacted(state, &state->helper_address)) state->filter_clean = 0;
@@ -715,8 +762,11 @@ static void owner_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce,
if (state->phase == NAT_OBSERVE_B) {
state->baseline = observed;
if ((state->measured_profile.mapping == ADDRESS_MAP_UNCHANGED && !route_ping_addr_equal(&state->local, &observed, 1)) ||
(state->socket->nat_addr.ss_family && !route_ping_addr_equal(&state->socket->nat_addr, &observed, 1)))
(state->measured_profile.mapping == ADDRESS_MAP_EIM &&
!route_ping_addr_equal(&state->measured_address, &observed, 1)))
clear_profile(state, NAT_REASON_ADDRESS_CHANGED);
remember_address(state, &observed);
if (!state->helper) { owner_finish(state, NAT_REASON_NO_HELPER); return; }
if (owner_run(state, NAT_STEP_PREPARE)) owner_finish(state, NAT_REASON_CONTROL_LOST);
return;
}
@@ -793,6 +843,7 @@ void nat_detection_receive_ingress(struct NAT_DETECTION *detector, struct ETCP_S
if (!state->pre_received) {
if (contacted(state, source) && !state->using_fallback) {
if (!state->fallback.group) state->fallback = state->cursor;
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);
owner_finish(state, NAT_REASON_CONTACT_HISTORY); return;
@@ -1066,15 +1117,17 @@ static void handle_result(struct nat_socket *state, const struct nat_wire *messa
int same_ip = route_ping_addr_equal(&state->observer_address, &primary, 0) ||
route_ping_addr_equal(&state->baseline, &primary, 0);
int old_contact = state->pre_received ? !state->filter_clean : contacted(state, &primary);
if (same_ip || (old_contact && !state->using_fallback)) {
if (!same_ip && !state->fallback.group) state->fallback = state->cursor;
if (same_ip) {
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT try another pair socket=%s group=%016llx helper=%016llx endpoint=%s reason=%s",
state->socket->name, (unsigned long long)state->group_id, (unsigned long long)state->helper,
sockaddr_storage_to_str(&primary).str, same_ip ? "same_external_ip" : "previous_udp_contact");
owner_finish(state, same_ip ? NAT_REASON_NO_HELPER : NAT_REASON_CONTACT_HISTORY); return;
sockaddr_storage_to_str(&primary).str, "same_external_ip");
owner_finish(state, NAT_REASON_NO_HELPER); return;
}
state->helper_address = primary; state->filter_clean = !old_contact;
if (state->pre_received || state->using_fallback) {
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) {
state->phase = NAT_OBSERVE_C;
if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR);
return;
@@ -1265,7 +1318,12 @@ static void detector_tick(void *arg) {
if (!socket->probe_only) socket_state(detector, socket, 1);
for (struct nat_socket *state = detector->sockets; state; state = state->next) {
if (now >= state->select_due) { select_profile(state); state->select_due = now + 10000; }
if (state->phase && now >= state->deadline) { owner_finish(state, NAT_REASON_TIMEOUT); continue; }
if (state->phase && now >= state->deadline) {
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT phase deadline socket=%s round=%016llx phase=%u cycle_remaining_tb=%llu",
state->socket->name, (unsigned long long)state->round, state->phase,
(unsigned long long)(state->cycle_deadline > now ? state->cycle_deadline - now : 0));
owner_finish(state, NAT_REASON_TIMEOUT); continue;
}
if (!state->phase && now >= state->due && (detector->automatic || state->forced)) {
state->forced = 0; owner_start(state);
}
@@ -1545,11 +1603,13 @@ void nat_detection_observed_by_peer(struct NAT_DETECTION *detector, struct ETCP_
if (!state) return;
if ((state->measured_profile.mapping == ADDRESS_MAP_UNCHANGED &&
!route_ping_addr_equal(&state->local, observed, 1)) ||
(link->conn->nat_addr.ss_family && !route_ping_addr_equal(&link->conn->nat_addr, observed, 1))) {
(state->measured_profile.mapping == ADDRESS_MAP_EIM &&
!route_ping_addr_equal(&state->measured_address, observed, 1))) {
if (state->phase) owner_finish(state, NAT_REASON_ADDRESS_CHANGED);
clear_profile(state, NAT_REASON_ADDRESS_CHANGED);
state->due = get_time_tb() + 2500;
}
if (link->conn->type != CFG_SERVER_TYPE_LOCAL && link->conn->type != CFG_SERVER_TYPE_PRIVATE) remember_address(state, observed);
}
void nat_detection_socket_removed(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket) {
+5 -4
View File
@@ -1166,10 +1166,10 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
a->type = TOPO_ADDR_INTERFACE; a->socket_id = e_sock->sock_id;
a->protocol = TOPO_PROTO_UDP;
a->next = v4_head; v4_head = a; }
/* Общий внешний адрес публикует только владелец после наблюдений EIM.
Наличие ответа интернет-NTP не связано с работоспособностью этого endpoint. */
/* Одно наблюдение даёт NAT-кандидат с типом UNKNOWN; только EIM подтверждает общий внешний адрес. */
if (e_sock->nat_addr.ss_family == AF_INET && nat_sin->sin_addr.s_addr != 0 &&
e_sock->address_profile.mapping == ADDRESS_MAP_EIM && e_sock->address_profile.source == ADDRESS_SOURCE_ETCP) {
(e_sock->address_profile.mapping == ADDRESS_MAP_UNKNOWN || e_sock->address_profile.mapping == ADDRESS_MAP_EIM) &&
e_sock->address_profile.source == ADDRESS_SOURCE_ETCP) {
struct TOPO_ADDR4* a = memory_pool_alloc(instance->topo_groups->v4_addr_pool);
if (!a) goto allocation_failed;
memcpy(a->addr, &nat_sin->sin_addr.s_addr, 4); a->port = ntohs(nat_sin->sin_port);
@@ -1188,7 +1188,8 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
memcpy(a6->addr, &if_sin6->sin6_addr, 16); a6->port = ntohs(if_sin6->sin6_port);
a6->type = TOPO_ADDR_INTERFACE; a6->socket_id = e_sock->sock_id; a6->protocol = TOPO_PROTO_UDP;
a6->next = v6_head; v6_head = a6; }
if (e_sock->nat_addr.ss_family == AF_INET6 && e_sock->address_profile.mapping == ADDRESS_MAP_EIM &&
if (e_sock->nat_addr.ss_family == AF_INET6 &&
(e_sock->address_profile.mapping == ADDRESS_MAP_UNKNOWN || e_sock->address_profile.mapping == ADDRESS_MAP_EIM) &&
e_sock->address_profile.source == ADDRESS_SOURCE_ETCP) {
const struct sockaddr_in6 *mapped = (const struct sockaddr_in6 *)&e_sock->nat_addr;
struct TOPO_ADDR6 *a6 = memory_pool_alloc(instance->topo_groups->v6_addr_pool);