Files
utun2/doc/etcp_router_arch.md
T

12 KiB
Raw Blame History

Архитектура ETCP Router

Маршрутизатор связывает сервис с удалённым узлом через группу, не привязывая логическую сессию к одному физическому ETCP. Ключ канала — (group_id, remote_node_id, svc_id). Wire-формат и сервисный API подробно описаны в etcp_router_doc.md, объявления — в etcp_router.h.

Отправка и приём

flowchart TD
    Producer["Сервис: payload + mode"] --> Send["send_q: plaintext, seq, mode"]
    Send --> Session{"peer_reset_id подтверждён?"}
    Session -- нет --> Hello["HELLO / challenge / confirm"]
    Hello --> Session
    Session -- да --> Route["Текущий маршрут: conn_mgr → BGP, либо прямой глобальный ETCP"]
    Route --> Wait["Waiter физической send_input_q"]
    Wait --> Encode["route_crypto_encode: encrypt → sign"]
    Encode --> Inflight["inflight_q: неизменяемое тело без visited-list"]
    Inflight --> Path["Добавить source ID"]
    Path --> ETCP["etcp_send / normalizer"]
    Inflight -- RTO 300 ms --> Wait
    ETCP --> Network["Сеть / транзитные узлы"]
    Network --> Check["Проверить visited-list"]
    Check --> Destination{"dst=self?"}
    Destination -- нет --> Transit["Добавить self один раз, BGP next hop / transit FIFO"]
    Transit --> Network
    Destination -- да --> Decode["Crypto policy + verify/decrypt + проверка эпох"]
    Decode --> Incoming["incoming_q: plaintext"]
    Incoming --> Reorder["recv_q: reorder по seq"]
    Reorder --> Ready{"receive_ready разрешает?"}
    Ready -- нет --> Busy["Сохранить пакет, ACK RECEIVE_BUSY"]
    Busy --> Resume["resume_receive: отложенное продолжение"]
    Resume --> Reorder
    Ready -- да --> Callback["Callback сервиса получает entry"]
    Callback --> Ack["rx_seq++ → ACK текущей границы"]
    Ack --> Release["Отправитель снимает подтверждённый inflight"]

Каждая кодограмма имеет самостоятельный seq; это не байтовый поток. send_q ограничена 64 пакетами при обычной отправке, force обходит этот порог. Inflight по умолчанию — 256, при minRTT probe — 4. Получатель ограничивает reorder окном max_inflight и суммарным размером очередей max_inflight+1 записей.

Все события выполняются в uasync-потоке. Штатный normalizer откладывает physical waiters, а callbacks производителей send_q могут выполняться синхронно при освобождении места. Поэтому код отправки повторно проверяет состояние/эпохи после callback.

Эпохи логического канала

Полный сетевой пакет:

[57-byte SVC_ROUTE_HDR][payload][signature?][visited_ids:8*N][count:1]

Числа в заголовке — native endian. flags — отдельный байт; биты seq не заняты флагами. Заголовок включает group/src/dst/service, seq/timestamp и поля reset_id/peer_reset_id/challenge. visited-list содержит 1..64 ID, находится вне подписи/шифрования и проверяется на каждом переходе.

Первичное согласование двух сторон имеет вид:

sequenceDiagram
    participant A as Узел A, local=a
    participant B as Узел B, local=b
    A->>B: HELLO(reset=a, peer=0)
    B->>A: CHALLENGE(reset=b, peer=a, cB)
    A->>B: CONFIRM(reset=a, peer=b, cB)
    A->>B: CHALLENGE(reset=a, peer=b, cA)
    B->>A: CONFIRM(reset=b, peer=a, cA)
    Note over A,B: Каждый узел принимает peer ID только после ответа на свой pending challenge

Получение HELLO/CHALLENGE с неизвестной эпохой само по себе не сбрасывает работающий канал. Переставленные/старые CONFIRM не совпадают с pending ID/challenge и игнорируются. Новые challenge ограничены интервалом 300 ms; первичный HELLO повторяется без отдельного timeout.

Событие Что сохраняется Что меняется
Смена ETCP/next hop Логические эпохи, seq, очереди Физический waiter/маршрут
Первое подтверждение peer Накопленная send_q Заполняется peer_reset_id, начинается отправка
Подтверждённый рестарт peer Собственный reset_id Очереди, seq, RTT/метрики; уведомление сервиса
Явный conn_restart Ключ group/peer/service и адрес rconn Новый reset_id, пустое состояние; затем callback и HELLO
conn_close Только данные, переданные во владение callback Запись снимается с реестра, ресурсы освобождаются

Сервис возобновляет прикладную синхронизацию после сброса; старые гарантии доставки через reset не продолжаются. Готовность router означает наличие подтверждённого peer ID. Это отдельное состояние от физического UP и READY BGP-группы.

Подтверждение и пауза потребителя

DATA проходят crypto policy и verify/decrypt до изменения reorder/rx_seq. Сервис получает [svc:1][src:8][dst:8][rx_flags:1][group:8][payload], всего 26 байт заголовка. Его callback владеет entry, ACK продвигается после успешной передачи callback.

ACK подтверждает только диапазон до tx_sent, в модульном порядке uint32. При продвижении снимаются существующие inflight-записи и один раз учитываются исходные payload bytes.

RECEIVE_BUSY не подтверждает отложенные DATA, но поддерживает живость ожидающего диска/буфера сервиса. Даже при прежнем seq допустимый BUSY обновляет отсчёт RTO и останавливает новую отправку/проход ретрансмитов. Обычный повторный ACK без продвижения этого не делает. Снятие BUSY возобновляет send_q.

receive_ready — предикат без I/O и изменения/закрытия роутера. При 0 пакет сохраняется в recv_q; resume_receive планирует продолжение. Управляющий CLOSE не проходит через этот предикат.

Механизм Интервал/условие
ACK Не чаще 10 ms; текущая граница повторяется при дубликате DATA после потери ACK
RECEIVE_BUSY Около 100 ms, пока сервис остановлен
Idle ACK 500 ms, только если rx_seq ещё не был отправлен в ACK
Handshake/RTO 300 ms
Нет маршрута после handshake Повторный поиск через 20 ms
Транзит при backpressure Waiter + повтор выбора маршрута через 20 ms
Watchdog send_q 500 ms, пока очередь непуста
Нет прогресса/BUSY Закрытие после 17 циклов, для сервиса CALL — после 67

Транзитные очереди принадлежат физическому ETCP, сгруппированы по group/src/dst и не имеют своих seq. При смене маршрута пакет переносится на актуальный next hop без ожидания освобождения старой очереди. Отсутствие пути приводит к потере транзитной копии; её восстанавливает end-to-end inflight источника.

Владение и локальная доставка

Объект Владение
Входной entry etcp_route_send Всегда поглощается, включая ошибку
Буфер etcp_router_conn_send Заимствован на время копирования
ETCP_ROUTER_CONN из conn_get Заимствован до close/destroy
Сервисный entry Передаётся callback; освободить dgram и entry
Физический conn в callback Заимствован, может обозначать транзитного пира
Синтетический conn Только на время callback, instance/peer=self; без сетевого send/ref_take
Send-ready handle/arg У производителя до callback/отмены; перед освобождением отменить

Self-доставка через etcp_route_send синхронна и не создаёт канал. Она обходит сетевые crypto policy и receive_ready, выдаёт rx_flags=0; режим проверяется на допустимые биты. Payload должен быть непустым и помещаться вместе с 26-байтным заголовком в uint16 длину. Нет обработчика, переполнение размера или отказ памяти возвращают -1. Отдельный синтетический conn каждого вызова сохраняет контекст при вложенных доставках разных ядер.

Закрываемый канал удаляется из реестра до callback. Callback может закрывать группу/другие каналы или открыть замену с тем же ключом; старая запись не возвращается повторным поиском. Память канала и send_q освобождается отложенно за пределами текущего callback. До массовой остановки сервисы отменяют producers/waiters; уничтожение ядра из callback останавливаемого сервиса запрещено.

close_group удаляет логические каналы и pending transit указанной группы. destroy снимает wire callback, закрывает все логические каналы и очищает транзитные очереди, таймеры и waiters на всех живых ETCP. Уничтожать сам ETCP для очистки роутера не требуется.

Метрики delivery считают первые подтверждения исходного payload, без заголовков/crypto/ретрансмитов. RTT — 0.1 ms, snapshot API выдаёт микросекунды; jitter — unsigned 16.16 EWMA, монитор публикует удвоенный EWMA. Очереди, метрики и параметры minRTT сбрасываются вместе с логической сессией.