|
|
|
|
@ -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); |
|
|
|
|
|