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