Document per-path attempt ACKs, loss timers, pacing and memory bounds

This commit is contained in:
evgeny committed 2026-10-23 11:12:40 +02:00
1 parent b442d131c9
commit 7bb43c42d6
2 files changed
+64 -38

No files matched your search

+49 -32
View File
@@ -13,48 +13,55 @@ HELLO/CHALLENGE/CONFIRM и keepalive передаются своим незав
2. Выбрать инициализированный UP-линк с готовым shaper и местом для целого DATA:
inflight_bytes + длина DATA <= inflight_lim_bytes. Проверка выполняется без переполнения.
Среди допустимых выбирается минимум inflight_bytes, при равенстве — round-robin.
Для повтора обход начинается после last_link самого фрагмента: первый готовый другой канал,
прежний канал — последним. Чужие DATA/ACK не меняют курсор фрагмента; удаления линка обнуляют его до free.
3. Если DATA не помещается, он остаётся в очереди; для накопленных ACK выбирается
рабочий линк без ограничения DATA-cwnd/pacing. ACK-only не увеличивает нагрузку DATA-shaper;
тот же линк, который принял соответствующий DATA, без ограничения DATA-cwnd/pacing.
Кумулятивный till можно отправить по любому рабочему пути. ACK-only не увеличивает нагрузку DATA-shaper;
физическая очередь канала остаётся общей, её накопление ограничивают DATA-cwnd/pacing BBR.
4. Выделить ETCP_DGRAM до удаления DATA из очереди и изменения счётчиков.
Отправляемый DATA занимает окно выбранного линка и переходит в input_wait_ack.
Выделить отдельную физическую попытку с новым attempt_id и зарезервировать окно выбранного линка.
Только успешное принятие транспортом фиксирует время, last_link и перенос DATA в input_wait_ack.
Ошибка send снимает резерв; логический DATA остаётся готовым без сетевого loss sample.
Пакет содержит ACK, опциональные timestamp/измерительные секции и последнюю секцию PAYLOAD.
5. В активной ограниченной burst-серии pacing обходится; DATA всё равно должен
помещаться в окно, иначе серия отправляет FILLER. ACK/FILLER окно DATA не занимают.
При приёме пакета (от etcp_connections):
- последовательно сканируем секции и отдаём их на обработку нужным обработчикам:
- ack (0x01): помечаем в inflight пакеты как подтверждённые и проставляем время подтверждения. также снимаем блокировку отправки
- ack (0x01): attempt_id подтверждает физическую попытку только принявшего ACK линка;
seq отдельно подтверждает логическую доставку. till освобождает логические записи, без BBR/снятия чужого flight.
- timestamp (0x06): обновляем last RTT, пересчитываем RTT avg10 и jitter по каждому линку; обновляем recv_dt_avg для раздельного измерения времени передачи/приёма
- [0x00] payload: добавляем пакет в нужное место сборочного linked-list (сверяем по ID если дубликат - игнорируем). также добавляем ACK запись в ack_q для отправки.
- [0x00] payload: сохраняем фрагмент в recv_q либо распознаём уже сохранённый/доставленный seq.
Только после этого ставим ACK с seq+attempt_id в ack_q принявшего линка; OOM до сохранения не подтверждается.
после сканирования проверяем появились ли в linked-list собранные данные которые можно переместить в выходную очередь. и перемещаем если есть.
пересканирование inflight:
- запускается при добавлении пакета в очередь ожидания ACK (wait_ack_cb). устанавливается таймер на первый неистекший пакет.
- возраст последней отправки сравнивается с (RTT_avg10*32+jitter*32)/16,
ограниченным диапазоном 100..10000 таймбаз (10ms..1s).
RTT_avg10 — историческое имя максимального RTT среди links; jitter — среднее
по links значение link->jitter/32768. Оба значения здесь в единицах 0.1ms.
При текущих коэффициентах формула равна 2*(RTT_avg10+jitter), включая запас.
Проверка таймером назначается с дополнительной 1ms и округлением uasync вверх
до миллисекунды; фактический callback может выполняться позже расчётного RTO.
- истёкший пакет освобождает окно прежнего линка и переходит в input_send_q;
его новая попытка проходит обычный допуск по окну и pacing выбранного линка.
- очередь FIFO (сортирована по timestamp добавления = времени отправки), сканирование прекращается на первом неистекшем пакете
- по истечению таймера (ack_timeout_cb) снова вызывается проверка очереди
Таймер попыток каждого линка (etcp_tx.c):
- RTO = clamp(2*(data_rtt_us+data_jitter_us), 10000, 1000000) мкс.
RTT использует собственный DATA ACK минус receiver_delay; EWMA RTT=1/8, jitter=1/4 |RTT-EWMA|.
Первый RTT задаёт jitter=RTT/2. До DATA ACK используется RTT keepalive этого же пути, без него RTO=1с.
- FIFO содержит физические попытки в порядке принятия транспортом. Таймер назначается по старейшей;
при изменении RTO проверяется новый срок. Допускается оставить уже запланированную более раннюю проверку.
uasync округляет задержку вверх до миллисекунды; callback может выполниться позже расчётного срока.
- Истечение снимает только собственный физический flight и сообщает loss собственному BBR.
Если логический seq ещё не доставлен и других активных копий нет, он возвращается в input_send_q.
Уже доставленный seq не возвращается, даже если его физическая копия истекла позже.
inflight - две очереди (ll_queue):
- input_send_q: список пакетов ожидающих отправку
- input_wait_ack: список неподтвержденных пакетов ожидающих ack
При отправке пакет перемещается из input_send_q в input_wait_ack.
При ретрансмиссии пакет перемещается из input_wait_ack обратно в input_send_q.
При принятии отправки транспортом пакет перемещается из input_send_q в input_wait_ack.
При необходимости повтора пакет перемещается из input_wait_ack обратно в input_send_q.
доп. свойства пакетов (struct INFLIGHT_PACKET):
- указатель на ETC_LINK последней отправки (last_link)
- timestamp последней отправки (last_timestamp)
- число отправок (send_count)
- send_hist[8] - история через какие линки отправлялся (индекс линка)
- inflight_bytes/inflight_packets линка равны сумме длин/числу WAIT_ACK с этим last_link.
WAIT_SEND окно не занимает, в том числе после RTO. last_link сохраняет историю попытки.
Поздний ACK удаляет WAIT_SEND без повторного вычитания; duplicate ACK учёт не меняет.
- send_hist[8] — local_link_id последних попыток, только диагностика
- last_attempt_id и active_attempts — последняя отправка и число ещё учитываемых физических копий
- Payload принадлежит только логической записи. Попытка ETCP_TX_ATTEMPT хранит seq и неизменные снимки, без payload.
inflight_bytes/inflight_packets линка равны сумме DATA-длин/числу записей в его attempts, включая резерв send.
ACK/till может уже удалить логический объект, пока другая физическая копия продолжает занимать своё окно.
Повторный ACK неизвестной/истёкшей попытки не вычитает flight и не создаёт BBR sample.
ACK известного ID с другим seq отклоняется целиком; неизвестный ID допускает только логическое подтверждение seq.
Удаление линка обнуляет last_link в обеих очередях до освобождения его памяти.
Общий optimal_inflight равен сумме лимитов всех линков, с насыщением UINT32_MAX;
это дополнительный backpressure подачи новых фрагментов, не замена индивидуальных окон.
@@ -63,6 +70,11 @@ inflight - две очереди (ll_queue):
Selective ACK освобождает отдельную inflight-запись, но не продвигает эту границу:
приёмник пока не может освободить пакеты за дыркой. Кумулятивный ACK возобновляет
input_queue даже если подтверждённые записи уже удалены по selective ACK.
При заполненном sequence-окне собственный таймер запрашивает актуальный till секцией ACK_REQUEST.
Получатель отвечает через запрашивающий путь, даже без ожидающих DATA/ACK-элементов.
При продвижении frontier, reset/close таймер отменяется. Это восстанавливает поток при потере последнего till.
Память физических попыток ограничена: 16384 на линк, 32768 на соединение; исчерпание блокирует DATA до освобождения.
Очередь ACK каждого линка ограничена 16384 записями; переполнение логируется, DATA остаётся сохранённым для повтора/till.
ACK ограничивается последним назначенным seq (next_tx_id - 1); сравнения учитывают uint32 wrap.
BBR каждого линка получает ACK/loss samples и возвращает cwnd и pacing rate.
Скорость — доставленные байты за max(интервал отправки, интервал доставки), по снимкам при отправке.
@@ -73,7 +85,9 @@ BBR каждого линка получает ACK/loss samples и возвра
PROBE_RTT осушает flight до max(4*MSS, 0.5*BDP), выдерживает >=200мс и раунд ACK, затем восстанавливает окно.
MSS — размер DATA-фрагмента normalizer без заголовков; начальное окно UDP и STCP равно 4*MSS с верхним лимитом конфига.
В PROBE_BW действует дополнительный целевой бюджет очереди 20мс: всё окно, включая резервы ACK и PROBE_UP,
ограничивается max(4*MSS, BDP + BW*20мс) по min_rtt. STARTUP/DRAIN не ограничиваются этим бюджетом.
ограничивается max(4*MSS, BDP + BW*20мс) по min_rtt. STARTUP/DRAIN не ограничивают окно этой формулой,
но целый раунд RTT выше min_rtt+бюджет завершает STARTUP и переводит его в DRAIN.
Shaper списывает только DATA-байты: единицы совпадают с delivery sample и pacing BBR.
Это цель по оценкам BBR; минимум окна, изменение полосы и очереди обратного пути могут давать большую задержку.
Отказ из-за нехватки окна сохраняет cwnd_limited до следующего ACK: вычитание ACK
не должно скрывать этот факт от BBR. app_limited учитывает ожидающие очереди и
@@ -99,14 +113,15 @@ pending/частичный буфер normalizer; ограничение окн
Формат и подтверждение эпох описаны в разделе «Подтверждение транспортной сессии» ниже.
2. Опциональные секции (одна или несколько) добавляются в etcp.c внутри DATA/ACK после заголовка сессии:
а) Подтверждения (ACK) - заголовок 0x01:
[0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][recv_ts(2 LE)][dly(2 LE)] × count]
- count: количество ACK записей (1 байт; до 32 ограничено размером пакета)
[0x01][count][last_delivered_id(4 байта LE)][rx_dup_count(2 байта LE)][[seq(4 LE)][attempt_id(8 LE)][delay_us(4 LE)] × count]
- count: количество ACK записей (1 байт; до 255, дополнительно ограничено MTU и оставшимся местом)
- last_delivered_id: uint32_t — последний ID, доставленный получателю (по этот ID всё собрано подряд)
- rx_dup_count: uint16_t — счётчик принятых дубликатов (локальный, для вычисления tx_dup_count на удалённой стороне)
- seq: uint32_t — номер подтверждаемого пакета
- recv_ts: uint16_t — локальный timestamp приёма пакета (в единицах 0.1 мс)
- dly: uint16_t — задержка между приёмом пакета и отправкой ACK (для расчёта RTT)
Каждая запись = 8 байт.
- attempt_id: uint64_t — ID конкретной физической попытки DATA; возвращается только тем же путём
- delay_us: uint32_t — задержка между приёмом DATA и формированием ACK, мкс; UINT32_MAX исключает RTT sample
Каждая запись = 16 байт, база ACK = 8 байт; count=0 допустим для передачи till.
ACK_REQUEST: [0x0a], одна секция длиной 1 байт, запрос повторной отправки актуального till.
в) channel timestamp (добавляется после выбора канала передачи, если есть свежие данные):
[0x06] [RET_Timestamp(2 байта LE)] [RECV_Rel_Timestamp(2 байта LE)]
- RET_Timestamp: uint16_t = last_recv_timestamp + (now - last_recv_local_time)
@@ -114,12 +129,14 @@ pending/частичный буфер normalizer; ограничение окн
- RECV_Rel_Timestamp: uint16_t = last_recv_local_time - last_recv_timestamp
(относительное смещение между локальным и удалённым временем)
Всего 5 байт. Добавляется только если last_recv_updated=1 (был принят пакет с момента последней отправки)
и если прошло не более ~100ms (dt < 1000000 таймбаз).
и если прошло менее 100с (dt < 1000000 таймбаз). Секция не является источником DATA RTT для BBR/RTO.
3. Полезная нагрузка - заголовок 0x00 (одна секция и всегда последняя):
[0x00] [ID 4 байта LE] [данные...]
[0x00] [ID 4 байта LE] [attempt_id 8 байт LE] [данные...]
- ID: uint32_t, 32-битный циклический номер пакета
- attempt_id: uint64_t, монотонный ненулевой ID; не сбрасывается при reset или пересоздании линка.
Переполнение закрывает соединение. Заголовок PAYLOAD занимает 13 байт; при повторе attempt_id новый, seq прежний.
Пакет может содержать несколько опциональных секций (например, ACK + channel_timestamp + данные),
всегда в порядке: ACK, channel_timestamp, payload; payload всегда последний.
Порядок: ACK, возможный ACK_REQUEST, измерительные/временные секции и FILLER, затем payload последним.
Разбор секций — последовательное сканирование с известными размерами (нет явного поля длины секции).
цель протокола:
+15 -6
View File
@@ -39,11 +39,16 @@ bbr_tx_start(&bbr_state); // вызывать при возобновлени
```
### Интеграция с ETCP_LINK
BBR создаётся в `etcp_link_new()` как `link->bbr`. `etcp_ack_recv()` передаёт новые ACK и измерительный sample.
Sample использует снимки delivered и времени доставки/отправки, сохранённые в INFLIGHT_PACKET.
BBR создаётся в `etcp_link_new()` как `link->bbr`. `etcp_tx_ack()` передаёт sample только известной попытки своего линка.
Sample использует неизменные снимки delivered и времени доставки/отправки из `ETCP_TX_ATTEMPT`.
DATA несёт `seq+attempt_id`, ACK возвращает их и задержку формирования ACK тем же каналом.
RTT попытки равен времени до ACK минус эта задержка. Неизвестный/истёкший ID не обновляет BBR/RTT или физическое окно.
`etcp_ack_recv()` освобождает только логическую запись; кумулятивный till не создаёт BBR samples.
Интервал — максимум времени отправки и подтверждения; ACK одной пачки не создают искусственную сверхскорость.
`acked_sacked` увеличивает общий счётчик, `delivered` описывает весь интервал и может включать предыдущие ACK.
Окно применяется через `etcp_link_update_inflight_lim`, pacing — к исходящим DATA.
Окно применяется через `etcp_link_update_inflight_lim`, точный pacing B/s — через `etcp_loadbalancer_set_pacing_rate`.
Shaper списывает DATA-байты, без заголовков и piggyback ACK. При смене ставки остаток долга пересчитывается;
сохраняется не более 1мс накопленного кредита для компенсации округления таймера, без неограниченного burst после idle.
BBR определяет минимум 4*MSS; транспорт ограничивает окно только сверху. `INFLIGHT_LIM_MIN=8192`
задаёт допустимый минимум настройки максимального окна, а не текущее окно.
PROBE_RTT осушает flight до `max(4*MSS, 0.5*BDP)`, выдерживает не менее 200мс и раунд ACK, затем восстанавливает окно.
@@ -60,10 +65,14 @@ ACK-only не ограничивается этим pacing, но проходи
в uasync-потоке, `0` отключает политику. `queue_cwnd_cap` показывает действующий предел в DATA-байтах;
`0` означает, что ограничение не активно. Callback окна получает уже ограниченный результат, включая fast path.
Это целевая задержка по оценке полосы, а не гарантия физического времени очереди. STARTUP и DRAIN
сохраняют свои правила поиска полосы/осушения. Минимум 4*MSS может требовать больше 20мс сериализации;
В STARTUP минимум достоверных RTT целого ACK-раунда сравнивается с `min_rtt+queue_budget_us`.
Устойчивое превышение завершает разгон и включает DRAIN; одиночный пик или отключённый бюджет этого не делает.
Первый достоверный RTT задаёт начальный pacing по окну `4*MSS`, независимо от размера первого служебного DATA.
Это целевая задержка по оценке полосы, а не гарантия физического времени очереди. Окно STARTUP и DRAIN
не ограничивается формулой бюджета. Минимум 4*MSS может требовать больше 20мс сериализации;
при неизвестном min_rtt или нулевой оценке полосы дополнительный предел не применяется.
Изменение пропускной способности и другой путь возврата ACK могут делать оценку неточной.
Изменение пропускной способности, jitter, обратная очередь и потери ACK могут делать оценку неточной.
После изучения полосы меньший предел может ограничивать скорость; реальную очередь отдельно измеряет multipath-тест.
### Тестирование