Browse Source
rx_acked was used for two conflicting purposes: 1. remote ack of our sends (inflight = tx_seq - rx_acked) 2. our last sent ACK seq (dedup: rx_seq != rx_acked) When the ack timer fired and set rx_acked = rx_seq, it overwrote the inflight-tracking value. If rx_seq > tx_seq, the computation tx_seq - rx_acked underflowed (e.g. 18 - 24 = 0xFFFFFFFA), permanently blocking router_drain_send_q and causing send_q to grow indefinitely. Fix: - Split rx_acked into tx_acked (remote ack, for inflight) and last_sent_ack_seq (our ACK, for dedup) - Incoming ACK handler only advances tx_acked forward (stale guard) - Use int32_t cast on all inflight comparisons to handle stale states - Add etcp_router architecture diagram (doc/etcp_router_arch.md)etcp-inflight-fix
4 changed files with 353 additions and 22 deletions
@ -0,0 +1,324 @@
|
||||
# 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_resume_timer: void* retry send_q 50ms │ |
||||
│ 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() |
||||
│ ещё нет │ |
||||
└────────┬────────────┘ |
||||
│ |
||||
▼ |
||||
┌──────────────────────────────┐ |
||||
│ inflight < ROUTER_MAX(256)? │ |
||||
│ tx_seq - tx_acked < 256 ? │ |
||||
└──────┬───────────┬───────────┘ |
||||
│ YES │ NO (inflight полон) |
||||
▼ ▼ |
||||
┌──────────────┐ ┌─────────────────────┐ |
||||
│router_send_one│ │ router_enqueue_send │ → send_q (FIFO) |
||||
│ (прямая) │ │ (в очередь) │ send_blocked = 1 |
||||
└──────┬────────┘ └─────────┬───────────┘ send_resume_timer (50ms) |
||||
│ │ |
||||
▼ │ |
||||
┌────────────────────┐ │ |
||||
│ tx_seq++ │ │ |
||||
│ hdr.dst = dst │ │ |
||||
│ hdr.src = node_id │ │ |
||||
│ hdr.seq = tx_seq │ │ |
||||
│ hdr.svc_id = svc │ │ |
||||
│ │ │ |
||||
│ etcp_send(conn, e) │ │ |
||||
└────────────────────┘ │ |
||||
│ |
||||
┌────────────────────────────┘ |
||||
│ (при получении ACK от remote) |
||||
▼ |
||||
┌────────────────────┐ |
||||
│ router_drain_send_q│ вызывается из ACK handler |
||||
│ │ или по send_resume_timer |
||||
│ while inflight < 256│ |
||||
│ e = send_q.pop() │ |
||||
│ router_send_one(e)│ |
||||
│ │ |
||||
│ если send_q пуст: │ |
||||
│ send_blocked = 0 │ |
||||
└────────────────────┘ |
||||
``` |
||||
|
||||
## 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? │ |
||||
│ │ (только ││ │ |
||||
│ │ вперёд) ││ Дубликат? │ |
||||
│ │ drain_q ││ 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: │ |
||||
│ router_drain_send_q(rconn) │ |
||||
└────────────────────────────────────────┘ |
||||
``` |
||||
|
||||
## Inflight Control |
||||
|
||||
``` |
||||
ROUTER_MAX_INFLIGHT = 256 |
||||
|
||||
Отправка возможна когда: tx_seq - tx_acked < 256 |
||||
Отправка заблокирована: tx_seq - tx_acked >= 256 |
||||
|
||||
При блокировке: |
||||
данные → send_q (FIFO, без дропов) |
||||
send_blocked = 1 |
||||
send_resume_timer = 50ms retry |
||||
|
||||
Разблокировка: |
||||
приход ACK от remote → tx_acked обновляется |
||||
→ router_drain_send_q() разгребает send_q |
||||
или send_resume_timer (50ms) → router_drain_send_q() |
||||
|
||||
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) │ |
||||
│ │ |
||||
│ send_resume_timer ─ 50ms ─────▶ router_send_resume │ |
||||
│ (retry когда send_q не пуст) │ |
||||
│ │ |
||||
└─────────────────────────────────────────────────────┘ |
||||
``` |
||||
|
||||
## Флаги 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 │ |
||||
└──────────────────────────────────────────────────┘ |
||||
``` |
||||
Loading…
Reference in new issue