nat: separate routed mapping reports from unsolicited UDP ingress

This commit is contained in:
evgeny committed 2026-10-17 15:43:38 +02:00
1 parent df80c5745d
commit 26ab3b13b4
3 files changed
+284 -179

No files matched your search

+279 -175
View File
@@ -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 <stdio.h>
@@ -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;
}
+4 -3
View File
@@ -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);
+1 -1
View File
@@ -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);
}