From 11aa760d4789d30120d8a9a2062b6bc7fab60724 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 22 Oct 2026 22:57:06 +0200 Subject: [PATCH] Document ETCP window ownership and completed connection fixes --- doc/etcp_protocol.txt | 66 +++++++++++++++++++++++++------------------ doc/tasks.md | 36 +++++++++++------------ 2 files changed, 57 insertions(+), 45 deletions(-) diff --git a/doc/etcp_protocol.txt b/doc/etcp_protocol.txt index 6663f769..56e09271 100644 --- a/doc/etcp_protocol.txt +++ b/doc/etcp_protocol.txt @@ -1,21 +1,25 @@ Формат кодограммы: ------------------ -пакеты идут по следующему пути: -пакет кем-то помещается в входную очередь (ETCP input_queue). -Когда load balancer готов отправить следующий пакет он делает вызов etcp_request_pkt в etcp.c/h и etcp либо формирует следующую кодограмму или встаёт в состояние async ожидания (возвращая null): - - либо wait ETCP input_queue (если нечего передавать) - - либо wait SACK + wait timeout (если sack очередь полная и нет необслуженных запросов ретрансмиссий). после истечения wait timeout он помечает используя round-robin очередной пакет как "нужна ретрансмиссия". -из async ожидания он может вызвать etcp_loadbalancer input_from_etcp и передать пакет на отправку. -как формируется пакет: - 1. выбираем канал передачи (etcp_loadbalancer_select_link) - 2. если есть место в rwin (объём inflight данных) то берем очередной пакет из ETCP input_queue и его отправляем - 3. если пакет найден или есть неподтверждённые ACK: - 1. вызываем функцию формирования опциональных секций (ACK, channel timestamp) - они записываются в начало - 2. в конец добавляем секцию 0x00 с данными пакета - при отправке пакета помечаем в inflight списке с какого интерфейса он отправлен -Функция прикрепления опциональных секций: - - запрашивает выбор канала передачи. - - добавляет накопившиеся ACK +Кодограмма сервиса через etcp_send попадает в normalizer; его фрагменты поступают в +input_queue, затем в indexed input_send_q с назначенным seq. +Очереди, ACK/RTO, изменение окна и готовность линка вызывают etcp_conn_wake_sender. +Один call_soon обслуживает накопленные события без рекурсивного входа и задержки таймером. +При OOM выделения сетевого пакета очередь сохраняется; retry назначается через 10ms. +close/reset отменяет ожидающий запуск. Не подтверждённая сессия не передаёт DATA/ACK; +HELLO/CHALLENGE/CONFIRM и keepalive передаются своим независимым путём. + +Формирование пакета: + 1. Посмотреть следующий DATA и его длину, не извлекая его из input_send_q. + 2. Выбрать инициализированный UP-линк с готовым shaper и местом для целого DATA: + inflight_bytes + длина DATA <= inflight_lim_bytes. Проверка выполняется без переполнения. + Среди допустимых выбирается минимум inflight_bytes, при равенстве — round-robin. + 3. Если DATA не помещается, он остаётся в очереди; для накопленных ACK выбирается + рабочий линк без ограничения DATA-cwnd, с учётом shaper. + 4. Выделить ETCP_DGRAM до удаления DATA из очереди и изменения счётчиков. + Отправляемый DATA занимает окно выбранного линка и переходит в input_wait_ack. + Пакет содержит ACK, опциональные timestamp/измерительные секции и последнюю секцию PAYLOAD. + 5. В активной ограниченной burst-серии pacing обходится; DATA всё равно должен + помещаться в окно, иначе серия отправляет FILLER. ACK/FILLER окно DATA не занимают. При приёме пакета (от etcp_connections): - последовательно сканируем секции и отдаём их на обработку нужным обработчикам: - ack (0x01): помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также снимаем блокировку отправки @@ -25,7 +29,10 @@ пересканирование inflight: - запускается при добавлении пакета в очередь ожидания ACK (wait_ack_cb). устанавливается таймер на первый неистекший пакет. - - если время последней отправки > RTT_avg10*K1+jitter*K2 (коэффициенты K1=32, K2=32, деление на 16) то пакет заново отправляется + - возраст последней отправки сравнивается с (RTT_avg10*32+jitter*32)/16, + ограниченным диапазоном 50..10000 таймбаз (5ms..1s). + - истёкший пакет освобождает окно прежнего линка и переходит в input_send_q; + его новая попытка проходит обычный допуск по окну и pacing выбранного линка. - очередь FIFO (сортирована по timestamp добавления = времени отправки), сканирование прекращается на первом неистекшем пакете - по истечению таймера (ack_timeout_cb) снова вызывается проверка очереди @@ -39,21 +46,26 @@ inflight - две очереди (ll_queue): - timestamp последней отправки (last_timestamp) - число отправок (send_count) - send_hist[8] - история через какие линки отправлялся (индекс линка) - - inflight_bytes/inflight_packets per-link отслеживается в ETCP_LINK -Размер буфера inflight лимитируем как сумму по всем активным линкам optimal_inflight. + - inflight_bytes/inflight_packets линка равны сумме длин/числу WAIT_ACK с этим last_link. + WAIT_SEND окно не занимает, в том числе после RTO. last_link сохраняет историю попытки. + Поздний ACK удаляет WAIT_SEND без повторного вычитания; duplicate ACK учёт не меняет. + Удаление линка обнуляет last_link в обеих очередях до освобождения его памяти. +Общий optimal_inflight равен сумме лимитов всех линков, с насыщением UINT32_MAX; +это дополнительный backpressure подачи новых фрагментов, не замена индивидуальных окон. Отдельно ограничиваем размах sequence относительно кумулятивного ACK: новый seq выдаётся только если (uint32_t)(next_tx_id - rx_ack_till) <= MAX_INFLIGHT_SIZE (16384). Selective ACK освобождает отдельную inflight-запись, но не продвигает эту границу: приёмник пока не может освободить пакеты за дыркой. Кумулятивный ACK возобновляет input_queue даже если подтверждённые записи уже удалены по selective ACK. ACK ограничивается последним назначенным seq (next_tx_id - 1); сравнения учитывают uint32 wrap. -optimal_inflight расчитывается из RTT и bandwidth линка. - -bandwidth по каждому линку адаптивно подстраиваем: -Периодически в stats_timer_cb собираем статистику окна (rtt_avg10, ретрансмиссии, переданные пакеты). -BW рассчитывается как: inflight_lim_bytes * 8 / rtt_min (в секундах) * 8 / 1024 * 1.3 (Kbits/sec). -Размер inflight_lim_bytes плавно подстраивается вверх/вниз в зависимости от того, растёт ли RTT. -Burst-измерения bandwidth (0x07, 0x08) зарезервированы, но на данный момент не реализованы. +BBR каждого линка получает ACK/loss samples и возвращает cwnd и pacing rate. +Лимит DATA — не менее 8192 байт и не более bbr_max_cwnd (по умолчанию 1048576). +Отказ из-за нехватки окна сохраняет cwnd_limited до следующего ACK: вычитание ACK +не должно скрывать этот факт от BBR. app_limited учитывает ожидающие очереди и +pending/частичный буфер normalizer; ограничение окном не означает отсутствие данных. +Потеря для BBR учитывается при RTO, фактическая повторная передача — при новой попытке. +При уменьшении окна уже отправленные пакеты остаются в учёте; новые DATA ждут ACK/RTO. +Измерительные секции burst 0x07/0x08 и FILLER поддерживаются реализацией. **** Формат кодограмм для etcp.c/h **** @@ -107,7 +119,7 @@ Burst-измерения bandwidth (0x07, 0x08) зарезервированы, - обновление текущего количества неподтвержденных данных inflight (в пакетах и байтах) - сколько по этому каналу отправленных и неподтвержденных на текущий момент Планировщик/балансир каналов (etcp_loadbalancer.c/h): для каждого канала ограничение полосы с тестированием отклика: -- полосу ограничиваем по объему данных - полному числу байт ethernet пакта (добавляем размер заголовков) +- временную нагрузку shaper считаем по data_len + 2 байта на пакет (текущая реализация). Ограничение реализуем следующим образом: - в структуре есть счётчик времени (квантизация стандартная 0.1ms) по которое канал загружен данными. И суб-разряды (nanotime) этого счетчика 0.1ns - 0.1ms (считает по модулю 1000000 и переполнения добавляет в основной счетчик) чтобы при высоких скоростях не было потери точности. Этим счётчиком контролируется полоса: при передаче к нему добавляется расчётное время передачи текущего ethernet пакета (используя текущий bandwidth считаем время передачи одного байта (float) и умножаем на число байт, переводим в таймбазу nanotime). А если при передаче время меньше current_time - delta_time (delta_time - константа в define, x0.1ms) то он устанавливается как current time-delta_time. (при неактивности обновляем время) diff --git a/doc/tasks.md b/doc/tasks.md index dc209364..d1ebbbc7 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -4,25 +4,26 @@ ## Текущие задачи -[ ] **ETCP: etcp_connect выдаёт готовность индексированного DOWN/reset-соединения** — воспроизведено 22.10.2026: - Ветка conn && !ctx в etcp_connect.c:281 проверяет только state==0; state==1 принимается за готовность. - Стенд на реальных очередях/реестре вызывает EARLY и LATE при links_up=0, initialized=1; - также при links_up=1, initialized=0, reinit_pending=1, session_required=1. Возврат 0 в обоих случаях. - Это противоречит контракту готовности в etcp_connect.h/etcp_api.h; state отражает индексирование, не UP сессии. - Проверить реальные вызывающие NCD/CM, определить условие готовности и ожидание восстановления/подтверждения; - отдельно учесть close_requested и повторную подписку после уже выданного EARLY. Комментариями проблему не маскировать. - Стенд вне репозитория /tmp/protocol-header-audit.c; лог /tmp/protocol-header-audit.log. +[+] **ETCP: единый установщик прямого транспорта через NCD** — исправлено 22.10.2026: + Удалён неиспользуемый рабочим кодом etcp_connect с EARLY/LATE, отдельными линками и таймерами; + три тестовых файла переведены на NCD handles с начала подключения. Сохранены проверки независимых владельцев, + отмены отложенного UP, TIMEOUT, выхода из UTUN с сохранением транспорта и shutdown с pending LEAVE. + Состояние реестра, физический UP, готовность ETCP-сессии и READY группы разделены в контрактах. + Начальный отправитель не создаёт преждевременный UP. Merkle-фикстура ждёт готовности обоих потоков. -[ ] **ETCP: проверить распределение cwnd/inflight между линками** — воспроизведено 22.10.2026: - etcp_loadbalancer.h обещает учёт cwnd/inflight в select_link; реализация фильтрует initialized/link_status/shaper - и выбирает минимум inflight_bytes, но inflight_lim_bytes не проверяет. При штатных лимитах стенд выбирает - полный линк 8192/8192 вместо свободного 16384/65536; сессия готова, общее окно свободно, shaper/burst отсутствуют. - Вызов etcp_request_pkt после выбора добавляет inflight выбранному линку; общий допуск данных ограничивается отдельно. - Определить границы ответственности общего окна/BBR/выбора линка, отдельно ACK/control/retrans/burst; - проверить, где требуется ограничение отдельного линка, и согласовать реализацию и контракт. Не добавлять слепой запрет, - способный заблокировать ACK или retrans. Стенд/лог: /tmp/protocol-header-audit.c, /tmp/protocol-header-audit.log. +[+] **ETCP: индивидуальные окна DATA, ACK и корректный учёт повторов** — исправлено 22.10.2026: + Выбор линка проверяет место для целого DATA, ACK не блокируется DATA-cwnd; burst без места использует FILLER. + WAIT_ACK занимает окно, RTO освобождает его до постановки в WAIT_SEND; поздний ACK не вычитает байты повторно. + BBR получает факт ограничения окном до вычитания ACK; app_limited учитывает подготовленные данные и normalizer. + Один call_soon объединяет пробуждения без таймерной задержки/рекурсии; OOM retry через 10ms, reset/close отменяет token. + Невозможность запланировать отправку закрывает транспорт отложенно, сохраняя вызывающий стек. + Проверяются размер max_cwnd, переполнение суммы окон; ошибки [global] теперь отклоняют конфигурацию. + Новый test_etcp_windows: 12 сценариев настоящих очередей/отправителя, сверка счётчиков и внедрение отказов. + STCP-тест проверяет каждый байт двустороннего 1MiB трафика при окнах 8KiB и независимых тестовых командах. + ASAN/UBSan с detect_leaks=1: windows/session/transport/connect/stcp_traffic/NCD, 6/6; + результаты /tmp/etcp-asan-final-results.log, логи /tmp/etcp-asan-final-logs/. -[ ] **Аудит .h ETCP/STCP: исправить подтверждённые расхождения контрактов** — проверено 22.10.2026, исходники не менялись: +[ ] **Аудит .h ETCP/STCP: оставшиеся расхождения контрактов** — проверено 22.10.2026: stcp.h: do_close уже очищает оба sc_stream и планирует free для allocated/free_on_close; комментарий утверждает обратное. Разделить контракты finish (drain/запрет новых данных/до 1с) и do_close (немедленное закрытие/потеря очередей/callback). Описать владение внешними rx_queue/tx_queue, преусловия размера frame и callback/reentrancy/lifetime. @@ -34,7 +35,6 @@ etcp.h: ref_count удерживает объект/phase-2, но close уже освобождает normalizer и линки в phase-1; reset_done не блокирует reinit (в реализации только запись/диагностика); last_rx_id не обновляется при RX. etcp_connection_ready объявлена без реализации/вызовов; рабочая функция — etcp_conn_ready. - etcp_loadbalancer.h ссылается на отсутствующее packet_request_fn; фактически вызывается link_ready_for_send_fn. etcp_connections.h проверен частично: у data_len исключены все 3 байта timestamp/flag, не только timestamp; send_hook уступает SOCKS UDP; etcp_send_tcp_ping при отказе запуска возвращает отрицательный код без callback. etcp_session.h и etcp_padding.h сверены с реализацией без подтверждённых смысловых расхождений.