Files
utun2/doc/etcp_multipath_plan.txt
T

960 lines
101 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
ETCP: независимая обратная связь каналов и доставка при отказах
Проверка архитектуры и последовательные исправления, 24.10.2026
Статус
24.10.2026: исправлены абсолютные сроки ACK и конкуренция ACK/DATA разных путей.
Обычный ACK-only обслуживается от received_at_us головы своего пути + 2 мс;
piggyback готового DATA и срочный ответ остаются немедленными. Среди готовых ACK
выбирается самый старый срок; DATA другого пути уступает ему, DATA того же пути
забирает его piggyback. Ограниченная измерительная серия заканчивается до смены пути.
Один прежний таймер пересчитывается после piggyback/частичной отправки; новый RX
не отодвигает старые записи после callback таймера. Логический till имеет собственный
абсолютный срок вместо отдельного boolean pending. При OOM таймера используется
существующий флаг срочного ACK, исходные received_at_us не меняются.
Новых очередей/таймеров нет; wire-формат, RTO, min_rtt и модель BBR сохранены.
Две регрессии до фикса воспроизводят ACK в возрасте 0 мкс и обход готового ACK
другого пути исходящим DATA: /tmp/etcp-ack-deadline-before.log,
/tmp/etcp-ack-due-priority-before.log. Новые проверки покрывают границы 1999/2000 мкс,
piggyback разных путей, смену головы, early timer callback, RX между timer/sender,
частичный ACK-кадр, срочный ответ, OOM, sender failure с close, reset и DOWN/UP/удаление.
Диагностика расширена до 106 полей: ранние записи внутри готовой пачки допустимы;
early_ordinary_ack_packets отдельно проверяет отправку всего пакета раньше срока.
Проверка после исправления: windows 60 сценариев PASS с управляемыми и реальными
часами; восемь профильных тестов ETCP/STCP ASAN/UBSan с detect_leaks=1 PASS без
ошибок/утечек. Отдельный ASAN two-stuck с диагностикой 10 мс: 5202 проверенных среза,
ранних ACK-пакетов нет, доставка/ресурсы PASS. Полный check.sh: 184/184, proxy 9/9,
burst 1/1, load 8/8 PASS, 134217728 проверенных байт, 185 с со сборкой.
Три последовательных bidirectional с диагностикой: ранних обычных ACK-пакетов 0
во всех трёх прогонах. На одиночном 8 Мбит/с пути полоса ACK 0.143..0.169 Мбит/с
вместо 0.347/0.390 в предыдущем диагностическом прогоне. Агрегат: A->B
8.667..8.804, B->A 8.695..8.796 из 10 Мбит/с. Сам агрегат проходит все свои пороги
каждый раз; весь профиль остаётся FAIL из-за одиночных каналов, с 3/2/4 нарушениями.
Средний возраст entries ACK-only на 8 Мбит/с может быть меньше 2 мс: внутри готового
кадра передаются и новые entries. Это учитывается отдельно от ранней отправки кадра.
Полная штатная последовательная матрица после калибровки эмулятора: первые шесть
скоростных профилей и все 9 профилей отказов PASS. Агрегированный goodput,
Мбит/с: equal 14.416/16, bandwidth 9.032/10, rtt 14.516/16, fast-distant 8.857/10,
slow-distant 8.978/10, three 11.122/12, bidirectional 8.750/8.768 из 10 на направление.
Остаются два FAIL одиночного дуплекса: 1.609 вместо 1.700 Мбит/с (путь 2 Мбит/с)
и 6.730 вместо 6.800 Мбит/с (обратное направление пути 8 Мбит/с).
Two-stuck: gap 206.227 мс, latency 375.457 мс; load-drop/load-hold: gap 41.402/41.845 мс,
steady goodput 7.229/7.466 из 8 Мбит/с. Max latency насыщенного источника
180.693/89.239 мс записана; отдельным порогом она здесь не ограничена.
Дальше отдельно разбирать недооценку полосы/PROBE_RTT одиночного дуплекса,
STCP backpressure и проверки изменения RTT/BW и повторного отказа.
Логи: /tmp/etcp-ack-deadline-{windows,real-time,full-check}.log,
/tmp/etcp-ack-deadline-asan-test_*.log, /tmp/etcp-ack-deadline-asan-two-stuck.log/.csv,
/tmp/etcp-ack-deadline-matrix.log/.csv, /tmp/etcp-ack-deadline-duplex-<1..3>.log/.csv.
Сводка ACK: /tmp/etcp-ack-deadline-summary.json; актуальный HTML с 106 полями:
/tmp/etcp-ack-deadline-duplex.html. Проверка CSV и JS smoke графиков/фильтров PASS.
Предыдущий этап: диагностика до исправления ACK.
24.10.2026: добавлена буферизованная диагностика multipath с шагом 100 мс.
Интервал задаётся UTUN_MULTIPATH_DIAG_MS=10..1000. Новых таймеров нет: срез снимается
существующим тестовым циклом, фактический dt_us записывается в каждой строке.
Конец фазы добавляет короткий срез, поэтому скорость считают по dt_us, а не всегда по 100 мс.
Данные сохраняются в память и выводятся в CSV/лог после drain каждого стенда;
при провале CHECK уже собранные срезы выводятся перед exit. Переполнение буфера — ошибка.
Этот этап меняет только диагностику теста, алгоритмы ETCP/BBR сохранены.
Запуск из корня репозитория:
UTUN_MULTIPATH_DIAG=/tmp/etcp-duplex.csv ./tests/test_etcp_multipath bidirectional > /tmp/etcp-duplex.log 2>&1
python3 tools/etcp_diag_report.py /tmp/etcp-duplex.csv /tmp/etcp-duplex.html
Для подробного отказа: UTUN_MULTIPATH_DIAG_MS=10 и профиль two-stuck.
Уровень категории debug явно включается самим тестом; без UTUN_MULTIPATH_DIAG новый вывод выключен.
104 поля связывают очередь приложения, partial/pending/ожидания normalizer, очередь
фрагментов, send_q (новые/повторы), wait_ack и recv_q с физическим flight/cwnd пути.
Видны suspended/resume/deferred callbacks, состояние sender, номера seq/till,
общий бюджет DATA, очередь ACK и возраст её головы, возраст старейшей попытки,
RTT/jitter/RTO, BW, режим/фаза BBR, pacing, долг и срок готовности pacer, флаг застревания.
Отдельно записываются DATA TX/первые DATA RX, ACK-only/ACK в DATA, задержки ACK,
обычные ACK-only раньше 2 мс, samples BBR, проверки блокировки окном и pacer,
наблюдаемые min/max flight/cwnd/долга и пики send_q/ACK внутри интервала.
Очереди соединения повторяются в строках его линков — их нельзя суммировать между путями.
У pacer положительный долг допускается строго меньше 1 мс; отрицательное значение —
сырой кредит, который при отправке ограничивается 1 мс. Блокировки выбора — число
проверок, а не длительность ожидания. Пики внутри среза — наблюдения на TX/RX/ACK/выборе,
не непрерывная запись каждого изменения. channel_pending включает propagation delay,
сериализацию и HOLD; channel_peak/delay_max накапливаются с последнего reset_stats.
Wire-байты — UDP без IP/UDP headers; ACK-only включает timestamp, crypto и SESSION.
Piggyback учитывает только положительную ACK-секцию, без повторного начисления crypto.
HTML автономен, без библиотек/сети: выбор стенда/направления/канала/фазы/интервала,
восемь графиков и все поля выбранного курсором среза. PROBE_RTT выделен фоном.
Перед построением проверяются схема CSV, учёт очередей, счётчиков и непрерывность интервалов.
Проверены 598 срезов дуплекса (100 мс), 5208 срезов two-stuck (10 мс) и 5196 срезов
ASAN/UBSan two-stuck с detect_leaks=1 без ошибок/утечек. Намеренный CHECK сохранил
72 среза до выхода. Неверные интервалы 6/6 и повреждённые CSV 3/3 отклонены.
Общий check.sh итоговой версии: 184/184, proxy 9/9, burst 1/1, load 8/8 PASS,
134217728 проверенных байт, 166 с. HTML прошёл JS smoke-проверку восьми графиков,
104 полей, фильтров, диапазона, курсора, пустого диапазона и сброса.
Длинный дуплекс отдельно по-прежнему FAIL по скорости: в последнем прогоне 5 нарушений,
агрегат 8.513/8.337 из 10 Мбит/с. Это результат одного прогона, не доказательство эффекта диагностики на скорость.
На одиночном дуплексном пути 8 Мбит/с итоговые срезы показывают расход ACK 0.347/0.390 Мбит/с.
Обычные ACK-only раньше 2 мс: 1774/1828 и 1961/2019 записей; среднее ожидание 230/271 мкс.
Наблюдаемый пик очереди ACK — 4 записи, включая короткие пики до немедленной отправки.
У side=1 path=1 mask=2 в 4.5..5.4 с есть 4 кодограммы приложения, фрагмент и до 8 DATA
в send_q, а pacing составляет около 6.24..6.26 Мбит/с. В 5.5..5.7 с PROBE_RTT уменьшает
окно примерно с 81.5 до 32.9 тыс. байт; после него pacing возвращается лишь к 6.27..6.35 Мбит/с.
В этих срезах виден заданный моделью темп, а не отсутствие данных у sender.
Обнаружен обход ACK_DELAY посторонним wake sender. Диагностическое отключение PROBE_RTT
повышает скорость одиночного 8 Мбит/с пути примерно с 6.8 до 7.1 Мбит/с, но не закрывает
весь дуплекс. Прототип ожидания общего таймера уменьшает полосу ACK примерно до 0.21 Мбит/с,
однако также не закрывает все пороги. Эти прототипы остаются в /tmp, в протокол не внесены.
Следующий разбор: абсолютные deadlines ACK и переустановка единственного таймера после
piggyback/частичной отправки. Проверка одного наличия timer недостаточна: новый RX между
срабатыванием timer и отложенным sender может повторно задержать уже готовые старые ACK.
Артефакты: /tmp/etcp-duplex-diag-{100ms,10ms,asan,abort}.csv/.log,
/tmp/etcp-duplex-diag-view.html, /tmp/etcp-fault-diag-view.html,
/tmp/etcp-duplex-diag-full-check.log. Полный пакетный трейс и контроль PROBE_RTT:
/tmp/etcp-duplex-next-{trace,no-probe-control,ack-control}.log,
/tmp/etcp-duplex-next-analysis.json; observer — /tmp/etcp-probe-review.c/.mk.
Сводка итоговых срезов: /tmp/etcp-duplex-diag-summary.json.
24.10.2026: реализованы сканирование send_q, история путей пакета и быстрое застревание.
Остались прежние input_send_q/input_wait_ack: первый готовый повтор имеет приоритет,
иначе выбирается первый готовый новый DATA. Заблокированная запись остаётся на месте
и не мешает следующим. Если кандидата нет, допускается одна подгрузка из input_queue
в пределах общего бюджета unacked_bytes/optimal_inflight и приёмного окна пира.
После синхронного callback проверяются reset/close/эпоха; прежний указатель линка не используется.
Во время ограниченной burst-серии готовый повтор другого пути ждёт её завершения;
FILLER сохраняет измерение, а новые DATA не обгоняют этот повтор.
Каждый логический пакет хранит 32 байта битовой истории живых слотов local_link_id.
Пока есть неиспользованный допустимый путь, повтор ждёт его cwnd/pacer, а не начинает
новый обход уже использованных. После полного обхода прежний путь также уступает
другой допустимой альтернативе. История/last_rr_link меняются только после принятия
DATA транспортом; rollback их сохраняет. Удаление линка очищает слот до unlink и
callbacks, поэтому новый линк с тем же local_link_id не наследует старую историю.
Три RTO физических попыток без ACK-прогресса ставят tx_stalled. Пока есть хотя бы
один незастрявший initialized+UP путь, повторы исключают застрявшие, даже когда
рабочий временно занят. Если застряли все — разрешены все UP-пути. Новые DATA
используют прежний выбор и могут проверить восстановление: флаг снимает действующий
ACK собственной попытки, отправленной после установки флага. Старый/чужой ACK
этого не делает. Reset очищает состояние; физический UP, ACK/keepalive и формула RTO
сохранены. Новых очередей, payload-копий, таймеров или ранних повторов до RTO нет.
Windows: 51 сценарий PASS с управляемыми и реальными часами. Покрыты заблокированный
префикс 256 записей, приоритет повторов, общий бюджет, OOM, rollback, полный обход,
ID 255/переиспользование слота из callback, три RTO, старые ACK, восстановление,
все застрявшие пути и reset/close/удаление burst-линка из callback подгрузки.
Восемь профильных тестов ASAN/UBSan с detect_leaks=1 проходят без ошибок/утечек.
Контроль занятости рабочего пути 1 мс теперь проходит: latency 377.012 мс вместо 736.623 мс,
gap 206.810 мс, ready_wait 2.318 мс при бюджете 460.552 мс. Отрицательный контроль 200 мс
по-прежнему проваливает ready_wait (200.764 мс), gap и latency; пороги не ослаблены.
Логи: /tmp/etcp-send-scan-{windows-final,real-time,busy_1ms,ready_delay_200ms}.log,
/tmp/etcp-send-scan-asan-test_*.log.
Полная последовательная матрица после калибровки эмулятора: все 9 профилей отказов PASS.
Two-stuck: gap 217.945 мс, latency 379.997 мс при пределе 462.219 мс, ready_wait 1.184 мс.
Ещё три отдельных повтора two-stuck: 3/3 PASS, latency 374.981..378.557 мс,
gap 203.057..205.265 мс, ready_wait максимум 13.268 мс. Slow-drop: gap 259.464 мс,
latency 264.704 мс при пределе 350.129 мс; это профиль с RTT 200 мс и ожиданием RTO.
Load-drop/load-hold: gap 39.234/39.355 мс, steady goodput 7.221/7.468 из 8 Мбит/с.
Для насыщенного источника проверяются gap и steady goodput; max_latency 161.287/86.306 мс
здесь записана, но отдельным порогом не ограничена. ASAN/UBSan two-stuck/load-drop
также PASS с detect_leaks=1, без ошибок и утечек.
Первые шесть скоростных профилей PASS. Агрегированный goodput
(min(application,fresh DATA), Мбит/с): equal 14.390/16, bandwidth 9.048/10,
rtt 14.244/16, fast-distant 8.841/10, slow-distant 8.966/10, three 11.017/12.
Дуплекс 8.412/8.435 из 10 Мбит/с остаётся FAIL: все 7 нарушений матрицы относятся
к его скоростным порогам (одиночные каналы и агрегат). Доставка, порядок и ресурсы
проходят. Общая multipath-задача остаётся открытой: далее разбирать дуплекс,
STCP backpressure и отдельные проверки смены RTT/BW/повторного отказа.
Логи: /tmp/etcp-send-scan-matrix.log/.csv,
/tmp/etcp-send-scan-native-two-stuck-<1..3>.log/.csv,
/tmp/etcp-send-scan-asan-{two-stuck,load-drop}.log/.csv.
Полный check.sh после изменений: 184/184, proxy 9/9, burst 1/1, load 8/8 PASS;
134217728 проверенных байт, 197 с со сборкой. Отдельная длинная multipath-матрица
в check.sh не входит; её оставшиеся нарушения дуплекса перечислены выше.
Лог: /tmp/etcp-send-scan-full-check.log.
Предыдущая диагностика (до реализации сканирования и истории):
24.10.2026: проверки отказов разделены на RTO, готовность повтора и доставку.
В этом этапе менялись только тесты/диагностика, логика ETCP сохранена.
TX учитывается после принятия send транспортом, а не при подготовке пакета.
Возврат логического DATA в send_q фиксирует окончание ожидания RTO; отдельно
измеряются опоздание обработки RTO, ожидание готового повтора и доставка сверх
заданной задержки канала. Проверки этих этапов:20мс,100мс и100мс соответственно.
Транзит измеряется при первом приёме DATA, если номер/путь совпадают с последней
принятой отправкой; старую копию не приписываем времени новой попытки.
RTO_count в логе — число возвратов логического DATA по RTO, не все снятия физических попыток.
Для быстрых профилей общий предел остаётся100мс. Если сумма RTO отказавших путей
превышает100мс, общий предел — эта сумма плюс100мс. Бюджет даётся на один обход
отказавших путей; дополнительные круги не увеличивают разрешённую задержку.
Максимальный RTO пути наблюдается независимо от измеряемых gap/latency.
Остальные8 профилей отказов проходят. Two-stuck нестабилен: в обычном прогоне
сообщение322 ждало738.317мс при пределе459.332мс. Разблокировавший фрагмент324
прошёл пути1->2->1->2->0 (5 принятых попыток); первая отправка произошла всего
через41мкс после постановки сообщения. Это дополнительный обход двух путей без
прогресса, а не обязательное ожидание единственного RTO или очередь источника.
Контроль с занятостью рабочего пути0 ровно1мс воспроизводит ту же цепочку:
736.623мс latency,534.548мс gap. При выборе2->1 рабочий путь initialized=UP=1,
окно свободно (1026/9514 байт), pacing_blocked=1. Повтор немедленно уходит в1,
после его RTO — снова в2, хотя0 уже готов. Готовый повтор ждёт максимум1.109мс,
обработка RTO опаздывает максимум1.951мс; лишнее время создают повторные RTO.
Отрицательный контроль отдельно задерживает готовый повтор на200мс: проверка
ready_wait падает независимо от разрешённого бюджета RTO. Самые медленные
сообщения логируются с номерами сообщения/фрагмента, временем постановки,
первой отправки и доставки, числом попыток и историей путей (по2 бита, последние16).
Решения между отказавшими путями буферизуются и печатаются после замера;
переполнение буфера — ошибка теста. Обычные повторы two-stuck также могут пройти
с latency около376мс; единичный PASS не закрывает обнаруженный лишний круг.
Slow-drop/two-stuck/load-drop проходят ASAN/UBSan с detect_leaks=1 без ошибок/утечек.
Полный check.sh:184/184, proxy9/9, burst1/1, load8/8 PASS,134217728 проверенных байт,
161с; /tmp/etcp-recovery-stages-full-check.log. Долгая multipath-матрица в check.sh не входит.
До дальнейшего изменения транспорта требуется отдельный разбор исключения
путей без DATA-прогресса из обычного выбора, ограниченных проб и возврата после
подтверждённого прогресса. Физический UP не следует подменять DATA-готовностью;
нужно проверить поздние ACK, отсутствие исправных путей и reset/close.
Ранних копий до RTO в плане нет. Дуплекс в этом этапе не менялся.
Логи: /tmp/etcp-recovery-stages-final-<profile>-<1..3>.log/.csv,
/tmp/etcp-recovery-stages-buffered-two-<1..5>.log, /tmp/etcp-recovery-stages-asan-<profile>.log.
Контроли: /tmp/etcp-recovery-stages-negative-final.log,
/tmp/etcp-recovery-stages-busy-control.log; исходники контролей — /tmp/etcp-recovery-*-control.c/.mk.
Предыдущий этап — ожидание альтернативы после RTO:
24.10.2026: уточнено и реализовано правило ретрансмита. При наличии другого
initialized+UP канала повтор ждёт его cwnd/pacing, даже если прежний уже готов.
Без таких альтернатив разрешён прежний канал; DOWN последней альтернативы
пробуждает sender. ACK/control/burst-FILLER проходят во время ожидания DATA.
Курсор и номер попытки меняются только после принятия DATA транспортом.
Новых состояний, ресурсов и таймеров нет; формула RTO сохранена.
Причины пауз разделены буферизованным трейсом первых800мс отказа: TX/RX/ACK,
снятие попытки, возврат в очередь, выбор пути, доставка сообщения и состояние очередей.
В slow-drop первый повтор начинается через232мс после отказа, ещё13мс занимает
доставка по рабочему пути. Исходная попытка достигла своего RTO около251мс;
разница объясняется временем в пути до включения отказа. При двух зависших
каналах ротация может последовательно выбрать оба, добавляя их ожидания RTO.
Под нагрузкой основная проблема — повторный выбор прежнего зависшего пути,
когда рабочая альтернатива занята pacer. В диагностическом load-drop за первые800мс
345 из677 повторов снова выбрали свой прежний путь; ожидание в send_q — медиана101мкс,
максимум2590мкс. Для load-hold —325 из676, медиана82мкс, максимум3503мкс.
Следовательно, перенос повтора из хвоста в голову не объясняет и не устраняет
основную паузу этих профилей. Такой диагностический контроль также не проходит
порог; изменение порядка очереди в производственный код не перенесено.
Исключение пути после RTO в отдельном контроле сокращает паузу load-drop до41мс,
но это диагностический опыт без полноценной модели проб/восстановления,
он не переносился в production. Исправлено именно правило ожидания альтернативы.
Регрессия до исправления падает на отправке через готовый прежний путь при
заблокированном pacer другого UP-линка. После исправления windows44 сценария PASS
с управляемыми и реальными часами. Проверены ожидание cwnd/pacer без новых
попыток/таймеров, ACK и burst во время ожидания, освобождение альтернативы,
DOWN/удаление последней альтернативы, неинициализированный линк и поздний ACK.
Восемь профильных тестов ASAN/UBSan с detect_leaks=1 проходят без ошибок/утечек.
Полный check.sh после исправления:184/184, proxy9/9, burst1/1, load8/8 PASS,
134217728 проверенных байт,161с; /tmp/etcp-retry-alternative-full-check.log.
Штатные профили отказов 8dd03c2b:7 из9 проходят прежние пороги. DROP/HOLD под
нагрузкой проходят каждый3/3: паузы43.172/44.755/43.493мс и40.965/39.796/44.982мс,
устойчивая полезная скорость7.189..7.476 из8Мбит/с оставшегося канала.
Для насыщенного источника тест ограничивает паузу и steady goodput, а не latency
каждого сообщения; измеренные максимальные latency102.572..114.403мс не скрыты.
Slow-drop остаётся FAIL: пауза238.569мс, latency264.727мс. Two-stuck — пауза219.338мс,
latency380.177мс. Это4 нарушения порогов; доставка/порядок/ресурсы проходят во всех9.
Скоростная матрица целиком после этого изменения не повторялась; дуплекс не закрыт.
Логи: /tmp/etcp-retry-alternative-{windows,real-time}.log,
/tmp/etcp-retry-alternative-asan-test_*.log, /tmp/etcp-retry-alternative-<profile>-<1..3>.log/.csv.
Диагностические исходники/сборка: /tmp/etcp-recovery-probe.c/.mk;
логи /tmp/etcp-recovery-{trace,priority,exclude}-<profile>.log.
Решение пользователя,24.10.2026: ранние копии до RTO исключены из плана.
Пока путь UP, срок повтора определяется его RTO; закрытие линка по-прежнему
снимает его попытки и возвращает недоставленные DATA без ожидания этого срока.
Ротация ждёт готовности другого initialized+UP пути, не возвращаясь в прежний,
пока такая альтернатива существует. Формула RTO остаётся согласованной:
clamp(ceil(1.2*RTT+1.5*jitter+3мс),10мс,1с), с округлением до микросекунды.
При RTT200мс и нулевом jitter RTO уже243мс; фиксированный порог паузы100мс
не учитывает это обязательное ожидание. Текущие пороги тестов не менялись,
приведённые выше FAIL сохранены. Следует отдельно оценивать ожидание RTO,
задержку готового повтора и доставку через выбранную альтернативу.
Дальнейшие механизмы здоровья каналов и STCP backpressure не предполагают
спекулятивного дублирования DATA до RTO. Прежние предложения ранних копий
в исторических результатах ниже отменены этим решением.
Состояние после исправления pacer, до уточнения правила ретрансмита:
Исправление pacer реализовано: таймер назначается на первый TB, разрешающий отправку,
а не на полное погашение долга. Кредит и допустимый положительный долг по-прежнему
ограничены 1мс; BBR/cwnd/min_rtt, RTO, wire-формат и порядок callbacks не менялись.
Новых полей, очередей и таймеров нет. Успешная постановка таймера видна в ETCP TRACE:
link/rate_Bps/now_tb/debt_until_tb/fraction/delay_tb. Контракт отражён в .h и протоколе.
Регрессия до исправления падает: при долге10TB таймер получает10TB вместо первого
допустимого1TB. После исправления проверены18 сочетаний долга/дроби, строгая граница,
INT_MAX и повторная постановка при раннем вызове. Callback с опозданием100мс сохраняет
ограниченный burst; OOM повторного таймера закрывает соединение без ложного ready.
Настоящий uasync callback проверен с reset и close, освобождающим физический линк.
Windows42 сценария PASS и с управляемыми часами, и с UTUN_TEST_REAL_TIME=1.
Восемь тестов windows/BBR/session/transport/init_liveness/STCP traffic/NCD/control_server
проходят ASAN/UBSan с detect_leaks=1, без ошибок и утечек. check.sh:184/184,
proxy9/9, burst1/1, load8/8 PASS,134217728 проверенных байт,162с.
Штатная последовательная multipath-матрица c7a53009 после калибровки эмулятора:
FAIL,14 нарушений прежних порогов. Восемь относятся к скорости дуплекса, включая
одиночные каналы; шесть — к паузам/задержкам slow-drop,two-stuck,load-drop/load-hold.
Агрегированный goodput (min(application,fresh DATA),Мбит/с): equal14.438/16,
bandwidth9.052/10, rtt14.299/16, fast-distant8.836/10, slow-distant9.343/10,
three11.125/12, bidirectional8.394/8.458 из10 на каждое направление.
Первые шесть скоростных профилей проходят. Drop-forward проходит с паузой58.001мс
и задержкой83.656мс. Оставшиеся паузы: slow-drop239.421мс, two-stuck201.381мс,
load-drop213.644мс, load-hold208.899мс; задержки slow-drop264.044мс и two-stuck482.693мс.
Все профили завершают доставку и контроль порядка/ресурсов. Это один прогон;
изменение отдельных скоростей/пауз относительно контроля не доказывает устойчивый эффект.
Полная матрица ещё не проходит. Далее отдельно разбирать ограничения BBR/cwnd и PROBE_RTT,
затем ранние копии/здоровье каналов и STCP backpressure; этот фикс их не реализует.
Логи: /tmp/etcp-pacer-ready-{windows-final,real-time,full-check}.log,
/tmp/etcp-pacer-ready-asan-test_*.log, /tmp/etcp-pacer-ready-matrix.log/.csv.
Диагностика и проверка плана до исправления,24.10.2026:
Контроль текущей master a22a27b7,24.10.2026. Эмулятор прошёл калибровку2/8Мбит/с,
затем штатная матрица выполнена последовательно с прежними порогами:15 нарушений.
Агрегированный goodput (min(application,fresh DATA),Мбит/с): equal14.238/16,
bandwidth9.000/10, rtt13.855/16, fast-distant8.602/10, slow-distant8.817/10,
three10.669/12, bidirectional8.335/8.212 из10 на каждое направление.
Первые пять профилей проходят. Three: единственный отказ одиночного канала
2Мбит/с RTT200мс —1.695 вместо1.700; три отдельных повтора профиля проходят,
этот одиночный канал даёт1.703..1.707. Дуплекс нарушает6 скоростных порогов.
Ещё8 нарушений — пауза/задержка при drop-forward,slow-drop,two-stuck,load-drop/load-hold.
Максимальные паузы соответственно122.308,244.610,208.533,304.969/178.050мс.
Все профили завершают доставку и контроль ресурсов; ошибок формата/порядка/REINIT нет.
Логи: /tmp/etcp-multipath-current-{build,dummynet,matrix}.log,
/tmp/etcp-multipath-current-matrix.csv, /tmp/etcp-three-current-{1,2,3}.log/.csv.
Первое изолированное исправление после контроля — таймер pacer.
Буферизованный трейс DATA/ACK/выбора пути/каждого BBR sample записывается в память,
выводится после замера и не теряет события. На одиночном дуплексном8Мбит/с RTT80мс
за4с BBR оставался PROBE_BW в обе стороны, но pacer терял6.5/9.4% заданного темпа.
Расчётные бюджеты отправки3,733,752/3,480,084 байт, фактические DATA TX3,491,124/3,151,512;
сброс кредита объясняет240,408/328,214 байт дефицита. После ожидания самого pacer
потеряно168,861/290,263 байта; остальное связано с ожиданием окна.
Причина: shaper_arm не ставит таймер, если долг меньше уже разрешённого допуска1мс,
но при большем долге ждёт его полного погашения. Округление uasync до миллисекунд
и поздний callback приводят к выходу за кредит1мс; следующий send отбрасывает этот кредит.
Готовность должна наступать при debt_tb<SHAPER_BURST_DELAY_TB:
первый допустимый TB = shaper_load_time_tb - SHAPER_BURST_DELAY_TB +1.
При текущем условии постановки delay>=1; +1 сохраняет строгую границу при любой
дробной части. Ограничение INT_MAX применяется после расчёта; callback уже повторно
вызывает shaper_arm перед уведомлением отправителя. Нового состояния/таймера не нужно.
Прототип в /tmp сдвигает только etcp_shaper timeout к этой границе, сохраняя кредит1мс,
обычный BBR/PROBE_RTT, расчёт окна/min_rtt и пороги теста. Два последовательных опыта:
потеря после pacer вместо169/290КБ составляет2..9КБ за4с; потери после ожидания cwnd остаются.
Это устраняет95..99% изолированной потери pacer, но профиль дуплекса в целом ещё FAIL.
Отдельный контроль без PROBE_RTT показывает вклад этой фазы в просадку агрегата,
однако также не проходит всю матрицу; отключение PROBE_RTT не предлагается как исправление.
При переносе выполнены: регрессия позднего timer callback при незаполненном cwnd, проверка
границы допуска с нулевой/ненулевой дробью и сохранения ограниченного burst после
долгого простоя; смена ставки/0/unlimited, INT_MAX, OOM таймера, reset/close из callback.
Профильные тесты и ASAN/UBSan проходят; полная матрица повторена последовательно,
результаты приведены выше. Следующими отдельно разбирать оставшиеся ограничения BBR/cwnd,
периодические PROBE_RTT и ранние копии; первый фикс не закрывает эти пункты.
Артефакты: /tmp/etcp-probe-review.c/.mk, /tmp/etcp-duplex-waits.log,
/tmp/etcp-ready-timer-{1,2}.log/.csv, /tmp/etcp-pacer-review-analysis.py,
/tmp/etcp-pacer-review-results.json. Во время исходных контрольных опытов код протокола
в репозитории не менялся; производственное исправление и его проверки описаны выше.
История реализации и предыдущих проверок,23.10.2026:
В fix/etcp-multipath-recovery реализованы начальный pacing, точные DATA B/s,
пересчёт pacing-долга, ограниченный кредит 1мс для округления таймеров и выход
STARTUP в DRAIN после целого раунда RTT выше min_rtt+бюджет очереди.
Предел PROBE_BW — max(4*MSS, BDP + BW*20мс), включая ACK/PROBE_UP резервы.
Это мягкая цель модели, не гарантия физической очереди <=20мс во всех состояниях.
Логический seq/payload отделён от физических ETCP_TX_ATTEMPT. Новый DATA содержит
seq:u32+attempt_no:u16; ACK своего пути содержит seq+attempt_no+receiver_delay_us.
Номер 1..65535 общий для всех линков одного seq; индекс очередей — внутренний
ключ (seq<<16)|attempt_no. При исчерпании номера или seq выполняется смена эпохи,
пара не оборачивается. По сравнению с прежним64-битным wire ID DATA короче на6 байт
(заголовок7 вместо13), ACK-запись —10 вместо16 байт; normalizer не менялся.
Till подтверждает только логическую доставку. Собственные очереди попыток,
ACK, RTT/jitter и RTO принадлежат линку. RTO=clamp(1.2*RTT+1.5*jitter+3мс,10мс,1с),
с округлением вверх до целой микросекунды;
до DATA ACK используется KA RTT того же пути, без него начальное значение 1с.
Retry обходит каналы относительно last_link фрагмента; курсор фиксируется только
после принятия send транспортом. Поздние/чужие ACK не меняют чужой flight/BBR.
ACK создаётся после сохранения DATA в recv_q. Потерянный till запрашивается,
когда окно sequence заполнено; ответ идёт по запросившему пути, без DATA.
Физические попытки ограничены 16384 на линк/32768 на соединение, ACK — 16384 на линк.
Проверка компактного номера,23.10.2026: windows38 сценариев, session/BBR/transport/init_liveness PASS.
Те же5 тестов ASAN/UBSan с detect_leaks=1 без ошибок/утечек. Проверены одинаковые
номера разных seq, ACK другого линка/дубликат/ноль, rollback send без возврата номера,
полный номер65535 в wire, исчерпание номера и seq с REINIT. Реальный AES/ingress
отвергает старый ACK при повторении пары seq=1/no=1 в новой эпохе.
Смена эпохи из input_queue callback прекращает текущую сборку пакета, включая burst.
check.sh184/184, proxy9/9, burst1/1, load8/8 PASS,165с; load проверил134217728 байт.
Отдельный bandwidth2+8Mbit/s RTT20/20:8.715Mbit/s, retries=0, очереди24.2/20.1мс, PASS.
Логи: /tmp/etcp-attempt16-full-check.log, /tmp/etcp-attempt16-{windows,session}.log,
/tmp/etcp-attempt16-asan-*.log, /tmp/etcp-attempt16-bandwidth.log/.csv.
Приведённые ниже прежние скоростные замеры относятся к исходному8-байтовому ID;
на момент той проверки матрица разных RTT/дуплекса после сокращения поля не повторялась.
Точечные проверки: BBR 20/20, windows 26 сценариев PASS. Восемь тестов
BBR/windows/session/transport/STCP traffic/NCD/stream_tx_stats/control_server проходят ASAN/UBSan
с detect_leaks=1, без отчётов об ошибках/утечках.
Логи: /tmp/etcp-bounds-windows.log, /tmp/etcp-delay-bbr.log,
/tmp/etcp-attempts-asan-test_*.log.
Основной регрессионный набор: 183/183 PASS (после адаптации fixture метрик).
check.sh завершён успешно за166с: дополнительные tcp_proxy_full, burst и load PASS;
load — 8/8 соединений,134217728 байт с проверкой содержимого за13.66с.
Лог: /tmp/etcp-attempts-final-check.log. Он не включает долгую multipath-матрицу.
До изменения формулы RTO штатная multipath-матрица FAIL: 20 нарушений порогов.
RTT20/200мс: 13.552/16Мбит/с, длинный канал wire7.173/8Мбит/с;
после исправления STARTUP пики очереди около 23мс вместо 220мс.
Есть недобор bidirectional (обратное направление 7.116/10Мбит/с).
Обычный DROP/HOLD с RTT20мс: пауза около 61–62мс. slow-drop с RTT200мс:
пауза 401мс; два застрявших канала — 676мс; под нагрузкой — 229–243мс.
Ни пороги, ни прогрев тестов не ослаблены. Ранние копии/здоровье каналов и
ограничение нижележащей STCP-очереди ещё не реализованы.
Полный лог: /tmp/etcp-attempts-matrix.log.
После смены RTO на 1.2*RTT+1.5*jitter+3мс: один полный прогон матрицы,
15 нарушений порогов; в установившихся интервалах исправных каналов retries=0.
Пауза slow-drop: 400.869 -> 243.650мс; two-stuck: 675.741 -> 277.974мс.
Под нагрузкой load-drop/load-hold: 243.407/229.179 -> 95.533/83.085мс.
Пороги не менялись. Недобор части RTT/BW/bidirectional профилей, drop-forward,
slow-drop и two-stuck ещё не закрыты. Это сравнение единичных прогонов.
31 сценарий windows, включая 20 наборов RTT/jitter/KA для реального таймера,
BBR20/20, session и transport PASS. windows/session/transport также проходят
ASAN/UBSan с detect_leaks=1. Целочисленная формула проверена на округление,
обе границы, UINT32_MAX, неизвестный RTT и приоритет DATA RTT перед KA RTT.
Логи: /tmp/etcp-rto-margin-matrix.log/.csv, /tmp/etcp-rto-margin-test_*.log,
/tmp/etcp-rto-margin-asan-test_*.log.
Дополнительный jitter-контроль: штатные ядро/шифрование/dummynet, RTT20/200мс,
8+8Мбит/с, случайная добавка 0..2/10/30мс в каждом направлении, без заданных потерь.
Исходники диагностического стенда и контрольная сборка со старой формулой — только /tmp.
Новая/старая формулы дали 14.272/14.214, 13.932/13.754, 7.561/8.235Мбит/с.
В измеряемых интервалах новой формулы повторы по двум путям: 0/0, 3/3, 0/8;
старой — 0/0 для всех трёх профилей. Полная доставка/порядок/память проверены.
Это единичные диагностические прогоны: оптимальность коэффициентов не установлена,
а уменьшение запаса при jitter создаёт отдельные лишние повторы. Недобор при
jitter30мс есть у обеих формул и требует отдельного изучения модели/переупорядочивания.
Логи: /tmp/etcp-rto-jitter-review.log, /tmp/etcp-rto-jitter-old.log;
стенд: /tmp/etcp-rto-jitter-review.c/.mk, /tmp/etcp-rto-old-control/.
Полный check.sh после смены формулы: 182/183, FAIL только test_bgp_route_exchange
(ожидание удаления A->C после отключения обоих линков). Это падение наблюдалось
и до смены формулы; отдельный повтор с UTUN_TEST_DEBUG=1 снова PASS. Причина
зависимости от запуска не установлена. test_encrypted_attachment в этом прогоне PASS;
ранее подтверждённый дефект удаления активного temp по-прежнему остаётся открытым.
Логи: /tmp/etcp-rto-margin-full-check.log, /tmp/etcp-rto-margin-bgp-repeat.log.
Дефект BGP-падения при односторонней потере A->B исправлен в ETCP.
Повторный INIT с flag_up=0 больше не сохраняет устаревший remote_keepalive=1:
RX/remote/status учитываются до перезаписи входного буфера ответом. При DOWN
handshake отвечает и запускает keepalive для двусторонней проверки. Живой
альтернативный линк сохраняет UP соединения. RX защищён от close/удаления
линка из callbacks; повторы прежнего состояния не создают новых DOWN/UP.
INIT_RESPONSE проверяет полное тело >=64 байт без timestamp/flag_up и node_id
до изменения состояния; прежняя проверка общей длины пропускала тело 61..63 байта.
Новых таймеров, очередей, полей или изменений формата нет; пороги тестов сохранены.
test_etcp_init_liveness: 130 отказов с сохранением link/conn и владения pkt,
12 допустимых ответов, потери в обе стороны/оба порядка ключей, восстановление,
резервный линк, close/удаление линка из INIT и KA callbacks PASS.
Тест проходит также с обычными часами. Семь профильных тестов ASAN/UBSan
с detect_leaks=1 PASS; дополненный сценарий удаления линка повторён отдельно.
BGP route exchange: 100/100 PASS (A<B:48, A>B:52; последние пять с debug).
Основной регрессионный набор: 184/184 PASS. Логи:
/tmp/etcp-init-boundary-before.log, /tmp/etcp-init-final-{liveness,realtime,asan-liveness}.log,
/tmp/etcp-init-fix-asan-test_*.log, /tmp/etcp-init-bgp-stable-*.log,
/tmp/etcp-init-fix-full-check.log. Скоростные ограничения и ранние копии остаются отдельной задачей.
Качество мультилинка: три последовательных прогона каждого скоростного профиля,
3с прогрев +4с измерение, штатный test_clock, UDP/dummynet и настоящее шифрование.
Ниже полезная скорость min..max в Мбит/с (минимум application и fresh DATA, как в тесте):
8+8, RTT20/20мс: 13.868..14.083 из16; 95.7..98.5% суммы одиночных скоростей.
2+8, RTT20/20мс: 8.754.. 9.066 из10; 99.7..102.9% суммы одиночных.
8+8, RTT20/200мс: 13.567..13.951 из16; 94.8..96.8% суммы одиночных.
2+8, RTT20/200мс: 8.467.. 8.534 из10; 95.3..96.3% суммы одиночных.
8+2, RTT20/200мс: 8.683.. 9.087 из10; 96.6..101.8% суммы одиночных.
2+8+2, RTT20/80/200мс: 10.566..10.749 из12; 99.0..102.8% суммы одиночных.
2+8, RTT20/80мс, одновременно в обе стороны:
A->B: 8.153.. 8.318 из10;
B->A: 8.080.. 8.379 из10.
Утилизация UDP-полосы агрегата с учётом протокольных байтов — 90.9..97.4% для
обычных профилей; меньшее приложение учитывает накладные расходы ETCP/шифрования.
В измеряемых исправных интервалах всех трёх прогонов DATA retries=0.
Полная доставка после drain, порядок, содержимое, баланс попыток и память проверены.
Превышение 100% от суммы solo возможно: solo/aggregate измеряются в разных окнах,
с независимыми фазами BBR; эти проценты не являются процентом физической полосы.
Недобор на длинных/двусторонних профилях сохраняется. В обратном solo-направлении
8Мбит/с RTT80мс получено лишь 4.514..4.972Мбит/с; причина в этом прогоне не диагностировалась.
Все замеры: /tmp/etcp-multilink-quality-20261023.csv/.txt.
Логи повторов: /tmp/etcp-quality-r{2,3}-{profile}.log/.csv; первый — /tmp/etcp-rto-margin-matrix.log/.csv.
Коррекция BW в рабочей реализации, 23.10.2026
По выбору пользователя перенесена только граница BW по приёмной точке пути.
Пара rx_data_bytes/rx_data_time_us добавляется после entries лишь при count>0:
ACK имеет размер8+10*count+(count>0?16:0), пустой ACK остаётся8-байтовым.
При первоначальной проверке BW ниже использовался прежний8-байтовый attempt_id
и16-байтовый ACK entry; текущий компактный формат описан в начале документа.
Снимок точки хранится в физической попытке, новые очереди/таймеры не нужны.
После пустого flight база начинается заново. Перестановка ACK не откатывает
точку, неизвестный/повторный ID её не меняет; переполнения проверяются.
Формула, единицы и формат: doc/etcp_protocol.txt, etcp_tx.h, etcp_connections.h.
min_rtt/RTT, политика окна, RTO и дедлайн/пакетирование ACK не менялись;
busy-флаг и дуплексный коэффициент не добавлены. Обе стороны используют новый формат ACK.
Windows35 сценариев, session, transport, init_liveness и BBR PASS;
те же пять тестов ASAN/UBSan с detect_leaks=1 PASS, без ошибок и утечек.
Проверены сжатие ACK (170502/186359 вместо170502/150732), перестановка/дубли,
недостоверные точки, простой/reset, независимые часы, округление/переполнение,
границы MTU, усечённые ACK и ограниченный диагностический буфер.
Полный check.sh:184/184 PASS, proxy9/9, burst1/1 и load8/8 PASS;
load проверил134217728 байт. Лог: /tmp/etcp-bw-correction-full-check.log.
Логи sanitizer: /tmp/etcp-bw-asan-{windows,session,bbr,transport,init_liveness}.log.
Отдельный последовательный bidirectional: агрегат2+8Mbit/s RTT20/80мс,
goodput8.223/8.182Mbit/s, очереди путей A->B22.8/22.7мс, B->A19.9/21.9мс,
DATA retries=0 в измеряемом интервале. Профиль FAIL:5 прежних порогов скорости,
включая solo; агрегат не достиг8.5Mbit/s на направление. Пороги не ослаблены.
bandwidth2+8Mbit/s RTT20/20мс:8.844Mbit/s, очереди24.2/20.7мс, retries=0,
профиль PASS. Это единичные контрольные прогоны, не гарантия максимальной утилизации.
RTT20/200мс,8+8Mbit/s:13.453Mbit/s, очереди19.8/24.0мс, retries=0;
профиль FAIL по3 прежним порогам скорости. Недобор разных RTT остаётся открытым.
Логи/CSV: /tmp/etcp-bw-duplex-final.log/.csv, /tmp/etcp-bw-bandwidth.log/.csv, /tmp/etcp-bw-rtt.log/.csv.
Первый диагностический дуплексный прогон совпал со сборкой/другими проверками
(/tmp/etcp-bw-duplex.log/.csv); для оценки качества используется отдельный повтор выше.
Проверка плана исправления дуплекса до внедрения BW, 23.10.2026
Пакетный трейс нашёл ACK compression при осушении обратной очереди: для DATA
seq257..413 принято 170502 байта за186359мкс, а max(send_elapsed,ack_elapsed)
составил150732мкс. BBR получил1.131MB/s вместо фактических0.915MB/s.
В одном устойчивом участке DATA-pacing A=1.104MB/s при BWlim=8Mbit/s;
очередь A->B держится около40мс и задерживает ACK обратных DATA.
RTT приходит в BBR на каждом ACK (около124мс), причина не в устаревшем RTT.
Полный трейс500мс: /tmp/etcp-duplex-packets.log/.csv; история разгона:
/tmp/etcp-duplex-overload-start-v2.log, /tmp/etcp-duplex-overload-history-v2.csv.
Прототипы этих опытов находятся в /tmp/etcp-duplex-native/. На момент опытов исходники проекта
не менялись. В прототипе wire ACK расширен с8 до24 байт парой rx_bytes:u64
и rx_time_us:u64; это одна согласованная точка физического приёма DATA пути.
Счётчик включает допустимые физические копии DATA; это не логический till.
Попытка хранит последнюю известную точку при отправке. Дополнительная граница
rate sample: interval >= ceil(delivered * delta_rx_time / delta_rx_bytes).
Это сохраняет прежние границы send/ACK и не увеличивает оценку полосы.
В отличие от первого опыта с восстановлением времени из16-битного timestamp,
согласованная пара счётчик/время не использует агрессивную инвалидацию samples
при обратном ходе восстановленного timestamp. Первый опыт ухудшал результат
и не предлагается для внедрения.
В header проверен флаг DATA-backlog + недавней передачи своего пути.
В окне применяется отдельное T_effective=k*min_rtt+delta; min_rtt не меняется.
Для опыта TTL=20мс, проверяется при расчёте политики на ACK. Это ещё не полный
контракт затухания при молчании: свежесть нужно проверять и при выборе DATA,
чтобы не ждать следующего ACK для снятия дополнительного запаса.
Пять вариантов, по три последовательных запуска штатного bidirectional:
по3с прогрев +4с измерение, solo каждого канала и агрегат2+8Mbit/s RTT20/80мс.
Ниже min..max goodput A->B / B->A агрегата и максимальная очередь всех повторов:
штатный wire: 7.930..8.610 / 8.309..8.503Mbit/s; 52.9мс;
ACK+16 байт, без новой границы:8.113..8.189 / 8.032..8.320Mbit/s; 53.7мс;
новая граница BW: 8.095..8.236 / 8.154..8.396Mbit/s; 23.1мс;
граница + k=282/256, delta=3мс:8.179..8.404 / 8.142..8.171Mbit/s; 35.5мс;
граница + k=1, delta=10мс: 8.146..8.234 / 8.133..8.142Mbit/s; 33.7мс.
В измеряемых интервалах DATA retries=0. Все15 запусков не проходят весь
исходный скоростной профиль; пороги не менялись. Увеличение окна не дало
устойчивого выигрыша и увеличило очереди: выбирать коэффициент пока рано.
Ещё3 опыта one-way -> duplex -> one-way на том же соединении PASS:
флаг/поправка не активны у отправителя в односторонних измеряемых фазах,
включаются при встречной загрузке и отключаются после её завершения.
Полная доставка, порядок, состояние сессии, попытки/очереди и память проверены.
ASAN/UBSan с detect_leaks=1: phases PASS; bidirectional FAIL по скоростным
порогам, отчётов sanitizer/утечек и нарушения полной доставки нет.
ACK учтены по принятым пакетам: ACK-only — вся UDP-кодограмма с шифрованием
и служебными полями; piggyback — только ACK-секция внутри DATA, без повторного
начисления общих заголовков DATA. Единицы BWlim — UDP-байты без IP/UDP headers.
Для штатного агрегата ACK=0.515..0.559Mbit/s на направление, 5.15..5.59%
суммарного BWlim и5.50..5.93% фактического UDP-трафика. В среднем ACK-only:
0.445..0.455Mbit/s, ACK внутри DATA:0.088..0.090Mbit/s.
По отдельным каналам ACK занимают0.121..0.131 из2Mbit/s и0.384..0.431 из8Mbit/s.
При безусловном добавлении16 байт телеметрии ACK расходует0.707..0.743Mbit/s;
с новой границей BW —0.745..0.763Mbit/s. Накладные расходы реальны и учтены.
Итоговый план после выбора только BW пользователем:
1. Выполнено: сохранить измеренный min_rtt и отдельные send/ACK/receiver интервалы.
Точку счётчик/время передавать только с положительными ACK, которые её
используют; не раздувать каждый DATA с пустой ACK-секцией. Изменение
формата выполнить целиком для TX/RX/dump/MTU и тестов.
2. Отложено, в текущий фикс не входит: испытать объединение ACK. Piggyback на готовом DATA — сразу; ACK-only
по существующему дедлайну2мс. Запрос границы окна/срочный ответ не задерживать.
Один таймер должен обслуживать ближайший deadline очередей путей, включая
повторное планирование после частичного piggyback/раннего пробуждения.
Сейчас посторонний wake sender может отправить ACK-only до этого срока.
3. Сначала повторить скорости/очереди с корректной оценкой BW и экономной
упаковкой ACK. Дуплексный коэффициент не вводить как обязательную часть
фикса без устойчивого выигрыша в дополнительных RTT/BW/асимметричных профилях.
4. Выполнено для BW: проверить reordering/loss/duplicate ACK, поздние попытки,
reset/recreate пути и простой; валидировать пару counter/time. Если новая
граница не представима в uint32 interval, пропустить оценку BW, а не
насыщать интервал с нарушением верхней границы скорости. TTL флага не относится
к текущей реализации: флаг не добавлялся.
Результаты всех18 запусков: /tmp/etcp-duplex-native/results.json,
/tmp/etcp-duplex-native/summary.csv, /tmp/etcp-duplex-native/ack_bandwidth.csv.
Сборка: /tmp/etcp-duplex-native/experiment.mk; код/логи сохранены рядом.
Sanitizer: /tmp/etcp-duplex-native-asan/{bidirectional,phases}.log.
OOM следующей ступени приёма исправлен аварийным перезапуском сессии.
До сохранения RX DATA в recv_q отказ остаётся обычной потерей без ACK.
После принятия данных OOM в packer/unpacker или TX inflight запускает новую
локальную эпоху: старый поток отбрасывается, DATA блокируются до подтверждения.
Физические линки сохраняются; владельцы после REINIT восстанавливают операции
своего протокола. Это прерывание сессии, без гарантии воспроизведения старых DATA.
Удалено восстановление старых inflight-фрагментов через realloc и normalizer.
queue_clear и очистка etcp_conn_reset не выделяют память; normalizer не получил
новых OOM-очередей, cursor или retry-таймеров. Отдельно от очистки согласование
эпох требует памяти: OOM control-пакета допускает повтор в пределах исходных 10с,
отказ обязательного таймера или RNG локальной эпохи приводит к явному закрытию.
После reset/close из пользовательского callback разбор старого фрагмента прекращается.
Новые проверки: windows31 сценарий PASS, ll_queue_regressions77/77 PASS.
Инъекции отказов покрывают RX первую/следующую кодограмму фрагмента, TX буфер,
метаданные фрагмента/inflight, flush/waiter, отказ очереди, RNG и session-таймеры.
При запрете всех allocator-ов etcp_conn_reset не делает ни одной попытки выделения.
Шифрованный двусторонний тест проходит настоящий HELLO/CHALLENGE/CONFIRM:
REINIT у обоих владельцев, четыре линка UP, старые DATA отклонены, новые доставлены.
Десять тестов очередей/normalizer/ETCP/STCP/NCD/метрик проходят ASAN/UBSan
с detect_leaks=1. Логи: /tmp/etcp-oom-reinit-asan-test_*.log,
/tmp/etcp-abort-faults-final.log, /tmp/etcp-abort-encrypted-recovery.log.
Исходное воспроизведение потери: /tmp/etcp-unpack-admission-review.c/.mk/.log.
Полный check.sh после OOM-правок: 181/183, FAIL test_bgp_route_exchange и
test_encrypted_attachment. BGP отдельно с диагностикой PASS; причина нестабильности
общего прогона не установлена. Вложения отдельно снова FAIL: очистка кеша удаляет
активный .uenc.tmp.<id>, затем запись BLOCK_CHUNK получает ENOENT. В этом сценарии
ETCP не сбрасывается. Отдельная задача внесена в doc/tasks.md; media-код не изменялся.
Логи: /tmp/etcp-oom-reinit-full-check.log,
/tmp/etcp-oom-reinit-repeat-test_bgp_route_exchange.log,
/tmp/utun_encrypted_attachment_HwkBUM/test.log.
Отдельный интеграционный прогон proxy/burst/load PASS: proxy9/9, burst1/1,
load8/8 с 134217728 проверенными байтами за13.03с. Сетевые ресурсы очищены.
Лог: /tmp/etcp-oom-reinit-proxy-check.log.
Исходные нарушения до реализации (пункты 1–5 и вход recv_q из пункта 8 исправлены)
1. ACK выбирает канал независимо от DATA: select_link(conn, 0), inflight/RR.
Одна общая ack_q смешивает подтверждения DATA разных каналов. Поэтому интервал
delivery sample включает фактический обратный путь ACK, а rtt_us берётся
из отдельного timestamp-обмена last_link. Для RTT 20/200мс измерены возвраты
быстрых DATA через медленный канал за >=112мс при RTT модели около 24мс.
Это несовместимые исходные данные для BDP, pacing и бюджета очереди.
2. Общий ACK.till вызывает тот же etcp_ack_recv(), что и явный ACK элемента.
Даже если явные ACK возвращать своим путём, till подтверждает DATA других
каналов и обновляет их BBR в чужой момент времени. Перенаправить ACK недостаточно.
3. INFLIGHT_PACKET хранит только последнюю попытку: last_link, last_timestamp,
delivery snapshots. При повторе они заменяются, ACK содержит только общий seq.
Поздний ACK старой копии может быть приписан новой попытке и другому каналу.
Неоднозначны и RTT, и скорость, и выбор окна, которое освобождается.
4. В начальном pacing есть отдельный дефект. bbr_update_min_rtt() устанавливает
has_seen_rtt до bbr_set_pacing_rate(), поэтому предусмотренная инициализация
на первом RTT пропускается. Её аргумент также ошибочен: передаётся B/s вместо
размера окна в байтах. Затем перевод pacing в целые Kbit/s превращает малую
ненулевую скорость в 0, который shaper трактует как unlimited. Обновление
скорости само по себе не пересчитывает уже поставленный shaper-таймер.
5. RTO общий, основан на RTT/jitter соединения. Очередь wait_ack просматривается
как FIFO с единым timeout; индивидуальные дедлайны с такой остановкой обхода
несовместимы. Повтор может снова уйти в зависший канал. Приоритет следующего
канала сейчас не реализован: select_link не получает last_link пакета,
send_hist только записывается, а общий RR относится ко всему соединению.
Воспроизведение на настоящих очередях/RTO при двух готовых UP-каналах дало
seq1 A->A и seq2 B->B. Тест late_ack_and_migration этого не покрывает:
он явно отключает A перед передачей через B.
6. Поток собирается по общему seq: при RTT 20/200мс разность времени доставки
неизбежно создаёт переупорядочивание. Исправление ACK не устраняет ожидание
пропущенного фрагмента. В диагностике при высокой скорости накоплено порядка
0.17–0.29MB. Память сборки и пауза приложения требуют отдельных проверок.
7. stcp_link_send() подтверждает постановку в tx_queue. Это не означает прогресс
записи в TCP. Для stalled TCP одной проверки link_status/cwnd/pacer недостаточно.
8. В приёмнике ACK ставится в ack_q до выделения payload/ETCP_FRAGMENT и вставки
в recv_q. При последующем OOM или отказе вставки уже подготовленный ACK может
подтвердить несохранённые DATA. В новом контракте положительный ACK допустим
только после сохранения фрагмента либо проверки, что он уже сохранён/доставлен.
Инъекция отказа data_pool подтвердила: ACK_queued=1, DATA_stored=0,
delivered_till=0. Это дефект надёжности, а не только неточность статистики BBR.
После исправления этой ступени дополнительно проверен unpacker: его OOM
теперь явно прерывает сессию вместо продолжения повреждённого потока (см. статус).
Экспериментальная проверка причин
Диагностический бинарник /tmp/etcp-rtt-experiment включает штатный multipath-стенд
и настоящее ядро. Подмена происходит через linker --wrap, исходники протокола
не менялись. Используются реальные шифрование, UDP, dummynet и проверка порядка,
содержимого, полного завершения доставки, таймеров и счётчика выделений.
Контроль RTT 20/200мс, 8+8Мбит/с, с начальным pacing от окна/RTT:
- после завершения STARTUP полезная скорость 8.055/16Мбит/с, быстрый канал 0.633;
- свой путь ACK, без обновления BBR через till, фактический RTT попытки:
8.831 и 8.945/16Мбит/с; длинный канал недогружен;
- дополнительно исключены неоднозначные повторы из оценки скорости:
14.127 и 14.559/16Мбит/с, проверки установившегося режима прошли.
С последним вариантом дополнительно проверены RTT20/200мс при разных BWlim:
- 2+8Мбит/с (fast-distant): полезная скорость 8.735/10Мбит/с;
- 8+2Мбит/с (slow-distant): полезная скорость 8.979/10Мбит/с.
Проверки каждого канала, суммарной скорости, очереди и полного drain прошли.
В агрегате максимум очереди составил соответственно 24.9/26.4мс и 22.6/27.6мс.
Для равной BW выполнено два успешных повтора, для разных BW — по одному;
этого достаточно для выбора направления, но не для заявления о полной устойчивости.
Контроль последнего варианта по штатному rtt с фиксированным прогревом 3с:
14.312/16Мбит/с, очереди в агрегате 23.4/24.9мс, failures=0.
Это один успешный прогон диагностического прототипа, а не всего рабочего ядра.
Выборочные логи показывают преждевременный выход длинного канала из STARTUP
с заниженной оценкой BW. До фильтрации в модель попадал sample от повторной
передачи: RTT отвергнут, но delivery interval и скорость использованы.
Это подтверждает необходимость идентификации попытки; фильтр по send_count
годится для диагностики, но теряет достоверные измерения повторов и не заменяет ID.
Успешный единичный запуск без полного разделения учёта давал 14.45Мбит/с,
но повторные проверки выявили нестабильность. Он не является критерием готовности.
Отдельный прогрев до выхода из STARTUP использован только для диагностики:
штатные требования к разгону, очереди и утилизации не ослаблялись.
Фильтрация till в последнем прототипе сохраняет кумулятивную границу rx_ack_till;
её удаление из протокола сломало бы ограничение окна приёмника.
Прототип не проверяет новый wire-формат, владение попытками или отказоустойчивость.
Контракт (базовый учёт реализован; STCP-очередь остаётся открытой; ранние копии исключены)
Разделить логическую доставку фрагмента и физическую попытку передачи.
- Логический объект: общий seq, payload, признак доставки. Он отвечает за порядок
потока, unacked_bytes приложения и очередь повторной доставки.
Подтверждение фрагмента означает принятие его приёмником в надёжную очередь;
сборка и выдача приложению идут своим порядком. Переход в очередь повтора
идемпотентен: таймеры нескольких копий не создают несколько записей одного seq.
Первый положительный ACK или till, покрывающий этот seq, удаляет ожидающий повтор.
- Попытка: номер uint16 attempt_no в пределах seq, ссылка на канал, размер, время
отправки, delivery snapshots и состояние учёта окна. Последующая отправка
создаёт новую попытку; предыдущие снимки остаются неизменными.
Глобальные очереди содержат логические фрагменты: готовые к отправке и ожидающие
доставки. Отдельные записи попыток принадлежат очередям линков; один ll_entry
не включается в две очереди. Попытка хранит seq, а не висячую ссылку на уже
доставленный логический объект.
Индекс очереди — uint64 attempt_id=(seq<<16)|attempt_no; это внутренний ключ,
не самостоятельное wire-поле. Номер выдаётся при prepare, даже если send отказал.
- attempt_no монотонен в пределах seq, общий для всех линков, начинается с1.
Удаление/создание линка его не сбрасывает. После65535 попыток либо UINT32_MAX
значений seq следующая выдача меняет эпоху через REINIT, не повторяя пару.
При reset поток отбрасывается; DATA/ACK прежней эпохи не попадают в учёт.
- DATA содержит seq + attempt_no. ACK элемента сохраняет оба идентификатора:
seq:u32 + attempt_no:u16 + receiver_delay_us:u32. seq позволяет подтвердить
логическую доставку даже после удаления метаданных старой попытки, без отдельной
истории попыток. Для известного attempt_id проверять соответствие seq.
Задержка насыщается, UINT32_MAX означает недействительный RTT sample.
Пересчитать DATA MSS, MTU и ACK batching; не использовать modulo-wrap задержки.
- ACK означает принятие DATA в надёжную очередь приёмника, не приход в сокет.
Сначала проверить границы, сохранить DATA/вставить в recv_q либо найти дубликат,
затем поставить ACK и лишь потом вызывать сборку/пользовательские callbacks.
Это сохраняет порядок при reentrant close/reset. При OOM до сохранения DATA
положительного ACK нет.
При OOM самого ACK данные остаются сохранёнными; следующая копия получает ACK.
- Подтверждение попытки возвращается через тот же ETCP_LINK, где она принята.
Очередь ACK принадлежит линку; piggyback содержит только ACK этого линка.
Дубликат логического DATA с новым attempt_id тоже подтверждается.
- BBR обновляется по реально подтверждённой попытке своего канала. RTT — elapsed
этой попытки минус допустимый receiver delay. KA RTT остаётся метрикой живости.
ACK другого канала не формирует sample. ACK/control по-прежнему обходят только
локальный DATA-pacer; общая физическая очередь коммуникации сохраняется.
- Общий till остаётся подтверждением логической доставки и места приёмника.
Он может прийти по любому живому пути, освобождает payload/очередь повторов,
но не придумывает доставку попытки, RTT/BW или освобождение чужого окна.
Продвижение till должно пробуждать отправку отдельного ACK с count=0 через
готовый рабочий канал, даже без DATA и без ACK-элементов на нём. Объединять
продвижения и ограничивать частоту, не создавать ответ на каждый ACK.
Потеря последнего till не должна оставлять полное окно приёмника навсегда:
ожидание места имеет таймер запроса актуального till через рабочий путь,
получатель отвечает текущей границей. Таймер принадлежит соединению, активен
только при ожидающих DATA и полном окне, отменяется при прогрессе/close/reset.
Session-wrapped запрос/ответ не меняет модели BBR; частота ограничена.
- Доставка приложения учитывается один раз. Физические попытки учитываются
каждая в своей модели; их суммарная доставка может включать дубликаты DATA.
- ACK или объявление потери освобождает окно попытки ровно один раз. Поздний ACK
не дебетует его повторно. После доставки payload можно освободить, метаданные
других попыток живут до ACK/потери/закрытия, без удержания лишнего payload.
При потере запись попытки можно удалить: поздний ACK по seq ещё подтверждает
логическую доставку, но неизвестный attempt_id не обновляет BBR или окно.
Отдельная история/tombstone для уже потерянных попыток не нужна.
- Удаление линка/сессии отменяет его таймеры, удаляет ACK и попытки, освобождает
учёт ровно один раз. Не хранить указатель на уже освобождённый ETCP_LINK.
Правило повторной передачи, независимо от определения DOWN
Повтор использует историю именно своего логического фрагмента, не last_rr_link
соединения и не позиции каналов в send_hist. При готовых A/B/C порядок попыток
одного фрагмента — A->B->C->A; отправки других фрагментов и ACK его не меняют.
Начать обход после last_link фрагмента, выбрать первый подходящий другой линк.
Пропускать DOWN, неготовые, не вмещающие DATA и заблокированные pacer/транспортом.
Если есть другой initialized+UP линк, прежний запрещён даже при временно занятых
альтернативах. Тогда фрагмент ждёт штатного события ACK/pacer/transport-ready,
без busy loop, новой попытки и нового RTO. Временно занятый следующий линк
не блокирует поиск остальных альтернатив. При отсутствии других initialized+UP
линков разрешён готовый прежний; DOWN последней альтернативы будит sender.
Отправлять без cwnd/pacing нельзя. Курсор удалённого линка очищается до free.
Для первоначальной отправки остаётся обычный выбор по загрузке. Retry — отдельный
режим той же функции выбора, с едиными проверками готовности, без второго набора
условий cwnd/pacing. Burst не навязывает DATA свой линк: при конфликте измерение
идёт с FILLER, повтор отправляется по своему правилу. Старый линк не объявляется
DOWN только потому, что копия отправлена другим путём.
last_link логического фрагмента — только заимствованный курсор выбора. При удалении
линка очистить такие курсоры до free; при NULL начать с первого рабочего линка.
Переиспользуемый uint8 local_link_id и порядковый номер списка не являются
идентичностью попытки. send_count не должен определять retry через оборачиваемый
uint8: хранить факт состоявшейся отправки отдельно от статистики.
Курсор меняется после принятия новой попытки транспортом. Ошибка подготовки/OOM
или отказ отправки не должны создавать фиктивный flight и ждать сетевого RTO.
Повтор планируется после RTO либо снятия попытки при закрытии линка; ранних
спекулятивных копий нет. После RTO исходная DATA ещё может прийти из сети:
поздний ACK подтверждает логический seq, но не создаёт sample удалённой попытки.
Потеря для BBR учитывается при RTO, не при постановке или отправке повтора.
Все попытки ограничены общим бюджетом памяти и окнами своих каналов.
Ротация сокращает число попыток через зависший путь, но не сокращает ожидание
первого таймера. Нельзя обещать паузу <=100мс только сменой канала при RTO>100мс.
Сохранять физически достижимые требования конкретных профилей, не обещать один
предел задержки для произвольных RTT, нагрузки и потерь.
Почему не общий самый быстрый обратный канал
Такой выбор не согласует интервалы и не устраняет неоднозначность повторов.
Понадобится учитывать смену обратного маршрута в каждой модели, а отказ общей
обратной дороги затронет остальные прямые пути. Собственный ACK сохраняет одну
модель на канал и независимость отказов. Общая логическая граница при этом
может распространяться любым рабочим каналом.
Последовательность реализации и критерии завершения (до завершения открытых проверок задача не закрыта)
1. Начальный pacing — реализован и проверен отдельно от смены wire-формата.
Инициализировать от настоящего начального окна и первого валидного RTT.
Сохранить точные B/s до shaper, отделить unlimited от округления. При смене
ставки пересчитать оставшийся pacing-долг и таймер, не создавая новый burst.
Проверки: короткий служебный DATA -> пауза -> bulk, RTT20/200мс, скорость ниже
1Kbit/s, увеличение/уменьшение ставки с активным таймером, полный STARTUP.
2. Цельный переход на логические фрагменты и идентифицируемые попытки — реализован;
функциональные и sanitizer-проверки проходят, вся матрица скорости пока нет.
Добавить индекс attempt_id в очереди попыток каждого линка и отдельную ACK-очередь.
Общий индекс попыток не нужен: ACK ищется на принявшем его линке.
Разделить обработчики logical till и ACK попытки; одновременно обновить DATA,
ACK, декодеры, MTU/MSS, lifecycle и .h. В этом же изменении нужны очередь/таймер
попыток каждого линка и собственный RTT/RTO: иначе старый глобальный FIFO
перестаёт соответствовать новому владению. FIFO допустима при одном текущем
RTO для всех попыток линка; пересчитывать таймер при его смене.
До первого DATA ACK использовать RTT того же KA-пути как начальную оценку
либо консервативный начальный RTO. Расчёт по собственным RTT/jitter с запасом
ограничить диапазоном 10мс..1с; 10мс — нижняя граница, не верхняя.
Текущая формула по выбору пользователя: 1.2*RTT+1.5*jitter+3мс, округление вверх;
начальный jitter после первого DATA ACK равен RTT/2, установившийся — EWMA отклонения.
Добавить ротацию retry сразу, пока старый канал ещё UP; не откладывать до DOWN.
Не оставлять промежуточный формат,
в котором новый ACK продолжает обращаться только к last_link.
Проверки: первая копия A -> повтор B -> поздний ACK A; обратный порядок ACK;
ACK дубликат; чужой/неизвестный ID; till с другого пути; link recreation;
потеря ACK при уже доставленном payload; close/reset с активными попытками;
отказ принятия пакета транспортом без продвижения курсора и фиктивного flight;
OOM сохранения DATA, вставки в recv_q и создания ACK. Проверить передачу till
без DATA по другому пути и восстановление после потери последнего till.
Для retry: A->B при обоих UP и меньшем inflight у A; A->B->C->A;
посторонние DATA/ACK между попытками; пропуск заблокированных каналов;
один доступный путь; все временно заняты; burst старого пути; удаление курсора.
Матрица повторена после сканирования send_q и истории путей; текущие результаты приведены в статусе.
Выбор пути первоначальных DATA менять только
при сохраняющейся недогрузке: эксперименты уже показывают полную утилизацию
без смены алгоритма выбора DATA, поэтому дополнительная эвристика пока лишняя.
3. Быстрое восстановление и здоровье каналов — согласованный этап реализован.
Предварительное исправление OOM завершено: выбран сброс всей сессии с сохранением
линков, без удержания старого fragment/cursor и без отдельного OOM retry.
Очистка не выделяет память, старые callbacks/ожидания отменяются; активные
обработчики удерживают teardown и проверяют reset_count/close после callbacks.
Проверены OOM на первой/следующей кодограмме и восстановление новой эпохи.
Собственный RTT/RTO и ротация уже обязательны в этапе 2. Проверять ожидание
RTO отдельно от задержки готового повтора и доставки альтернативным путём.
Ранние копии до RTO исключены по решению пользователя от24.10.2026.
Реализованы флаг после трёх RTO, исключение застрявшего пути только из повторов,
полная история обхода, сканирование send_q и восстановление по свежему собственному ACK.
Отдельные probes/ограничение новых DATA не входят в выбранное решение: исходный
выбор новых DATA сохранён, подтверждённая новая отправка снимает флаг застревания.
DROP/HOLD отдельно по направлениям и вместе, несколько застрявших каналов,
возврат старых копий после HOLD проходят; повторный отказ и смена RTT/BW на ходу
остаются отдельными проверками. Отсутствие рабочих альтернатив и reset/close покрыты.
4. Учёт нижележащей очереди STCP.
Ограниченный tx_queue и уведомление о реальном прогрессе/готовности должны
участвовать в выборе пути. Определить время начала попытки и учёт данных,
принятых STCP, но ещё не записанных в сокет; не смешивать их с wire delivery.
Проверки: полностью остановленная TCP-запись рядом с рабочим UDP/STCP,
последующий выпуск очереди, закрытие соединения и отсутствие роста памяти.
Общие проверки
Матрица: equal, bandwidth, rtt, fast-distant, slow-distant, three, bidirectional.
Отдельно измерять время STARTUP, steady goodput, очередь каждого направления,
переупорядочивание, повторы и паузу приложения. Отчёт содержит данные каждого
повтора и минимум, а не только лучший результат. Не выполнять сетевые замеры
параллельно. Сохранить исходные пороги штатного test_etcp_multipath.
Добавить асимметричные прямой/обратный пути, ACK compression, поздние копии,
потери ACK и исчерпание лимитов памяти. Проверять счётчики по очередям, однократный
учёт подтверждения попытки и одну логическую доставку; утечки/UAF — ASAN/UBSan.
Логи: session/seq/attempt_id, DATA link, ACK link, send/receive/ack times,
receiver delay, причина retire, inflight до/после и вход/выход BBR.
Проверено для текущего бюджета: BBR18/18, windows16 сценариев, шесть транспортных
ASAN/UBSan тестов с detect_leaks=1 PASS. Общий check.sh: 180/183; FAIL
test_nat_detection_faults, test_nat_detection, test_encrypted_attachment.
Эти падения внесены отдельной задачей; связь с BBR не установлена.
Логи опытов: /tmp/etcp-rtt-ab-baseline.log,
/tmp/etcp-rtt-settled-control.log, /tmp/etcp-rtt-settled-candidate-diagnostic.log,
/tmp/etcp-rtt-settled-karn.log, /tmp/etcp-rtt-settled-karn-repeat.log,
/tmp/etcp-rtt-settled-fast-distant.log, /tmp/etcp-rtt-settled-slow-distant.log,
/tmp/etcp-rtt-original-karn.log.
Проверки отсутствующей ротации и ложного ACK при OOM: /tmp/etcp-retry-review.c,
/tmp/etcp-retry-review.mk, /tmp/etcp-retry-review.log
(exit=1: retry_rotation=2, false_ack=1, total=3; учёт/освобождение ресурсов проверены).
Сборка диагностического бинарника:
make -C tests -f Makefile -f /tmp/etcp-rtt-experiment.mk /tmp/etcp-rtt-experiment
Основной повтор:
RTT_SETTLED=1 RTT_SEED=1 RTT_SAME_ACK=1 RTT_NO_CUM=1 RTT_ACTUAL=1 RTT_KARN_RATE=1 \
/tmp/etcp-rtt-experiment rtt