# EIM NAT (Endpoint-Independent Mapping NAT) ## 1. Назначение Преобразование IPv4 TCP/UDP и ICMP Echo на месте. Ключ mapping — `(src_node_id, протокол, внутренний IP, внутренний порт/echo ID)`; внешний адресат в ключ не входит. Разные узлы с одинаковыми частными адресами получают разные внешние порты. Отправка пакетов и владение TUN/ETCP находятся в `nat_transport`, нормативный API — в `eim_nat.h`. ## 2. Как пользоваться ```c struct eim_nat_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` — голова двусвязного списка свободных портов, 0 означает исчерпание. Выделение порта и резервирование статического порта выполняются за O(1), удалённый порт возвращается в хвост. При исчерпании новый поток получает `-1`, существующий продолжает использовать свой mapping. Исходящий индекс — 65536 хеш-корзин по 2 байта, цепочки состоят из индексов портовой таблицы. Сравнивается полный внутренний ключ; поиск в среднем O(1), в худшем случае зависит от длины коллизий. После init обработка пакетов, создание и удаление mapping не выделяют память. **eim_nat_egress:** находит внутренний ключ в хеше, включая статические порты вне динамического пула, либо создаёт ACTIVE-запись. Меняет source IP и порт/echo ID, обновляет checksums. `src_conn` только сохраняется при создании ACTIVE-записи; отправка ответа использует `src_node_id`, а не этот указатель. `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.2)). `expire` проверяет не более заданного числа слотов с сохранением курсора, статические записи не удаляет. В nat_transport проверяются 128 слотов каждые 100мс: очистка после timeout может задержаться примерно на 51.2с плюс задержки выполнения event loop. Полного обхода таблицы на пути пакета нет. **eim_nat_ingress:** для пакета на `gateway_ip` проверяет запись по внешнему destination-порту/echo ID и протоколу, меняет destination на внутренний адрес. `out_entry` необязателен; при совпадении получает заимствованную запись. До обработки `*out_entry` обнуляется. Только EIM_NAT_OK возвращает запись; указатель действует до удаления/истечения/уничтожения. Для асинхронного ожидания ответа транспорт сохраняет преобразованный пакет и ID получателя. Если исходящий пакет провайдера ждёт записи в TUN, `hold/release` запрещают удаление и повторное использование его внешнего порта. **Статический проброс:** TCP/UDP-запись с явным ненулевым ID узла назначения, внутренним портом в network order и внешним портом 1..65535 в host order. Внешний порт может быть вне динамического диапазона. Первый входящий пакет уже имеет адресата; исходящий пакет этого же узла переиспользует статический порт. Занятый внешний порт и повтор внутреннего ключа отклоняются. Некорректный проброс при init освобождает всю создаваемую таблицу. **Контрольные суммы:** `csum_update_n()` складывает `~csum`, 16-битные дополнения старых слов и новые слова, затем переносит старшие биты в младшие и инвертирует результат. Обычное беззнаковое вычитание здесь теряет заём. IP checksum учитывает замену адреса; TCP/UDP — адрес в pseudo-header и порт; ICMP Echo — идентификатор. В IPv4 UDP исходная checksum=0 остаётся нулевой; вычисленный ноль записывается как 0xffff. Исходные checksums не проверяются: инкрементальное обновление требует корректного входного пакета. **Ограничения:** работа с полными доступными для записи IPv4-пакетами, последовательные вызовы в потоке ядра. Проверяются версия 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 [nat] tun_ip = 10.0.0.1 port_start = 10000 port_end = 20000 forward = tcp:0123456789abcdef:192.168.1.2:80:8080 ``` Опции `[nat]` включают NAT; параметра `enabled` и секции `[nat_forward]` нет. `forward=proto:node_id_hex:IPv4:internal_port:external_port` задаётся на провайдере; узел назначения должен работать как NAT-клиент этого провайдера. Ошибки разбора NAT отклоняют весь конфиг. Шлюз берётся из IPv4 `nat_tun_ip`, иначе из IPv4 основного `tun_ip`. ## 3. API | Функция/Структура | Описание | |---|---| | `struct eim_nat_entry` | Запись: internal_ip, internal_port, proto, state (FREE/ACTIVE/STATIC), src_node_id, src_conn, last_seen | | `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, шифрования и сети.