From d08b14a8c12555617c172eb7e8c8b799e3a4b173 Mon Sep 17 00:00:00 2001 From: Evgeny Date: Sat, 14 Feb 2026 11:07:00 +0300 Subject: [PATCH] ETCP: Add NAT IP:port tracking to handshake protocol - Add peer_ipv4 (4 bytes) and peer_port (2 bytes) to INIT_RESPONSE packet - Server now returns client's external IP:port in handshake response - Client parses and stores NAT address in ETCP_LINK structure - Track NAT address changes with nat_changes_count counter - Track NAT address matches with nat_hits_count counter - Legacy protocol support: detect old format without NAT info - Add DEBUG logging for NAT address initialization/changes/hits This allows clients behind NAT to discover their external address during the ETCP handshake process. All 21 tests pass successfully. --- src/etcp_connections.c | 62 ++++++++++++++++++++++++++++++++++++++++++ src/etcp_connections.h | 6 ++++ 2 files changed, 68 insertions(+) diff --git a/src/etcp_connections.c b/src/etcp_connections.c index 303ebcbe..5c07c83a 100644 --- a/src/etcp_connections.c +++ b/src/etcp_connections.c @@ -655,6 +655,8 @@ static void etcp_connections_read_callback(int fd, void* arg) { uint8_t id[8]; uint8_t mtu[2]; uint8_t link_id; + uint8_t peer_ipv4[4]; + uint8_t peer_port[2]; } *ack_repl_hdr=(void*)&pkt->data[0]; ack_repl_hdr->code+=1; memcpy(ack_repl_hdr->id, &e_sock->instance->node_id, 8); @@ -662,6 +664,18 @@ static void etcp_connections_read_callback(int fd, void* arg) { ack_repl_hdr->mtu[0]=mtu>>8; ack_repl_hdr->mtu[1]=mtu; ack_repl_hdr->link_id = link->local_link_id; + // Add client's IP:port (so client behind NAT can know its external address) + if (addr.ss_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in*)&addr; + memcpy(ack_repl_hdr->peer_ipv4, &sin->sin_addr.s_addr, 4); + uint16_t port = ntohs(sin->sin_port); + ack_repl_hdr->peer_port[0] = port >> 8; + ack_repl_hdr->peer_port[1] = port & 0xFF; + } else { + // For IPv6, set to 0 (not supported for NAT traversal) + memset(ack_repl_hdr->peer_ipv4, 0, 4); + memset(ack_repl_hdr->peer_port, 0, 2); + } pkt->data_len=sizeof(*ack_repl_hdr); pkt->noencrypt_len=0; pkt->link=link; @@ -707,6 +721,54 @@ static void etcp_connections_read_callback(int fd, void* arg) { } link->mtu = (pkt->data[offset++] << 8) | pkt->data[offset++]; link->remote_link_id = pkt->data[offset++]; + + // Parse NAT IP:port from response (new format includes 4+2 bytes) + if (pkt_len >= 18) { + uint32_t new_nat_ip = (pkt->data[offset] << 24) | (pkt->data[offset+1] << 16) | + (pkt->data[offset+2] << 8) | pkt->data[offset+3]; + offset += 4; + uint16_t new_nat_port = (pkt->data[offset] << 8) | pkt->data[offset+1]; + offset += 2; + + // Check if NAT address changed + if (link->nat_ip == 0 && link->nat_port == 0) { + // First time receiving NAT info + link->nat_ip = new_nat_ip; + link->nat_port = new_nat_port; + char ip_str[INET_ADDRSTRLEN]; + struct in_addr addr; + addr.s_addr = htonl(new_nat_ip); + inet_ntop(AF_INET, &addr, ip_str, sizeof(ip_str)); + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address initialized: %s:%u", + link->etcp->log_name, ip_str, new_nat_port); + } else if (link->nat_ip != new_nat_ip || link->nat_port != new_nat_port) { + // NAT address changed + char old_ip_str[INET_ADDRSTRLEN], new_ip_str[INET_ADDRSTRLEN]; + struct in_addr old_addr, new_addr; + old_addr.s_addr = htonl(link->nat_ip); + new_addr.s_addr = htonl(new_nat_ip); + inet_ntop(AF_INET, &old_addr, old_ip_str, sizeof(old_ip_str)); + inet_ntop(AF_INET, &new_addr, new_ip_str, sizeof(new_ip_str)); + + link->nat_ip = new_nat_ip; + link->nat_port = new_nat_port; + link->nat_changes_count++; + + DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address changed: %s:%u -> %s:%u (change #%u)", + link->etcp->log_name, old_ip_str, link->nat_port, new_ip_str, new_nat_port, + link->nat_changes_count); + } else { + // NAT address unchanged + link->nat_hits_count++; + DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] NAT address unchanged (%u matches)", + link->etcp->log_name, link->nat_hits_count); + } + } else { + // Legacy format without NAT info + DEBUG_DEBUG(DEBUG_CATEGORY_CONNECTION, "[%s] Received legacy INIT_RESPONSE without NAT info", + link->etcp->log_name); + } + if (offset > pkt_len) { errorcode=13; DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "etcp_connections_read_callback: packet parsing overflow, offset=%zu, pkt_len=%zu", offset, pkt_len); goto ec_fr; } // DEBUG_INFO(DEBUG_CATEGORY_CONNECTION, "Received INIT_RESPONSE from server_node_id=%llu, mtu=%d", (unsigned long long)server_node_id, link->mtu); diff --git a/src/etcp_connections.h b/src/etcp_connections.h index e13cd66e..458eaf9f 100644 --- a/src/etcp_connections.h +++ b/src/etcp_connections.h @@ -84,6 +84,12 @@ struct ETCP_LINK { size_t total_decrypted; uint32_t bandwidth; // Link bandwidth in Kbits/sec + + // NAT address tracking (from INIT_RESPONSE) + uint32_t nat_ip; // NAT IPv4 address (network byte order), 0 = not set + uint16_t nat_port; // NAT port (network byte order) + uint32_t nat_changes_count; // Counter of NAT address changes + uint32_t nat_hits_count; // Counter of NAT address matches (new init response with same IP:port) }; // INITIALIZATION (создаёт listen-сокеты и подключения из конфига)