Describe NAT transport backpressure, ownership and mapping holds

This commit is contained in:
evgeny committed 2026-10-21 23:08:31 +02:00
1 parent 7de54ab3e4
commit 1ccb266971
2 files changed
+78 -49

No files matched your search

+1 -1
View File
@@ -36,7 +36,7 @@ if (eim_nat_init_ctx(&nat, &global_cfg) == 0 && nat.initialized) {
`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)).
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. Полного обхода таблицы на пути пакета нет.
+77 -48
View File
@@ -1,60 +1,89 @@
# NAT Transport Layer (nat_transport)
## 1. Назначение
Транспортный слой NAT поверх ETCP. Обеспечивает проброс IP-трафика между клиентом и провайдером NAT через ETCP-роутер (канал `ETCP_RT_ID_NAT`). Клиент захватывает пакеты из TUN-интерфейса и отправляет провайдеру; провайдер выпускает их в интернет через свой NAT-движок, а обратные пакеты возвращает клиенту.
Передача IPv4 между отдельным TUN и удалённым NAT-провайдером через сервис `ETCP_RT_ID_NAT`
в группе `TOPO_GROUP_UTUN`. Провайдер преобразует пакеты движком `eim_nat`, клиент использует
только транспорт. Нормативный API и правила владения описаны в `nat_transport.h` и `eim_nat.h`.
## 2. Как пользоваться
## 2. Конфигурация и поток данных
- **Провайдер** (`nat_via_node_id = 0`): создаёт таблицу `eim_nat`. В `[nat]` задаются `tun_ifname`,
`tun_ip`, `port_start`, `port_end`; проброс — `forward=udp:node_id_hex:IPv4:internal_port:external_port`.
- **Клиент**: в `[nat]` задаётся `nat_via=<ID провайдера в HEX>` и параметры своего NAT TUN.
Таблица `eim_nat` не создаётся; `inst->nat.initialized=1` означает готовность транспорта.
Опции секции `[nat]` включают NAT; параметра `enabled` нет.
- Создаётся отдельный `nat_tun` с `TUN_MTU_DEFAULT`. Адрес берётся из IPv4 `nat_tun_ip`, иначе из `tun_ip`.
Маршруты ОС и доступ провайдера в интернет настраиваются отдельно.
### Конфигурация
- **Провайдер** (`nat_via_node_id = 0`): создаёт таблицу `eim_nat`. В секции `[nat]` задаются `tun_ifname`,
`tun_ip`, `port_start`, `port_end`; статический проброс — `forward=udp:node_id_hex:IPv4:internal_port:external_port`.
- **Клиент** (`nat_via_node_id != 0`): в `[nat]` задаётся `nat_via=<ID провайдера в HEX>` и параметры своего NAT TUN.
Таблица `eim_nat` не создаётся; `inst->nat.initialized=1` используется как флаг готовности транспорта.
Параметра `enabled` нет: NAT включают опции секции `[nat]`.
1. Клиент получает `[prefix:1][IP]` из `nat_tun->output_queue`. `nat_tun_output` заменяет prefix
на `ETCP_RT_ID_NAT` и передаёт исходный entry/dgram роутеру с адресатом `nat_via_node_id`.
2. Провайдер получает пакет в `nat_receive`, выполняет `eim_nat_egress` и пишет чистый IP в NAT TUN.
3. Ответ/статический проброс из TUN провайдера проходит `eim_nat_ingress` в `nat_tun_output`.
Преобразованный пакет отправляется узлу из `mapping->src_node_id`.
4. Клиент принимает ответ только от своего провайдера, проверяет IPv4 и записывает в свой NAT TUN.
### Поток данных
1. **Клиент → Провайдер**: `[prefix:1][IP]` из `nat_tun->output_queue` → callback клиента →
`[ETCP_RT_ID_NAT:1][IP]` в `etcp_route_send(inst, TOPO_GROUP_UTUN, provider_id, ...)`.
Source/destination и заголовки протокола добавляет роутер.
2. **Провайдер получает**: `nat_transport_etcp_recv_cb` (зарегистрирован через `etcp_router_bind`) → `eim_nat_egress()` (source NAT) → `tun_write()` в NAT TUN.
3. **Провайдер → Клиент (ответ/статический проброс)**: пакет из NAT TUN → `eim_nat_ingress()` →
`[ETCP_RT_ID_NAT:1][IP]` в роутер с адресатом `entry->src_node_id`. Без mapping/адресата пакет отбрасывается.
4. **Клиент получает ответ**: `nat_transport_etcp_recv_cb` → `tun_write()` в NAT TUN клиента.
Роутер добавляет source/destination и протокольные заголовки. Callback получает служебный заголовок
`[svc:1][src:8][dst:8][rx_flags:1][group:8][IP]`, размером `ROUTER_SVC_HDR_SIZE=26`.
Это формат доставки callback, а не заголовок на проводе. CLOSE/reset без IP освобождается без трансляции.
Приём ограничен группой UTUN. Повреждённые пакеты, фрагменты, неподдерживаемые протоколы и входящие пакеты
провайдера без mapping отбрасываются. Ограничения проверки и преобразования — в `eim_nat.h`.
### Ключевые нюансы
- Используется отдельный TUN (`nat_tun`), MTU=`TUN_MTU_DEFAULT`. Шлюз берётся из IPv4 `nat_tun_ip`, иначе из `tun_ip`.
Маршруты ОС и выход провайдера в интернет настраиваются отдельно; модуль их не создаёт.
- Callback'и работают через очередь `output_queue` TUN-интерфейса. По одному пакету за вызов.
- Сервисный receive callback получает общий заголовок роутера размером 26 байтов:
`[svc:1][src:8][dst:8][rx_flags:1][group:8][IP]` (`ROUTER_SVC_*` в `etcp_router.h`).
Это формат доставки callback, не заголовок пакета на проводе. Отдельного 9-байтного NAT-заголовка нет.
- Приём ограничен `TOPO_GROUP_UTUN`; клиент принимает IP только от своего `nat_via_node_id`.
- `tun_write()` получает чистый IP без служебного префикса. Роутер принимает владение отправленным entry/dgram
при любом результате; NAT не освобождает их повторно при отказе.
- Все операции выполняются в uasync-потоке ядра. `init` вызывается после создания роутера,
`destroy` — до освобождения роутера/uasync и вне NAT callback; перед повторным `init` нужен `destroy`.
- Ограничения преобразования описаны в `eim_nat.h`: фрагменты и неподдерживаемые протоколы не преобразуются.
Сейчас провайдер передаёт такие исходящие пакеты в свой TUN без NAT, а входящие без mapping отбрасывает.
## 3. Очереди, ожидания и владение
NAT не копирует IP из TUN и не создаёт для него новый пакет. Роутер синхронно копирует payload
в свою очередь и принимает владение исходным entry/dgram при любом результате отправки.
После передачи роутеру NAT больше не обращается к пакету и не освобождает его повторно.
## 3. API
Для каждого адресата создаётся FIFO и одно отменяемое ожидание готовности router send_q.
Адресаты индексируются через хеш `ll_queue`; выделения требуются при первом обращении к узлу.
Перед передачей владения проверяется место в router send_q. При заполнении ожидается `on_send_ready`.
Порядок пакетов одного адресата сохраняется; ожидания разных адресатов независимы.
### Структуры
| Структура | Назначение |
|-----------|------------|
| `nat_transport_ctx` | Контекст: `self_node_id`, `nat_via_node_id` (0=провайдер, иначе id провайдера), `nat_tun`, флаг `initialized` |
- `NAT_PENDING_PER_PEER=64` ограничивает дополнительный буфер адресата, включая пакет в активном ожидании.
Новый пакет переполненного адресата отбрасывается. Остальные адресаты продолжают обслуживаться.
Счётчик потерь обновляется всегда, предупреждения о перегрузке выводятся не чаще раза в секунду.
- `NAT_PENDING_TOTAL=1024` ограничивает сумму дополнительных буферов. При достижении лимита
текущий преобразованный пакет возвращается в голову `output_queue` TUN, callback остаётся приостановленным.
Непустая очередь останавливает чтение TUN; освобождение места возобновляет callback.
- Дополнительно существуют очереди роутера: указанные лимиты ограничивают именно NAT-буферы.
- Адресат ответа фиксируется вместе с уже преобразованным пакетом до ожидания. Истечение/повторное
использование mapping во время ожидания не меняет адресата и не запускает повторную трансляцию.
### Функции
| Функция | Назначение |
|---------|------------|
| `nat_transport_init(inst)` | Инициализация: создаёт NAT TUN, настраивает eim_nat (если провайдер), биндит `ETCP_RT_ID_NAT` через etcp_router, вешает callback'и на TUN output_queue |
| `nat_transport_destroy(inst)` | Деинициализация: анбиндит ETCP-канал, закрывает TUN, разрушает eim_nat |
При `tun_write` с `EAGAIN/EWOULDBLOCK` удерживается один принятый пакет. `nat_receive_ready` запрещает
доставку следующих: они остаются в router recv_q без ACK. Повтор записи назначается через 10мс;
maintenance также повторяет запись. После успеха или окончательной ошибки пакет освобождается,
приём NAT-каналов UTUN возобновляется. Ошибки записи диагностируются в TUN-модуле.
### Внутренние коллбэки
| Коллбэк | Срабатывание |
|---------|-------------|
| `nat_transport_client_tun_out_cb` | Пакет из TUN output_queue на клиенте — упаковывает и шлёт провайдеру |
| `nat_transport_provider_tun_out_cb` | Пакет из TUN output_queue на провайдере — делает ingress NAT lookup, шлёт обратно клиенту |
| `nat_transport_etcp_recv_cb` | Получен пакет из канала `ETCP_RT_ID_NAT` — клиент пишет в TUN, провайдер делает egress NAT |
Провайдер дополнительно вызывает `eim_nat_hold` для внешнего source-порта/echo ID пакета,
ожидающего записи в TUN. Удержанный mapping нельзя удалить или переиспользовать при очистке.
После успешной записи `eim_nat_release` обновляет время активности; при ошибке/остановке снимает
удержание без обновления. Это удержание относится к исходящему пакету провайдера; ожидающий
обратной отправки ответ уже содержит внутренний адрес и сохранённый ID клиента.
### Зависимости
`eim_nat.h`, `etcp_router.h`, `etcp_api.h`, `tun_if.h`, `utun_instance.h`, `ll_queue.h`
## 4. Обслуживание и остановка
Таймер `nat_maintenance` каждые 100мс:
- проверяет 128 слотов NAT на истечение: TCP — 7440с, UDP — 120с, ICMP Echo — 60с простоя;
- восстанавливает ожидания, отменённые при close/reset router-канала;
- удаляет пустые FIFO адресатов после 60с простоя;
- повторяет ожидающую запись TUN.
Статические и удержанные mapping не истекают. Полный проход таблицы занимает около 51.2с
при своевременном выполнении таймеров; очистка после timeout дополнительно зависит от задержек event loop.
Полного обхода mapping на пути пакета нет. Если выделение таймера повтора TUN не удалось,
ошибка логируется и повтор остаётся на maintenance; ошибка планирования maintenance также логируется.
Все вызовы выполняются в uasync-потоке ядра. `init` требует созданного роутера, перед повторным `init`
нужен `destroy`. Остановка выполняется вне NAT callback, до освобождения роутера/uasync:
сначала отменяются таймеры и binding, затем waiters и FIFO освобождают исходные TUN-пакеты,
закрываются NAT router-каналы, освобождаются удержанный пакет/его mapping, TUN и таблица NAT.
Освобождение пакетов предшествует уничтожению их TUN-пула. Другие router-сервисы продолжают работать.
## 5. API и проверки
| API | Назначение |
|---|---|
| `nat_transport_ctx` | TUN, хеш адресатов, счётчик pending, глобально остановленный пакет, ожидающая запись TUN и таймеры |
| `nat_transport_init(inst)` | Создать транспорт по конфигу; 0 — успех/выключенный NAT, -1 — ошибка с освобождением созданных ресурсов |
| `nat_transport_destroy(inst)` | Отменить ожидания и освободить транспорт; NULL/повторный вызов допустимы |
`test_nat_transport` проверяет обе роли через настоящий роутер и TUN в test mode: порядок и контрольные суммы,
отсутствие копии исходного entry, владение при ошибке, фильтрацию источника/группы, некорректные IP,
давление router send_q, оба лимита NAT, прогресс исправного клиента рядом с медленным,
истечение/повторное использование mapping во время ожидания, EAGAIN TUN и остановку с активными ожиданиями.