> Исторический документ. Актуальная реализация и wire-формат описаны в [ETCP Router](../src/routing_layer/etcp_router_doc.md). # ETCP Router Architecture ## Структуры данных ``` ETCP_ROUTER_CONN (один на пару remote_node_id + svc_id) ┌─────────────────────────────────────────────────────┐ │ ll_entry (хеш-индекс: remote_node_id[8] + svc_id[1]) │ │ remote_node_id: u64 │ │ svc_id: u8 │ │ last_dgram_ts: u16 (timestamp последней датаграммы) │ │ │ │ tx_seq: u32 следующий seq для отправки │ │ rx_seq: u32 ожидаемый seq для сборки │ │ tx_acked: u32 сколько наших пакетов подтвердил remote │ │ last_sent_ack_seq: u32 последний отправленный ACK │ │ │ │ recv_q: ll_queue* очередь reorder (хеш по seq[4]) │ │ send_q: ll_queue* очередь при переполнении inflight │ │ │ │ ack_timer: void* периодический 100ms │ │ idle_ack_timer: void* idle таймаут 500ms │ │ send_waiter: queue_waiter_handle waiter на send_input_q │ │ watchdog_timer: void* проверка инварианта 500ms │ │ send_blocked: u8 inflight полон │ └─────────────────────────────────────────────────────┘ SVC_ROUTE_HDR (22 байта) ┌──────┬────────────────┬────────────────┬───────┬────────┬──────────┐ │ cmd │ dst_node_id │ src_node_id │ seq │ svc_id │ payload │ │ u8 │ u64 │ u64 │ u32 │ u8 │ ... │ └──────┴────────────────┴────────────────┴───────┴────────┴──────────┘ seq в data-пакетах = tx_seq (счетчик отправленных) seq в ACK-пакетах = rx_seq (ожидаемый следующий) CLOSE-флаг = 0x80000000 в seq, RST-флаг = 0x40000000 ``` ## Send Path (отправка данных) ``` etcp_route_send(inst, dst, entry) etcp_router_conn_send(rconn, data, len) │ ▼ ┌─────────────────────┐ │ rconn = find/get │ поиск по (dst, svc_id) │ авто-создание если │ или etcp_router_conn_get() │ ещё нет │ └────────┬────────────┘ │ ▼ ┌──────────────────────────────┐ │ router_enqueue_send(rconn) │ всегда через send_q (FIFO) │ if send_q >= 64 && !force: │ → вернуть -1 (backpressure) │ return -1 │ └──────┬───────────────────────┘ │ копия в send_q + router_send_kick() ▼ ┌─────────────────────────────────────────┐ │ router_send_kick(rconn) │ │ closed/no_route → return │ │ send_q пуст → return │ │ inflight >= limit → send_blocked=1, ret │ │ waiter уже ждёт → return │ │ нет conn → set_no_route │ │ queue_waiter_wait(send_input_q, │ │ &send_waiter, drain_cb) │ └──────┬───────────────────────────────────┘ │ send_input_q опустел (round-robin: FIFO waiter-ов) ▼ ┌──────────────────────────────┐ │ router_send_drain_cb(rconn) │ шлём ОДИН пакет │ e = send_q.pop() │ │ router_send_one_flags(e) │ │ → tx_seq++ │ │ → etcp_send(conn, entry) │ │ если send_q ещё непуст: │ │ inflight полон → blocked=1│ │ иначе → queue_waiter_wait │ (в хвост → round-robin) │ если пуст → send_blocked=0 │ └──────────────────────────────┘ Повторный запуск drain: • приход ACK → router_handle_ack → kick • маршрут появился → router_no_route_retry_cb → kick • рост inflight-лимита → router_update_inflight_limit → kick • страховка → router_send_watchdog_cb (500ms) → DEBUG_ERROR + kick ``` ## Receive Path (приём данных) ``` etcp_router_recv_cb(conn, entry) │ ▼ ┌──────────────────────┐ │ dst == node_id? │ └──┬───────────────┬───┘ │ YES │ NO (транзит) ▼ ▼ ┌────────────────────┐ ┌──────────────────────────┐ │ Мы — целевая нода │ │ Транзит: найти next hop │ └────────┬───────────┘ │ route_bgp_find_conn_for_ │ │ │ node(bgp, hdr.dst) │ ▼ │ etcp_send(next, entry) │ ┌────────────────────┐ └──────────────────────────┘ │ pl_len == 0? │ └──┬───────────┬─────┘ │ YES │ NO (данные) ▼ ▼ ┌─────────┐ ┌─────────────────────────┐ │ CLOSE/ │ │ rconn = find/get │ │ RST? │ │ (авто-создание если нет) │ └──┬──┬───┘ └───────────┬─────────────┘ │ │ │ │ │ NO (ACK) ▼ │ ▼ ┌───────────────────────────────────────┐ │ ┌──────────┐│ Проверка границ seq: │ │ │ tx_acked ││ d = seq - rx_seq │ │ │ = hdr.seq││ |d| < ROUTER_MAX_INFLIGHT? │ │ │ (только ││ │ │ │ вперёд) ││ Дубликат? │ │ │ kick ││ rx_seq - seq > 0 ИЛИ │ │ └──────────┘│ seq уже в recv_q? │ │ └──────┬─────────────┬──────────────────┘ │ │ OK │ out-of-bounds / dup │ ▼ ▼ │ ┌──────────────┐ ┌─────────┐ │ │ recv_q.put() │ │ DROP │ │ │ хеш по seq │ └─────────┘ │ └──────┬───────┘ │ │ │ ▼ │ ┌──────────────────────┐ │ │ seq == rx_seq? │ │ └──┬───────────────┬───┘ │ │ YES │ NO │ ▼ │ │ ┌──────────────┐ │ │ │ try_assembly │ │ │ │ while next в │ │ │ │ recv_q: │ │ │ │ deliver() │ │ │ │ rx_seq++ │ │ │ └──────┬───────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────┴──┐ │ │ router_deliver() │ │ │ вызывает callback │ │ │ сервиса (svc_id) │ │ │ entry: [svc_id|payload] │ │ └──────────────┬──────────┘ │ │ ▼ ▼ ┌────────────────────────────────────┐ │ router_schedule_ack(rconn) │ │ запускает ack_timer (100ms) если │ │ ещё не запущен │ └────────────────────────────────────┘ ``` ``` CLOSE / RST ветка: ┌────────────────────────────────────────┐ │ pl_len == 0 && seq & CLOSE/RST │ │ → router_close_and_notify(rconn) │ │ ├─ router_send_close_to_service() │ │ │ (callback с NULL conn) │ │ ├─ отправить CLOSE удалённой стороне│ │ ├─ отмена всех таймеров │ │ ├─ очистка recv_q + send_q │ │ └─ удаление из inst->router_conns │ └────────────────────────────────────────┘ ``` ## ACK Mechanism ``` ┌─────────────────────────────────────────┐ │ router_schedule_ack(rconn) │ │ (вызывается при получении данных) │ └────────────────┬────────────────────────┘ │ ┌────────────────▼────────────────────────┐ │ если idle_ack_timer активен — отменить │ │ если ack_timer не активен — запустить │ │ ROUTER_ACK_INTERVAL_TB = 1000 (100ms) │ └────────────────┬────────────────────────┘ │ ▼ (через 100ms) ┌─────────────────────────────────────────┐ │ router_ack_timer_cb() │ │ │ │ if rx_seq != last_sent_ack_seq: │ │ router_send_ack(rconn) │ │ last_sent_ack_seq = rx_seq │ │ перезапустить ack_timer │ │ else: │ │ запустить idle_ack_timer (500ms) │ └────────────────┬────────────────────────┘ │ ▼ (через 500ms) ┌─────────────────────────────────────────┐ │ router_idle_ack_timer_cb() │ │ │ │ if rx_seq != last_sent_ack_seq: │ │ router_send_ack(rconn) (досылка) │ │ last_sent_ack_seq = rx_seq │ └─────────────────────────────────────────┘ router_send_ack(rconn): hdr.seq = rx_seq hdr.svc_id = svc_id pl_len = 0 → это ACK-пакет etcp_send(conn, entry) ``` ``` Обработка входящего ACK: ┌────────────────────────────────────────┐ │ pl_len == 0 && нет CLOSE/RST флагов │ │ │ │ if seq >= tx_acked: // только вперёд │ │ tx_acked = seq // обновить │ │ else: │ │ DEBUG_WARN "stale ACK" │ │ │ │ if send_blocked: │ │ send_blocked = 0 │ │ router_send_kick(rconn) │ └────────────────────────────────────────┘ ``` ## Inflight Control ``` ROUTER_MAX_INFLIGHT = 256 Отправка возможна когда: tx_seq - tx_acked < 256 Отправка заблокирована: tx_seq - tx_acked >= 256 При блокировке: данные → send_q (FIFO, без дропов) send_blocked = 1 Разблокировка: приход ACK от remote → tx_acked обновляется → router_send_kick() → waiter → drain_cb разгребает send_q или маршрут появился / inflight-лимит вырос → kick страховка: router_send_watchdog_cb (500ms) → force kick send_q полностью разобран → send_blocked = 0 ``` ## Полная схема потоков ``` ┌──────────┐ │ Сервис │ (routing, proxy, NAT, ...) │ (svc_id)│ └────┬─────┘ │ etcp_route_send() │ callback(svc_id, entry) ▼ ▲ ┌─────────┐ send ┌─────────────────────────────────┐ recv ┌─────────┐ │ TUN │─────────▶│ ETCP ROUTER │─────────▶│ TUN │ │ (выход) │ │ │ │ (вход) │ └─────────┘ │ ┌──────────┐ ┌──────────┐ │ └─────────┘ │ │ send_q │ │ recv_q │ │ │ │ (FIFO) │ │ (хеш seq)│ │ │ └────┬─────┘ └────┬─────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────────┐ │ │ │ Inflight / Assembly │ │ │ │ tx_seq - tx_acked │ │ │ │ rx_seq → deliver │ │ │ └───────────┬─────────────┘ │ │ │ │ └──────────────┼──────────────────┘ │ ┌──────▼──────┐ │ BGP │ │ (нахождение│ │ next hop) │ └──────┬──────┘ │ ┌──────▼──────┐ │ ETCP conn │──▶ сеть └─────────────┘ ──▶ send (исходящие данные) ──▶ recv (входящие данные, callback сервису) ──▶ транзит (через BGP к следующему hop) ``` ## Таймеры (один rconn) ``` ┌─────────────────────────────────────────────────────┐ │ │ │ ack_timer ──────── 100ms ─────▶ router_ack_timer_cb│ │ (периодический) │ │ │ │ idle_ack_timer ─── 500ms ─────▶ router_idle_ack_cb │ │ (досылка последнего ACK) │ │ │ │ watchdog_timer ── 500ms ─────▶ router_send_watchdog│ │ (страховка инварианта drain, force kick) │ │ │ └─────────────────────────────────────────────────────┘ ``` ## Флаги seq ``` ┌──────────────────────────────────────────────────┐ │ CLOSE: 0x80000000 нормальное закрытие conn │ │ RST: 0x40000000 conn не найден (reset) │ │ │ │ CLOSE/RST-пакеты: seq=0 | флаг, pl_len=0 │ │ Data-пакеты: seq=tx_seq, pl_len>0 │ │ ACK-пакеты: seq=rx_seq, pl_len=0 │ └──────────────────────────────────────────────────┘ ```