Hold outgoing NAT mappings through asynchronous TUN delivery

This commit is contained in:
evgeny committed 2026-10-21 22:52:19 +02:00
1 parent 824c663ebd
commit dea768c21a
2 files changed
+56 -1

No files matched your search

+23 -1
View File
@@ -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);
+33
View File
@@ -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);