- Router: unified service delivery format [svc_id][src][dst][payload] (end-to-end src/dst from SVC_ROUTE header) - tcp_proxy_client: replace single tx_buf drop-on-backpressure with bounded tx_queue + send_q waiter + retry timer (no data loss, bulk window reopen — removes 62.5ms delayed-ACK stall) - tcp_io: configurable connect timeout (tcp_conn_set_connect_timeout) - tcp_proxy_server: connect_timeout config option (ms, default 2000, 0=off) - check.sh: run tcp_proxy_full integration test with sudo (skip if passwordless sudo unavailable) - test_tcp_io: connect timeout unit test; docs (lwip_nuances.md)
67 lines
4.7 KiB
Markdown
67 lines
4.7 KiB
Markdown
# Важные нюансы работы встроенного lwIP TCP-стека
|
||
|
||
Краткая памятка по тонким местам `src/lwip_tcp/`, критичным для прокси.
|
||
Полное описание модуля — в `src/lwip_tcp/lwip_tcp_doc.md`.
|
||
|
||
## Ключевые константы (`lwip_tcp_opts.h`)
|
||
|
||
| Константа | Значение | Смысл |
|
||
|-----------|----------|-------|
|
||
| `TCP_MSS` | 1460 | максимальный размер сегмента |
|
||
| `TCP_WND` | 8×MSS = 11680 | окно приёма lwIP (объявляется пиру) |
|
||
| `TCP_SND_BUF` | 16×MSS = 23360 | буфер отправки |
|
||
| `TCP_TMR_INTERVAL` | 250 мс | базовый интервал таймера |
|
||
| `tmr_interval_ms` | `TCP_TMR_INTERVAL/4` = 62.5 мс | фактический шаг `tcp_fasttmr` |
|
||
| `TCP_WND_UPDATE_THRESHOLD` | `TCP_WND/4` = 2920 | порог немедленного ACK окна |
|
||
|
||
**Важно:** таймер uasync использует timebase 0.1 мс — `uasync_set_timeout(ua, N, …)`
|
||
задаёт N×0.1 мс. Например, 5000 = 500 мс.
|
||
|
||
## Контракт recv_cb — главный источник багов
|
||
|
||
lwIP вызывает `recv_cb(arg, pcb, pbuf, err)` для данных, **уже извлечённых из pcb**.
|
||
Дальше приложение само решает судьбу данных и окна:
|
||
|
||
| Действие | Результат |
|
||
|----------|-----------|
|
||
| скопировать + `tcp_recved(pcb, len)` | данные приняты, окно восстановлено (норма) |
|
||
| `return != LERR_OK` | lwIP кладёт pbuf в `refused_data` и отдаст позже — **без потери**, окно закрывается |
|
||
| `pbuf_free(p)` без `tcp_recved` | данные потеряны, окно не восстановлено → схлопывание окна |
|
||
|
||
`refused_data` — штатный backpressure lwIP: одноканальный (один pbuf), ре-доставка
|
||
через `tcp_fasttmr` (62.5 мс) или при следующем входящем сегменте.
|
||
|
||
**Ошибка, из-за которой падал `tcp_proxy_full`:** при backpressure прокси делал
|
||
`pbuf_free(p)` без `tcp_recved`. Каждый такой сброс уменьшал `rcv_wnd` на MSS; после
|
||
~8 пакетов окно падало в 0, OS-TCP переставал слать, и передача умирала.
|
||
|
||
## Управление окном и «деградация 62.5 мс»
|
||
|
||
`tcp_recved(pcb, len)` увеличивает `rcv_wnd` и через `tcp_update_rcv_ann_wnd` решает,
|
||
слать ли ACK окна немедленно. Немедленный ACK идёт только если прирост окна
|
||
`wnd_inflation ≥ TCP_WND_UPDATE_THRESHOLD` (=2920 = 2×MSS); иначе ACK откладывается
|
||
до `tcp_fasttmr` (62.5 мс).
|
||
|
||
Отсюда классическая ловушка: если после backpressure окно восстанавливать **по одному
|
||
пакету** (`tcp_recved(1460)` за раз), прирост 1460 < 2920 → ACK уходит раз в 62.5 мс →
|
||
пир шлёт 1 пакет за 62.5 мс ≈ 23 КБ/с. Это выглядит как «пропускная способность
|
||
застряла», хотя сеть свободна.
|
||
|
||
**Правильно:** восстанавливать окно **пачкой** (несколько `tcp_recved` подряд либо один
|
||
`tcp_recved(TCP_WND_MAX - rcv_wnd)`), тогда прирост ≥ 2920 → ACK немедленный → пир
|
||
сразу возобновляет бурст.
|
||
|
||
## Рекомендуемый паттерн для relay-потребителя (как в `tcp_proxy_client.c`)
|
||
|
||
1. При получении данных — копировать в ограниченную очередь, **не вызывать**
|
||
`tcp_recved` (окно само плавно закрывается; очередь ограничена `TCP_WND`).
|
||
2. Дрейн очереди по сигналу освобождения нижележащего канала
|
||
(`etcp_router_on_send_ready`) + retry-таймер (force=1) как safety-net.
|
||
3. `tcp_recved(len)` вызывать **только после успешной отправки** — окно
|
||
восстанавливается пачкой по мере дрена, ACK уходит немедленно.
|
||
4. Никогда не сбрасывать pbuf без `tcp_recved`; не держать данные в `refused_data`
|
||
дольше одного пакета (single-slot).
|
||
|
||
См. реализацию: `tcp_proxy_client_tx_queue_drain_cb` / `tcp_proxy_client_fin_flush`
|
||
в `src/proxy/tcp_proxy_client.c`.
|