diff --git a/doc/etcp_protocol.txt b/doc/etcp_protocol.txt index dc9f0e07..bd25176e 100644 --- a/doc/etcp_protocol.txt +++ b/doc/etcp_protocol.txt @@ -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 последним. Разбор секций — последовательное сканирование с известными размерами (нет явного поля длины секции). цель протокола: diff --git a/src/transport_layer/etcp_bbr_doc.md b/src/transport_layer/etcp_bbr_doc.md index d343f81d..971494d6 100644 --- a/src/transport_layer/etcp_bbr_doc.md +++ b/src/transport_layer/etcp_bbr_doc.md @@ -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-тест. ### Тестирование