diff --git a/src/eim_nat.c b/src/eim_nat.c index 99adde0c..a75142ff 100644 --- a/src/eim_nat.c +++ b/src/eim_nat.c @@ -388,6 +388,10 @@ int eim_nat_remove(struct eim_nat_ctx* ctx, uint16_t external_port) { } struct eim_nat_entry* e = &ctx->table[external_port]; if (e->state == EIM_NAT_ENTRY_FREE) return 0; + if (e->holds) { + DEBUG_WARN(DEBUG_CATEGORY_NAT, "NAT mapping is held by pending transmission: port=%u holds=%u", external_port, e->holds); + return -1; + } uint16_t* link = &ctx->buckets[eim_nat_bucket(e->proto, e->internal_ip, e->internal_port, e->src_node_id)]; while (*link && *link != external_port) link = &ctx->table[*link].hash_next; if (!*link) { @@ -415,7 +419,7 @@ size_t eim_nat_expire(struct eim_nat_ctx* ctx, uint64_t now_tb, size_t budget) { uint32_t port = ctx->gc_cursor++; if (ctx->gc_cursor == EIM_NAT_TABLE_SIZE) ctx->gc_cursor = 1; struct eim_nat_entry* e = &ctx->table[port]; - if (e->state != EIM_NAT_ENTRY_ACTIVE) continue; + if (e->state != EIM_NAT_ENTRY_ACTIVE || e->holds) continue; uint64_t timeout = e->proto == IPPROTO_TCP_UINT8 ? EIM_NAT_TCP_IDLE_TB : e->proto == IPPROTO_UDP_UINT8 ? EIM_NAT_UDP_IDLE_TB : EIM_NAT_ICMP_IDLE_TB; if (now_tb >= e->last_seen && now_tb - e->last_seen >= timeout && eim_nat_remove(ctx, port) == 1) removed++; @@ -423,3 +427,26 @@ size_t eim_nat_expire(struct eim_nat_ctx* ctx, uint64_t now_tb, size_t budget) { if (removed) DEBUG_DEBUG(DEBUG_CATEGORY_NAT, "NAT expired mappings=%zu free=%u", removed, ctx->free_count); return removed; } + +// Зафиксировать внешний порт, уже записанный в IP-пакет, до завершения асинхронной передачи. +int eim_nat_hold(struct eim_nat_ctx* ctx, uint16_t external_port) { + if (!ctx || !ctx->initialized || !ctx->table || !external_port || + ctx->table[external_port].state == EIM_NAT_ENTRY_FREE || ctx->table[external_port].holds == UINT16_MAX) { + DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Invalid NAT hold: ctx=%p port=%u", (void*)ctx, external_port); + return -1; + } + ctx->table[external_port].holds++; + return 0; +} + +// Освободить удержание; время простоя после отправки отсчитывается от фактической записи в TUN. +int eim_nat_release(struct eim_nat_ctx* ctx, uint16_t external_port, int refresh) { + if (!ctx || !ctx->initialized || !ctx->table || !external_port || !ctx->table[external_port].holds) { + DEBUG_ERROR(DEBUG_CATEGORY_NAT, "Invalid NAT release: ctx=%p port=%u", (void*)ctx, external_port); + return -1; + } + struct eim_nat_entry* entry = &ctx->table[external_port]; + entry->holds--; + if (refresh) entry->last_seen = get_time_tb(); + return 0; +} diff --git a/src/eim_nat.h b/src/eim_nat.h index 38f4fb07..dd5e3852 100644 --- a/src/eim_nat.h +++ b/src/eim_nat.h @@ -47,6 +47,7 @@ struct eim_nat_entry { struct ETCP_CONN* src_conn; // Заимствованная метка исходного conn, может быть NULL; модуль её не разыменовывает. uint16_t hash_next; // Внутренняя цепочка корзины; 0 — конец. Ключи и состояние извне не менять. uint16_t free_prev, free_next; // Внутренний список свободных динамических портов; 0 — конец. + uint16_t holds; // Временные удержания уже преобразованных исходящих пакетов до записи в TUN. }; // Состояние преобразования; TUN и транспортные ресурсы принадлежат nat_transport. @@ -107,6 +108,11 @@ int eim_nat_remove(struct eim_nat_ctx* ctx, uint16_t external_port); * Возврат — число удалённых ACTIVE-записей; STATIC не истекают. Очистка не выполняется из обработки пакета. * budget ограничивается 65535. NULL/неинициализированный ctx — 0. */ size_t eim_nat_expire(struct eim_nat_ctx* ctx, uint64_t now_tb, size_t budget); +/* Удержать существующий mapping на время асинхронной отправки; удаление/expiry запрещены до release. + * release с refresh != 0 обновляет last_seen при фактической отправке; при отбрасывании refresh=0. + * 0 — успех, -1 — неверная/свободная запись, переполнение holds или непарный release. */ +int eim_nat_hold(struct eim_nat_ctx* ctx, uint16_t external_port); +int eim_nat_release(struct eim_nat_ctx* ctx, uint16_t external_port, int refresh); #ifdef __cplusplus diff --git a/src/nat_transport.h b/src/nat_transport.h index c8e571a6..9ed1bc05 100644 --- a/src/nat_transport.h +++ b/src/nat_transport.h @@ -34,6 +34,7 @@ struct nat_transport_ctx { uint64_t blocked_destination; // Зафиксирован до ожидания/истечения mapping. size_t pending_count; // Пакеты в FIFO получателей и их активных ожиданиях, <= NAT_PENDING_TOTAL. struct ll_entry* tun_pending; // Единственный принятый IP-пакет, ожидающий доступности TUN для записи. + uint16_t tun_pending_port; // Удержанный mapping провайдера при EAGAIN; 0 у клиента/без удержания. void* maintenance_timer; // Порционная очистка mapping, восстановление ожиданий и уборка получателей. void* tun_retry_timer; // Повтор записи TUN при EAGAIN; отменяется до tun_close. uint64_t dropped, last_drop_log; // Счётчик отбрасываний и ограничение частоты диагностики перегрузки.