Protect NAT mappings referenced by pending TUN transmissions

This commit is contained in:
evgeny committed 2026-10-21 22:51:25 +02:00
1 parent 80b481827c
commit 824c663ebd
3 files changed
+35 -1

No files matched your search

+28 -1
View File
@@ -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;
}
+6
View File
@@ -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
+1
View File
@@ -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; // Счётчик отбрасываний и ограничение частоты диагностики перегрузки.