From df2f6ff30098a72562212fab74745d615bbb60cd Mon Sep 17 00:00:00 2001 From: evgeny Date: Sun, 18 Oct 2026 00:41:53 +0200 Subject: [PATCH] feat(nat): confirm direct open profile from accepted public connections --- src/routing_layer/nat_detection.c | 43 +++++++++++++++++++++++--- src/routing_layer/nat_detection.h | 10 ++++-- src/transport_layer/etcp_connections.c | 1 + 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/routing_layer/nat_detection.c b/src/routing_layer/nat_detection.c index 5c2c6002..b9d709e3 100644 --- a/src/routing_layer/nat_detection.c +++ b/src/routing_layer/nat_detection.c @@ -11,6 +11,7 @@ #include "etcp_api.h" #include "etcp.h" #include "etcp_connections.h" +#include "stcp_link.h" #include "route_ping.h" #include "topo_node.h" #include "topo_group.h" @@ -1586,7 +1587,38 @@ static void connection_status(struct ETCP_CONN *connection, int status, void *ar 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; + if (!state) return; + if (!state->phase && !state->completed_tb) state->due = get_time_tb() + 2500; + if (!link->is_server || !link->initialized || link->conn->socks_udp || + link->conn->type == CFG_SERVER_TYPE_PRIVATE || link->conn->type == CFG_SERVER_TYPE_LOCAL) return; + const struct sockaddr_storage *local = link->is_tcp ? stcp_link_get_local_addr(link->tcp_link) : &state->local; + if (!local) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT incoming connection cannot read local address socket=%s", link->conn->name); return; + } + if (local->ss_family != link->remote_addr.ss_family || !public_address(detector, local) || + !public_address(detector, &link->remote_addr) || !route_ping_addr_equal(local, &state->local, 0)) { + DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT incoming connection ignored socket=%s protocol=%s local=%s peer=%s interface=%s" + " reason=non_public_or_other_interface", link->conn->name, link->is_tcp ? "TCP" : "UDP", + sockaddr_storage_to_str(local).str, sockaddr_storage_to_str(&link->remote_addr).str, + sockaddr_storage_to_str(&state->local).str); return; + } + if (state->measured_profile.mapping == ADDRESS_MAP_UNCHANGED && state->measured_profile.inbound == ADDRESS_IN_OPEN && + !state->phase && !state->cycling && !state->forced) return; + state->measured_profile = (struct address_profile){ADDRESS_MAP_UNCHANGED, ADDRESS_IN_OPEN, ADDRESS_SOURCE_ETCP}; + state->measured_address = state->local; + memset(&state->observed_address, 0, sizeof(state->observed_address)); + if (link->is_tcp) { + state->observed_address = *local; + if (local->ss_family == AF_INET) ((struct sockaddr_in *)&state->observed_address)->sin_port = 0; + else ((struct sockaddr_in6 *)&state->observed_address)->sin6_port = 0; + } + state->completed_tb = get_time_tb(); state->forced = 0; + DEBUG_INFO(DEBUG_CATEGORY_NAT, "NAT incoming connection confirms DIRECT/OPEN socket=%s id=%u generation=%016llx" + " protocol=%s local=%s peer=%s evidence=accepted_connection", link->conn->name, link->conn->sock_id, + (unsigned long long)link->conn->generation, link->is_tcp ? "TCP" : "UDP", + sockaddr_storage_to_str(local).str, sockaddr_storage_to_str(&link->remote_addr).str); + owner_finish(state, NAT_REASON_NONE); + if (link->is_tcp) state->due = UINT64_MAX; } /* Одно TCP-наблюдение сравнивает только IP; нулевой порт не обещает доступный listener. */ @@ -1610,11 +1642,14 @@ void nat_detection_tcp_observed(struct NAT_DETECTION *detector, struct ETCP_LINK else ((struct sockaddr_in6 *)&address)->sin6_port = 0; if (state->measured_profile.source == ADDRESS_SOURCE_ETCP && state->measured_profile.mapping == mapping && route_ping_addr_equal(&state->observed_address, &address, 1)) return; - state->measured_profile = (struct address_profile){.mapping = mapping, .source = ADDRESS_SOURCE_ETCP}; + uint8_t inbound = mapping == ADDRESS_MAP_UNCHANGED && state->measured_profile.mapping == mapping && + route_ping_addr_equal(&state->observed_address, &address, 0) ? + state->measured_profile.inbound : ADDRESS_IN_UNKNOWN; + state->measured_profile = (struct address_profile){.mapping = mapping, .inbound = inbound, .source = ADDRESS_SOURCE_ETCP}; state->observed_address = address; state->completed_tb = get_time_tb(); state->due = UINT64_MAX; state->reason = NAT_REASON_NONE; - DEBUG_INFO(DEBUG_CATEGORY_NAT, "TCP NAT presence socket=%s local=%s observed=%s NAT=%s type=UNKNOWN inbound=UNKNOWN", + DEBUG_INFO(DEBUG_CATEGORY_NAT, "TCP NAT presence socket=%s local=%s observed=%s NAT=%s type=UNKNOWN inbound=%s", link->conn->name, sockaddr_storage_to_str(local).str, sockaddr_storage_to_str(&address).str, - mapping == ADDRESS_MAP_UNCHANGED ? "no" : "yes"); + mapping == ADDRESS_MAP_UNCHANGED ? "no" : "yes", address_inbound_name(inbound)); select_profile(state); } diff --git a/src/routing_layer/nat_detection.h b/src/routing_layer/nat_detection.h index 0f6e89d7..3e51e073 100644 --- a/src/routing_layer/nat_detection.h +++ b/src/routing_layer/nat_detection.h @@ -2,13 +2,15 @@ * Результат — address_profile с двумя независимыми свойствами: * mapping: UNCHANGED — адрес не меняется; EIM — внешний IP:порт одинаков для разных IP назначения; * DEPENDENT (strict) — внешний IP:порт зависит от IP назначения. - * inbound: OPEN — пакеты от незнакомого IP проходят; CONTACT — проходят после исходящего контакта (оценка filtered). + * inbound: OPEN — подтверждён вход; CONTACT — пакеты проходят после исходящего контакта (оценка filtered). + * Принятое UDP/TCP-подключение с публичными локальным и удалённым IP сразу даёт UNCHANGED+OPEN (direct/open). + * Для этого правила прежний исходящий контакт с IP пира не препятствие; LAN и SOCKS его не подтверждают. * UNKNOWN в любом свойстве означает, что данных для его определения пока недостаточно. * Одно соединение может подтвердить NAT-адрес: он сохраняется в socket->nat_addr с типом UNKNOWN * и публикуется как NAT-кандидат. Это не адрес интерфейса и не подтверждение открытого входа. * Для UDP сочетания EIM+OPEN и EIM+CONTACT означают eim open и eim filtered. * Для TCP непубличный локальный IP сразу означает NAT; публичный IP сравнивается с наблюдением одного пира. - * TCP не определяет тип NAT/входящие: сохраняет внешний IP с портом 0, непригодным для подключения. + * TCP не запускает активных проб входа: OPEN даёт только принятое подключение; внешний IP сохраняется с портом 0. * EIM+UNKNOWN отображается как EIM?: внешний адрес совпал, незнакомый вход не удалось проверить. * Если своего измерения mapping нет, совпадение у двух STUN-серверов на том же IP/интерфейсе даёт предварительный EIM?. * Его source=STUN, inbound=UNKNOWN; внешний порт берётся только из наблюдения рабочего сокета, а не из STUN. @@ -95,7 +97,9 @@ int nat_detection_ping_allowed(struct NAT_DETECTION *detector, struct ETCP_SOCKE const uint8_t peer_key[32], const struct sockaddr_storage *source, uint8_t flags, const uint8_t *tag, size_t length); -/* Сообщить о готовности link; при отсутствии результата запланировать измерение его локального сокета. */ +/* Сообщить о готовности link после UDP INIT или STCP handshake. Принятый вход с двумя публичными IP + * подтверждает direct/open у принимающего сокета; иначе при отсутствии результата планируется измерение. + * Для TCP фактический локальный IP должен совпадать с IP интерфейса этого сокета. */ void nat_detection_link_ready(struct NAT_DETECTION *detector, struct ETCP_LINK *link); /* Сообщить об утрате/перезапуске connection; запланировать пересмотр маршрутов и отмену зависимых проверок при их потере. */ diff --git a/src/transport_layer/etcp_connections.c b/src/transport_layer/etcp_connections.c index 0d9065d8..d04d9820 100644 --- a/src/transport_layer/etcp_connections.c +++ b/src/transport_layer/etcp_connections.c @@ -1320,6 +1320,7 @@ void etcp_link_enter_ready_tcp(struct ETCP_LINK *link) { DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[%s] TCP link %d UP (mtu=%d init=%d up=%d tcp_links=%d)", etcp->log_name, link->local_link_id, link->mtu, etcp->initialized, etcp->links_up, etcp->tcp_link_count); + if (etcp->instance->nat_det) nat_detection_link_ready(etcp->instance->nat_det, link); tcp_send_observed_ip(link); }