From 3d0c4ff0a5b75dc07b18351d09359aafc8500479 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 21 Oct 2026 21:53:17 +0200 Subject: [PATCH] Isolate NAT mappings by source node identity --- src/eim_nat.c | 6 +++--- src/eim_nat.h | 1 + tests/test_nat_engine.c | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/eim_nat.c b/src/eim_nat.c index c0a475fb..c10dfec6 100644 --- a/src/eim_nat.c +++ b/src/eim_nat.c @@ -72,10 +72,10 @@ static void csum_update_icmp(uint8_t* icmp, uint16_t old_word, uint16_t new_word // Поиск в замкнутом диапазоне; индекс должен вмещать 65536 после последнего порта. static struct eim_nat_entry* eim_nat_find_egress(struct eim_nat_ctx* ctx, - uint8_t proto, uint32_t ip_host, uint16_t port_net) { + uint8_t proto, uint32_t ip_host, uint16_t port_net, uint64_t src_node_id) { for (uint32_t i = ctx->port_start; i <= ctx->port_end; i++) { struct eim_nat_entry* e = &ctx->table[i]; - if (e->state != EIM_NAT_ENTRY_FREE && e->proto == proto && + if (e->state != EIM_NAT_ENTRY_FREE && e->proto == proto && e->src_node_id == src_node_id && e->internal_ip == ip_host && e->internal_port == port_net) return e; } return NULL; @@ -131,7 +131,7 @@ int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len, } if (!is_tcp && !is_udp && !is_icmp_echo) return 0; - struct eim_nat_entry* entry = eim_nat_find_egress(ctx, proto, src_ip_host, src_port_net); + struct eim_nat_entry* entry = eim_nat_find_egress(ctx, proto, src_ip_host, src_port_net, src_node_id); uint16_t ext_port_host; if (entry) { ext_port_host = (uint16_t)(entry - ctx->table); diff --git a/src/eim_nat.h b/src/eim_nat.h index a058a1ad..83ac74f4 100644 --- a/src/eim_nat.h +++ b/src/eim_nat.h @@ -1,4 +1,5 @@ /* eim_nat — таблица Endpoint-Independent Mapping для IPv4 TCP/UDP и ICMP echo. + * Ключ mapping: (src_node_id, протокол, внутренний IP, внутренний порт/echo ID), без внешнего адресата. * init_ctx создаёт состояние из global_config; при отключённом NAT оставляет initialized=0. * egress/ingress меняют IP/порты и checksums на месте, не отправляют пакеты и не владеют TUN/ETCP. * IPv4 UDP: отключённая checksum=0 сохраняется; вычисленный ноль записывается как 0xffff. diff --git a/tests/test_nat_engine.c b/tests/test_nat_engine.c index 160ce947..e258a21e 100644 --- a/tests/test_nat_engine.c +++ b/tests/test_nat_engine.c @@ -937,6 +937,42 @@ static void test_udp_checksum_encoding(int computed_zero) { } } +/* Перекрывающиеся частные адреса разных узлов не должны делить mapping и адресата ответа. */ +static void test_node_mapping_isolation(void) { + TEST("mapping_isolated_by_source_node"); + struct global_config g = make_global_config(); + struct eim_nat_ctx ctx; + ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "node isolation context initialized"); + const size_t len = 28 + TEST_PAYLOAD_LEN; + for (uint64_t node = 1; node <= 2; node++) { + uint8_t* pkt = make_udp_pkt(TEST_IP_SRC_HOST, TEST_SRC_PORT, TEST_IP_DST_HOST, TEST_DST_PORT); + int r = eim_nat_egress(&ctx, pkt, len, node, NULL); + uint16_t port; memcpy(&port, pkt + 20, 2); + free(pkt); + ASSERT_EQ(r, 0, "node flow accepted"); + ASSERT_EQ(be16toh(port), TEST_PORT_START + node - 1, "each node has its own external port"); + } + for (uint64_t node = 1; node <= 2; node++) { + uint16_t external_port = TEST_PORT_START + node - 1; + uint8_t* pkt = make_udp_pkt(TEST_IP_DST_HOST, TEST_DST_PORT, TEST_GW_HOST, external_port); + struct eim_nat_entry* entry = NULL; + int r = eim_nat_ingress(&ctx, pkt, len, &entry); + uint16_t port; memcpy(&port, pkt + 22, 2); + free(pkt); + ASSERT_EQ(r, 0, "node response accepted"); + ASSERT(entry && entry->src_node_id == node, "response returns to its source node"); + ASSERT_EQ(be16toh(port), TEST_SRC_PORT, "node response restores private port"); + pkt = make_udp_pkt(TEST_IP_SRC_HOST, TEST_SRC_PORT, 0x08080404, TEST_DST_PORT); + r = eim_nat_egress(&ctx, pkt, len, node, NULL); + memcpy(&port, pkt + 20, 2); + free(pkt); + ASSERT_EQ(r, 0, "node flow reused for a different destination"); + ASSERT_EQ(be16toh(port), external_port, "node mapping remains endpoint independent"); + } + eim_nat_destroy_ctx(&ctx); + PASS(); +} + /* ================================================================ * Main * ================================================================ */ @@ -959,6 +995,7 @@ int main(void) { test_checksum_update(); test_udp_checksum_encoding(0); test_udp_checksum_encoding(1); + test_node_mapping_isolation(); printf("\n=== Results: %d run, %d passed, %d failed ===\n", stats.run, stats.passed, stats.failed);