From 01b0992c39f66f5965416f7df16ec4bfcfd5fb41 Mon Sep 17 00:00:00 2001 From: evgeny Date: Mon, 12 Oct 2026 00:57:54 +0200 Subject: [PATCH] Select interface STUN fallback only when ETCP lacks independent observers --- src/routing_layer/nat_detection.c | 60 +++++++++++++++++++++++++--- src/routing_layer/nat_detection.h | 3 ++ src/routing_layer/topo_node_sqlite.c | 8 ++-- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/routing_layer/nat_detection.c b/src/routing_layer/nat_detection.c index 1996f5f7..48cfb8f6 100644 --- a/src/routing_layer/nat_detection.c +++ b/src/routing_layer/nat_detection.c @@ -15,6 +15,7 @@ #include "topo_group.h" #include "ntp_time.h" #include "stcp_client.h" +#include "stun_nat.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../lib/u_async.h" @@ -51,6 +52,9 @@ struct nat_socket { struct ETCP_SOCKET *socket, *auxiliary; struct sockaddr_storage local, observer_address, helper_address, alternate_address; struct sockaddr_storage baseline, helper_seen, alternate_seen; + struct address_profile measured_profile; + uint64_t select_due; + uint8_t fallback_allowed; uint8_t observer_key[32], helper_key[32]; uint64_t round, observer, helper, token, deadline, due, completed_tb, preferred_observer; uint64_t excluded[NAT_MAX_HELPERS]; @@ -356,6 +360,7 @@ static void clear_profile(struct nat_socket *state, uint8_t reason) { memset(&state->socket->address_profile, 0, sizeof(state->socket->address_profile)); memset(&state->socket->nat_addr, 0, sizeof(state->socket->nat_addr)); state->completed_tb = 0; + memset(&state->measured_profile, 0, sizeof(state->measured_profile)); 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)); @@ -363,9 +368,47 @@ static void clear_profile(struct nat_socket *state, uint8_t reason) { } } +/* Единственный выбор публикуемого профиля. STUN переносит только тип IP/netif, + * никогда не mapped endpoint тестового порта. ETCP сохраняет приоритет до TTL. */ +static void select_profile(struct nat_socket *state) { + uint64_t now = get_time_tb(); + struct address_profile selected = {0}; + if (state->completed_tb && now >= state->completed_tb && + now - state->completed_tb <= ADDRESS_PROFILE_TTL_US / 100) selected = state->measured_profile; + else if (state->fallback_allowed && !state->socket->is_tcp) { + struct stun_nat_result result; + if (!stun_nat_get_socket_result(state->detector->instance, state->socket, &result) && + result.generation && result.evidence_id && result.measured_at_us && result.measured_at_tb && + now >= result.measured_at_tb && now - result.measured_at_tb < ADDRESS_PROFILE_TTL_US / 100 && + (result.status == STUN_NAT_COMPLETE || result.status == STUN_NAT_PARTIAL)) { + if (result.mapping == STUN_MAP_EIM) selected.mapping = result.translated == 0 ? ADDRESS_MAP_UNCHANGED : ADDRESS_MAP_EIM; + else if (result.mapping == STUN_MAP_DEPENDENT || result.mapping == STUN_MAP_ADM || result.mapping == STUN_MAP_APDM) + selected.mapping = ADDRESS_MAP_DEPENDENT; + if (result.filtering == STUN_FILTER_EIF) selected.inbound = ADDRESS_IN_OPEN; + else if (result.filtering == STUN_FILTER_ADF || result.filtering == STUN_FILTER_APDF) selected.inbound = ADDRESS_IN_CONTACT; + if (selected.mapping || selected.inbound) { + selected.source = ADDRESS_SOURCE_STUN; + selected.evidence_id = result.evidence_id; selected.checked_at = result.measured_at_us; + } + } + } + const struct address_profile *current = &state->socket->address_profile; + if (selected.mapping == current->mapping && selected.inbound == current->inbound && selected.source == current->source && + selected.evidence_id == current->evidence_id && selected.checked_at == current->checked_at) return; + DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT selected socket=%s generation=%016llx source=%s previous=%s mapping=%s inbound=%s" + " evidence=%016llx reason=%s", + state->socket->name, (unsigned long long)state->socket->generation, address_profile_source_name(selected.source), + address_profile_source_name(current->source), address_mapping_name(selected.mapping), address_inbound_name(selected.inbound), + (unsigned long long)selected.evidence_id, nat_check_reason_name(state->reason)); + state->socket->address_profile = selected; + if (selected.source != ADDRESS_SOURCE_ETCP) memset(&state->socket->nat_addr, 0, sizeof(state->socket->nat_addr)); + publish(state->detector); +} + static void owner_finish(struct nat_socket *state, uint8_t reason) { etcp_cancel_pings(state->detector->instance, owner_ping_cb, state); state->active_ping = 0; state->phase = NAT_IDLE; state->reason = reason; + if (reason == NAT_REASON_NO_OBSERVER || reason == NAT_REASON_NO_HELPER) state->fallback_allowed = 1; state->preferred_observer = 0; state->due = get_time_tb() + (reason ? NAT_RETRY_TB : NAT_REFRESH_TB); if (reason) { @@ -373,15 +416,17 @@ static void owner_finish(struct nat_socket *state, uint8_t reason) { state->socket->name, (unsigned long long)state->socket->generation, (unsigned long long)state->round, nat_check_reason_name(reason)); } + select_profile(state); } static void owner_apply(struct nat_socket *state, uint8_t mapping, uint8_t inbound) { int64_t now = ntp_time_get_us(state->detector->instance); if (now <= 0) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT profile time unavailable"); owner_finish(state, NAT_REASON_INVALID_OBSERVATION); return; } - struct address_profile profile = {.mapping = mapping, .inbound = inbound, .evidence_id = state->round, .checked_at = now}; + struct address_profile profile = {.mapping = mapping, .inbound = inbound, .source = ADDRESS_SOURCE_ETCP, + .evidence_id = state->round, .checked_at = now}; uint8_t partial_reason = inbound != ADDRESS_IN_UNKNOWN ? NAT_REASON_NONE : !state->filter_clean ? NAT_REASON_CONTACT_HISTORY : mapping == ADDRESS_MAP_DEPENDENT ? NAT_REASON_DEPENDENT_MAPPING : NAT_REASON_TIMEOUT; - state->socket->address_profile = profile; + state->measured_profile = profile; state->fallback_allowed = 0; memset(&state->socket->nat_addr, 0, sizeof(state->socket->nat_addr)); if (mapping == ADDRESS_MAP_EIM) state->socket->nat_addr = state->baseline; state->completed_tb = get_time_tb(); @@ -392,7 +437,7 @@ static void owner_apply(struct nat_socket *state, uint8_t mapping, uint8_t inbou sockaddr_storage_to_str(&state->local).str, sockaddr_storage_to_str(&state->baseline).str, (unsigned long long)state->observer, (unsigned long long)state->helper, state->filter_clean, nat_check_reason_name(partial_reason)); - owner_finish(state, NAT_REASON_NONE); state->reason = partial_reason; publish(state->detector); + owner_finish(state, NAT_REASON_NONE); state->reason = partial_reason; } static int owner_find(struct nat_socket *state) { @@ -823,7 +868,8 @@ static void owner_start(struct nat_socket *state) { if (select_observer(state)) { if (state->reason != NAT_REASON_NO_OBSERVER) DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT profile remains UNKNOWN: no live observer socket=%s", state->socket->name); - state->reason = NAT_REASON_NO_OBSERVER; state->due = get_time_tb() + NAT_RETRY_TB; return; + state->reason = NAT_REASON_NO_OBSERVER; state->fallback_allowed = 1; + state->due = get_time_tb() + NAT_RETRY_TB; select_profile(state); return; } if (random_id(&state->round)) { state->reason = NAT_REASON_SEND_ERROR; state->due = get_time_tb() + NAT_RETRY_TB; return; } history_load(state); @@ -852,6 +898,7 @@ static void detector_tick(void *arg) { for (struct nat_socket *state = detector->sockets; state; state = state->next) { if (state->completed_tb && now - state->completed_tb > ADDRESS_PROFILE_TTL_US / 100) clear_profile(state, NAT_REASON_TIMEOUT); + 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->due && (detector->automatic || state->forced)) { state->forced = 0; owner_start(state); @@ -913,7 +960,7 @@ void nat_detection_observed_by_peer(struct NAT_DETECTION *detector, struct ETCP_ if (!detector || !link || !link->conn || link->is_tcp || link->conn->socks_udp || !observed->ss_family) return; struct nat_socket *state = socket_state(detector, link->conn, 1); if (!state) return; - if ((link->conn->address_profile.mapping == ADDRESS_MAP_UNCHANGED && + 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))) clear_profile(state, NAT_REASON_ADDRESS_CHANGED); @@ -1029,6 +1076,7 @@ int nat_detection_get_status(struct NAT_DETECTION *detector, struct ETCP_SOCKET if (!state || !status) return -1; *status = (struct nat_check_status){.generation = socket->generation, .round = state->round, .observer = state->observer, .helper = state->helper, .rounds = state->rounds, .helper_reselections = state->helper_reselections, - .phase = state->phase, .reason = state->reason, .history_complete = state->history_complete}; + .phase = state->phase, .reason = state->reason, .history_complete = state->history_complete, + .measured_profile = state->measured_profile, .effective_profile = socket->address_profile, .fallback_allowed = state->fallback_allowed}; return 0; } diff --git a/src/routing_layer/nat_detection.h b/src/routing_layer/nat_detection.h index 98a602e2..c349f5f2 100644 --- a/src/routing_layer/nat_detection.h +++ b/src/routing_layer/nat_detection.h @@ -7,6 +7,7 @@ #include #include #include "../lib/socket_compat.h" +#include "address_profile.h" #ifdef __cplusplus extern "C" { @@ -25,10 +26,12 @@ enum nat_check_reason { }; struct nat_check_status { + struct address_profile measured_profile, effective_profile; uint64_t generation, round; uint64_t observer, helper; uint32_t rounds, helper_reselections; uint8_t phase, reason, history_complete; + uint8_t fallback_allowed; }; struct NAT_DETECTION *nat_detection_create(struct UTUN_INSTANCE *instance); diff --git a/src/routing_layer/topo_node_sqlite.c b/src/routing_layer/topo_node_sqlite.c index 2bcdc305..df0f240d 100644 --- a/src/routing_layer/topo_node_sqlite.c +++ b/src/routing_layer/topo_node_sqlite.c @@ -123,6 +123,7 @@ static int address_row_put(sqlite3_stmt *statement, struct TOPO_NODE *node, uint sqlite3_bind_int(statement, 11, kind); sqlite3_bind_int(statement, 12, profile->inbound); sqlite3_bind_int64(statement, 13, (sqlite3_int64)profile->evidence_id); sqlite3_bind_int64(statement, 14, (sqlite3_int64)profile->checked_at); + sqlite3_bind_int(statement, 15, profile->source); int result = sqlite3_step(statement); sqlite3_reset(statement); if (result != SQLITE_DONE) { @@ -146,8 +147,8 @@ int topo_node_sqlite_addrs_put(sqlite3 *db, uint64_t node_id, struct TOPO_NODE * if (sqlite3_step(statement) != SQLITE_DONE) goto fail; sqlite3_finalize(statement); statement = NULL; if (sqlite3_prepare_v2(db, "INSERT INTO node_addresses(node_id,family,protocol,address,port,addr_type,config_type," - "nat_mapping,socket_id,options,address_kind,nat_inbound,nat_evidence,nat_checked)" - " VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?)", -1, &statement, NULL) != SQLITE_OK) goto fail; + "nat_mapping,socket_id,options,address_kind,nat_inbound,nat_evidence,nat_checked,nat_source)" + " VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", -1, &statement, NULL) != SQLITE_OK) goto fail; int written = 0; for (const struct TOPO_ADDR4 *address = node->v4_addrs; address; address = address->next) { uint8_t config; @@ -251,7 +252,7 @@ int topo_node_sqlite_init(sqlite3* db) { sqlite3_finalize(schema); schema = NULL; if (exists) { int compatible = sqlite3_prepare_v2(db, - "SELECT nat_mapping,nat_inbound,nat_evidence,nat_checked,address_kind FROM node_addresses LIMIT 0", + "SELECT nat_mapping,nat_inbound,nat_evidence,nat_checked,nat_source,address_kind FROM node_addresses LIMIT 0", -1, &schema, NULL); sqlite3_finalize(schema); if (compatible == SQLITE_ERROR) { @@ -293,6 +294,7 @@ int topo_node_sqlite_init(sqlite3* db) { " nat_inbound INTEGER NOT NULL DEFAULT 0," " nat_evidence INTEGER NOT NULL DEFAULT 0," " nat_checked INTEGER NOT NULL DEFAULT 0," + " nat_source INTEGER NOT NULL DEFAULT 0," " address_kind INTEGER NOT NULL DEFAULT 0," " socket_id INTEGER NOT NULL DEFAULT 0," " options BLOB," // reality-параметры (TOPO_ADDR_REALITY_OPTS)