# Задача: продолжить исправление TCP-proxy (крупные файлы через транзит) Продолжение незавершённой работы. Читай целиком, прежде чем что-то менять. ## 1. Что уже сделано (НЕ переделывать) Унифицирован формат доставки сервисных кодограмм роутером. Раньше сервисы определяли источник через `conn->peer_node_id`, что ломало транзит (при промежуточном узле это был id промежуточного, а не реального клиента). Теперь роутер доставляет сервисам единый формат: ``` [svc_id(1)][src_node_id(8)][dst_node_id(8)][payload...] ``` где src/dst — реальные end-to-end узлы из SVC_ROUTE заголовка. Ключевые константы в `src/routing_layer/etcp_router.h`: `ROUTER_SVC_SRC_OFF=1`, `ROUTER_SVC_DST_OFF=9`, `ROUTER_SVC_PAYLOAD_OFF=17`, `ROUTER_SVC_HDR_SIZE=17`. Изменённые файлы (все в некоммиченном состоянии, `git status`): - `src/routing_layer/etcp_router.{h,c}` — `router_deliver` + `router_deliver_loopback` + CLOSE/RESTART - `src/proxy/tcp_proxy_server.{h,c}` — recv +8→+16 сдвиг, `src_node_id` из `dgram[1..8]` - `src/proxy/tcp_proxy_client.{h,c}` — recv сдвиг + счётчики `bytes_to_exit/from_exit/bp_count` - `src/proxy/udp_proxy.{h,c}`, `src/proxy/icmp_proxy.{h,c}` — сдвиг + убран встроенный node_id - `src/nat_transport.c` — убран встроенный node_id - `src/routing_layer/routing.c`, `conn_mgr_core.c` — сдвиг - `src/media_delivery/media_delivery.c` — сдвиг + `from_node` из `dgram[1..8]` - `tools/chatgui/transport/utun_node.cpp` — сдвиг - тесты `tests/test_etcp_router*.c`, `test_udp_proxy.c`, `test_icmp_proxy.c`, `test_nat_transport.c` - `tests/tcp_proxy_full/` — добавлен `intermediate.conf` (3-узловая топология), обновлены `client.conf`/`exit.conf`/`run_test.sh` **Статус проверки: `./check.sh` = 73 passed, 0 failed, 1 skipped.** Форматная часть ВЕРНА. ## 2. Что осталось (собственно задача) Интеграционный тест прокси `tests/tcp_proxy_full/run_test.sh` (нужен sudo) — крупные передачи НЕ проходят. Проверено: проблема **НЕ в формате и НЕ в транзите** — прямой 2-узловой прогон падает идентично. Это предсуществующая проблема прокси/lwIP. Результат `sudo ./tests/tcp_proxy_full/run_test.sh`: - `idle` (32 КБ) — PASS - `half_close` (64 КБ) — 64280/65536 (почти, теряется хвост) - `basic_1mb` (1 МБ), `concurrent_*`, `stress` — FAIL (timeout) ## 3. Диагноз (уже установлен по логам) Пропускная способность ~24 КБ/с (1 пакет 1460 байт за ~60 мс). Для 1 МБ это ~43 с → таймаут 10 с. Цепочка (видна в добавленных логах `PROXY BP *` и `SEND_Q_ACKED`): 1. Клиент быстро шлёт 320 пакетов (initial burst ~33 МБ/с). 2. Роутерный `send_q` переполняется (`router_send_q: FULL count=64`) → `etcp_route_send` возвращает -1. 3. Прокси в `tcp_proxy_client_recv_cb` ставит одиночный `tx_buf` и **сбрасывает** дальнейшие данные (`if (pc->tx_buf) { pbuf_free(p); return; }`) — лог `PROXY BP drop`, без `tcp_recved` → окно lwIP схлопывается. 4. Клиент вырождается в режим «1 пакет за ~60 мс». 5. ~60 мс = таймер lwIP: `ctx->tmr_interval_ms = TCP_TMR_INTERVAL / 4` = 62.5 мс (`src/lwip_tcp/lwip_tcp.c:106`). В режиме «пакет-за-пакетом» lwIP не шлёт ACK сразу (порог `TCP_WND_UPDATE_THRESHOLD = TCP_WND/4 = 2920` не набирается за 1 пакет), и отложенный ACK уходит только по `tcp_fasttmr` раз в 62.5 мс. Триггер — сброс данных при backpressure в прокси (`tcp_proxy_client.c`), НЕ lwIP. ## 4. Ограничения (ВАЖНО) - **В `src/lwip_tcp/` ничего НЕ править без явного согласования пользователя. Разрешён только debug-лог.** - Правки исходников только через Edit tool (никакого sed для массовых замен). - Ошибки/нештатные ветки — обязательно `DEBUG_ERROR/DEBUG_WARN`. - Логи информативные, без спама. Debug-категории: `proxy`, `etcp_route`. - Перед сборкой `make clean`. ## 5. План продолжения Шаг 1 (диагностика, закрыть пробел — по желанию, уже почти доказано): - Добавить в lwIP только DEBUG-лог (разрешено): в `tcp_fasttmr` — счётчик отложенных ACK; в `tcp_recved` — `rcv_wnd`. - В прокси (`tcp_proxy_client.c`, НЕ lwIP) в момент `BP drop` лог `pcb->rcv_wnd`. - Подтвердить, что 62.5 мс — это отложенный ACK. Шаг 2 (фикс, требуется согласование — предложи пользователю): - **Вариант A (предпочтительно, НЕ lwIP):** в прокси заменить одиночный `tx_buf` + сброс на очередь с backpressure (буферизовать все пакеты во время backpressure, не сбрасывать; `tcp_recved` вызывать по мере отправки). Тогда окно закрывается плавно, OS TCP не сваливается в congestion avoidance, деградации нет. Смотри `struct tcp_proxy_client_conn` в `tcp_proxy_client.h` (сейчас: `tx_buf`/`tx_len`/`tx_waiter`). Для образца очереди с backpressure смотри `src/proxy/tcp_proxy_server.c` (там `read_queue` + `pause_waiter`). - **Вариант B (lwIP, только по согласованию):** уменьшить `tmr_interval_ms` (`TCP_TMR_INTERVAL/4` → чаще), либо повысить `TCP_WND_UPDATE_THRESHOLD`/`TCP_WND`, чтобы ACK слался сразу. Шаг 3 (почистить тестовую обвязку): - В `tests/tcp_proxy_full/*.conf` сейчас включены отладочные уровни (`etcp_route=trace`, `proxy=trace`) — перед коммитом вернуть к разумным (`etcp_route=debug`/`proxy=debug` или `error`). - Решить: оставить добавленные в `tcp_proxy_client.c` счётчики/BP-логи или вычистить. - В `run_test.sh` тайминги `elapsed` через `date +%s%3N` дают мусор в этой среде — поправить (например `date +%s` без миллисекунд, или `SECONDS`). Stress-таймаут `60s` можно уменьшить до ~15-20s. ## 6. Сборка и тест ```bash ./build.sh --full -j4 # или make -j4 (после make clean) ./check.sh # unit-тесты: должны остаться 73 passed cp src/utun utun # обновить корневой бинарник для run_test.sh! sudo ./tests/tcp_proxy_full/run_test.sh # интеграционный (нужен root/TUN/iptables) ``` Важно: `run_test.sh` использует `UTUN_BIN="$SCRIPT_DIR/../../utun"` (корневой `utun`), а `make` собирает `src/utun`. После каждой пересборки делать `cp src/utun utun`. ## 7. Как быстро убедиться, что фикс помог Прогнать `basic_1mb` и смотреть лог клиента `tests/tcp_proxy_full/log/client_utun.log`: - ДО фикса: `PROXY BP drop` + `SEND_Q_ACKED` с интервалом ~60 мс. - ПОСЛЕ фикса: нет `BP drop`, интервал ACK ~10 мс, `basic_1mb` PASS. Полезные grep-и по логу клиента: ``` grep -E "PROXY BP|SEND_Q_ACKED|PROXY FIN" client_utun.log ```