- u_async.c: process immediate_queue BEFORE epoll_wait in uasync_poll - stcp_link.c: defensive NULL etcp_conn/etcp_link in stcp_link_close - utun_instance.c: move stcp_server_list_destroy_all to Phase J (before ETCP), add deferred drain in both Phase J and L - stcp.h/c: STCP_HS_ENC_CLIENT 6→38, STCP_HS_ENC_SERVER 7→39, exchange ed25519_pubkey in both handshake directions - etcp_connections.c: copy peer_ed25519 from STCP to ETCP_CONN in etcp_link_enter_ready_tcp; guard link->etcp in tcp_link_close_cb - chat_sync.c: fix lk->conn NULL dereference for TCP links - test_stcp.c: update stcp_server_create/stcp_client_connect callsites - tools/chat_tcp_test: change transport udp→tcp
7.4 KiB
ETCP Router (etcp_router)
1. Назначение
Сервисный слой маршрутизации поверх ETCP — упрощённый TCP с восстановлением порядка доставки, дедупликацией и контролем inflight. Мультиплексирует до 256 сервисов (svc_id) на одном ETCP-соединении. Поддерживает transit (многошаговую маршрутизацию через промежуточные узлы), ретрансмиты, minRTT-измерения и подписи Ed25519.
2. Как пользоваться
Инициализация
// В utun_instance_init():
etcp_router_init(inst);
// Зарегистрировать обработчик сервиса (svc_id 0..255)
etcp_router_bind(inst, svc_id, my_service_callback);
Отправка
// Простой способ: сформировать ll_entry с svc_id в первом байте
struct ll_entry* e = queue_entry_new(0);
e->dgram = u_malloc(1 + payload_len);
e->dgram[0] = svc_id;
memcpy(e->dgram + 1, payload, payload_len);
e->len = 1 + payload_len;
etcp_route_send(inst, dst_node_id, e, force);
// Или через существующий ROUTER_CONN:
struct ETCP_ROUTER_CONN* rconn = etcp_router_conn_get(inst, remote_node_id, svc_id);
etcp_router_conn_send(rconn, data, len);
Приём
void my_service_callback(struct ETCP_CONN* conn, struct ll_entry* entry) {
if (!entry) { /* соединение закрыто */ return; }
uint8_t svc_id = entry->dgram[0];
uint64_t from = *(uint64_t*)(entry->dgram + 1); // remote_node_id
// ... данные начиная с entry->dgram[9] ...
queue_dgram_free(entry);
queue_entry_free(entry);
}
Backpressure
// На send_q (inflight переполнен):
struct queue_waiter_handle h;
etcp_router_on_send_ready(inst, node_id, svc_id, &h, my_ready_cb, my_arg);
// Когда send_q освободится — вызовется my_ready_cb
// На normalizer->input (сетевая очередь):
etcp_router_waiter_register(inst, node_id, &h, my_ready_cb, my_arg);
Подписанные сообщения
etcp_router_conn_send_signed(rconn, data, len); // добавляет Ed25519-подпись
3. API
Ключевые структуры
SVC_ROUTE_HDR (25 байт) — заголовок пакета:
cmd(1) + dst_node_id(8) + src_node_id(8) + seq(4) + svc_id(1) + flags(1) + timestamp(2)
Флаги: ROUTER_FLAG_START (0x80), ROUTER_FLAG_RST (0x40), ROUTER_FLAG_SIGNED (0x08), ROUTER_FLAG_CLOSE (0x02). Sess_id в bits 5-4.
ETCP_ROUTER_CONN — состояние логического подключения (remote_node_id + svc_id):
tx_seq,rx_seq,tx_acked— seq-нумерация для порядка и inflightrecv_q— reorder-очередь с хеш-индексом по seq (восстановление порядка)send_q— очередь ожидания при полном inflight (backpressure)inflight_q— копии отправленных пакетов для ретрансмита (хеш по seq)incoming_q— FIFO между сетевым приёмом и recv_q (защита от гонок)rtt,rtt_jitter,minrtt— измерения задержкиinflight_limit— текущий лимит пакетов в полёте (minrtt_probe снижает до 4)no_ack_count— последовательные ретрансмиты без ACK (при 17 — закрытие)start_sent,peer_sync_done— синхронизация после (пере)подключенияclosed— флаг закрытия (игнорирование таймеров)
ROUTER_INFLIGHT — запись в inflight_q: seq, last_sent_tb, send_count, payload*
TRANSIT_QUEUE — per (src,dst) пара на промежуточном узле:
q— FIFO транзитных пакетовwaiter— backpressure на send_input_q next_hop'аconn— ETCP_CONN следующего шага
Внутренняя архитектура
Приём: сеть → incoming_q (FIFO) → router_incoming_q_cb → recv_q (хеш по seq)
↓
router_try_assembly → deliver (rx_seq++)
Отправка: router_enqueue_send → send_q (FIFO, backpressure)
router_drain_send_q → router_send_one → etcp_send + inflight_q (копия)
↓
router_track_inflight_state + retrans_schedule
ACK: периодический (10ms) + idle (500ms) → router_send_ack(rx_seq)
Константы
| Константа | Значение | Описание |
|---|---|---|
ROUTER_MAX_INFLIGHT |
256 | Макс. пакетов в полёте |
ROUTER_ACK_INTERVAL_TB |
100 | ACK интервал 10ms (0.1ms timebase) |
ROUTER_ACK_IDLE_TB |
5000 | Idle таймаут 500ms |
ROUTER_RETRANS_TIMEOUT_TB |
3000 | Таймаут ретрансмита 300ms |
ROUTER_NO_ACK_MAX_RETRANS |
17 | Макс. ретрансмитов без ACK (≈5s) |
ROUTER_MAX_SEND_Q_PACKETS |
64 | Порог backpressure send_q |
Функции
| Функция | Описание |
|---|---|
etcp_router_init(inst) |
Инициализация: bind ETCP_ID_SVC_ROUTE, создание router_conns |
etcp_router_destroy(inst) |
Деинициализация: unbind + close_all + free |
etcp_router_bind(inst, svc_id, cb) |
Зарегистрировать обработчик сервиса |
etcp_router_unbind(inst, svc_id) |
Удалить обработчик сервиса |
etcp_route_send(inst, dst, entry, force) |
Отправить пакет (loopback/transit/direct) |
etcp_router_conn_get(inst, remote, svc_id) |
Найти или создать ROUTER_CONN |
etcp_router_conn_send(rconn, data, len) |
Отправить данные с авто-seq и inflight-контролем |
etcp_router_conn_send_signed(rconn, data, len) |
Отправить с Ed25519-подписью |
etcp_router_conn_close(rconn) |
Закрыть seq-подключение (CLOSE + уведомление сервиса) |
etcp_router_conn_close_async(rconn, cb, arg) |
Асинхронное закрытие с коллбэком |
etcp_router_conn_close_all_for_node(inst, node_id) |
Закрыть все conn к узлу |
etcp_router_conn_restart(inst, remote, svc_id) |
Сброс состояния (перезапуск удалённой стороны) |
etcp_router_pause_retrans_for_node(inst, node_id) |
Сбросить ретрансмиты без закрытия (для conn reinit) |
router_set_max_inflight(rconn, new_max) |
Установить рабочий max_inflight |
etcp_router_input_q_count(inst, node_id) |
Размер normalizer->input очереди узла |
etcp_router_waiter_register/cancel(inst, node_id, h, cb, arg) |
Backpressure на normalizer->input |
etcp_router_on_send_ready/cancel_send_ready(inst, node, svc, h, cb, arg) |
Backpressure на send_q |
etcp_router_transit_queues_destroy(conn) |
Удалить все транзитные очереди ETCP_CONN |