Document ETCP window ownership and completed connection fixes

This commit is contained in:
evgeny committed 2026-10-22 22:57:06 +02:00
1 parent bc27352710
commit 11aa760d47
2 files changed
+57 -45

No files matched your search

+39 -27
View File
@@ -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. (при неактивности обновляем время)
+18 -18
View File
@@ -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 сверены с реализацией без подтверждённых смысловых расхождений.