Select interface STUN fallback only when ETCP lacks independent observers
This commit is contained in:
1 parent
735ac36579
commit
01b0992c39
3 files changed
+62
-9
No files matched your search
@@ -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;
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user