diff --git a/src/routing_layer/nat_detection.c b/src/routing_layer/nat_detection.c index 694291e3..4384a782 100644 --- a/src/routing_layer/nat_detection.c +++ b/src/routing_layer/nat_detection.c @@ -1,6 +1,6 @@ -/* Владелец выбирает пару из всех групп; управление A->B->C идёт через router. - * S->B observation, C->S controlled ingress, S->C/C-alt observations, S->B repeat. - * Два отдельных порта C и короткий приоритет холодного owner сохраняют независимость ingress. */ +/* Владелец измеряет рабочий UDP endpoint по разным IP. Наблюдения возвращаются + * через router, независимо от прямого UDP-ответа. Входящие пробы не получают PONG; + * незнакомость фактического source IP проверяет только получатель. */ #include "nat_detection.h" #include @@ -16,13 +16,12 @@ #include "etcp_router.h" #include "route_crypto.h" #include "stcp_client.h" -#include "stun_nat.h" #include "sock_match.h" #include "../lib/debug_config.h" #include "../lib/mem.h" #include "../lib/u_async.h" -#define NAT_WIRE_VERSION 3 +#define NAT_WIRE_VERSION 4 #define NAT_MAX_BINDINGS 128 #define NAT_MAX_PROBES 64 #define NAT_MAX_CONTACTS 256 @@ -34,39 +33,42 @@ #define NAT_PROBE_COUNT 3 #define NAT_QUERY_TAG_SIZE (ROUTE_OBSERVE_TAG_SIZE + 9) -enum nat_command { NAT_RUN = 1, NAT_RESULT, NAT_RECHECK }; -enum nat_phase { NAT_IDLE, NAT_OBSERVE_B, NAT_PREPARE_C, NAT_PRE, NAT_OBSERVE_C, NAT_PREPARE_ALT, - NAT_OBSERVE_ALT, NAT_POST, NAT_REPEAT_B, NAT_TCP }; -enum nat_step { NAT_STEP_PREPARE = 1, NAT_STEP_PRE, NAT_STEP_ALT, NAT_STEP_POST, NAT_STEP_TCP }; +enum nat_command { NAT_RUN = 1, NAT_RESULT, NAT_RECHECK, NAT_OBSERVATION }; +enum nat_phase { NAT_IDLE, NAT_OBSERVE_B, NAT_PREPARE_C, NAT_PRE, NAT_OBSERVE_C, + NAT_POST, NAT_REPEAT_B, NAT_REPEAT_C, NAT_PRE_WAIT, NAT_POST_WAIT, NAT_TCP }; +enum nat_step { NAT_STEP_PREPARE = 1, NAT_STEP_PRE, NAT_STEP_POST, NAT_STEP_TCP, NAT_STEP_HELPER_OBSERVE = 128 }; /* Только байты: формат независим от alignment/endian платформы. */ struct nat_wire { uint8_t cmd, command, version, owner_socket, helper_socket, step, reason, sent, received, errors; uint8_t owner[8], generation[8], round[8], token[8], observer[8], helper[8], timeout_ms[2]; - uint8_t target[ROUTE_ADDR_WIRE_SIZE], primary[ROUTE_ADDR_WIRE_SIZE], secondary[ROUTE_ADDR_WIRE_SIZE]; + uint8_t target[ROUTE_ADDR_WIRE_SIZE], primary[ROUTE_ADDR_WIRE_SIZE]; uint8_t observer_address[ROUTE_ADDR_WIRE_SIZE]; }; -struct nat_pair { uint64_t group, observer, helper; }; +struct nat_pair { + uint64_t group, observer, helper; + uint8_t observer_endpoint[ROUTE_ADDR_WIRE_SIZE], helper_endpoint[ROUTE_ADDR_WIRE_SIZE]; +}; struct nat_recheck_wire { uint8_t cmd, command, version, socket_id, address[ROUTE_ADDR_WIRE_SIZE]; }; struct nat_socket { struct nat_socket *next; struct NAT_DETECTION *detector; - struct ETCP_SOCKET *socket, *helper_primary, *auxiliary; - struct sockaddr_storage local, observer_address, helper_address, alternate_address; - struct sockaddr_storage baseline, helper_seen, alternate_seen; + 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 address_profile measured_profile; uint64_t select_due, available_fingerprint, reservation_fingerprint, cold_until, failure_recheck_tb; - uint8_t fallback_allowed; uint8_t observer_key[32], helper_key[32]; - uint64_t group_id, round, observer, helper, token, deadline, due, completed_tb, preferred_observer; + 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, fallback; uint8_t contacts[NAT_MAX_CONTACTS][17]; - unsigned contact_count, phase, reason, retries; + unsigned contact_count, contact_cutoff, phase, reason, retries; uint8_t history_loaded, history_complete, history_incomplete, filter_clean, pre_received, post_received, pre_healthy; - uint8_t helper_socket, active_ping, forced, cycling, using_fallback, prefer_fresh; + uint8_t helper_socket, active_ping, heartbeat_active, forced, cycling, using_fallback, prefer_fresh; uint32_t rounds, helper_reselections; }; @@ -83,7 +85,7 @@ struct nat_probe { struct nat_wire message; uint64_t group_id, reply, source_generation, io_generation, due, expires; uint8_t owner_key[32], observer_key[32]; - uint8_t source_socket, io_socket, active, cancelled, alternate_fallback; + uint8_t source_socket, io_socket, active, cancelled, finding_primary; struct stcp_client *tcp; }; @@ -99,6 +101,7 @@ struct NAT_DETECTION { }; static void owner_ping_cb(int, uint16_t, void *, uint64_t, const uint8_t *, size_t); +static void owner_heartbeat_cb(int, uint16_t, void *, uint64_t, const uint8_t *, size_t); static void helper_ping_cb(int, uint16_t, void *, uint64_t, const uint8_t *, size_t); static void detector_tick(void *); static int owner_observe(struct nat_socket *); @@ -179,8 +182,9 @@ static int control_route(struct NAT_DETECTION *detector, uint64_t group_id, uint /* Выбирает сокет по NODEINFO. Помощнику разрешён приватный endpoint: его mapping измеряется перед ingress. */ static int node_udp_endpoint(struct NAT_DETECTION *detector, const struct TOPO_NODE *node, int family, int allow_private, - struct sockaddr_storage *address, uint8_t *socket_id) { - int best = 4; + const uint8_t after[ROUTE_ADDR_WIRE_SIZE], struct sockaddr_storage *address, uint8_t *socket_id) { + uint8_t best[ROUTE_ADDR_WIRE_SIZE] = {0}; + if (!node) return 0; for (const struct TOPO_ADDR4 *a = family == AF_INET ? node->v4_addrs : NULL; a; a = a->next) { if (!(a->protocol & TOPO_PROTO_UDP) || !a->port) continue; const struct TOPO_SOCKMETA4 *meta = node->v4_sock_meta; @@ -188,9 +192,10 @@ static int node_udp_endpoint(struct NAT_DETECTION *detector, const struct TOPO_N if (meta && (meta->config_type == CFG_SERVER_TYPE_PRIVATE || meta->config_type == CFG_SERVER_TYPE_LOCAL)) continue; struct sockaddr_storage candidate = {0}; struct sockaddr_in *v4 = (struct sockaddr_in *)&candidate; v4->sin_family = AF_INET; memcpy(&v4->sin_addr, a->addr, 4); v4->sin_port = htons(a->port); - int rank = public_address(detector, &candidate) ? 0 : 2; - if ((!allow_private && rank) || rank > best || (rank == best && a->socket_id >= *socket_id)) continue; - best = rank; *address = candidate; *socket_id = a->socket_id; + uint8_t wire[ROUTE_ADDR_WIRE_SIZE]; + if ((!allow_private && !public_address(detector, &candidate)) || route_ping_addr_encode(wire, &candidate)) continue; + if ((after && memcmp(wire, after, sizeof(wire)) <= 0) || (best[0] && memcmp(wire, best, sizeof(wire)) >= 0)) continue; + memcpy(best, wire, sizeof(best)); *address = candidate; *socket_id = a->socket_id; } for (const struct TOPO_ADDR6 *a = family == AF_INET6 ? node->v6_addrs : NULL; a; a = a->next) { if (!(a->protocol & TOPO_PROTO_UDP) || !a->port) continue; @@ -199,25 +204,30 @@ static int node_udp_endpoint(struct NAT_DETECTION *detector, const struct TOPO_N if (meta && (meta->config_type == CFG_SERVER_TYPE_PRIVATE || meta->config_type == CFG_SERVER_TYPE_LOCAL)) continue; struct sockaddr_storage candidate = {0}; struct sockaddr_in6 *v6 = (struct sockaddr_in6 *)&candidate; v6->sin6_family = AF_INET6; memcpy(&v6->sin6_addr, a->addr, 16); v6->sin6_port = htons(a->port); - int rank = public_address(detector, &candidate) ? 0 : 2; - if ((!allow_private && rank) || rank > best || (rank == best && a->socket_id >= *socket_id)) continue; - best = rank; *address = candidate; *socket_id = a->socket_id; + uint8_t wire[ROUTE_ADDR_WIRE_SIZE]; + if ((!allow_private && !public_address(detector, &candidate)) || route_ping_addr_encode(wire, &candidate)) continue; + if ((after && memcmp(wire, after, sizeof(wire)) <= 0) || (best[0] && memcmp(wire, best, sizeof(wire)) >= 0)) continue; + memcpy(best, wire, sizeof(best)); *address = candidate; *socket_id = a->socket_id; } /* Наблюдаемый адрес уже работающего UDP помогает bootstrap за NAT; прямой линк не является условием отбора. */ struct ETCP_CONN *connection = instance_find_conn(detector->instance, node->node_id); for (struct ETCP_LINK *link = connection ? connection->links : NULL; link; link = link->next) { if (link->is_tcp || !link->initialized || !link->link_status || link->remote_only_local || link->remote_addr.ss_family != family || !public_address(detector, &link->remote_addr)) continue; - if (best == 0) break; - *address = link->remote_addr; *socket_id = link->remote_socket_id; best = 1; break; + uint8_t wire[ROUTE_ADDR_WIRE_SIZE]; + if (route_ping_addr_encode(wire, &link->remote_addr)) continue; + if ((after && memcmp(wire, after, sizeof(wire)) <= 0) || (best[0] && memcmp(wire, best, sizeof(wire)) >= 0)) continue; + memcpy(best, wire, sizeof(best)); *address = link->remote_addr; *socket_id = link->remote_socket_id; } - return best < 4; + return best[0] != 0; } static int pair_after(struct nat_pair a, struct nat_pair b) { if (a.group != b.group) return a.group > b.group; if (a.observer != b.observer) return a.observer > b.observer; - return a.helper > b.helper; + if (a.helper != b.helper) return a.helper > b.helper; + int order = memcmp(a.observer_endpoint, b.observer_endpoint, sizeof(a.observer_endpoint)); + return order ? order > 0 : memcmp(a.helper_endpoint, b.helper_endpoint, sizeof(a.helper_endpoint)) > 0; } /* Обходит пары всех групп без ограничения числа узлов и без хранения указателей на топологию. */ @@ -236,24 +246,35 @@ static int select_pair(struct nat_socket *state) { if (observer == detector->instance->node_id || (state->preferred_observer && observer != state->preferred_observer) || !control_route(detector, group->group_id, observer)) continue; const struct TOPO_NODE *bn = group_node(detector, group->group_id, observer); + uint8_t previous_b[ROUTE_ADDR_WIRE_SIZE] = {0}; struct sockaddr_storage ba = {0}; uint8_t bs = 0; - if (!state->socket->is_tcp && !node_udp_endpoint(detector, bn, state->local.ss_family, 0, &ba, &bs)) continue; + for (int more_b = state->socket->is_tcp || node_udp_endpoint(detector, bn, state->local.ss_family, 0, previous_b, &ba, &bs); + more_b; more_b = !state->socket->is_tcp && 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++; 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) || !control_route(detector, group->group_id, helper)) continue; const struct TOPO_NODE *cn = group_node(detector, group->group_id, helper); - struct sockaddr_storage ca = {0}; uint8_t cs = 0; - if (!state->socket->is_tcp && !node_udp_endpoint(detector, cn, state->local.ss_family, 1, &ca, &cs)) continue; - if (!state->socket->is_tcp && state->prefer_fresh && !state->using_fallback && contacted(state, &ca)) continue; - struct nat_pair candidate = {group->group_id, observer, helper}; + struct sockaddr_storage ca = {0}; uint8_t cs = 0, previous_c[ROUTE_ADDR_WIRE_SIZE] = {0}; + for (int more_c = state->socket->is_tcp || node_udp_endpoint(detector, cn, state->local.ss_family, 1, previous_c, &ca, &cs); + more_c; more_c = !state->socket->is_tcp && node_udp_endpoint(detector, cn, state->local.ss_family, 1, previous_c, &ca, &cs)) { + if (!state->socket->is_tcp && route_ping_addr_encode(previous_c, &ca)) break; + struct nat_pair candidate = {.group = group->group_id, .observer = observer, .helper = helper}; + memcpy(candidate.observer_endpoint, previous_b, sizeof(previous_b)); + memcpy(candidate.helper_endpoint, previous_c, sizeof(previous_c)); if (state->using_fallback) { - if (memcmp(&candidate, &state->fallback, sizeof(candidate))) continue; + if (pair_after(candidate, state->fallback) || pair_after(state->fallback, candidate)) continue; } else if (!pair_after(candidate, state->cursor)) continue; + if (!state->socket->is_tcp && state->prefer_fresh && !state->using_fallback && contacted(state, &ca)) continue; if (best.group && !pair_after(best, candidate)) continue; best = candidate; observer_address = ba; helper_address = ca; helper_socket = cs; memcpy(observer_key, bn->public_key, 32); memcpy(helper_key, cn->public_key, 32); + if (state->socket->is_tcp) break; + } + } + if (state->socket->is_tcp) break; } } } @@ -389,11 +410,12 @@ void nat_detection_note_send(struct NAT_DETECTION *detector, struct ETCP_SOCKET struct nat_socket *state = socket_state(detector, socket, 1); if (!state) return; history_load(state); - if (state->phase == NAT_PRE && !state->pre_received && + if ((state->phase == NAT_PREPARE_C || state->phase == NAT_PRE || state->phase == NAT_PRE_WAIT) && !state->pre_received && route_ping_addr_equal(destination, &state->helper_address, 0)) { + if (state->filter_clean) DEBUG_WARN(DEBUG_CATEGORY_NAT, + "NAT ingress contaminated by outgoing UDP socket=%s round=%016llx destination=%s", + socket->name, (unsigned long long)state->round, sockaddr_storage_to_str(destination).str); state->filter_clean = 0; - DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT ingress contaminated by outgoing UDP socket=%s round=%016llx destination=%s", - socket->name, (unsigned long long)state->round, sockaddr_storage_to_str(destination).str); } uint8_t local[ROUTE_ADDR_WIRE_SIZE], remote[ROUTE_ADDR_WIRE_SIZE]; if (route_ping_addr_encode(local, &state->local) || route_ping_addr_encode(remote, destination)) return; @@ -457,6 +479,16 @@ static int make_message(struct nat_socket *state, struct nat_wire *message, uint return 0; } +/* UDP-запрос коррелируется отдельным token; результат придёт через защищённый router, не через PONG. */ +static int observe_control(struct ETCP_SOCKET *socket, const uint8_t key[32], const struct sockaddr_storage *target, + uint64_t group, uint64_t round, uint8_t step, uint64_t token, int timeout, + etcp_ping_callback_t callback, void *arg) { + uint8_t tag[NAT_QUERY_TAG_SIZE]; route_ping_make_tag(tag, socket, group, round); + tag[ROUTE_OBSERVE_TAG_SIZE] = step; write64(tag + ROUTE_OBSERVE_TAG_SIZE + 1, token); + return etcp_send_ping_to_socket(socket->instance, socket, key, target, timeout, callback, arg, tag, sizeof(tag), + ETCP_PING_FLAG_OBSERVE | ETCP_PING_FLAG_NO_REPLY); +} + static struct nat_binding *binding_find(struct NAT_DETECTION *detector, uint64_t group_id, uint64_t owner, uint64_t generation, uint64_t round) { for (struct nat_binding *binding = detector->bindings; binding; binding = binding->next) @@ -469,7 +501,8 @@ void nat_detection_record_observation(struct NAT_DETECTION *detector, struct ETC const uint8_t peer_key[32], const struct sockaddr_storage *source, const uint8_t *tag, size_t length) { if (!detector || detector->stopping) return; - if (!socket || socket->socks_udp || length != ROUTE_OBSERVE_TAG_SIZE || !tag || memcmp(tag, "NAT3UDP!", 8)) { + if (!socket || socket->socks_udp || (length != ROUTE_OBSERVE_TAG_SIZE && length != NAT_QUERY_TAG_SIZE) || + !tag || memcmp(tag, "NAT4UDP!", 8)) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT observation query has invalid tag length=%zu", length); return; } uint64_t owner = sc_derive_node_id_from_pubkey(peer_key), generation = read64(tag + 9), round = read64(tag + 17); @@ -494,6 +527,15 @@ void nat_detection_record_observation(struct NAT_DETECTION *detector, struct ETC DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT observation group=%016llx owner=%016llx socket=%u generation=%016llx round=%016llx observed=%s via=%s", (unsigned long long)group_id, (unsigned long long)owner, tag[8], (unsigned long long)generation, (unsigned long long)round, sockaddr_storage_to_str(source).str, socket->name); + if (length == NAT_QUERY_TAG_SIZE) { + struct nat_wire report = {.command = NAT_OBSERVATION, .owner_socket = tag[8], .step = tag[ROUTE_OBSERVE_TAG_SIZE]}; + write64(report.owner, owner); write64(report.generation, generation); write64(report.round, round); + write64(report.token, read64(tag + ROUTE_OBSERVE_TAG_SIZE + 1)); + write64(report.observer, detector->instance->node_id); write64(report.helper, detector->instance->node_id); + if (route_ping_addr_encode(report.target, source) || send_control(detector, group_id, owner, &report)) + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT routed observation delivery failed group=%016llx owner=%016llx round=%016llx", + (unsigned long long)group_id, (unsigned long long)owner, (unsigned long long)round); + } } static void clear_profile(struct nat_socket *state, uint8_t reason) { @@ -512,37 +554,15 @@ static void clear_profile(struct nat_socket *state, uint8_t reason) { } } -/* Единственный выбор публикуемого профиля. STUN переносит только тип IP/netif, - * никогда не mapped endpoint тестового порта. Успешный ETCP действует до явного сброса. */ +/* Публикуется только измерение рабочего endpoint; отдельный STUN-порт остаётся диагностикой. */ static void select_profile(struct nat_socket *state) { const struct address_profile *current = &state->socket->address_profile; - struct address_profile selected = *current; + struct address_profile selected = state->measured_profile; struct sockaddr_storage selected_address = {0}; if (state->measured_profile.source == ADDRESS_SOURCE_ETCP) { selected = state->measured_profile; if (selected.mapping == ADDRESS_MAP_EIM) selected_address = state->measured_address; } - 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.status == STUN_NAT_COMPLETE || result.status == STUN_NAT_PARTIAL)) { - struct address_profile fallback = {0}; - if (result.mapping == STUN_MAP_EIM) fallback.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) - fallback.mapping = ADDRESS_MAP_DEPENDENT; - if (result.filtering == STUN_FILTER_EIF) fallback.inbound = ADDRESS_IN_OPEN; - else if (result.filtering == STUN_FILTER_ADF || result.filtering == STUN_FILTER_APDF) fallback.inbound = ADDRESS_IN_CONTACT; - if (fallback.mapping || fallback.inbound) { - fallback.source = ADDRESS_SOURCE_STUN; - if (current->source == ADDRESS_SOURCE_STUN) { - if (!fallback.mapping) fallback.mapping = current->mapping; - if (fallback.mapping == current->mapping && !fallback.inbound) fallback.inbound = current->inbound; - } - selected = fallback; - } - } - } 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 && @@ -561,8 +581,13 @@ static void owner_finish(struct nat_socket *state, uint8_t reason) { 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); - 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; + 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); + } + 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++; } else { @@ -590,7 +615,7 @@ static void owner_apply(struct nat_socket *state, uint8_t mapping, uint8_t inbou state->socket->name, (unsigned long long)state->round, address_inbound_name(profile.inbound), nat_check_reason_name(partial_reason)); } - state->measured_profile = profile; state->measured_address = state->baseline; state->fallback_allowed = 0; + state->measured_profile = profile; state->measured_address = state->baseline; 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", @@ -609,27 +634,54 @@ static int owner_run(struct nat_socket *state, uint8_t step) { if (make_message(state, &message, NAT_RUN)) return -1; message.step = step; state->phase = step == NAT_STEP_TCP ? NAT_TCP : step == NAT_STEP_PREPARE ? NAT_PREPARE_C : - step == NAT_STEP_PRE ? NAT_PRE : step == NAT_STEP_ALT ? NAT_PREPARE_ALT : NAT_POST; + step == NAT_STEP_PRE ? NAT_PRE : NAT_POST; + if (step == NAT_STEP_PRE) state->pre_started = get_time_tb(); return send_control(state->detector, state->group_id, state->observer, &message); } static int owner_observe(struct nat_socket *state) { const struct sockaddr_storage *target = &state->observer_address; const uint8_t *key = state->observer_key; - if (state->phase == NAT_OBSERVE_C || state->phase == NAT_OBSERVE_ALT) { - target = state->phase == NAT_OBSERVE_C ? &state->helper_address : &state->alternate_address; - key = state->helper_key; - } + if (state->phase == NAT_OBSERVE_C || state->phase == NAT_REPEAT_C) { target = &state->helper_address; key = state->helper_key; } int timeout = 1000; struct ETCP_CONN *connection = topo_group_find_conn_for_node( topo_groups_find(state->detector->instance->topo_groups, state->group_id), state->observer); for (struct ETCP_LINK *link = connection ? connection->links : NULL; link; link = link->next) if (link->rtt_last / 10 * 4 > timeout) timeout = link->rtt_last / 10 * 4; - if (timeout > 3000) timeout = 3000; - if (route_ping_observe(state->socket, key, target, state->group_id, state->round, timeout, owner_ping_cb, state)) return -1; + if (state->phase == NAT_OBSERVE_C && !state->retries) { + state->contact_cutoff = state->contact_count; + if (contacted(state, &state->helper_address)) state->filter_clean = 0; + } + if (random_id(&state->observation_token)) return -1; + DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT observe TX socket=%s phase=%u round=%016llx token=%016llx target=%s retries=%u", + state->socket->name, state->phase, (unsigned long long)state->round, + (unsigned long long)state->observation_token, sockaddr_storage_to_str(target).str, state->retries); + if (observe_control(state->socket, key, target, state->group_id, state->round, state->phase, + state->observation_token, timeout, owner_ping_cb, state)) return -1; state->active_ping = 1; return 0; } +/* Прямой контроль к B поддерживает mapping и позволяет отличать входящее испытание от общей недоступности UDP. */ +static void owner_heartbeat_cb(int success, uint16_t rtt, void *arg, uint64_t nonce, const uint8_t *data, size_t length) { + (void)rtt; (void)nonce; + struct nat_socket *state = arg; state->heartbeat_active = 0; + if (!state->phase) return; + struct sockaddr_storage observed; + if (success && !route_ping_observation(data, length, state->socket, state->group_id, state->round, &observed)) { + if (state->baseline.ss_family && !route_ping_addr_equal(&state->baseline, &observed, 1)) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT anchor mapping changed socket=%s round=%016llx previous=%s current=%s", + state->socket->name, (unsigned long long)state->round, + sockaddr_storage_to_str(&state->baseline).str, sockaddr_storage_to_str(&observed).str); + clear_profile(state, NAT_REASON_ADDRESS_CHANGED); owner_finish(state, NAT_REASON_ADDRESS_CHANGED); return; + } + state->heartbeat_received = get_time_tb(); + DEBUG_TRACE(DEBUG_CATEGORY_NAT, "NAT anchor healthy socket=%s round=%016llx observed=%s", + state->socket->name, (unsigned long long)state->round, sockaddr_storage_to_str(&observed).str); + } else DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT anchor direct response unavailable socket=%s round=%016llx", + state->socket->name, (unsigned long long)state->round); + state->heartbeat_due = get_time_tb() + 10000; +} + static void owner_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce, const uint8_t *data, size_t length) { (void)rtt; (void)nonce; struct nat_socket *state = arg; @@ -637,6 +689,9 @@ static void owner_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce, if (state->phase == NAT_IDLE) 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", + state->socket->name, state->phase, (unsigned long long)state->round, state->retries + 1, + success ? "invalid_observation" : "routed_report_timeout"); if (++state->retries < NAT_PROBE_COUNT) { state->due = get_time_tb() + 1000; return; } owner_finish(state, success ? NAT_REASON_INVALID_OBSERVATION : NAT_REASON_TIMEOUT); return; } @@ -653,16 +708,8 @@ static void owner_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce, } if (state->phase == NAT_OBSERVE_C) { state->helper_seen = observed; - if (route_ping_addr_equal(&state->baseline, &observed, 1)) { - if (owner_run(state, NAT_STEP_ALT)) owner_finish(state, NAT_REASON_CONTROL_LOST); - return; - } - state->phase = NAT_REPEAT_B; - } else if (state->phase == NAT_OBSERVE_ALT) { - state->alternate_seen = observed; - if (state->filter_clean && state->pre_healthy && !state->pre_received && - route_ping_addr_equal(&state->baseline, &state->helper_seen, 1) && - route_ping_addr_equal(&state->baseline, &state->alternate_seen, 1)) { + if (route_ping_addr_equal(&state->baseline, &observed, 1) && state->filter_clean && + state->pre_healthy && !state->pre_received) { if (owner_run(state, NAT_STEP_POST)) owner_finish(state, NAT_REASON_CONTROL_LOST); return; } @@ -671,12 +718,20 @@ static void owner_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce, if (!route_ping_addr_equal(&observed, &state->baseline, 1)) { clear_profile(state, NAT_REASON_ADDRESS_CHANGED); owner_finish(state, NAT_REASON_ADDRESS_CHANGED); return; } - int same = route_ping_addr_equal(&state->baseline, &state->helper_seen, 1) && - route_ping_addr_equal(&state->baseline, &state->alternate_seen, 1); + state->phase = NAT_REPEAT_C; + } else if (state->phase == NAT_REPEAT_C) { + if (!route_ping_addr_equal(&observed, &state->helper_seen, 1)) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper-direction mapping unstable socket=%s round=%016llx previous=%s current=%s", + state->socket->name, (unsigned long long)state->round, + sockaddr_storage_to_str(&state->helper_seen).str, sockaddr_storage_to_str(&observed).str); + clear_profile(state, NAT_REASON_ADDRESS_CHANGED); owner_finish(state, NAT_REASON_ADDRESS_CHANGED); return; + } + int same = route_ping_addr_equal(&state->baseline, &state->helper_seen, 1); uint8_t mapping = same ? (route_ping_addr_equal(&state->local, &state->baseline, 1) ? ADDRESS_MAP_UNCHANGED : ADDRESS_MAP_EIM) : ADDRESS_MAP_DEPENDENT; uint8_t inbound = state->filter_clean && state->pre_received ? ADDRESS_IN_OPEN : - state->filter_clean && state->pre_healthy && state->post_received && same ? ADDRESS_IN_CONTACT : ADDRESS_IN_UNKNOWN; + state->filter_clean && state->pre_healthy && state->post_received && same && + state->heartbeat_received >= state->pre_started ? ADDRESS_IN_CONTACT : ADDRESS_IN_UNKNOWN; owner_apply(state, mapping, inbound); return; } else { DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT observation in unexpected phase=%u", state->phase); @@ -693,45 +748,68 @@ void nat_detection_receive_ingress(struct NAT_DETECTION *detector, struct ETCP_S DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT ingress ignored without pending endpoint/valid tag length=%zu", length); return; } uint8_t expected[ROUTE_OBSERVE_TAG_SIZE]; route_ping_make_tag(expected, socket, state->group_id, state->round); - uint8_t step = state->phase == NAT_PREPARE_C ? NAT_STEP_PREPARE : state->phase == NAT_PRE ? NAT_STEP_PRE : - state->phase == NAT_POST ? NAT_STEP_POST : 0; + uint8_t step = state->phase == NAT_PREPARE_C ? NAT_STEP_PREPARE : + (state->phase == NAT_PRE || state->phase == NAT_PRE_WAIT) ? NAT_STEP_PRE : + (state->phase == NAT_POST || state->phase == NAT_POST_WAIT) ? NAT_STEP_POST : 0; if (!step || memcmp(tag, expected, sizeof(expected)) || tag[ROUTE_OBSERVE_TAG_SIZE] != step || read64(tag + ROUTE_OBSERVE_TAG_SIZE + 1) != state->token || sc_derive_node_id_from_pubkey(peer_key) != state->helper || memcmp(peer_key, state->helper_key, 32) || source->ss_family != state->local.ss_family || !public_address(detector, source) || - ((step == NAT_STEP_POST || state->pre_received) && !route_ping_addr_equal(source, &state->helper_address, 1)) || - route_ping_addr_equal(source, &state->observer_address, 0)) { + ((step == NAT_STEP_POST || state->pre_received) && !route_ping_addr_equal(source, &state->helper_address, 1))) { 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 (route_ping_addr_equal(source, &state->observer_address, 0) || route_ping_addr_equal(source, &state->baseline, 0)) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper rejected by receiver socket=%s round=%016llx helper=%016llx source=%s reason=%s", + socket->name, (unsigned long long)state->round, (unsigned long long)state->helper, + sockaddr_storage_to_str(source).str, route_ping_addr_equal(source, &state->baseline, 0) ? "hairpin" : "same_observer_ip"); + owner_finish(state, NAT_REASON_NO_HELPER); return; + } if (step == NAT_STEP_PRE || step == NAT_STEP_PREPARE) { - if (!state->pre_received) { state->filter_clean = !contacted(state, source); state->helper_address = *source; } + if (!state->pre_received) { + if (contacted(state, source) && !state->using_fallback) { + if (!state->fallback.group) state->fallback = state->cursor; + 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; + } + state->filter_clean = !contacted(state, source); state->helper_address = *source; + } state->pre_received = 1; } - else state->post_received = 1; + else { + uint8_t wire[ROUTE_ADDR_WIRE_SIZE]; + if (!state->history_complete || route_ping_addr_encode(wire, source)) state->filter_clean = 0; + else for (unsigned i = 0; i < state->contact_cutoff; i++) + if (!memcmp(state->contacts[i], wire, 17)) state->filter_clean = 0; + if (!state->filter_clean) DEBUG_DEBUG(DEBUG_CATEGORY_NAT, + "NAT post-contact ingress cannot establish filtering socket=%s round=%016llx source=%s reason=contact_history", + socket->name, (unsigned long long)state->round, sockaddr_storage_to_str(source).str); + state->post_received = 1; + } DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT direct ingress socket=%s round=%016llx step=%u source=%s clean=%u", socket->name, (unsigned long long)state->round, step, sockaddr_storage_to_str(source).str, state->filter_clean); } /* Дополнительный порт наблюдателя не публикуется и не участвует в NCD. */ -static struct ETCP_SOCKET *helper_socket(struct NAT_DETECTION *detector, struct ETCP_SOCKET *source, int alternate) { +static struct ETCP_SOCKET *helper_socket(struct NAT_DETECTION *detector, struct ETCP_SOCKET *source) { struct nat_socket *state = socket_state(detector, source, 1); if (!state) return NULL; - struct ETCP_SOCKET **slot = alternate ? &state->auxiliary : &state->helper_primary; + struct ETCP_SOCKET **slot = &state->helper_primary; if (*slot) return *slot; struct CFG_SERVER server; memset(&server, 0, sizeof(server)); server.ip = source->local_addr; server.netif_index = source->netif_index; server.type = CFG_SERVER_TYPE_PRIVATE; server.mtu = source->mtu; server.fib = -1; for (const struct CFG_SERVER *cfg = detector->instance->config->servers; cfg; cfg = cfg->next) if (!strcmp(cfg->name, source->name)) { server.so_mark = cfg->so_mark; server.fib = cfg->fib; break; } - snprintf(server.name, sizeof(server.name), "nat-helper-%u-%u", source->sock_id, alternate); + snprintf(server.name, sizeof(server.name), "nat-helper-%u", source->sock_id); if (server.ip.ss_family == AF_INET) ((struct sockaddr_in *)&server.ip)->sin_port = 0; else ((struct sockaddr_in6 *)&server.ip)->sin6_port = 0; *slot = etcp_socket_add(detector->instance, &server); - if (!*slot) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT helper socket failed source=%s alternate=%d", source->name, alternate); return NULL; } + if (!*slot) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT helper socket failed source=%s", source->name); return NULL; } (*slot)->probe_only = 1; - DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT isolated helper socket source=%s alternate=%d local=%s helper=%s", - source->name, alternate, sockaddr_storage_to_str(&source->local_addr).str, + DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT isolated helper socket source=%s local=%s helper=%s", + source->name, sockaddr_storage_to_str(&source->local_addr).str, sockaddr_storage_to_str(&(*slot)->local_addr).str); return *slot; } @@ -762,7 +840,7 @@ static void helper_finish(struct nat_probe *probe, uint8_t reason) { /* Входящая проба несёт идентификаторы owner; OBSERVE с помощника — идентификаторы его собственного socket. */ static void ingress_tag(const struct nat_wire *message, uint64_t group_id, uint8_t tag[NAT_QUERY_TAG_SIZE]) { - memcpy(tag, "NAT3UDP!", 8); tag[8] = message->owner_socket; + memcpy(tag, "NAT4UDP!", 8); tag[8] = message->owner_socket; memcpy(tag + 9, message->generation, 8); memcpy(tag + 17, message->round, 8); write64(tag + 25, group_id); tag[ROUTE_OBSERVE_TAG_SIZE] = message->step; memcpy(tag + ROUTE_OBSERVE_TAG_SIZE + 1, message->token, 8); @@ -774,46 +852,29 @@ static void helper_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce, if (probe->cancelled) return; probe->active = 0; probe->tcp = NULL; uint8_t step = probe->message.step; - if (step == NAT_STEP_ALT || (step == NAT_STEP_PREPARE && probe->alternate_fallback)) { + if (step == NAT_STEP_PREPARE && probe->finding_primary) { struct ETCP_SOCKET *socket = find_socket(probe->detector, probe->io_socket, probe->io_generation); struct sockaddr_storage mapped; if (success && socket && !route_ping_observation(data, length, socket, probe->group_id, read64(probe->message.round), &mapped)) { - uint8_t *endpoint = step == NAT_STEP_PREPARE ? probe->message.primary : probe->message.secondary; - if (route_ping_addr_encode(endpoint, &mapped)) { helper_finish(probe, NAT_REASON_INVALID_OBSERVATION); return; } + if (route_ping_addr_encode(probe->message.primary, &mapped)) { helper_finish(probe, NAT_REASON_INVALID_OBSERVATION); return; } probe->message.received++; DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT helper endpoint measured group=%016llx owner=%016llx step=%u socket=%s mapped=%s fallback=%u", (unsigned long long)probe->group_id, (unsigned long long)read64(probe->message.owner), step, - socket->name, sockaddr_storage_to_str(&mapped).str, probe->alternate_fallback); + socket->name, sockaddr_storage_to_str(&mapped).str, probe->finding_primary); helper_finish(probe, NAT_REASON_NONE); return; } if (success) { helper_finish(probe, NAT_REASON_INVALID_OBSERVATION); return; } - if (probe->message.sent >= NAT_PROBE_COUNT) { - if (step == NAT_STEP_ALT && !probe->alternate_fallback) { - probe->alternate_fallback = 1; probe->message.sent = 0; - DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT alternate endpoint blocked by owner; observe through broker group=%016llx owner=%016llx", - (unsigned long long)probe->group_id, (unsigned long long)read64(probe->message.owner)); - } else { helper_finish(probe, NAT_REASON_TIMEOUT); return; } - } + if (probe->message.sent >= NAT_PROBE_COUNT) { helper_finish(probe, NAT_REASON_TIMEOUT); return; } } else if (step == NAT_STEP_TCP) { if (success) probe->message.received++; helper_finish(probe, success ? NAT_REASON_NONE : NAT_REASON_TIMEOUT); return; } else { - if (success) { - uint8_t expected[NAT_QUERY_TAG_SIZE]; ingress_tag(&probe->message, probe->group_id, expected); - struct sockaddr_storage mapped; - if (length != ROUTE_ADDR_WIRE_SIZE + sizeof(expected) || memcmp(data + ROUTE_ADDR_WIRE_SIZE, expected, sizeof(expected)) || - route_ping_addr_decode(data, &mapped) || route_ping_addr_encode(probe->message.primary, &mapped)) { - DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT ingress PONG has invalid endpoint/tag group=%016llx length=%zu", - (unsigned long long)probe->group_id, length); - helper_finish(probe, NAT_REASON_INVALID_OBSERVATION); return; - } - probe->message.received++; - if (step == NAT_STEP_PREPARE) { helper_finish(probe, NAT_REASON_NONE); return; } - } + if (success) DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT one-way ingress unexpectedly received PONG group=%016llx step=%u", + (unsigned long long)probe->group_id, step); if (step == NAT_STEP_PREPARE && probe->message.sent >= NAT_PROBE_COUNT) { - probe->alternate_fallback = 1; probe->message.sent = 0; - DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT first ingress blocked; discover helper mapping through broker group=%016llx owner=%016llx", + probe->finding_primary = 1; probe->message.sent = 0; + DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT initial ingress series sent; discover helper endpoint through broker group=%016llx owner=%016llx", (unsigned long long)probe->group_id, (unsigned long long)read64(probe->message.owner)); } if (probe->message.sent >= NAT_PROBE_COUNT) { helper_finish(probe, NAT_REASON_NONE); return; } @@ -826,7 +887,9 @@ static int helper_send(struct nat_probe *probe) { struct sockaddr_storage target; if (route_ping_addr_decode(probe->message.target, &target)) return -1; int timeout = ((unsigned)probe->message.timeout_ms[0] << 8) | probe->message.timeout_ms[1]; - if (timeout < 500 || timeout > 3000) return -1; + if (timeout < 500 || timeout > 10000) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper has invalid timeout_ms=%d", timeout); return -1; + } int result; if (probe->message.step == NAT_STEP_TCP) { struct reality_client_config reality, *camouflage = NULL; @@ -835,29 +898,27 @@ static int helper_send(struct nat_probe *probe) { if (parameters) { topo_node_reality_config(&reality, parameters); camouflage = &reality; } result = etcp_send_tcp_ping(detector->instance, probe->owner_key, &target, timeout, helper_ping_cb, probe, &probe->tcp, camouflage); - } else if (probe->message.step == NAT_STEP_ALT || (probe->message.step == NAT_STEP_PREPARE && probe->alternate_fallback)) { + } else if (probe->message.step == NAT_STEP_PREPARE && probe->finding_primary) { struct ETCP_SOCKET *source = find_socket(detector, probe->source_socket, probe->source_generation); if (!source) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper source generation disappeared"); return -1; } - struct ETCP_SOCKET *socket = helper_socket(detector, source, probe->message.step == NAT_STEP_ALT); + struct ETCP_SOCKET *socket = helper_socket(detector, source); if (!socket) return -1; - const uint8_t *peer_key = probe->owner_key; - if (probe->message.step == NAT_STEP_PREPARE || probe->alternate_fallback) { - if (route_ping_addr_decode(probe->message.observer_address, &target)) return -1; - peer_key = probe->observer_key; - } + if (route_ping_addr_decode(probe->message.observer_address, &target)) return -1; probe->io_socket = socket->sock_id; probe->io_generation = socket->generation; - result = route_ping_observe(socket, peer_key, &target, probe->group_id, read64(probe->message.round), - timeout, helper_ping_cb, probe); + result = observe_control(socket, probe->observer_key, &target, probe->group_id, read64(probe->message.round), + NAT_STEP_HELPER_OBSERVE, read64(probe->message.token), timeout, helper_ping_cb, probe); } else { struct ETCP_SOCKET *source = find_socket(detector, probe->source_socket, probe->source_generation); - if (!source || source->is_tcp || source->socks_udp) return -1; - struct ETCP_SOCKET *socket = helper_socket(detector, source, 0); + if (!source || source->is_tcp || source->socks_udp) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper source is unavailable socket=%u", probe->source_socket); return -1; + } + struct ETCP_SOCKET *socket = helper_socket(detector, source); if (!socket) return -1; probe->io_socket = socket->sock_id; probe->io_generation = socket->generation; uint8_t tag[NAT_QUERY_TAG_SIZE]; ingress_tag(&probe->message, probe->group_id, tag); result = etcp_send_ping_to_socket(detector->instance, socket, probe->owner_key, &target, timeout, - helper_ping_cb, probe, tag, sizeof(tag), ETCP_PING_FLAG_ADDRESS_CHECK); + helper_ping_cb, probe, tag, sizeof(tag), ETCP_PING_FLAG_ADDRESS_CHECK | ETCP_PING_FLAG_NO_REPLY); } if (result) { probe->message.errors++; return -1; } probe->message.sent++; probe->active = 1; return 0; @@ -961,8 +1022,7 @@ reject: static void handle_result(struct nat_socket *state, const struct nat_wire *message) { uint8_t step = state->phase == NAT_PRE ? NAT_STEP_PRE : state->phase == NAT_POST ? NAT_STEP_POST : - state->phase == NAT_TCP ? NAT_STEP_TCP : state->phase == NAT_PREPARE_C ? NAT_STEP_PREPARE : - state->phase == NAT_PREPARE_ALT ? NAT_STEP_ALT : 0; + state->phase == NAT_TCP ? NAT_STEP_TCP : state->phase == NAT_PREPARE_C ? NAT_STEP_PREPARE : 0; 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; } @@ -978,17 +1038,19 @@ static void handle_result(struct nat_socket *state, const struct nat_wire *messa struct sockaddr_storage primary; if (route_ping_addr_decode(message->primary, &primary) || primary.ss_family != state->local.ss_family || !public_address(state->detector, &primary)) { owner_finish(state, NAT_REASON_INVALID_OBSERVATION); return; } - int same_ip = route_ping_addr_equal(&state->observer_address, &primary, 0); + if (state->pre_received) primary = state->helper_address; /* Source полученной пробы важнее объявленного mapping помощника. */ + 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 = (struct nat_pair){state->group_id, state->observer, state->helper}; + if (!same_ip && !state->fallback.group) state->fallback = state->cursor; 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; } state->helper_address = primary; state->filter_clean = !old_contact; - if (state->pre_received) { + if (state->pre_received || state->using_fallback) { state->phase = NAT_OBSERVE_C; if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR); return; @@ -996,22 +1058,46 @@ static void handle_result(struct nat_socket *state, const struct nat_wire *messa if (owner_run(state, NAT_STEP_PRE)) owner_finish(state, NAT_REASON_CONTROL_LOST); return; } - if (step == NAT_STEP_ALT) { - if (route_ping_addr_decode(message->secondary, &state->alternate_address) || - !route_ping_addr_equal(&state->alternate_address, &state->helper_address, 0) || - route_ping_addr_equal(&state->alternate_address, &state->helper_address, 1)) { - owner_finish(state, NAT_REASON_INVALID_OBSERVATION); return; + state->pre_healthy = step == NAT_STEP_PRE ? message->sent == NAT_PROBE_COUNT && + state->heartbeat_received >= state->pre_started : state->pre_healthy; + state->phase = step == NAT_STEP_PRE ? NAT_PRE_WAIT : NAT_POST_WAIT; + state->due = get_time_tb() + 10000; /* Отчёт router может обогнать последнюю UDP-пробу. */ + DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT ingress series complete socket=%s round=%016llx step=%u sent=%u pre=%u post=%u clean=%u anchor=%u", + state->socket->name, (unsigned long long)state->round, step, message->sent, + state->pre_received, state->post_received, state->filter_clean, state->pre_healthy); +} + +/* Отчёт привязан к точному UDP-запросу и подписан наблюдателем; старый этап не завершит новый запрос. */ +static void receive_observation(struct NAT_DETECTION *detector, uint64_t group, uint64_t sender, const struct nat_wire *message) { + if (read64(message->owner) != detector->instance->node_id || read64(message->observer) != sender || + read64(message->helper) != sender) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT observation report has invalid participants group=%016llx sender=%016llx", + (unsigned long long)group, (unsigned long long)sender); return; + } + struct ETCP_SOCKET *socket = find_socket(detector, message->owner_socket, read64(message->generation)); + if (!socket) { DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT observation report for old socket ignored"); return; } + uint8_t data[ROUTE_ADDR_WIRE_SIZE + ROUTE_OBSERVE_TAG_SIZE]; + memcpy(data, message->target, ROUTE_ADDR_WIRE_SIZE); + route_ping_make_tag(data + ROUTE_ADDR_WIRE_SIZE, socket, group, read64(message->round)); + if (message->step == NAT_STEP_HELPER_OBSERVE) { + for (struct nat_probe *probe = detector->probes; probe; probe = probe->next) { + if (!probe->active || !probe->finding_primary || probe->group_id != group || probe->io_socket != socket->sock_id || + probe->io_generation != socket->generation || read64(probe->message.round) != read64(message->round) || + read64(probe->message.token) != read64(message->token) || read64(probe->message.observer) != sender) continue; + etcp_cancel_pings(detector->instance, helper_ping_cb, probe); + helper_ping_cb(1, 0, probe, 0, data, sizeof(data)); return; } - state->phase = NAT_OBSERVE_ALT; - } else if (step == NAT_STEP_PRE) { - if (!state->pre_received && route_ping_addr_decode(message->primary, &state->helper_address)) { - owner_finish(state, NAT_REASON_INVALID_OBSERVATION); return; - } - state->pre_healthy = message->sent == NAT_PROBE_COUNT; - state->phase = NAT_OBSERVE_C; - } else state->phase = NAT_REPEAT_B; - state->retries = 0; - if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR); + DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT stale helper observation report ignored"); return; + } + struct nat_socket *state = socket_state(detector, socket, 0); + if (!state || !state->active_ping || state->group_id != group || state->round != read64(message->round) || + state->observation_token != read64(message->token) || state->phase != message->step || + ((state->phase == NAT_OBSERVE_C || state->phase == NAT_REPEAT_C) ? state->helper : state->observer) != sender) { + 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; + } + etcp_cancel_pings(detector->instance, owner_ping_cb, state); + owner_ping_cb(1, 0, state, 0, data, sizeof(data)); } /* Потеря router-сессии отменяет только роли этого узла в данной группе, не общий транспорт. */ @@ -1073,8 +1159,9 @@ static void receive_control(struct ETCP_CONN *from, struct ll_entry *entry) { if (message.cmd != ETCP_RT_ID_NAT_DETECTION || message.version != NAT_WIRE_VERSION || !read64(message.owner) || !read64(message.observer) || !read64(message.helper) || !read64(message.generation) || !read64(message.round) || !read64(message.token) || - message.command < NAT_RUN || message.command > NAT_RESULT || message.reason > NAT_REASON_DEPENDENT_MAPPING || - message.step < NAT_STEP_PREPARE || message.step > NAT_STEP_TCP) { + (message.command != NAT_RUN && message.command != NAT_RESULT && message.command != NAT_OBSERVATION) || + message.reason > NAT_REASON_DEPENDENT_MAPPING || + (message.command != NAT_OBSERVATION && (message.step < NAT_STEP_PREPARE || message.step > NAT_STEP_TCP))) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT control malformed command=%u version=%u step=%u", message.command, message.version, message.step); return; } @@ -1082,6 +1169,7 @@ static void receive_control(struct ETCP_CONN *from, struct ll_entry *entry) { (unsigned long long)group_id, message.command, message.step, (unsigned long long)sender, (unsigned long long)from->peer_node_id, (unsigned long long)read64(message.owner), (unsigned long long)read64(message.round)); if (message.command == NAT_RUN) { handle_run(detector, group_id, sender, &message); return; } + if (message.command == NAT_OBSERVATION) { receive_observation(detector, group_id, sender, &message); return; } uint64_t owner = read64(message.owner); if (owner != detector->instance->node_id) { struct nat_binding *binding = binding_find(detector, group_id, owner, read64(message.generation), read64(message.round)); @@ -1119,14 +1207,16 @@ static void owner_start(struct nat_socket *state) { if (!state->cycling) { state->cycling = 1; state->cursor = (struct nat_pair){0}; state->fallback = (struct nat_pair){0}; state->using_fallback = 0; state->prefer_fresh = 1; + state->cycle_deadline = get_time_tb() + NAT_ROUND_TB; } history_load(state); 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->alternate_seen, 0, sizeof(state->alternate_seen)); - state->retries = 0; state->pre_received = 0; state->post_received = 0; state->filter_clean = 0; state->pre_healthy = 0; - state->deadline = get_time_tb() + NAT_ROUND_TB; state->reason = NAT_REASON_NONE; + 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; + state->deadline = state->cycle_deadline; state->reason = NAT_REASON_NONE; DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT round start socket=%s group=%016llx id=%u generation=%016llx round=%016llx observer=%016llx helper=%016llx protocol=%s", state->socket->name, (unsigned long long)state->group_id, state->socket->sock_id, (unsigned long long)state->socket->generation, (unsigned long long)state->round, @@ -1152,8 +1242,22 @@ static void detector_tick(void *arg) { if (!state->phase && now >= state->due && (detector->automatic || state->forced)) { state->forced = 0; owner_start(state); } - else if ((state->phase == NAT_OBSERVE_B || state->phase == NAT_OBSERVE_C || - state->phase == NAT_OBSERVE_ALT || state->phase == NAT_REPEAT_B) && !state->active_ping && now >= state->due) { + else if (state->phase && !state->socket->is_tcp) { + if ((state->phase == NAT_PRE_WAIT || state->phase == NAT_POST_WAIT) && now >= state->due) { + state->phase = state->phase == NAT_PRE_WAIT ? NAT_OBSERVE_C : NAT_REPEAT_B; state->retries = 0; + if (owner_observe(state)) { owner_finish(state, NAT_REASON_SEND_ERROR); continue; } + } + if (!state->heartbeat_active && state->baseline.ss_family && now >= state->heartbeat_due) { + if (route_ping_observe(state->socket, state->observer_key, &state->observer_address, state->group_id, state->round, + 1000, owner_heartbeat_cb, state)) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT anchor probe send failed socket=%s round=%016llx", + state->socket->name, (unsigned long long)state->round); + state->heartbeat_due = now + 10000; + } else state->heartbeat_active = 1; + } + } + if ((state->phase == NAT_OBSERVE_B || state->phase == NAT_OBSERVE_C || state->phase == NAT_REPEAT_C || + state->phase == NAT_REPEAT_B) && !state->active_ping && now >= state->due) { if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR); } } @@ -1192,11 +1296,11 @@ static uint64_t candidate_fingerprint(struct nat_socket *state, int ready) { uint8_t bytes[16 + ROUTE_ADDR_WIRE_SIZE + 1] = {0}; write64(bytes, group->group_id); write64(bytes + 8, id); if (!state->socket->is_tcp) { - if (!node_udp_endpoint(detector, node, state->local.ss_family, 1, &endpoint, &socket_id)) continue; + if (!node_udp_endpoint(detector, node, state->local.ss_family, 1, NULL, &endpoint, &socket_id)) continue; if (route_ping_addr_encode(bytes + 16, &endpoint)) continue; bytes[16 + ROUTE_ADDR_WIRE_SIZE] = socket_id; struct sockaddr_storage observer_address = {0}; uint8_t observer_socket = 0; - observers += node_udp_endpoint(detector, node, state->local.ss_family, 0, &observer_address, &observer_socket); + observers += node_udp_endpoint(detector, node, state->local.ss_family, 0, NULL, &observer_address, &observer_socket); } candidates++; uint64_t hash = UINT64_C(14695981039346656037); @@ -1246,7 +1350,7 @@ uint64_t nat_detection_probe_delay(struct NAT_DETECTION *detector) { int nat_detection_ping_allowed(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, const uint8_t peer_key[32], const struct sockaddr_storage *source, uint8_t flags, const uint8_t *tag, size_t length) { - if (!detector || !socket || socket->probe_only || (flags & ETCP_PING_FLAG_ADDRESS_CHECK)) return 1; + if (!detector || !socket || socket->probe_only || (flags & (ETCP_PING_FLAG_ADDRESS_CHECK | ETCP_PING_FLAG_NO_REPLY))) return 1; nat_detection_probe_delay(detector); struct nat_socket *state = socket_state(detector, socket, 0); if (!state || (!state->reservation_fingerprint && !state->available_fingerprint) || state->cold_until <= get_time_tb() || @@ -1261,7 +1365,7 @@ int nat_detection_ping_allowed(struct NAT_DETECTION *detector, struct ETCP_SOCKE socket->name, (unsigned long long)peer, sockaddr_storage_to_str(source).str); return 0; } - if (!tag || length != ROUTE_OBSERVE_TAG_SIZE || memcmp(tag, "NAT3UDP!", 8)) return 1; + if (!tag || length != ROUTE_OBSERVE_TAG_SIZE || memcmp(tag, "NAT4UDP!", 8)) return 1; uint64_t group_id = read64(tag + 25), round = read64(tag + 17), owner = peer; if (state->phase && state->group_id == group_id && state->round == round && (state->helper == peer || state->observer == peer)) return 1; for (struct nat_binding *binding = detector->bindings; binding; binding = binding->next) @@ -1424,12 +1528,12 @@ void nat_detection_socket_removed(struct NAT_DETECTION *detector, struct ETCP_SO if (!*cursor) return; struct nat_socket *state = *cursor; *cursor = state->next; etcp_cancel_pings(detector->instance, owner_ping_cb, state); - struct ETCP_SOCKET *primary = state->helper_primary, *auxiliary = state->auxiliary; + etcp_cancel_pings(detector->instance, owner_heartbeat_cb, state); + struct ETCP_SOCKET *primary = state->helper_primary; DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT endpoint removed socket=%s generation=%016llx", socket->name, (unsigned long long)socket->generation); u_free(state); if (primary) etcp_socket_remove(primary); - if (auxiliary) etcp_socket_remove(auxiliary); } static void socket_changed(struct ETCP_SOCKET *socket, int event, void *arg) { @@ -1476,9 +1580,9 @@ void nat_detection_destroy(struct NAT_DETECTION *detector) { while (detector->sockets) { struct nat_socket *state = detector->sockets; detector->sockets = state->next; etcp_cancel_pings(detector->instance, owner_ping_cb, state); - struct ETCP_SOCKET *primary = state->helper_primary, *auxiliary = state->auxiliary; u_free(state); + etcp_cancel_pings(detector->instance, owner_heartbeat_cb, state); + struct ETCP_SOCKET *primary = state->helper_primary; u_free(state); if (primary) etcp_socket_remove(primary); - if (auxiliary) etcp_socket_remove(auxiliary); } while (detector->bindings) { struct nat_binding *binding = detector->bindings; detector->bindings = binding->next; u_free(binding); @@ -1538,6 +1642,6 @@ int nat_detection_get_status(struct NAT_DETECTION *detector, struct ETCP_SOCKET .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, .cycling = state->cycling, - .measured_profile = state->measured_profile, .effective_profile = socket->address_profile, .fallback_allowed = state->fallback_allowed}; + .measured_profile = state->measured_profile, .effective_profile = socket->address_profile}; return 0; } diff --git a/src/routing_layer/nat_detection.h b/src/routing_layer/nat_detection.h index 54c3e86a..c4d68872 100644 --- a/src/routing_layer/nat_detection.h +++ b/src/routing_layer/nat_detection.h @@ -1,5 +1,7 @@ -/* Владелец измеряет собственный endpoint. Пиры передают наблюдения и выполняют - * прямые пробы; управление идёт через маршруты одной из доступных групп. +/* Владелец измеряет рабочий UDP endpoint по разным IP, без сравнения портов одного IP. + * Наблюдения возвращаются через router. Прямые входящие пробы не получают UDP-ответа; + * незнакомость фактического source IP проверяет получатель до исходящего контакта. + * Mapping и inbound завершаются независимо; CONTACT — оценка по сериям до/после контакта. * Все операции и callbacks выполняются в uasync-потоке ядра. */ #ifndef NAT_DETECTION_H #define NAT_DETECTION_H @@ -31,7 +33,6 @@ struct nat_check_status { uint64_t group_id, observer, helper; uint32_t rounds, helper_reselections; uint8_t phase, reason, history_complete, cycling; - uint8_t fallback_allowed; }; struct NAT_DETECTION *nat_detection_create(struct UTUN_INSTANCE *instance); diff --git a/src/routing_layer/route_ping.c b/src/routing_layer/route_ping.c index e5f2a4f7..e3264ef1 100644 --- a/src/routing_layer/route_ping.c +++ b/src/routing_layer/route_ping.c @@ -51,7 +51,7 @@ int route_ping_addr_equal(const struct sockaddr_storage *a, const struct sockadd } void route_ping_make_tag(uint8_t out[ROUTE_OBSERVE_TAG_SIZE], const struct ETCP_SOCKET *socket, uint64_t group, uint64_t round) { - memcpy(out, "NAT3UDP!", 8); out[8] = socket->sock_id; + memcpy(out, "NAT4UDP!", 8); out[8] = socket->sock_id; write64(out + 9, socket->generation); write64(out + 17, round); write64(out + 25, group); }