Isolate NAT mappings by source node identity

This commit is contained in:
evgeny committed 2026-10-21 21:53:17 +02:00
1 parent a9820bea42
commit 3d0c4ff0a5
3 files changed
+41 -3

No files matched your search

+3 -3
View File
@@ -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);
+1
View File
@@ -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.
+37
View File
@@ -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);