Files
utun2/src/eim_nat_doc.md
T

11 KiB

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. Как пользоваться

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), UDP — 120 секунд (RFC 4787, REQ-5), ICMP Echo — 60 секунд (RFC 5508). 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.

Конфиг:

[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, шифрования и сети.