fix(nat): continue helper traversal after partial mapping results
This commit is contained in:
1 parent
7c7b9646f3
commit
6ad3674904
2 files changed
+47
-21
No files matched your search
@@ -66,10 +66,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;
|
||||
struct nat_pair cursor, current_pair; /* Курсор продвигается только после завершённой попытки. */
|
||||
uint8_t contacts[NAT_MAX_CONTACTS][17];
|
||||
unsigned contact_count, contact_cutoff, phase, reason, retries;
|
||||
unsigned contact_count, contact_cutoff, cycle_contact_cutoff, phase, reason, retries;
|
||||
uint8_t history_loaded, history_complete, history_incomplete, filter_clean, pre_received, post_received, pre_healthy;
|
||||
uint8_t cycle_history_complete, cycle_mapping_seen;
|
||||
uint8_t helper_socket, active_ping, heartbeat_active, forced, cycling;
|
||||
uint32_t rounds, helper_reselections;
|
||||
};
|
||||
@@ -143,7 +144,8 @@ static int random_id(uint64_t *value) {
|
||||
|
||||
const char *nat_check_reason_name(uint8_t reason) {
|
||||
static const char *const names[] = {"none", "no_observer", "no_helper", "control_lost", "send_error", "timeout",
|
||||
"address_changed", "contact_history", "proxy", "no_listener", "cancelled", "invalid_observation", "dependent_mapping"};
|
||||
"address_changed", "contact_history", "proxy", "no_listener", "cancelled", "invalid_observation", "dependent_mapping",
|
||||
"cycle_budget"};
|
||||
return reason < sizeof(names) / sizeof(names[0]) ? names[reason] : "invalid_reason";
|
||||
}
|
||||
|
||||
@@ -274,8 +276,11 @@ static int select_pair(struct nat_socket *state) {
|
||||
for (; more_c; more_c =
|
||||
node_udp_endpoint(detector, cn, state->local.ss_family, 1, previous_c, &ca, &cs)) {
|
||||
if (route_ping_addr_encode(previous_c, &ca)) break;
|
||||
int old_contact = !state->cycle_history_complete;
|
||||
for (unsigned i = 0; i < state->cycle_contact_cutoff; i++)
|
||||
if (!memcmp(state->contacts[i], previous_c, 17)) { old_contact = 1; break; }
|
||||
struct nat_pair candidate = {.group = group->group_id, .observer = observer, .helper = helper,
|
||||
.helper_priority = 2 * !public_address(detector, &ca) + contacted(state, &ca)};
|
||||
.helper_priority = 2 * !public_address(detector, &ca) + old_contact};
|
||||
memcpy(candidate.observer_endpoint, previous_b, sizeof(previous_b));
|
||||
memcpy(candidate.helper_endpoint, previous_c, sizeof(previous_c));
|
||||
if (!pair_after(candidate, state->cursor)) continue;
|
||||
@@ -287,16 +292,17 @@ static int select_pair(struct nat_socket *state) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!best.group && single.group) {
|
||||
if (!best.group && single.group && !state->cycle_mapping_seen) {
|
||||
best = single; observer_address = single_address; memcpy(observer_key, single_key, 32);
|
||||
}
|
||||
if (!best.group) {
|
||||
state->reason = observers ? NAT_REASON_NO_HELPER : NAT_REASON_NO_OBSERVER;
|
||||
if (!observers || !state->cursor.group || !state->reason)
|
||||
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;
|
||||
}
|
||||
state->cursor = best;
|
||||
state->current_pair = 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);
|
||||
@@ -650,20 +656,25 @@ static void owner_finish(struct nat_socket *state, uint8_t reason) {
|
||||
if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR);
|
||||
return;
|
||||
}
|
||||
uint64_t now = get_time_tb();
|
||||
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);
|
||||
if (next_pair && get_time_tb() >= state->cycle_deadline) {
|
||||
next_pair = 0;
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT candidate cycle budget exhausted socket=%s round=%016llx",
|
||||
state->socket->name, (unsigned long long)state->round);
|
||||
}
|
||||
int pause = next_pair && now >= state->cycle_deadline;
|
||||
if (next_pair && reason != NAT_REASON_BUDGET) state->cursor = state->current_pair;
|
||||
state->active_ping = 0; state->heartbeat_active = 0; state->phase = NAT_IDLE; state->reason = reason;
|
||||
if (next_pair) {
|
||||
state->forced = 1; state->due = get_time_tb() + 1000; state->helper_reselections++;
|
||||
state->forced = 1; state->due = now + (pause ? NAT_RETRY_TB : 1000);
|
||||
if (reason != NAT_REASON_BUDGET) state->helper_reselections++;
|
||||
if (pause) DEBUG_INFO(DEBUG_CATEGORY_NAT,
|
||||
"NAT candidate cycle paused socket=%s round=%016llx retry_current=%d resume_in_tb=%llu mapping=%s inbound=%s",
|
||||
state->socket->name, (unsigned long long)state->round, reason == NAT_REASON_BUDGET,
|
||||
(unsigned long long)NAT_RETRY_TB, address_mapping_name(state->measured_profile.mapping),
|
||||
address_inbound_name(state->measured_profile.inbound));
|
||||
} else {
|
||||
state->cycling = 0; state->preferred_observer = 0; state->cursor = (struct nat_pair){0};
|
||||
state->current_pair = (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);
|
||||
@@ -688,6 +699,7 @@ static void owner_apply(struct nat_socket *state, uint8_t mapping, uint8_t inbou
|
||||
nat_check_reason_name(partial_reason));
|
||||
}
|
||||
state->measured_profile = profile; state->measured_address = state->baseline;
|
||||
state->cycle_mapping_seen = 1;
|
||||
state->completed_tb = get_time_tb();
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT profile socket=%s id=%u generation=%016llx round=%016llx mapping=%s inbound=%s effective_inbound=%s"
|
||||
" local=%s external=%s observer=%016llx helper=%016llx clean=%u partial_reason=%s",
|
||||
@@ -697,8 +709,9 @@ 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;
|
||||
if (!profile.inbound && mapping != ADDRESS_MAP_DEPENDENT) state->due = get_time_tb() + NAT_RETRY_TB;
|
||||
int complete = inbound != ADDRESS_IN_UNKNOWN || mapping == ADDRESS_MAP_DEPENDENT;
|
||||
owner_finish(state, complete ? NAT_REASON_NONE : partial_reason);
|
||||
if (complete) state->reason = partial_reason;
|
||||
}
|
||||
|
||||
static int owner_run(struct nat_socket *state, uint8_t step) {
|
||||
@@ -740,6 +753,7 @@ static void owner_heartbeat_cb(int success, uint16_t rtt, void *arg, uint64_t no
|
||||
(void)rtt; (void)nonce;
|
||||
struct nat_socket *state = arg; state->heartbeat_active = 0;
|
||||
if (!state->phase) return;
|
||||
if (get_time_tb() >= state->cycle_deadline) { owner_finish(state, NAT_REASON_BUDGET); return; }
|
||||
struct sockaddr_storage observed;
|
||||
if (success && !route_ping_observation(data, length, state->socket, state->group_id, state->round, &observed) &&
|
||||
public_address(state->detector, &observed)) {
|
||||
@@ -762,6 +776,7 @@ static void owner_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce,
|
||||
struct nat_socket *state = arg;
|
||||
state->active_ping = 0;
|
||||
if (state->phase == NAT_IDLE) return;
|
||||
if (get_time_tb() >= state->cycle_deadline) { owner_finish(state, NAT_REASON_BUDGET); return; }
|
||||
struct sockaddr_storage observed;
|
||||
if (!success || route_ping_observation(data, length, state->socket, state->group_id, state->round, &observed)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT observation incomplete socket=%s phase=%u round=%016llx attempt=%u reason=%s",
|
||||
@@ -842,6 +857,7 @@ void nat_detection_receive_ingress(struct NAT_DETECTION *detector, struct ETCP_S
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT ingress correlation/source mismatch socket=%s phase=%u round=%016llx source=%s",
|
||||
socket->name, state->phase, (unsigned long long)state->round, sockaddr_storage_to_str(source).str); return;
|
||||
}
|
||||
if (get_time_tb() >= state->cycle_deadline) { owner_finish(state, NAT_REASON_BUDGET); return; }
|
||||
if (!public_address(detector, source)) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper rejected by receiver socket=%s round=%016llx helper=%016llx source=%s reason=non_public_source",
|
||||
socket->name, (unsigned long long)state->round, (unsigned long long)state->helper, sockaddr_storage_to_str(source).str);
|
||||
@@ -1081,6 +1097,7 @@ static void handle_result(struct nat_socket *state, const struct nat_wire *messa
|
||||
if (!step || message->step != step || read64(message->token) != state->token || read64(message->helper) != state->helper) {
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT stale helper result ignored socket=%s phase=%u", state->socket->name, state->phase); return;
|
||||
}
|
||||
if (get_time_tb() >= state->cycle_deadline) { owner_finish(state, NAT_REASON_BUDGET); return; }
|
||||
if (message->reason || message->errors || !message->sent || message->received > message->sent) {
|
||||
owner_finish(state, message->reason ? message->reason : NAT_REASON_SEND_ERROR); return;
|
||||
}
|
||||
@@ -1150,6 +1167,7 @@ static void receive_observation(struct NAT_DETECTION *detector, uint64_t group,
|
||||
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT stale/foreign observation report ignored group=%016llx sender=%016llx step=%u",
|
||||
(unsigned long long)group, (unsigned long long)sender, message->step); return;
|
||||
}
|
||||
if (get_time_tb() >= state->cycle_deadline) { owner_finish(state, NAT_REASON_BUDGET); return; }
|
||||
etcp_cancel_pings(detector->instance, owner_ping_cb, state);
|
||||
if (message->reason) { owner_finish(state, message->reason); return; }
|
||||
owner_ping_cb(1, 0, state, 0, data, sizeof(data));
|
||||
@@ -1259,14 +1277,21 @@ static void owner_start(struct nat_socket *state) {
|
||||
if (state->socket->type == CFG_SERVER_TYPE_LOCAL || state->socket->type == CFG_SERVER_TYPE_PRIVATE) {
|
||||
state->due = UINT64_MAX; return;
|
||||
}
|
||||
uint64_t now = get_time_tb();
|
||||
history_load(state);
|
||||
if (!state->cycling) {
|
||||
state->cycling = 1; state->cursor = (struct nat_pair){0};
|
||||
state->cycle_deadline = get_time_tb() + NAT_ROUND_TB;
|
||||
state->cycle_contact_cutoff = state->contact_count; state->cycle_history_complete = state->history_complete;
|
||||
state->cycle_mapping_seen = 0; state->reason = NAT_REASON_NONE;
|
||||
state->cycle_deadline = now + NAT_ROUND_TB;
|
||||
} else if (now >= state->cycle_deadline) {
|
||||
state->cycle_deadline = now + NAT_ROUND_TB;
|
||||
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT candidate cycle resumed socket=%s contacts_at_start=%u current_contacts=%u",
|
||||
state->socket->name, state->cycle_contact_cutoff, state->contact_count);
|
||||
}
|
||||
history_load(state);
|
||||
if (select_pair(state)) { owner_finish(state, state->reason); return; }
|
||||
if (random_id(&state->round)) { owner_finish(state, NAT_REASON_SEND_ERROR); return; }
|
||||
state->rounds++;
|
||||
if (select_pair(state)) { owner_finish(state, state->reason); return; }
|
||||
memset(&state->helper_seen, 0, sizeof(state->helper_seen)); memset(&state->baseline, 0, sizeof(state->baseline));
|
||||
state->retries = 0; state->pre_received = 0; state->post_received = 0; state->filter_clean = 1; state->pre_healthy = 0;
|
||||
state->heartbeat_due = get_time_tb(); state->heartbeat_received = 0; state->pre_started = 0;
|
||||
@@ -1291,7 +1316,7 @@ static void detector_tick(void *arg) {
|
||||
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;
|
||||
owner_finish(state, now >= state->cycle_deadline ? NAT_REASON_BUDGET : NAT_REASON_TIMEOUT); continue;
|
||||
}
|
||||
if (!state->phase && now >= state->due && (detector->automatic || state->forced)) {
|
||||
state->forced = 0; owner_start(state);
|
||||
|
||||
@@ -50,7 +50,8 @@ enum nat_check_reason {
|
||||
NAT_REASON_NO_LISTENER, /* Нет публичного TCP-listener для проверки входящих. */
|
||||
NAT_REASON_CANCELLED, /* Проверка отменена. */
|
||||
NAT_REASON_INVALID_OBSERVATION, /* Наблюдение непригодно, в том числе из-за непубличного source IP. */
|
||||
NAT_REASON_DEPENDENT_MAPPING /* Определён strict, входящая доступность осталась неизвестной. */
|
||||
NAT_REASON_DEPENDENT_MAPPING, /* Определён strict, входящая доступность осталась неизвестной. */
|
||||
NAT_REASON_BUDGET /* Локальная пауза по общему бюджету: текущая попытка будет повторена. */
|
||||
};
|
||||
|
||||
/* Снимок состояния одного сокета для диагностики; получение снимка не запускает проверку. */
|
||||
@@ -67,7 +68,7 @@ struct nat_check_status {
|
||||
uint8_t phase; /* 0 — ожидание; иначе внутренний номер текущего этапа. */
|
||||
uint8_t reason; /* Причина завершения или неполного результата: enum nat_check_reason. */
|
||||
uint8_t history_complete; /* 1 — история исходящих IP загружена без известных потерь. */
|
||||
uint8_t cycling; /* 1 — идёт цикл проверки с перебором кандидатов. */
|
||||
uint8_t cycling; /* 1 — идёт обход кандидатов, включая паузу по бюджету; phase=0 во время паузы. */
|
||||
};
|
||||
|
||||
/* Создать детектор для instance с готовым маршрутизатором и группами; автоматические проверки включены.
|
||||
|
||||
Reference in new issue
Block a user