feat(nat): confirm direct open profile from accepted public connections

This commit is contained in:
evgeny committed 2026-10-18 00:41:53 +02:00
1 parent 37e092672a
commit df2f6ff300
3 files changed
+47 -7

No files matched your search

+39 -4
View File
@@ -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);
}
+7 -3
View File
@@ -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; запланировать пересмотр маршрутов и отмену зависимых проверок при их потере. */