Document NAT hash, expiry, mapping holds and bounded transport ownership
This commit is contained in:
1 parent
2c10272e15
commit
7de54ab3e4
3 files changed
+55
-18
No files matched your search
+5
-1
@@ -1,11 +1,14 @@
|
||||
/* eim_nat — таблица Endpoint-Independent Mapping для IPv4 TCP/UDP и ICMP echo.
|
||||
* Ключ mapping: (src_node_id, протокол, внутренний IP, внутренний порт/echo ID), без внешнего адресата.
|
||||
* Исходящий поиск — хеш, в среднем O(1); входящий — table[port], O(1); свободные порты — двусвязный FIFO, O(1).
|
||||
* Таблица и 65536 uint16_t-корзин выделяются только при init; обработка/создание mapping не выделяют память.
|
||||
* init_ctx создаёт состояние из global_config; при отключённом NAT оставляет initialized=0.
|
||||
* egress/ingress меняют IP/порты и checksums на месте, не отправляют пакеты и не владеют TUN/ETCP.
|
||||
* IPv4 UDP: отключённая checksum=0 сохраняется; вычисленный ноль записывается как 0xffff.
|
||||
* Перед обработкой нужен инициализированный ctx; вызовы последовательны в uasync-потоке.
|
||||
* gateway/internal IP — host order, internal_port — network order, внешний порт — host order.
|
||||
* Динамические записи истекают по idle-timeout; владелец регулярно вызывает expire с ограниченным budget.
|
||||
* Исходящие/входящие пакеты обновляют last_seen. STATIC и временно удержанные записи не истекают.
|
||||
* Фрагменты и неподдерживаемые протоколы возвращаются без изменений с EIM_NAT_SKIP.
|
||||
* src_conn только сохраняется при создании динамической записи, для отправки не используется. */
|
||||
#ifndef EIM_NAT_H
|
||||
@@ -88,6 +91,7 @@ int eim_nat_egress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
|
||||
uint64_t src_node_id, struct ETCP_CONN* src_conn);
|
||||
/* Для пакета на gateway_ip найти запись по внешнему порту/echo ID и протоколу, восстановить destination.
|
||||
* ctx/ip_data/checksum — как у egress. out_entry необязателен, перед обработкой обнуляется.
|
||||
* Указатель заимствован до remove/expiry/destroy; для ожидания сохранить node_id/преобразованный пакет или вызвать hold.
|
||||
* При EIM_NAT_OK получает заимствованную запись; иначе остаётся NULL. Возврат EIM_NAT_OK / SKIP / ERROR. */
|
||||
int eim_nat_ingress(struct eim_nat_ctx* ctx, uint8_t* ip_data, size_t ip_len,
|
||||
struct eim_nat_entry** out_entry);
|
||||
@@ -101,7 +105,7 @@ int eim_nat_add_forward(struct eim_nat_ctx* ctx, uint8_t proto,
|
||||
uint16_t external_port, uint64_t node_id);
|
||||
|
||||
/* Удалить запись по внешнему порту из всех индексов; порт из динамического диапазона вернуть в пул.
|
||||
* 1 — удалена, 0 — уже свободна, -1 — неверный ctx/порт или нарушен индекс. Включая STATIC-записи.
|
||||
* 1 — удалена, 0 — уже свободна, -1 — неверный ctx/порт, удержанная запись или нарушен индекс. Включая STATIC-записи.
|
||||
* Все ранее полученные указатели на эту запись теряют смысл: слот может быть переиспользован. */
|
||||
int eim_nat_remove(struct eim_nat_ctx* ctx, uint16_t external_port);
|
||||
/* Проверить не более budget слотов начиная с gc_cursor; now_tb — get_time_tb(), не календарное время.
|
||||
|
||||
+41
-16
@@ -9,29 +9,43 @@
|
||||
## 2. Как пользоваться
|
||||
```c
|
||||
struct eim_nat_ctx nat;
|
||||
if (eim_nat_init_ctx(&nat, &global_cfg) != 0) { /* ошибка — см. лог */ }
|
||||
// Обработка допустима только при nat.initialized != 0.
|
||||
eim_nat_egress(&nat, pkt, len, node_id, conn); // src IP/port -> внешние; проверить результат
|
||||
struct eim_nat_entry* entry = NULL;
|
||||
eim_nat_ingress(&nat, pkt, len, &entry); // dst IP/port -> внутренние; 0 не гарантирует entry != NULL
|
||||
eim_nat_add_forward(&nat, proto, ip_host, port_net, ext_port, target_node_id); // проверить результат
|
||||
eim_nat_destroy_ctx(&nat);
|
||||
if (eim_nat_init_ctx(&nat, &global_cfg) == 0 && nat.initialized) {
|
||||
int result = eim_nat_egress(&nat, pkt, len, node_id, conn);
|
||||
if (result == EIM_NAT_OK) { /* отправить преобразованный пакет */ }
|
||||
// EIM_NAT_SKIP — неподдерживаемый пакет; EIM_NAT_ERROR — ошибка. В обоих случаях пакет не изменён.
|
||||
struct eim_nat_entry* entry = NULL;
|
||||
result = eim_nat_ingress(&nat, reply, reply_len, &entry);
|
||||
if (result == EIM_NAT_OK) { /* сохранить entry->src_node_id для обратной отправки */ }
|
||||
eim_nat_expire(&nat, get_time_tb(), 128); // регулярно из housekeeping, вне обработки пакета
|
||||
eim_nat_destroy_ctx(&nat);
|
||||
}
|
||||
```
|
||||
|
||||
**Таблица:** 65536 записей, индекс — внешний порт/echo ID. Для всех протоколов используется один пул:
|
||||
один внешний порт занимает только одна запись. Динамический диапазон включителен,
|
||||
`1 <= port_start < port_end <= 65535`; свободный порт ищется циклически с `next_port`.
|
||||
`1 <= port_start < port_end <= 65535`; `next_port` — голова двусвязного списка свободных портов,
|
||||
0 означает исчерпание. Выделение порта и резервирование статического порта выполняются за O(1), удалённый порт возвращается в хвост.
|
||||
При исчерпании новый поток получает `-1`, существующий продолжает использовать свой mapping.
|
||||
Записи не истекают и автоматически не удаляются; таблица живёт до `destroy_ctx`.
|
||||
Исходящий индекс — 65536 хеш-корзин по 2 байта, цепочки состоят из индексов портовой таблицы.
|
||||
Сравнивается полный внутренний ключ; поиск в среднем O(1), в худшем случае зависит от длины коллизий.
|
||||
После init обработка пакетов, создание и удаление mapping не выделяют память.
|
||||
|
||||
**eim_nat_egress:** находит внутренний ключ во всей таблице, включая статические порты вне динамического пула,
|
||||
**eim_nat_egress:** находит внутренний ключ в хеше, включая статические порты вне динамического пула,
|
||||
либо создаёт ACTIVE-запись. Меняет source IP и порт/echo ID, обновляет checksums.
|
||||
`src_conn` только сохраняется при создании ACTIVE-записи; отправка ответа использует `src_node_id`, а не этот указатель.
|
||||
`last_seen` остаётся нулевым и не управляет временем жизни.
|
||||
`last_seen` обновляется по `get_time_tb()` в обоих направлениях. Время простоя до удаления:
|
||||
TCP — 7440 секунд без отслеживания TCP-состояния ([RFC 5382, REQ-5](https://www.rfc-editor.org/rfc/rfc5382#section-5)),
|
||||
UDP — 120 секунд ([RFC 4787, REQ-5](https://www.rfc-editor.org/rfc/rfc4787#section-4.3)),
|
||||
ICMP Echo — 60 секунд ([RFC 5508](https://www.rfc-editor.org/rfc/rfc5508#section-3.1)).
|
||||
`expire` проверяет не более заданного числа слотов с сохранением курсора, статические записи не удаляет.
|
||||
В nat_transport проверяются 128 слотов каждые 100мс: очистка после timeout может задержаться примерно на 51.2с
|
||||
плюс задержки выполнения event loop. Полного обхода таблицы на пути пакета нет.
|
||||
|
||||
**eim_nat_ingress:** для пакета на `gateway_ip` проверяет запись по внешнему destination-порту/echo ID и протоколу,
|
||||
меняет destination на внутренний адрес. `out_entry` необязателен; при совпадении получает заимствованную запись.
|
||||
При отсутствии совпадения значение `*out_entry` не изменяется — перед вызовом установить его в NULL.
|
||||
До обработки `*out_entry` обнуляется. Только EIM_NAT_OK возвращает запись; указатель действует до удаления/истечения/уничтожения.
|
||||
Для асинхронного ожидания ответа транспорт сохраняет преобразованный пакет и ID получателя.
|
||||
Если исходящий пакет провайдера ждёт записи в TUN, `hold/release` запрещают удаление и повторное использование его внешнего порта.
|
||||
|
||||
**Статический проброс:** TCP/UDP-запись с явным ненулевым ID узла назначения, внутренним портом в network order
|
||||
и внешним портом 1..65535 в host order. Внешний порт может быть вне динамического диапазона.
|
||||
@@ -45,9 +59,12 @@ IP checksum учитывает замену адреса; TCP/UDP — адрес
|
||||
Исходные checksums не проверяются: инкрементальное обновление требует корректного входного пакета.
|
||||
|
||||
**Ограничения:** работа с полными доступными для записи IPv4-пакетами, последовательные вызовы в потоке ядра.
|
||||
Минимальный IP-заголовок/IHL проверяются, полной проверки версии/total_length и checksums нет.
|
||||
Фрагменты (MF или ненулевой offset), неподдерживаемые протоколы и ICMP без Echo проходят без изменений с кодом 0.
|
||||
Это не означает успешную NAT-трансляцию. Перед повторным init нужно вызвать destroy; повторный destroy допустим.
|
||||
Проверяются версия IPv4, IHL, точное соответствие total_length размеру буфера, TCP data offset,
|
||||
границы UDP length и ICMP Echo. Невыровненные буферы допустимы. Исходные checksums не проверяются.
|
||||
Фрагменты (MF или ненулевой offset), неподдерживаемые протоколы и ICMP без Echo возвращают EIM_NAT_SKIP;
|
||||
nat_transport их отбрасывает. Повреждённые пакеты возвращают EIM_NAT_ERROR до изменения данных/таблицы.
|
||||
Перед повторным init нужен destroy. При удалении запись сначала исключается из хеша, затем порт возвращается в пул.
|
||||
Ключи/состояние записей извне изменять нельзя: для удаления использовать `eim_nat_remove`.
|
||||
|
||||
**Конфиг:**
|
||||
```ini
|
||||
@@ -67,9 +84,17 @@ forward = tcp:0123456789abcdef:192.168.1.2:80:8080
|
||||
| Функция/Структура | Описание |
|
||||
|---|---|
|
||||
| `struct eim_nat_entry` | Запись: internal_ip, internal_port, proto, state (FREE/ACTIVE/STATIC), src_node_id, src_conn, last_seen |
|
||||
| `struct eim_nat_ctx` | Контекст: gateway_ip, port_start/end, next_port (циклический аллокатор), table[64K], initialized |
|
||||
| `struct eim_nat_ctx` | Портовая таблица, хеш-индекс, свободный FIFO и курсор порционной очистки |
|
||||
| `eim_nat_init_ctx(ctx, cfg)` | Инициализация таблицы, загрузка статических пробросов из конфига |
|
||||
| `eim_nat_destroy_ctx(ctx)` | Освобождение таблицы, обнуление |
|
||||
| `eim_nat_egress(ctx, ip_data, ip_len, src_node_id, src_conn)` | Трансляция исходящего пакета: src IP/port → шлюзовые. Создаёт динамическую запись при первом проходе |
|
||||
| `eim_nat_ingress(ctx, ip_data, ip_len, out_entry)` | Обратная трансляция входящего пакета: dst IP/port → внутренние. Заполняет out_entry для маршрутизации |
|
||||
| `eim_nat_add_forward(ctx, proto, ip_host, port_net, ext_port, node_id)` | Добавление статического проброса на заданный узел |
|
||||
| `eim_nat_remove(ctx, ext_port)` | Согласованное удаление из хеша и возврат порта; удержанная запись не удаляется |
|
||||
| `eim_nat_expire(ctx, now_tb, budget)` | Ограниченная по числу слотов очистка динамических записей |
|
||||
| `eim_nat_hold/release` | Удержание внешнего порта на время асинхронной исходящей передачи |
|
||||
|
||||
Проверка скорости: `make -C tests test_nat_stress`, затем `tests/test_nat_stress --benchmark`.
|
||||
Измеряются создание/попадание/промах заполненного пула для 1/1000/10000/60000 потоков и 64/1500 байтов;
|
||||
подготовка payload и выделения памяти вынесены из измерений, NAT-диагностика отключена.
|
||||
Это benchmark движка, без стоимости TUN, шифрования и сети.
|
||||
+9
-1
@@ -4,6 +4,13 @@
|
||||
* init создаёт TUN/обработчики по nat_enabled; destroy снимает их и освобождает ресурсы.
|
||||
* TUN-очередь содержит [prefix:1][IP]; tun_write принимает только IP без prefix.
|
||||
* Отправка роутеру: [svc_id:1][IP], владение entry/dgram передаётся ему даже при ошибке.
|
||||
* Используется исходный TUN entry: NAT не копирует IP и не выделяет новый пакет.
|
||||
* FIFO/ожидания разделены по адресату: router send_q проверяется до передачи владения.
|
||||
* При заполнении буфера одного пира новые пакеты этого пира отбрасываются; общий лимит приостанавливает TUN.
|
||||
* EAGAIN записи в TUN удерживает один пакет, приём из router recv_q приостанавливается до его доставки.
|
||||
* У провайдера mapping исходящего пакета удерживается до записи в TUN, чтобы внешний порт не переиспользовали.
|
||||
* Раз в 100мс проверяются 128 mapping: полный проход <=51.2с при своевременном исполнении таймера.
|
||||
* Idle-timeout TCP=7440с, UDP=120с, ICMP=60с; статические записи не истекают.
|
||||
* Приём использует общий ROUTER_SVC_HDR_SIZE=26: [svc][src][dst][flags][group][IP].
|
||||
* Клиент не создаёт таблицу eim_nat: inst->nat.initialized служит флагом готовности транспорта.
|
||||
* Контекст принадлежит UTUN_INSTANCE; все вызовы и callbacks работают в его uasync-потоке. */
|
||||
@@ -48,7 +55,8 @@ struct UTUN_INSTANCE;
|
||||
* 0 — успех, включая выключенный NAT; -1 — ошибка, созданные NAT-ресурсы освобождены.
|
||||
* Настройка внешних маршрутов/доступа в интернет выполняется отдельно от этого API. */
|
||||
int nat_transport_init(struct UTUN_INSTANCE* inst);
|
||||
/* Снять binding, закрыть NAT TUN и уничтожить eim_nat; NULL и повторный вызов допустимы.
|
||||
/* Отменить таймеры/waiters, освободить удержанные пакеты до TUN-пула, закрыть каналы NAT и уничтожить eim_nat.
|
||||
* Остальные router-сервисы сохраняются. NULL и повторный вызов допустимы.
|
||||
* Вызвать до освобождения роутера/uasync, вне выполняемого NAT callback. */
|
||||
void nat_transport_destroy(struct UTUN_INSTANCE* inst);
|
||||
|
||||
|
||||
Reference in new issue
Block a user