You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1101 lines
64 KiB
1101 lines
64 KiB
/* NAT-детектор владельца endpoint. Один раунд на рабочий сокет: |
|
* S->B observation, B->C ingress(S), S->C/C-alt observations, S->B repeat. |
|
* Команды к C проходят через B: контроль не открывает фильтр S->C. */ |
|
#include "nat_detection.h" |
|
|
|
#include <stdio.h> |
|
#include <string.h> |
|
#include "utun_instance.h" |
|
#include "config_parser.h" |
|
#include "etcp_api.h" |
|
#include "etcp.h" |
|
#include "etcp_connections.h" |
|
#include "route_ping.h" |
|
#include "topo_node.h" |
|
#include "topo_group.h" |
|
#include "stcp_client.h" |
|
#include "stun_nat.h" |
|
#include "../lib/debug_config.h" |
|
#include "../lib/mem.h" |
|
#include "../lib/u_async.h" |
|
|
|
#define NAT_WIRE_VERSION 2 |
|
#define NAT_MAX_HELPERS 32 |
|
#define NAT_MAX_BINDINGS 128 |
|
#define NAT_MAX_PROBES 64 |
|
#define NAT_MAX_CONTACTS 256 |
|
#define NAT_TICK_TB 1000 |
|
#define NAT_ROUND_TB 300000 |
|
#define NAT_REFRESH_TB 18000000 |
|
#define NAT_RETRY_TB 300000 |
|
#define NAT_PROBE_COUNT 3 |
|
#define NAT_QUERY_TAG_SIZE (ROUTE_OBSERVE_TAG_SIZE + 9) |
|
|
|
enum nat_command { NAT_FIND = 1, NAT_OFFER, NAT_RUN, NAT_RESULT }; |
|
enum nat_phase { NAT_IDLE, NAT_OBSERVE_B, NAT_FIND_C, NAT_PRE, NAT_OBSERVE_C, NAT_OBSERVE_ALT, NAT_POST, NAT_REPEAT_B, NAT_TCP }; |
|
enum nat_step { NAT_STEP_PRE = 1, NAT_STEP_POST, NAT_STEP_TCP }; |
|
|
|
/* Только байты: формат независим от alignment/endian платформы. */ |
|
struct nat_wire { |
|
uint8_t cmd, command, version, owner_socket, helper_socket, step, reason, excluded_count; |
|
uint8_t owner[8], generation[8], round[8], token[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 owner_key[SC_PUBKEY_SIZE], helper_key[SC_PUBKEY_SIZE]; |
|
uint8_t excluded[NAT_MAX_HELPERS][8]; |
|
uint8_t sent, received, errors; |
|
}; |
|
|
|
struct nat_socket { |
|
struct nat_socket *next; |
|
struct NAT_DETECTION *detector; |
|
struct ETCP_SOCKET *socket, *auxiliary; |
|
struct sockaddr_storage local, observer_address, helper_address, alternate_address; |
|
struct sockaddr_storage baseline, helper_seen, alternate_seen; |
|
struct sockaddr_storage measured_address; |
|
struct address_profile measured_profile; |
|
uint64_t select_due; |
|
uint8_t fallback_allowed; |
|
uint8_t observer_key[32], helper_key[32]; |
|
uint64_t round, observer, helper, token, deadline, due, completed_tb, preferred_observer; |
|
uint64_t excluded[NAT_MAX_HELPERS]; |
|
uint8_t contacts[NAT_MAX_CONTACTS][17]; |
|
unsigned contact_count, excluded_count, 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; |
|
uint32_t rounds, helper_reselections; |
|
/* Уже контактировавший помощник годится для mapping после исчерпания свежих. */ |
|
uint64_t fallback_helper; |
|
uint8_t fallback_key[32], fallback_socket; |
|
struct sockaddr_storage fallback_address; |
|
}; |
|
|
|
struct nat_binding { |
|
struct nat_binding *next; |
|
uint64_t owner, generation, round, expires; |
|
uint8_t owner_socket, owner_key[32]; |
|
struct sockaddr_storage target; |
|
}; |
|
|
|
struct nat_probe { |
|
struct nat_probe *next; |
|
struct NAT_DETECTION *detector; |
|
struct nat_wire message; |
|
uint64_t reply, source_generation, due, expires; |
|
uint8_t source_socket, active, cancelled; |
|
struct stcp_client *tcp; |
|
}; |
|
|
|
struct NAT_DETECTION { |
|
struct UTUN_INSTANCE *instance; |
|
struct nat_socket *sockets; |
|
struct nat_binding *bindings; |
|
struct nat_probe *probes; |
|
void *timer; |
|
uint64_t publish_after; |
|
uint8_t allow_local, stopping, history_table_ready, automatic, publish_pending; |
|
}; |
|
|
|
static void owner_ping_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 *); |
|
static int owner_run(struct nat_socket *, uint8_t); |
|
static void owner_finish(struct nat_socket *, uint8_t); |
|
static void helper_remove(struct nat_probe *); |
|
|
|
static uint64_t read64(const uint8_t *data) { |
|
uint64_t value = 0; |
|
for (unsigned i = 0; i < 8; i++) value = (value << 8) | data[i]; |
|
return value; |
|
} |
|
static void write64(uint8_t *data, uint64_t value) { |
|
for (unsigned i = 0; i < 8; i++) data[i] = (uint8_t)(value >> (56 - i * 8)); |
|
} |
|
static int random_id(uint64_t *value) { |
|
do { |
|
if (random_bytes((uint8_t *)value, sizeof(*value))) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT round/token RNG failed"); return -1; |
|
} |
|
} while (!*value); |
|
return 0; |
|
} |
|
|
|
const char *nat_check_reason_name(uint8_t reason) { |
|
static const char *const names[] = {"none", "no_observer", "no_helper", "control_lost", "send_error", "timeout", |
|
"address_changed", "contact_history", "proxy", "no_listener", "cancelled", "invalid_observation", "dependent_mapping"}; |
|
return reason < sizeof(names) / sizeof(names[0]) ? names[reason] : "invalid_reason"; |
|
} |
|
|
|
static struct ETCP_CONN *live_connection(struct NAT_DETECTION *detector, uint64_t node) { |
|
struct ETCP_CONN *connection = instance_find_conn(detector->instance, node); |
|
return connection && connection->state != 2 && !connection->close_requested && connection->links_up && |
|
connection->crypto_ctx.peer_key_set ? connection : NULL; |
|
} |
|
|
|
static struct ETCP_SOCKET *find_socket(struct NAT_DETECTION *detector, uint8_t id, uint64_t generation) { |
|
for (struct ETCP_SOCKET *socket = detector->instance->etcp_sockets; socket; socket = socket->next) |
|
if (socket->sock_id == id && (!generation || socket->generation == generation)) return socket; |
|
return NULL; |
|
} |
|
|
|
static int public_address(struct NAT_DETECTION *detector, const struct sockaddr_storage *address) { |
|
if (address->ss_family == AF_INET) { |
|
uint32_t ip = ((const struct sockaddr_in *)address)->sin_addr.s_addr; |
|
return ip && (ip_is_public(ip) || detector->allow_local); |
|
} |
|
if (address->ss_family == AF_INET6) { |
|
const uint8_t *ip = ((const struct sockaddr_in6 *)address)->sin6_addr.s6_addr; |
|
return (ip[0] & 0xe0) == 0x20 || (detector->allow_local && !memcmp(ip, &in6addr_loopback, 16)); |
|
} |
|
return 0; |
|
} |
|
|
|
static void publish(struct NAT_DETECTION *detector) { |
|
if (detector->stopping || !detector->instance->topo_groups) return; |
|
detector->publish_pending = topo_node_update_my_addresses(detector->instance) < 0; |
|
if (detector->publish_pending) detector->publish_after = get_time_tb() + 10000; |
|
if (detector->publish_pending) DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT profile publication failed; retry scheduled"); |
|
} |
|
|
|
static struct nat_socket *socket_state(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, int create) { |
|
if (!detector || !socket || socket->probe_only || detector->stopping) return NULL; |
|
for (struct nat_socket *state = detector->sockets; state; state = state->next) |
|
if (state->socket == socket) return state; |
|
if (!create) return NULL; |
|
struct nat_socket *state = u_calloc(1, sizeof(*state)); |
|
if (!state) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT endpoint state allocation failed socket=%s", socket->name); return NULL; } |
|
state->detector = detector; state->socket = socket; state->local = socket->local_addr; |
|
if (socket->is_tcp) state->local = socket->interface_addr; |
|
state->due = get_time_tb() + 2500; |
|
state->next = detector->sockets; detector->sockets = state; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT endpoint created socket=%s id=%u generation=%016llx protocol=%s local=%s", |
|
socket->name, socket->sock_id, (unsigned long long)socket->generation, |
|
socket->is_tcp ? "TCP" : "UDP", sockaddr_storage_to_str(&state->local).str); |
|
return state; |
|
} |
|
|
|
/* История сохраняется один раз на новый IP. Перезапуск на том же IP:port её не очищает. */ |
|
static int history_table(struct NAT_DETECTION *detector) { |
|
if (detector->history_table_ready) return 0; |
|
sqlite3 *db = detector->instance->topo_sqlite_db; |
|
if (!db) return -1; |
|
if (sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS nat_contacts(local BLOB NOT NULL,remote BLOB NOT NULL," |
|
"PRIMARY KEY(local,remote)) WITHOUT ROWID", NULL, NULL, NULL) != SQLITE_OK) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT contact history initialization failed: %s", sqlite3_errmsg(db)); return -1; |
|
} |
|
detector->history_table_ready = 1; return 0; |
|
} |
|
|
|
static int history_insert(struct nat_socket *state, const uint8_t remote[17]) { |
|
uint8_t local[ROUTE_ADDR_WIRE_SIZE]; |
|
if (route_ping_addr_encode(local, &state->local) || history_table(state->detector)) return -1; |
|
sqlite3_stmt *statement = NULL; |
|
sqlite3 *db = state->detector->instance->topo_sqlite_db; |
|
if (sqlite3_prepare_v2(db, "INSERT OR IGNORE INTO nat_contacts(local,remote) VALUES(?,?)", |
|
-1, &statement, NULL) != SQLITE_OK) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT contact write failed: %s", sqlite3_errmsg(db)); return -1; |
|
} |
|
sqlite3_bind_blob(statement, 1, local, sizeof(local), SQLITE_TRANSIENT); |
|
sqlite3_bind_blob(statement, 2, remote, 17, SQLITE_TRANSIENT); |
|
int result = sqlite3_step(statement); sqlite3_finalize(statement); |
|
if (result != SQLITE_DONE) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT contact insert failed: %s", sqlite3_errmsg(db)); return -1; } |
|
return 0; |
|
} |
|
|
|
static void history_mark_incomplete(struct nat_socket *state) { |
|
static const uint8_t marker[17]; |
|
state->history_complete = 0; state->history_incomplete = 1; |
|
/* family=0 — постоянный маркер неполной истории, не адрес помощника. */ |
|
if (history_insert(state, marker)) DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT incomplete history marker persistence failed"); |
|
} |
|
|
|
static void history_load(struct nat_socket *state) { |
|
if (state->history_loaded || state->socket->is_tcp) return; |
|
struct NAT_DETECTION *detector = state->detector; |
|
if (history_table(detector)) { state->history_complete = 0; return; } |
|
uint8_t local[ROUTE_ADDR_WIRE_SIZE]; |
|
if (route_ping_addr_encode(local, &state->local)) return; |
|
sqlite3_stmt *statement = NULL; |
|
sqlite3 *db = detector->instance->topo_sqlite_db; |
|
if (sqlite3_prepare_v2(db, "SELECT remote FROM nat_contacts WHERE local=?", -1, &statement, NULL) != SQLITE_OK) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT history read failed: %s", sqlite3_errmsg(db)); return; |
|
} |
|
sqlite3_bind_blob(statement, 1, local, sizeof(local), SQLITE_TRANSIENT); |
|
state->history_complete = !state->history_incomplete; |
|
int result; |
|
while ((result = sqlite3_step(statement)) == SQLITE_ROW) { |
|
const uint8_t *remote = sqlite3_column_blob(statement, 0); |
|
if (!remote || sqlite3_column_bytes(statement, 0) != 17 || (remote[0] != 4 && remote[0] != 6)) { |
|
state->history_incomplete = 1; state->history_complete = 0; continue; |
|
} |
|
unsigned index; |
|
for (index = 0; index < state->contact_count; index++) if (!memcmp(state->contacts[index], remote, 17)) break; |
|
if (index < state->contact_count) continue; |
|
if (state->contact_count == NAT_MAX_CONTACTS) { state->history_incomplete = 1; state->history_complete = 0; continue; } |
|
memcpy(state->contacts[state->contact_count++], remote, 17); |
|
} |
|
if (result != SQLITE_DONE) { |
|
state->history_complete = 0; |
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT history query failed: %s", sqlite3_errmsg(db)); |
|
} |
|
sqlite3_finalize(statement); state->history_loaded = 1; |
|
/* Сохраняем также отправки, которые случились до открытия общей SQLite. */ |
|
for (unsigned i = 0; i < state->contact_count; i++) |
|
if (history_insert(state, state->contacts[i])) { history_mark_incomplete(state); break; } |
|
if (state->history_incomplete) history_mark_incomplete(state); |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT history loaded socket=%s contacts=%u complete=%u", |
|
state->socket->name, state->contact_count, state->history_complete); |
|
} |
|
|
|
static int contacted(struct nat_socket *state, const struct sockaddr_storage *address) { |
|
uint8_t remote[ROUTE_ADDR_WIRE_SIZE]; |
|
if (!state->history_complete || route_ping_addr_encode(remote, address)) return 1; |
|
for (unsigned i = 0; i < state->contact_count; i++) if (!memcmp(state->contacts[i], remote, 17)) return 1; |
|
return 0; |
|
} |
|
|
|
void nat_detection_note_send(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, |
|
const struct sockaddr_storage *destination) { |
|
if (!detector || !socket || socket->is_tcp || socket->probe_only || socket->socks_udp || detector->stopping) return; |
|
struct nat_socket *state = socket_state(detector, socket, 1); |
|
if (!state) return; |
|
history_load(state); |
|
if (state->phase == NAT_PRE && !state->pre_received && |
|
route_ping_addr_equal(destination, &state->helper_address, 0)) { |
|
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; |
|
for (unsigned i = 0; i < state->contact_count; i++) if (!memcmp(state->contacts[i], remote, 17)) return; |
|
if (state->contact_count == NAT_MAX_CONTACTS) { |
|
if (state->history_complete) DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT contact history full socket=%s", socket->name); |
|
if (!state->history_incomplete) history_mark_incomplete(state); |
|
return; |
|
} |
|
memcpy(state->contacts[state->contact_count++], remote, 17); |
|
if (!state->history_loaded || !detector->instance->topo_sqlite_db) { state->history_complete = 0; return; } |
|
if (history_insert(state, remote)) history_mark_incomplete(state); |
|
} |
|
|
|
static int send_control(struct NAT_DETECTION *detector, uint64_t destination, struct nat_wire *message) { |
|
struct ETCP_CONN *connection = live_connection(detector, destination); |
|
if (!connection) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT control peer unavailable peer=%016llx", (unsigned long long)destination); return -1; |
|
} |
|
struct ll_entry *entry = queue_entry_new(0); |
|
if (!entry) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT control queue allocation failed"); return -1; } |
|
entry->dgram = u_malloc(sizeof(*message)); |
|
if (!entry->dgram) { queue_entry_free(entry); DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT control allocation failed"); return -1; } |
|
message->cmd = ETCP_ID_NAT_DETECTION; message->version = NAT_WIRE_VERSION; |
|
memcpy(entry->dgram, message, sizeof(*message)); entry->len = sizeof(*message); |
|
if (etcp_send(connection, entry)) { |
|
queue_dgram_free(entry); queue_entry_free(entry); |
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT control send failed peer=%016llx command=%u", |
|
(unsigned long long)destination, message->command); return -1; |
|
} |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT control TX command=%u step=%u peer=%016llx owner=%016llx round=%016llx helper=%016llx", |
|
message->command, message->step, (unsigned long long)destination, |
|
(unsigned long long)read64(message->owner), (unsigned long long)read64(message->round), |
|
(unsigned long long)read64(message->helper)); |
|
return 0; |
|
} |
|
|
|
static void make_message(struct nat_socket *state, struct nat_wire *message, uint8_t command) { |
|
memset(message, 0, sizeof(*message)); message->command = command; |
|
message->owner_socket = state->socket->sock_id; message->helper_socket = state->helper_socket; |
|
write64(message->owner, state->detector->instance->node_id); |
|
write64(message->generation, state->socket->generation); write64(message->round, state->round); |
|
write64(message->helper, state->helper); write64(message->token, state->token); |
|
memcpy(message->owner_key, state->detector->instance->my_keys.public_key, 32); |
|
memcpy(message->helper_key, state->helper_key, 32); |
|
route_ping_addr_encode(message->target, state->socket->is_tcp ? &state->local : &state->baseline); |
|
if (state->helper_address.ss_family) route_ping_addr_encode(message->primary, &state->helper_address); |
|
int timeout = 1000; |
|
struct ETCP_CONN *observer = live_connection(state->detector, state->observer); |
|
for (struct ETCP_LINK *link = observer ? observer->links : NULL; link; link = link->next) |
|
if (link->rtt_last / 10 * 4 > timeout) timeout = link->rtt_last / 10 * 4; |
|
if (timeout > 3000) timeout = 3000; |
|
message->timeout_ms[0] = timeout >> 8; message->timeout_ms[1] = timeout; |
|
message->excluded_count = state->excluded_count; |
|
for (unsigned i = 0; i < state->excluded_count; i++) write64(message->excluded[i], state->excluded[i]); |
|
} |
|
|
|
static struct nat_binding *binding_find(struct NAT_DETECTION *detector, uint64_t owner, uint64_t generation, uint64_t round) { |
|
for (struct nat_binding *binding = detector->bindings; binding; binding = binding->next) |
|
if (binding->owner == owner && binding->generation == generation && binding->round == round && |
|
binding->expires > get_time_tb()) return binding; |
|
return NULL; |
|
} |
|
|
|
void nat_detection_record_observation(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, |
|
const uint8_t peer_key[32], const struct sockaddr_storage *source, |
|
const uint8_t *tag, size_t length) { |
|
if (!detector || detector->stopping) return; |
|
if (length != ROUTE_OBSERVE_TAG_SIZE || !tag || memcmp(tag, "NAT2UDP!", 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); |
|
if (!generation || !round) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT observation query has zero generation/round"); return; } |
|
struct nat_binding *binding = binding_find(detector, owner, generation, round); |
|
if (!binding) { |
|
unsigned count = 0; |
|
for (struct nat_binding *item = detector->bindings; item; item = item->next) count++; |
|
if (count >= NAT_MAX_BINDINGS) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT observation cache full"); return; } |
|
binding = u_calloc(1, sizeof(*binding)); |
|
if (!binding) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT observation cache allocation failed"); return; } |
|
binding->next = detector->bindings; detector->bindings = binding; |
|
} |
|
binding->owner = owner; binding->generation = generation; binding->round = round; binding->owner_socket = tag[8]; |
|
memcpy(binding->owner_key, peer_key, 32); binding->target = *source; binding->expires = get_time_tb() + NAT_ROUND_TB; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT observation owner=%016llx socket=%u generation=%016llx round=%016llx observed=%s via=%s", |
|
(unsigned long long)owner, tag[8], (unsigned long long)generation, (unsigned long long)round, |
|
sockaddr_storage_to_str(source).str, socket->name); |
|
} |
|
|
|
static void clear_profile(struct nat_socket *state, uint8_t reason) { |
|
int changed = state->socket->address_profile.mapping || state->socket->address_profile.inbound || |
|
state->socket->nat_addr.ss_family; |
|
memset(&state->socket->address_profile, 0, sizeof(state->socket->address_profile)); |
|
memset(&state->socket->nat_addr, 0, sizeof(state->socket->nat_addr)); |
|
state->completed_tb = 0; |
|
memset(&state->measured_profile, 0, sizeof(state->measured_profile)); |
|
memset(&state->measured_address, 0, sizeof(state->measured_address)); |
|
if (changed) { |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT profile invalidated socket=%s generation=%016llx reason=%s", |
|
state->socket->name, (unsigned long long)state->socket->generation, nat_check_reason_name(reason)); |
|
publish(state->detector); |
|
} |
|
} |
|
|
|
/* Единственный выбор публикуемого профиля. STUN переносит только тип IP/netif, |
|
* никогда не mapped endpoint тестового порта. Успешный ETCP действует до явного сброса. */ |
|
static void select_profile(struct nat_socket *state) { |
|
const struct address_profile *current = &state->socket->address_profile; |
|
struct address_profile selected = *current; |
|
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 && |
|
selected.source == current->source && !address_changed) return; |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT selected socket=%s generation=%016llx source=%s previous=%s mapping=%s inbound=%s" |
|
" address_changed=%d reason=%s", |
|
state->socket->name, (unsigned long long)state->socket->generation, address_profile_source_name(selected.source), |
|
address_profile_source_name(current->source), address_mapping_name(selected.mapping), address_inbound_name(selected.inbound), |
|
address_changed, nat_check_reason_name(state->reason)); |
|
state->socket->address_profile = selected; |
|
state->socket->nat_addr = selected_address; |
|
publish(state->detector); |
|
} |
|
|
|
static void owner_finish(struct nat_socket *state, uint8_t reason) { |
|
etcp_cancel_pings(state->detector->instance, owner_ping_cb, state); |
|
state->active_ping = 0; state->phase = NAT_IDLE; state->reason = reason; |
|
if (reason == NAT_REASON_NO_OBSERVER || reason == NAT_REASON_NO_HELPER) state->fallback_allowed = 1; |
|
state->preferred_observer = 0; |
|
state->due = get_time_tb() + (reason == NAT_REASON_ADDRESS_CHANGED ? 2500 : |
|
state->socket->address_profile.source || !reason ? NAT_REFRESH_TB : NAT_RETRY_TB); |
|
if (reason) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT round incomplete socket=%s generation=%016llx round=%016llx reason=%s", |
|
state->socket->name, (unsigned long long)state->socket->generation, |
|
(unsigned long long)state->round, nat_check_reason_name(reason)); |
|
} |
|
select_profile(state); |
|
} |
|
|
|
static void owner_apply(struct nat_socket *state, uint8_t mapping, uint8_t inbound) { |
|
struct address_profile profile = {.mapping = mapping, .inbound = inbound, .source = ADDRESS_SOURCE_ETCP}; |
|
uint8_t partial_reason = inbound != ADDRESS_IN_UNKNOWN ? NAT_REASON_NONE : !state->filter_clean ? NAT_REASON_CONTACT_HISTORY : |
|
mapping == ADDRESS_MAP_DEPENDENT ? NAT_REASON_DEPENDENT_MAPPING : NAT_REASON_TIMEOUT; |
|
if (inbound == ADDRESS_IN_UNKNOWN && state->measured_profile.source == ADDRESS_SOURCE_ETCP && |
|
mapping == state->measured_profile.mapping && route_ping_addr_equal(&state->measured_address, &state->baseline, 1)) { |
|
profile.inbound = state->measured_profile.inbound; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT incomplete ingress preserves previous result socket=%s round=%016llx inbound=%s reason=%s", |
|
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->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", |
|
state->socket->name, state->socket->sock_id, (unsigned long long)state->socket->generation, |
|
(unsigned long long)state->round, address_mapping_name(mapping), address_inbound_name(inbound), |
|
address_inbound_name(profile.inbound), |
|
sockaddr_storage_to_str(&state->local).str, sockaddr_storage_to_str(&state->baseline).str, |
|
(unsigned long long)state->observer, (unsigned long long)state->helper, state->filter_clean, |
|
nat_check_reason_name(partial_reason)); |
|
owner_finish(state, NAT_REASON_NONE); state->reason = partial_reason; |
|
} |
|
|
|
static int owner_find(struct nat_socket *state) { |
|
struct nat_wire message; make_message(state, &message, NAT_FIND); |
|
state->phase = NAT_FIND_C; |
|
return send_control(state->detector, state->observer, &message); |
|
} |
|
|
|
static int owner_run(struct nat_socket *state, uint8_t step) { |
|
if (random_id(&state->token)) return -1; |
|
struct nat_wire message; make_message(state, &message, NAT_RUN); message.step = step; |
|
state->phase = step == NAT_STEP_TCP ? NAT_TCP : step == NAT_STEP_PRE ? NAT_PRE : NAT_POST; |
|
return send_control(state->detector, 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; |
|
} |
|
int timeout = 1000; |
|
struct ETCP_CONN *connection = live_connection(state->detector, 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->round, timeout, owner_ping_cb, state)) return -1; |
|
state->active_ping = 1; return 0; |
|
} |
|
|
|
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; |
|
state->active_ping = 0; |
|
if (state->phase == NAT_IDLE) return; |
|
struct sockaddr_storage observed; |
|
if (!success || route_ping_observation(data, length, state->socket, state->round, &observed)) { |
|
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; |
|
} |
|
state->retries = 0; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT own observation socket=%s phase=%u round=%016llx observed=%s", |
|
state->socket->name, state->phase, (unsigned long long)state->round, sockaddr_storage_to_str(&observed).str); |
|
if (state->phase == NAT_OBSERVE_B) { |
|
state->baseline = observed; |
|
if ((state->measured_profile.mapping == ADDRESS_MAP_UNCHANGED && !route_ping_addr_equal(&state->local, &observed, 1)) || |
|
(state->socket->nat_addr.ss_family && !route_ping_addr_equal(&state->socket->nat_addr, &observed, 1))) |
|
clear_profile(state, NAT_REASON_ADDRESS_CHANGED); |
|
if (owner_find(state)) owner_finish(state, NAT_REASON_CONTROL_LOST); |
|
return; |
|
} |
|
if (state->phase == NAT_OBSERVE_C) { |
|
state->helper_seen = observed; state->phase = NAT_OBSERVE_ALT; |
|
} 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)) { |
|
if (owner_run(state, NAT_STEP_POST)) owner_finish(state, NAT_REASON_CONTROL_LOST); |
|
return; |
|
} |
|
state->phase = NAT_REPEAT_B; |
|
} else if (state->phase == NAT_REPEAT_B) { |
|
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); |
|
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; |
|
owner_apply(state, mapping, inbound); return; |
|
} else { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT observation in unexpected phase=%u", state->phase); |
|
owner_finish(state, NAT_REASON_INVALID_OBSERVATION); return; |
|
} |
|
if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR); |
|
} |
|
|
|
void nat_detection_receive_ingress(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, |
|
const uint8_t peer_key[32], const struct sockaddr_storage *source, |
|
const uint8_t *tag, size_t length) { |
|
struct nat_socket *state = socket_state(detector, socket, 0); |
|
if (!state || !tag || length != NAT_QUERY_TAG_SIZE) { |
|
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->round); |
|
uint8_t step = state->phase == NAT_PRE ? NAT_STEP_PRE : state->phase == NAT_POST ? NAT_STEP_POST : 0; |
|
if (!step || memcmp(tag, expected, sizeof(expected)) || tag[25] != step || read64(tag + 26) != state->token || |
|
sc_derive_node_id_from_pubkey(peer_key) != state->helper || memcmp(peer_key, state->helper_key, 32) || |
|
!route_ping_addr_equal(source, &state->helper_address, 1) || |
|
route_ping_addr_equal(source, &state->observer_address, 0)) { |
|
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 (step == NAT_STEP_PRE) state->pre_received = 1; |
|
else 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 *auxiliary_socket(struct NAT_DETECTION *detector, struct ETCP_SOCKET *source) { |
|
struct nat_socket *state = socket_state(detector, source, 1); |
|
if (!state) return NULL; |
|
if (state->auxiliary) return state->auxiliary; |
|
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; |
|
snprintf(server.name, sizeof(server.name), "nat-observer-%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; |
|
state->auxiliary = etcp_socket_add(detector->instance, &server); |
|
if (!state->auxiliary) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT alternate observer socket failed"); return NULL; } |
|
state->auxiliary->probe_only = 1; |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT alternate observer socket=%s primary=%s alternate=%s", |
|
source->name, sockaddr_storage_to_str(&source->local_addr).str, |
|
sockaddr_storage_to_str(&state->auxiliary->local_addr).str); |
|
return state->auxiliary; |
|
} |
|
|
|
static void helper_remove(struct nat_probe *probe) { |
|
struct NAT_DETECTION *detector = probe->detector; |
|
struct nat_probe **cursor = &detector->probes; |
|
while (*cursor && *cursor != probe) cursor = &(*cursor)->next; |
|
if (*cursor) *cursor = probe->next; |
|
probe->cancelled = 1; |
|
etcp_cancel_pings(detector->instance, helper_ping_cb, probe); |
|
if (probe->tcp) { struct stcp_client *tcp = probe->tcp; probe->tcp = NULL; stcp_client_destroy(tcp); } |
|
u_free(probe); |
|
} |
|
|
|
static void helper_finish(struct nat_probe *probe, uint8_t reason) { |
|
probe->message.command = NAT_RESULT; probe->message.reason = reason; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT helper done owner=%016llx round=%016llx step=%u sent=%u received=%u errors=%u reason=%s", |
|
(unsigned long long)read64(probe->message.owner), (unsigned long long)read64(probe->message.round), |
|
probe->message.step, probe->message.sent, probe->message.received, probe->message.errors, nat_check_reason_name(reason)); |
|
if (send_control(probe->detector, probe->reply, &probe->message)) |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper result delivery failed"); |
|
helper_remove(probe); |
|
} |
|
|
|
static void helper_ping_cb(int success, uint16_t rtt, void *arg, uint64_t nonce, const uint8_t *data, size_t length) { |
|
(void)rtt; (void)nonce; (void)data; (void)length; |
|
struct nat_probe *probe = arg; |
|
if (probe->cancelled) return; |
|
probe->active = 0; probe->tcp = NULL; |
|
if (success) probe->message.received++; |
|
if (probe->message.step == NAT_STEP_TCP || probe->message.sent >= NAT_PROBE_COUNT) |
|
helper_finish(probe, NAT_REASON_NONE); |
|
else probe->due = get_time_tb() + 1000; |
|
} |
|
|
|
static int helper_send(struct nat_probe *probe) { |
|
struct NAT_DETECTION *detector = probe->detector; |
|
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; |
|
int result; |
|
if (probe->message.step == NAT_STEP_TCP) { |
|
struct reality_client_config reality, *camouflage = NULL; |
|
const struct TOPO_NODE *owner = topo_node_registry_find(detector->instance->topo_groups, read64(probe->message.owner)); |
|
const struct TOPO_REALITY_SOCK *parameters = topo_node_find_reality_sock(owner, probe->message.owner_socket); |
|
if (parameters) { topo_node_reality_config(&reality, parameters); camouflage = &reality; } |
|
result = etcp_send_tcp_ping(detector->instance, probe->message.owner_key, &target, timeout, |
|
helper_ping_cb, probe, &probe->tcp, camouflage); |
|
} else { |
|
struct ETCP_SOCKET *socket = find_socket(detector, probe->source_socket, probe->source_generation); |
|
if (!socket || socket->is_tcp || socket->socks_udp) return -1; |
|
uint8_t tag[NAT_QUERY_TAG_SIZE]; |
|
memcpy(tag, "NAT2UDP!", 8); tag[8] = probe->message.owner_socket; |
|
memcpy(tag + 9, probe->message.generation, 8); memcpy(tag + 17, probe->message.round, 8); |
|
tag[25] = probe->message.step; memcpy(tag + 26, probe->message.token, 8); |
|
result = etcp_send_ping_to_socket(detector->instance, socket, probe->message.owner_key, &target, timeout, |
|
helper_ping_cb, probe, tag, sizeof(tag), ETCP_PING_FLAG_ADDRESS_CHECK); |
|
} |
|
if (result) { probe->message.errors++; return -1; } |
|
probe->message.sent++; probe->active = 1; return 0; |
|
} |
|
|
|
static void handle_find(struct NAT_DETECTION *detector, struct ETCP_CONN *from, struct nat_wire *message) { |
|
struct nat_binding *binding = binding_find(detector, from->peer_node_id, read64(message->generation), read64(message->round)); |
|
struct sockaddr_storage target; |
|
if (!binding || read64(message->owner) != from->peer_node_id || |
|
route_ping_addr_decode(message->target, &target) || !route_ping_addr_equal(&binding->target, &target, 1) || |
|
memcmp(binding->owner_key, message->owner_key, 32)) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper selection rejected: no matching own observation peer=%016llx", |
|
(unsigned long long)from->peer_node_id); return; |
|
} |
|
message->command = NAT_OFFER; message->reason = NAT_REASON_NO_HELPER; memset(message->helper, 0, 8); |
|
uint64_t selected = UINT64_MAX; |
|
struct ETCP_LINK *selected_link = NULL; |
|
for (struct ll_entry *entry = detector->instance->connections->head; entry; entry = entry->next) { |
|
struct ETCP_CONN *candidate = ((struct conn_queue_entry *)entry->data)->conn; |
|
if (!candidate || candidate == from || !live_connection(detector, candidate->peer_node_id) || |
|
candidate->peer_node_id >= selected) continue; |
|
int excluded = 0; |
|
for (unsigned i = 0; i < message->excluded_count; i++) |
|
if (read64(message->excluded[i]) == candidate->peer_node_id) excluded = 1; |
|
if (excluded) continue; |
|
for (struct ETCP_LINK *link = candidate->links; link; link = link->next) { |
|
if (link->is_tcp || !link->link_status || !link->initialized || |
|
link->remote_addr.ss_family != target.ss_family || !public_address(detector, &link->remote_addr) || |
|
(link->remote_type != CFG_SERVER_TYPE_PUBLIC && !detector->allow_local)) continue; |
|
selected = candidate->peer_node_id; selected_link = link; break; |
|
} |
|
} |
|
if (selected_link) { |
|
write64(message->helper, selected); message->helper_socket = selected_link->remote_socket_id; |
|
memcpy(message->helper_key, selected_link->etcp->crypto_ctx.peer_public_key, 32); |
|
route_ping_addr_encode(message->primary, &selected_link->remote_addr); |
|
message->reason = NAT_REASON_NONE; |
|
} |
|
if (send_control(detector, from->peer_node_id, message)) |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper offer delivery failed"); |
|
} |
|
|
|
static void handle_offer(struct nat_socket *state, struct nat_wire *message) { |
|
uint64_t helper = read64(message->helper); |
|
if (!helper) { |
|
if (state->fallback_helper) { |
|
state->helper = state->fallback_helper; state->helper_socket = state->fallback_socket; |
|
memcpy(state->helper_key, state->fallback_key, 32); state->helper_address = state->fallback_address; |
|
state->filter_clean = 0; |
|
if (owner_run(state, NAT_STEP_PRE)) owner_finish(state, NAT_REASON_CONTROL_LOST); |
|
} else owner_finish(state, NAT_REASON_NO_HELPER); |
|
return; |
|
} |
|
struct sockaddr_storage address; |
|
if (route_ping_addr_decode(message->primary, &address) || sc_derive_node_id_from_pubkey(message->helper_key) != helper) { |
|
owner_finish(state, NAT_REASON_INVALID_OBSERVATION); return; |
|
} |
|
int same_ip = route_ping_addr_equal(&state->observer_address, &address, 0); |
|
int old_contact = contacted(state, &address); |
|
if (same_ip || old_contact) { |
|
if (!same_ip && !state->fallback_helper) { |
|
state->fallback_helper = helper; state->fallback_socket = message->helper_socket; |
|
state->fallback_address = address; memcpy(state->fallback_key, message->helper_key, 32); |
|
} |
|
if (state->excluded_count < NAT_MAX_HELPERS) { |
|
state->excluded[state->excluded_count++] = helper; state->helper_reselections++; |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT request another helper socket=%s round=%016llx rejected=%016llx reason=%s ip=%s", |
|
state->socket->name, (unsigned long long)state->round, (unsigned long long)helper, |
|
same_ip ? "same_external_ip" : "previous_udp_contact", sockaddr_storage_to_str(&address).str); |
|
if (owner_find(state)) owner_finish(state, NAT_REASON_CONTROL_LOST); |
|
} else owner_finish(state, NAT_REASON_NO_HELPER); |
|
return; |
|
} |
|
state->helper = helper; state->helper_socket = message->helper_socket; |
|
state->helper_address = address; memcpy(state->helper_key, message->helper_key, 32); |
|
state->filter_clean = 1; |
|
if (owner_run(state, NAT_STEP_PRE)) owner_finish(state, NAT_REASON_CONTROL_LOST); |
|
} |
|
|
|
static int tcp_target_advertised(struct NAT_DETECTION *detector, const struct nat_wire *message, |
|
const struct sockaddr_storage *target) { |
|
const struct TOPO_NODE *owner = topo_node_registry_find(detector->instance->topo_groups, read64(message->owner)); |
|
if (!owner || memcmp(owner->public_key, message->owner_key, 32)) return 0; |
|
uint8_t address[ROUTE_ADDR_WIRE_SIZE]; |
|
if (route_ping_addr_encode(address, target)) return 0; |
|
if (target->ss_family == AF_INET) { |
|
for (const struct TOPO_ADDR4 *item = owner->v4_addrs; item; item = item->next) |
|
if (item->socket_id == message->owner_socket && (item->protocol & TOPO_PROTO_TCP) && |
|
!memcmp(item->addr, address + 1, 4) && item->port == (((unsigned)address[17] << 8) | address[18])) return 1; |
|
} else { |
|
for (const struct TOPO_ADDR6 *item = owner->v6_addrs; item; item = item->next) |
|
if (item->socket_id == message->owner_socket && (item->protocol & TOPO_PROTO_TCP) && |
|
!memcmp(item->addr, address + 1, 16) && item->port == (((unsigned)address[17] << 8) | address[18])) return 1; |
|
} |
|
return 0; |
|
} |
|
|
|
static void handle_run(struct NAT_DETECTION *detector, struct ETCP_CONN *from, struct nat_wire *message) { |
|
uint64_t owner = read64(message->owner), helper = read64(message->helper); |
|
struct sockaddr_storage target; |
|
if (route_ping_addr_decode(message->target, &target) || sc_derive_node_id_from_pubkey(message->owner_key) != owner) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT run has invalid owner/target"); return; |
|
} |
|
if (owner == from->peer_node_id && helper != detector->instance->node_id) { |
|
struct nat_binding *binding = binding_find(detector, owner, read64(message->generation), read64(message->round)); |
|
if (!binding || !route_ping_addr_equal(&binding->target, &target, 1) || |
|
memcmp(binding->owner_key, message->owner_key, 32) || !live_connection(detector, helper)) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT run forwarding rejected: stale observation/helper"); return; |
|
} |
|
if (send_control(detector, helper, message)) DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT run forwarding failed"); |
|
return; |
|
} |
|
if (helper != detector->instance->node_id || (message->step == NAT_STEP_TCP && !tcp_target_advertised(detector, message, &target))) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper run rejected helper=%016llx step=%u", (unsigned long long)helper, message->step); |
|
return; |
|
} |
|
unsigned count = 0; |
|
for (struct nat_probe *probe = detector->probes; probe; probe = probe->next) { |
|
count++; |
|
if (read64(probe->message.owner) == owner && read64(probe->message.round) == read64(message->round) && |
|
read64(probe->message.token) == read64(message->token)) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT duplicate helper run ignored"); return; |
|
} |
|
} |
|
if (count >= NAT_MAX_PROBES) { DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper probe limit reached"); return; } |
|
struct ETCP_SOCKET *source = NULL; |
|
if (message->step != NAT_STEP_TCP) { |
|
struct sockaddr_storage primary; |
|
source = find_socket(detector, message->helper_socket, 0); |
|
if (!source || source->is_tcp || source->probe_only || source->socks_udp || |
|
route_ping_addr_decode(message->primary, &primary) || !route_ping_addr_equal(&primary, &source->local_addr, 1)) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper source is not the requested native UDP endpoint"); return; |
|
} |
|
struct ETCP_SOCKET *alternate = auxiliary_socket(detector, source); |
|
if (!alternate || route_ping_addr_encode(message->secondary, &alternate->local_addr)) return; |
|
} |
|
struct nat_probe *probe = u_calloc(1, sizeof(*probe)); |
|
if (!probe) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT helper probe allocation failed"); return; } |
|
probe->detector = detector; probe->message = *message; probe->reply = from->peer_node_id; |
|
if (source) { probe->source_socket = source->sock_id; probe->source_generation = source->generation; } |
|
probe->expires = get_time_tb() + NAT_ROUND_TB; |
|
probe->next = detector->probes; detector->probes = probe; |
|
if (helper_send(probe)) helper_finish(probe, NAT_REASON_SEND_ERROR); |
|
} |
|
|
|
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 : 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; |
|
} |
|
if (message->reason || message->errors || !message->sent || message->received > message->sent) { |
|
owner_finish(state, message->reason ? message->reason : NAT_REASON_SEND_ERROR); return; |
|
} |
|
if (step == NAT_STEP_TCP) { |
|
if (message->received) owner_apply(state, ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN); |
|
else owner_finish(state, NAT_REASON_TIMEOUT); |
|
return; |
|
} |
|
if (step == NAT_STEP_PRE) { |
|
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 = 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); |
|
} |
|
|
|
static void receive_control(struct ETCP_CONN *from, struct ll_entry *entry) { |
|
struct NAT_DETECTION *detector = from && from->instance ? from->instance->nat_det : NULL; |
|
if (!detector || !entry || entry->len != sizeof(struct nat_wire)) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT control invalid size=%zu", entry ? entry->len : 0); |
|
if (entry) { queue_dgram_free(entry); queue_entry_free(entry); } return; |
|
} |
|
struct nat_wire message; memcpy(&message, entry->dgram, sizeof(message)); |
|
queue_dgram_free(entry); queue_entry_free(entry); |
|
if (message.cmd != ETCP_ID_NAT_DETECTION || message.version != NAT_WIRE_VERSION || |
|
message.excluded_count > NAT_MAX_HELPERS || !read64(message.generation) || !read64(message.round) || |
|
message.command < NAT_FIND || message.command > NAT_RESULT || message.reason > NAT_REASON_INVALID_OBSERVATION || |
|
message.step > NAT_STEP_TCP) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT control malformed command=%u version=%u", message.command, message.version); return; |
|
} |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT control RX command=%u step=%u from=%016llx owner=%016llx round=%016llx", |
|
message.command, message.step, (unsigned long long)from->peer_node_id, |
|
(unsigned long long)read64(message.owner), (unsigned long long)read64(message.round)); |
|
if (message.command == NAT_FIND) { handle_find(detector, from, &message); return; } |
|
if (message.command == NAT_RUN) { handle_run(detector, from, &message); return; } |
|
uint64_t owner = read64(message.owner); |
|
if (message.command == NAT_RESULT && owner != detector->instance->node_id) { |
|
struct nat_binding *binding = binding_find(detector, owner, read64(message.generation), read64(message.round)); |
|
if (!binding || read64(message.helper) != from->peer_node_id) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper result has no matching delegation"); return; |
|
} |
|
if (send_control(detector, owner, &message)) DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT helper result forwarding failed"); |
|
return; |
|
} |
|
struct ETCP_SOCKET *socket = find_socket(detector, message.owner_socket, read64(message.generation)); |
|
struct nat_socket *state = socket_state(detector, socket, 0); |
|
if (!state || owner != detector->instance->node_id || state->round != read64(message.round) || |
|
state->observer != from->peer_node_id || state->phase == NAT_IDLE) { |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT stale/foreign owner response ignored"); return; |
|
} |
|
if (message.command == NAT_OFFER) { |
|
if (state->phase == NAT_FIND_C) handle_offer(state, &message); |
|
else DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT stale helper offer ignored"); |
|
} else if (message.command == NAT_RESULT) handle_result(state, &message); |
|
} |
|
|
|
static int select_observer(struct nat_socket *state) { |
|
struct NAT_DETECTION *detector = state->detector; |
|
uint64_t selected = UINT64_MAX; |
|
struct ETCP_LINK *selected_link = NULL; |
|
for (struct ll_entry *entry = detector->instance->connections->head; entry; entry = entry->next) { |
|
struct ETCP_CONN *connection = ((struct conn_queue_entry *)entry->data)->conn; |
|
if (!connection || !live_connection(detector, connection->peer_node_id) || connection->peer_node_id >= selected) continue; |
|
if (state->preferred_observer && state->preferred_observer != connection->peer_node_id) continue; |
|
for (struct ETCP_LINK *link = connection->links; link; link = link->next) { |
|
if (!link->link_status || !link->initialized) continue; |
|
if (state->socket->is_tcp) { |
|
if (!public_address(detector, &link->remote_addr)) continue; |
|
} else { |
|
if (link->is_tcp || link->remote_addr.ss_family != state->local.ss_family || |
|
!public_address(detector, &link->remote_addr) || |
|
(link->remote_type != CFG_SERVER_TYPE_PUBLIC && !detector->allow_local)) continue; |
|
} |
|
selected = connection->peer_node_id; selected_link = link; break; |
|
} |
|
} |
|
if (!selected_link) return -1; |
|
state->observer = selected; state->observer_address = selected_link->remote_addr; |
|
memcpy(state->observer_key, selected_link->etcp->crypto_ctx.peer_public_key, 32); return 0; |
|
} |
|
|
|
static void owner_start(struct nat_socket *state) { |
|
struct NAT_DETECTION *detector = state->detector; |
|
if (state->socket->socks_udp) { |
|
state->reason = NAT_REASON_PROXY; state->due = UINT64_MAX; |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT native endpoint not measured through SOCKS5 socket=%s", state->socket->name); return; |
|
} |
|
if (state->socket->type == CFG_SERVER_TYPE_LOCAL || state->socket->type == CFG_SERVER_TYPE_PRIVATE) { |
|
state->due = UINT64_MAX; return; |
|
} |
|
if (state->socket->is_tcp && !public_address(detector, &state->local)) { |
|
state->reason = NAT_REASON_NO_LISTENER; state->due = UINT64_MAX; |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "TCP inbound profile remains UNKNOWN: no public listener candidate socket=%s local=%s", |
|
state->socket->name, sockaddr_storage_to_str(&state->local).str); return; |
|
} |
|
if (select_observer(state)) { |
|
if (state->reason != NAT_REASON_NO_OBSERVER) |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT recheck has no live observer socket=%s retained=%s/%s source=%s", state->socket->name, |
|
address_mapping_name(state->socket->address_profile.mapping), |
|
address_inbound_name(state->socket->address_profile.inbound), |
|
address_profile_source_name(state->socket->address_profile.source)); |
|
state->reason = NAT_REASON_NO_OBSERVER; state->fallback_allowed = 1; |
|
select_profile(state); |
|
state->due = get_time_tb() + (state->socket->address_profile.source ? NAT_REFRESH_TB : NAT_RETRY_TB); return; |
|
} |
|
if (random_id(&state->round)) { state->reason = NAT_REASON_SEND_ERROR; state->due = get_time_tb() + NAT_RETRY_TB; return; } |
|
history_load(state); |
|
state->rounds++; state->excluded_count = 0; state->fallback_helper = 0; state->helper = 0; |
|
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; |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT round start socket=%s id=%u generation=%016llx round=%016llx observer=%016llx protocol=%s", |
|
state->socket->name, state->socket->sock_id, (unsigned long long)state->socket->generation, |
|
(unsigned long long)state->round, (unsigned long long)state->observer, state->socket->is_tcp ? "TCP" : "UDP"); |
|
if (state->socket->is_tcp) { |
|
state->baseline = state->local; |
|
state->helper = state->observer; |
|
if (owner_run(state, NAT_STEP_TCP)) owner_finish(state, NAT_REASON_CONTROL_LOST); |
|
} else { |
|
state->phase = NAT_OBSERVE_B; |
|
if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR); |
|
} |
|
} |
|
|
|
static void detector_tick(void *arg) { |
|
struct NAT_DETECTION *detector = arg; detector->timer = NULL; |
|
if (detector->publish_pending && get_time_tb() >= detector->publish_after) publish(detector); |
|
uint64_t now = get_time_tb(); |
|
for (struct ETCP_SOCKET *socket = detector->instance->etcp_sockets; socket; socket = socket->next) |
|
if (!socket->probe_only) socket_state(detector, socket, 1); |
|
for (struct nat_socket *state = detector->sockets; state; state = state->next) { |
|
if (now >= state->select_due) { select_profile(state); state->select_due = now + 10000; } |
|
if (state->phase && now >= state->deadline) { owner_finish(state, NAT_REASON_TIMEOUT); continue; } |
|
if (!state->phase && now >= state->due && (detector->automatic || state->forced)) { |
|
state->forced = 0; owner_start(state); |
|
} |
|
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) { |
|
if (owner_observe(state)) owner_finish(state, NAT_REASON_SEND_ERROR); |
|
} |
|
} |
|
struct nat_probe *probe = detector->probes; |
|
while (probe) { |
|
struct nat_probe *next = probe->next; |
|
if (now >= probe->expires) helper_finish(probe, NAT_REASON_TIMEOUT); |
|
else if (!probe->active && now >= probe->due && helper_send(probe)) helper_finish(probe, NAT_REASON_SEND_ERROR); |
|
probe = next; |
|
} |
|
struct nat_binding **cursor = &detector->bindings; |
|
while (*cursor) { |
|
struct nat_binding *binding = *cursor; |
|
if (now < binding->expires) { cursor = &binding->next; continue; } |
|
*cursor = binding->next; u_free(binding); |
|
} |
|
detector->timer = uasync_set_timeout(detector->instance->ua, NAT_TICK_TB, detector, detector_tick, "nat_detection"); |
|
if (!detector->timer) DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT detector timer scheduling failed"); |
|
} |
|
|
|
void nat_detection_cancel_for_conn(struct NAT_DETECTION *detector, struct ETCP_CONN *connection) { |
|
if (!detector || !connection || detector->stopping) return; |
|
for (struct nat_socket *state = detector->sockets; state; state = state->next) |
|
if (state->phase && (state->observer == connection->peer_node_id || state->helper == connection->peer_node_id)) |
|
owner_finish(state, NAT_REASON_CONTROL_LOST); |
|
struct nat_probe *probe = detector->probes; |
|
while (probe) { |
|
struct nat_probe *next = probe->next; |
|
if (probe->reply == connection->peer_node_id) helper_remove(probe); |
|
probe = next; |
|
} |
|
} |
|
|
|
static void connection_status(struct ETCP_CONN *connection, int status, void *arg) { |
|
struct NAT_DETECTION *detector = arg; |
|
if (status == ETCP_CONN_STATUS_DOWN || status == ETCP_CONN_STATUS_DELETE || status == ETCP_CONN_STATUS_REINIT) |
|
nat_detection_cancel_for_conn(detector, connection); |
|
else if (status == ETCP_CONN_STATUS_UP) { |
|
for (struct nat_socket *state = detector->sockets; state; state = state->next) |
|
if (!state->phase && (state->reason == NAT_REASON_NO_OBSERVER || state->reason == NAT_REASON_NO_HELPER)) |
|
state->due = get_time_tb() + 2500; |
|
} |
|
} |
|
|
|
void nat_detection_link_ready(struct NAT_DETECTION *detector, struct ETCP_LINK *link) { |
|
if (!detector || !link || !link->conn || link->conn->probe_only || detector->stopping) return; |
|
struct nat_socket *state = socket_state(detector, link->conn, 1); |
|
if (state && !state->phase && !state->completed_tb) state->due = get_time_tb() + 2500; |
|
} |
|
|
|
void nat_detection_observed_by_peer(struct NAT_DETECTION *detector, struct ETCP_LINK *link, |
|
const struct sockaddr_storage *observed) { |
|
if (!detector || !link || !link->conn || link->is_tcp || link->conn->socks_udp || !observed->ss_family) return; |
|
struct nat_socket *state = socket_state(detector, link->conn, 1); |
|
if (!state) return; |
|
if ((state->measured_profile.mapping == ADDRESS_MAP_UNCHANGED && |
|
!route_ping_addr_equal(&state->local, observed, 1)) || |
|
(link->conn->nat_addr.ss_family && !route_ping_addr_equal(&link->conn->nat_addr, observed, 1))) { |
|
if (state->phase) owner_finish(state, NAT_REASON_ADDRESS_CHANGED); |
|
clear_profile(state, NAT_REASON_ADDRESS_CHANGED); |
|
state->due = get_time_tb() + 2500; |
|
} |
|
} |
|
|
|
void nat_detection_socket_removed(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket) { |
|
if (!detector || !socket || detector->stopping) return; |
|
if (!socket->probe_only) { detector->publish_pending = 1; detector->publish_after = get_time_tb(); } |
|
struct nat_probe *probe = detector->probes; |
|
while (probe) { |
|
struct nat_probe *next = probe->next; |
|
if (probe->source_socket == socket->sock_id && probe->source_generation == socket->generation) helper_remove(probe); |
|
probe = next; |
|
} |
|
struct nat_socket **cursor = &detector->sockets; |
|
while (*cursor && (*cursor)->socket != socket) cursor = &(*cursor)->next; |
|
if (!*cursor) return; |
|
struct nat_socket *state = *cursor; *cursor = state->next; |
|
etcp_cancel_pings(detector->instance, owner_ping_cb, state); |
|
struct ETCP_SOCKET *auxiliary = state->auxiliary; |
|
DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT endpoint removed socket=%s generation=%016llx", socket->name, |
|
(unsigned long long)socket->generation); |
|
u_free(state); |
|
if (auxiliary) etcp_socket_remove(auxiliary); |
|
} |
|
|
|
static void socket_changed(struct ETCP_SOCKET *socket, int event, void *arg) { |
|
struct NAT_DETECTION *detector = arg; |
|
struct nat_socket *state = socket_state(detector, socket, 0); |
|
if (!state || !(event & ETCP_SOCKET_EVENT_ADDR_CHANGED)) return; |
|
clear_profile(state, NAT_REASON_ADDRESS_CHANGED); |
|
nat_detection_socket_removed(detector, socket); |
|
if (random_id(&socket->generation)) return; |
|
socket_state(detector, socket, 1); |
|
} |
|
|
|
struct NAT_DETECTION *nat_detection_create(struct UTUN_INSTANCE *instance) { |
|
if (!instance || instance->api_bindings.callbacks[ETCP_ID_NAT_DETECTION]) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT detector invalid instance/binding collision"); return NULL; |
|
} |
|
struct NAT_DETECTION *detector = u_calloc(1, sizeof(*detector)); |
|
if (!detector) { DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT detector allocation failed"); return NULL; } |
|
detector->instance = instance; detector->automatic = 1; |
|
if (etcp_bind(instance, ETCP_ID_NAT_DETECTION, receive_control)) { u_free(detector); return NULL; } |
|
etcp_add_conn_status_cbk(instance, connection_status, detector); |
|
etcp_add_socket_cbk(instance, socket_changed, detector, ETCP_SOCKET_EVENT_ADDR_CHANGED); |
|
detector->timer = uasync_set_timeout(instance->ua, NAT_TICK_TB, detector, detector_tick, "nat_detection"); |
|
if (!detector->timer) { |
|
etcp_remove_conn_status_cbk(instance, connection_status, detector); |
|
etcp_remove_socket_cbk(instance, socket_changed, detector); |
|
etcp_unbind(instance, ETCP_ID_NAT_DETECTION); u_free(detector); |
|
DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT detector initial timer failed"); return NULL; |
|
} |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT endpoint detector started: UDP mapping/inbound measured separately, TCP listener probes"); |
|
return detector; |
|
} |
|
|
|
void nat_detection_destroy(struct NAT_DETECTION *detector) { |
|
if (!detector) return; |
|
detector->stopping = 1; |
|
if (detector->timer) uasync_cancel_timeout(detector->instance->ua, detector->timer); |
|
etcp_remove_conn_status_cbk(detector->instance, connection_status, detector); |
|
etcp_remove_socket_cbk(detector->instance, socket_changed, detector); |
|
etcp_unbind(detector->instance, ETCP_ID_NAT_DETECTION); |
|
while (detector->probes) helper_remove(detector->probes); |
|
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 *auxiliary = state->auxiliary; u_free(state); |
|
if (auxiliary) etcp_socket_remove(auxiliary); |
|
} |
|
while (detector->bindings) { |
|
struct nat_binding *binding = detector->bindings; detector->bindings = binding->next; u_free(binding); |
|
} |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT detector stopped; all endpoint rounds and helper probes cancelled"); |
|
u_free(detector); |
|
} |
|
|
|
void nat_detection_request_check_all(struct NAT_DETECTION *detector) { |
|
if (!detector || detector->stopping) return; |
|
for (struct ETCP_SOCKET *socket = detector->instance->etcp_sockets; socket; socket = socket->next) { |
|
struct nat_socket *state = socket_state(detector, socket, 1); |
|
if (!state) continue; |
|
if (state->phase) owner_finish(state, NAT_REASON_CANCELLED); |
|
state->due = get_time_tb(); state->forced = 1; state->preferred_observer = 0; |
|
} |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT endpoint recheck requested"); |
|
} |
|
|
|
int nat_detection_request_check_socket(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, uint64_t observer) { |
|
struct nat_socket *state = socket_state(detector, socket, 1); |
|
if (!state || !observer || !live_connection(detector, observer)) { |
|
DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT endpoint recheck has no live observer"); return -1; |
|
} |
|
if (state->phase) owner_finish(state, NAT_REASON_CANCELLED); |
|
state->preferred_observer = observer; state->due = get_time_tb(); state->forced = 1; |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT endpoint recheck socket=%s observer=%016llx", socket->name, (unsigned long long)observer); |
|
return 0; |
|
} |
|
|
|
void nat_detection_set_allow_local(struct NAT_DETECTION *detector, int allow) { |
|
if (detector) detector->allow_local = !!allow; |
|
} |
|
|
|
void nat_detection_set_automatic(struct NAT_DETECTION *detector, int automatic) { |
|
if (!detector) return; |
|
detector->automatic = !!automatic; |
|
DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT automatic endpoint measurements %s", automatic ? "enabled" : "disabled"); |
|
} |
|
|
|
int nat_detection_get_status(struct NAT_DETECTION *detector, struct ETCP_SOCKET *socket, struct nat_check_status *status) { |
|
struct nat_socket *state = socket_state(detector, socket, 0); |
|
if (!state || !status) return -1; |
|
*status = (struct nat_check_status){.generation = socket->generation, .round = state->round, .observer = state->observer, |
|
.helper = state->helper, .rounds = state->rounds, .helper_reselections = state->helper_reselections, |
|
.phase = state->phase, .reason = state->reason, .history_complete = state->history_complete, |
|
.measured_profile = state->measured_profile, .effective_profile = socket->address_profile, .fallback_allowed = state->fallback_allowed}; |
|
return 0; |
|
}
|
|
|