From 95f209d098b6a3c18745733098cfd3503984efa8 Mon Sep 17 00:00:00 2001 From: evgeny Date: Sat, 17 Oct 2026 19:45:29 +0200 Subject: [PATCH] nat: determine TCP translation from one public IP observation --- src/routing_layer/nat_detection.c | 43 +++++++++++++++++++++++++++---- src/routing_layer/nat_detection.h | 10 ++++++- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/routing_layer/nat_detection.c b/src/routing_layer/nat_detection.c index 482aedb3..8ab56c95 100644 --- a/src/routing_layer/nat_detection.c +++ b/src/routing_layer/nat_detection.c @@ -580,6 +580,10 @@ static void select_profile(struct nat_socket *state) { const struct address_profile *current = &state->socket->address_profile; struct address_profile selected = state->measured_profile; struct sockaddr_storage selected_address = {0}; + if (state->socket->is_tcp) { + selected_address = state->observed_address; + if (!selected.source && !public_address(state->detector, &state->local)) selected.source = ADDRESS_SOURCE_ETCP; + } if (state->measured_profile.source == ADDRESS_SOURCE_ETCP) { selected = state->measured_profile; if (selected.mapping == ADDRESS_MAP_EIM) selected_address = state->measured_address; @@ -1286,6 +1290,11 @@ static void receive_control(struct ETCP_CONN *from, struct ll_entry *entry) { static void owner_start(struct nat_socket *state) { struct NAT_DETECTION *detector = state->detector; + if (state->socket->is_tcp) { + select_profile(state); state->due = UINT64_MAX; + state->reason = state->socket->address_profile.source ? NAT_REASON_NONE : NAT_REASON_NO_OBSERVER; + return; + } 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; @@ -1293,11 +1302,6 @@ static void owner_start(struct nat_socket *state) { 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 (!state->cycling) { state->cycling = 1; state->cursor = (struct nat_pair){0}; state->cycle_deadline = get_time_tb() + NAT_ROUND_TB; @@ -1604,6 +1608,35 @@ void nat_detection_link_ready(struct NAT_DETECTION *detector, struct ETCP_LINK * if (state && !state->phase && !state->completed_tb) state->due = get_time_tb() + 2500; } +/* Одно TCP-наблюдение сравнивает только IP; нулевой порт не обещает доступный listener. */ +void nat_detection_tcp_observed(struct NAT_DETECTION *detector, struct ETCP_LINK *link, + const struct sockaddr_storage *local, const struct sockaddr_storage *observed) { + if (!detector || detector->stopping || !link || !link->is_tcp || !link->conn || !local || !observed) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "TCP NAT observation has invalid socket/addresses"); return; + } + if (local->ss_family != observed->ss_family || !public_address(detector, observed) || + !public_address(detector, &link->remote_addr)) { + DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "TCP NAT observation ignored socket=%s local=%s observed=%s peer=%s reason=non_public_or_family", + link->conn->name, sockaddr_storage_to_str(local).str, sockaddr_storage_to_str(observed).str, + sockaddr_storage_to_str(&link->remote_addr).str); return; + } + struct nat_socket *state = socket_state(detector, link->conn, 1); + if (!state) return; + uint8_t mapping = public_address(detector, local) && route_ping_addr_equal(local, observed, 0) ? + ADDRESS_MAP_UNCHANGED : ADDRESS_MAP_UNKNOWN; + struct sockaddr_storage address = *observed; + if (address.ss_family == AF_INET) ((struct sockaddr_in *)&address)->sin_port = 0; + 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}; + 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", + link->conn->name, sockaddr_storage_to_str(local).str, sockaddr_storage_to_str(&address).str, + mapping == ADDRESS_MAP_UNCHANGED ? "no" : "yes"); + select_profile(state); +} + 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; diff --git a/src/routing_layer/nat_detection.h b/src/routing_layer/nat_detection.h index 86091cb3..9e7b78b7 100644 --- a/src/routing_layer/nat_detection.h +++ b/src/routing_layer/nat_detection.h @@ -6,7 +6,9 @@ * UNKNOWN в любом свойстве означает, что данных для его определения пока недостаточно. * Одно соединение может подтвердить NAT-адрес: он сохраняется в socket->nat_addr с типом UNKNOWN * и публикуется как NAT-кандидат. Это не адрес интерфейса и не подтверждение открытого входа. - * Для UDP сочетания EIM+OPEN и EIM+CONTACT означают eim open и eim filtered. TCP-вход проверяется отдельно. + * Для UDP сочетания EIM+OPEN и EIM+CONTACT означают eim open и eim filtered. + * Для TCP непубличный локальный IP сразу означает NAT; публичный IP сравнивается с наблюдением одного пира. + * TCP не определяет тип NAT/входящие: сохраняет внешний IP с портом 0, непригодным для подключения. * EIM+UNKNOWN отображается как EIM?: внешний адрес совпал, незнакомый вход не удалось проверить. * Если своего измерения mapping нет, совпадение у двух STUN-серверов на том же IP/интерфейсе даёт предварительный EIM?. * Его source=STUN, inbound=UNKNOWN; внешний порт берётся только из наблюдения рабочего сокета, а не из STUN. @@ -115,6 +117,12 @@ void nat_detection_note_send(struct NAT_DETECTION *detector, struct ETCP_SOCKET void nat_detection_observed_by_peer(struct NAT_DETECTION *detector, struct ETCP_LINK *link, const struct sockaddr_storage *observed); +/* Учесть IP, сообщённый по защищённому TCP link: local — фактический локальный адрес соединения, + * observed — адрес, увиденный пиром. Публичные IP сравниваются без портов; сохраняемый внешний порт всегда 0. + * Непубличный источник/наблюдение и другая семья адресов не меняют результат; вызов — в потоке ядра. */ +void nat_detection_tcp_observed(struct NAT_DETECTION *detector, struct ETCP_LINK *link, + const struct sockaddr_storage *local, const struct sockaddr_storage *observed); + /* Обработать запрос наблюдения, полученный на socket: source — реальный IP:порт отправителя, * peer_key — его проверенный ключ (32 байта), tag/length — тег попытки/его размер в байтах. * Непубличный source отклоняется; результат возвращается через защищённый управляющий канал. */