From dea768c21a48cc97ac7b16fef1853d569225ea81 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 21 Oct 2026 22:52:19 +0200 Subject: [PATCH] Hold outgoing NAT mappings through asynchronous TUN delivery --- src/nat_transport.c | 24 +++++++++++++++++++++++- tests/test_nat_engine.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/nat_transport.c b/src/nat_transport.c index 5f804a44..757d4e46 100644 --- a/src/nat_transport.c +++ b/src/nat_transport.c @@ -178,11 +178,32 @@ static void nat_tun_flush(struct UTUN_INSTANCE* inst) { struct ll_entry* entry = tr->tun_pending; if (!entry) return; size_t len = entry->len - ROUTER_SVC_PAYLOAD_OFF; - if (tun_write(tr->nat_tun, entry->dgram + ROUTER_SVC_PAYLOAD_OFF, len) < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + const uint8_t* ip = entry->dgram + ROUTER_SVC_PAYLOAD_OFF; + ssize_t written = tun_write(tr->nat_tun, ip, len); + if (written < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) { + if (!tr->nat_via_node_id && !tr->tun_pending_port) { + uint16_t port; + memcpy(&port, ip + (ip[0] & 15) * 4 + (ip[9] == IPPROTO_ICMP_UINT8 ? 4 : 0), 2); + port = ntohs(port); + if (eim_nat_hold(&inst->nat, port)) { + nat_drop(inst, entry, 0, "cannot hold outgoing mapping"); tr->tun_pending = NULL; + return; + } + tr->tun_pending_port = port; + DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT TUN blocked: held mapping external=%u", port); + } if (!tr->tun_retry_timer) tr->tun_retry_timer = uasync_set_timeout(inst->ua, NAT_WRITE_RETRY_TB, inst, nat_tun_retry, "nat_tun_retry"); if (!tr->tun_retry_timer) DEBUG_ERROR(DEBUG_CATEGORY_NAT, "NAT TUN retry allocation failed; maintenance will retry"); return; } + if (tr->tun_pending_port) { + eim_nat_release(&inst->nat, tr->tun_pending_port, written >= 0); + tr->tun_pending_port = 0; + } + if (tr->tun_retry_timer) { + uasync_cancel_timeout(inst->ua, tr->tun_retry_timer); + tr->tun_retry_timer = NULL; + } nat_packet_free(entry); tr->tun_pending = NULL; if (inst->router_conns) { for (struct ll_entry* e = inst->router_conns->head; e; e = e->next) { @@ -325,6 +346,7 @@ void nat_transport_destroy(struct UTUN_INSTANCE* inst) { if (!route) break; etcp_router_conn_close(route); } + if (tr->tun_pending_port) eim_nat_release(&inst->nat, tr->tun_pending_port, 0); nat_packet_free(tr->tun_pending); tun_close(tr->nat_tun); eim_nat_destroy_ctx(&inst->nat); diff --git a/tests/test_nat_engine.c b/tests/test_nat_engine.c index 98d2455d..da564b00 100644 --- a/tests/test_nat_engine.c +++ b/tests/test_nat_engine.c @@ -1220,6 +1220,38 @@ static void test_mapping_expiry(void) { #endif } +/* Каждый успешный пакет обновляет idle-time; удержанный внешний порт не может перейти другому владельцу. */ +static void test_refresh_and_holds(void) { + TEST("idle_refresh_and_pending_mapping_hold"); + struct global_config g = make_global_config(); struct eim_nat_ctx ctx; + ASSERT_EQ(eim_nat_init_ctx(&ctx, &g), 0, "hold context initialized"); + uint8_t* packet = make_udp_pkt(TEST_IP_SRC_HOST, TEST_SRC_PORT, TEST_IP_DST_HOST, TEST_DST_PORT); + ASSERT_EQ(eim_nat_egress(&ctx, packet, 42, 1, NULL), 0, "create held mapping"); + free(packet); + uint16_t external = TEST_PORT_START; + struct eim_nat_entry* entry = &ctx.table[external]; + entry->last_seen = 0; // Управляем только возрастом; ключ/индексы остаются неизменными. + packet = make_udp_pkt(TEST_IP_SRC_HOST, TEST_SRC_PORT, TEST_IP_DST_HOST, TEST_DST_PORT); + ASSERT_EQ(eim_nat_egress(&ctx, packet, 42, 1, NULL), 0, "refresh outbound mapping"); free(packet); + ASSERT(entry->last_seen > 0, "outbound refreshes last_seen"); + entry->last_seen = 0; + packet = make_udp_pkt(TEST_IP_DST_HOST, TEST_DST_PORT, TEST_GW_HOST, external); + struct eim_nat_entry* incoming = NULL; + ASSERT_EQ(eim_nat_ingress(&ctx, packet, 42, &incoming), 0, "refresh inbound mapping"); free(packet); + ASSERT(incoming == entry && entry->last_seen > 0, "inbound refreshes last_seen"); + ASSERT_EQ(eim_nat_hold(&ctx, external), 0, "hold external port"); + ASSERT_EQ(eim_nat_expire(&ctx, entry->last_seen + EIM_NAT_UDP_IDLE_TB, 65535), 0, "pending transmission prevents expiry"); + ASSERT_EQ(eim_nat_remove(&ctx, external), -1, "pending transmission prevents explicit reuse"); + entry->last_seen = 0; + ASSERT_EQ(eim_nat_release(&ctx, external, 1), 0, "successful transmission releases hold"); + ASSERT(entry->last_seen > 0 && !entry->holds, "release restarts idle timer at actual send"); + ASSERT_EQ(eim_nat_expire(&ctx, entry->last_seen + EIM_NAT_UDP_IDLE_TB, 65535), 1, "released mapping expires"); + ASSERT_EQ(eim_nat_release(&ctx, external, 0), -1, "unpaired release rejected"); + ASSERT(check_indices(&ctx), "holds preserve indices and free count"); + eim_nat_destroy_ctx(&ctx); + PASS(); +} + /* ================================================================ * Main * ================================================================ */ @@ -1247,6 +1279,7 @@ int main(void) { test_packet_boundaries(); test_hash_lifecycle(); test_mapping_expiry(); + test_refresh_and_holds(); printf("\n=== Results: %d run, %d passed, %d failed ===\n", stats.run, stats.passed, stats.failed);