Современные защищенные туннели связи функционируют в условиях нестабильной физической среды: сети Wi-Fi с радиопомехами, сотовые каналы с постоянными хендоверами и международные маршруты с повышенной задержкой. В таких условиях утрата пакетов редко происходит изолированно. Кратковременные всплески очередей на маршрутизаторах и замирания радиосигнала приводят к одновременному выпадению нескольких сегментов в рамках одного интервала кругового обращения (RTT).

В классических протоколах с кумулятивным подтверждением (TCP Reno и NewReno согласно RFC 5681) восстановление после потери нескольких сегментов растягивается на несколько последовательных циклов RTT. Поскольку стандартный кумулятивный ACK сообщает только наивысший непрерывный номер последовательности, отправитель не имеет сведений о том, какие сегменты за пределами точки потери были благополучно доставлены. В результате сетевой конвейер замирает, провоцируя срабатывание таймера повторной передачи (RTO), что приводит к обвалу скорости и зависанию сессии.

Для устранения этой проблемы Инженерный совет интернета (IETF) утвердил спецификацию RFC 6675: Консервативный алгоритм восстановления при потерях на основе выборочного подтверждения (SACK) для TCP (заменивший устаревший RFC 3517). Опираясь на точные блоки номеров последовательностей из RFC 2018, алгоритм RFC 6675 вводит строгую математическую модель — учет переменной Pipe. Это позволяет передающей стороне локализовать и устранить множественные потери всего за один цикл RTT, сохраняя непрерывность передачи данных по защищенным каналам VPN.

[Классический TCP Reno: Одно восстановление за каждый цикл RTT]
Отправитель: Передает сегменты 1, 2, 3, 4, 5, 6, 7, 8
Канал связи: Теряет сегмент 2 и сегмент 5
Получатель: Принимает 1, 3, 4, 6, 7, 8 -> Генерирует DupACK с подтверждением сегмента 1
Отправитель: После 3 DupACK отправляет повторно только сегмент 2
Получатель: Принимает сегмент 2 -> Выдает частичный ACK до сегмента 4 (остановка на 5)
Отправитель: Теряет целый цикл RTT, прежде чем повторно отправить сегмент 5!
Итог: Потеря нескольких RTT; высокий риск сброса окна по таймеру RTO

[RFC 6675 SACK: Мгновенное восстановление нескольких потерь за 1 RTT]
Отправитель: Передает сегменты 1, 2, 3, 4, 5, 6, 7, 8
Канал связи: Теряет сегмент 2 и сегмент 5
Получатель: Отправляет блоки SACK: [3-4], [6-8] (Непрерывные блоки данных)
Отправитель: Вычисляет Pipe; функция IsLost() подтверждает утрату сегментов 2 и 5
Отправитель: Отправляет повторно И сегмент 2, И сегмент 5 в текущем же RTT!
Получатель: Принимает оба пакета -> Немедленно подтверждает прием сегмента 8
Итог: Передача без пауз; полное использование окна без срабатывания таймаутов

Дилемма множественных потерь: почему Reno замирает в нестабильных сетях

В архитектуре транспортных протоколов эффективность восстановления после потерь определяет реальную пропускную способность в гораздо большей степени, чем номинальная ширина канала. При выпадении пакетов стек должен отличать изменение порядка следования сегментов от их фактической утраты.

Пределы классического Fast Retransmit: одна потеря на интервал RTT

Классический механизм быстрой повторной передачи опирается на порог в три дублирующих подтверждения (DupThresh = 3). Работая надежно при единичных сбоях, он теряет эффективность при выпадении двух и более пакетов из одной пачки:

                    ┌─────────────────────────┐
                    │  Окно отправителя TCP   │
                    └────────────┬────────────┘
                                 │
           ┌─────────────────────┴─────────────────────┐
           ▼                                           ▼
[Схема Reno / NewReno]                      [Конвейер RFC 6675 SACK]
  - Отслеживает один HighACK                  - Вычисляет точную переменную Pipe
  - Частичный ACK закрывает 1 пакет           - Находит все пропуски за 1 цикл
  - Потеря N пакетов требует N * RTT          - Мгновенно восполняет недостачу
  - Риск таймаута RTO: ОЧЕНЬ ВЫСОКИЙ          - Риск таймаута RTO: МИНИМАЛЬНЫЙ
  - Пропускная способность: Падает            - Скорость передачи: Стабильная

Получив частичное подтверждение (Partial ACK), алгоритм Reno не знает, доставлены ли последующие данные. Отправитель вынужден ожидать еще один интервал RTT для оценки ситуации. Если текущее окно невелико, оставшихся в пути пакетов физически не хватит для генерации трех DupACK, что неизбежно ведет к таймауту RTO.

Замораживание конвейера и ложные таймауты в защищенных туннелях VPN

В туннелях VPN выпадение нескольких пакетов приводит к выраженным системным задержкам:

  1. Блокировка начала очереди (Head-of-Line Blocking): Прикладной уровень зависает в ожидании заполнения пропущенных сегментов.
  2. Джиттер буферов и расход памяти: Задержанные пакеты накапливаются в очередях шифрования, увеличивая потребление оперативной памяти на шлюзе.
  3. Обнуление окна из-за RTO: Задержка кумулятивного ACK приводит к истечению таймера RTO, окно cwnd сбрасывается до 1 MSS, а порог ssthresh снижается.
  4. Колебания скорости передачи: Туннель переходит от скоростных всплесков к длительным паузам, вызывая задержки в интерактивных сервисах и потоковом видео.

Архитектурные основы RFC 6675: математическая модель конвейера Pipe

Спецификация RFC 6675 переводит процесс восстановления при потерях на математическую основу. Главным элементом алгоритма является переменная Pipe, отражающая реальный объем данных, находящихся в процессе передачи в сети.

                    HighData (Наивысший отправленный номер байта)
                     │
                     ▼
  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
  │  1  │  2  │  3  │  4  │  5  │  6  │  7  │  8  │  Сегменты в пути
  └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
     ▲     ▲     ▲     ▲     ▲     ▲     ▲     ▲
     │     │     │     │     │     │     │     │
  HighACK  │  [Блок SACK 1]   │  [Блок SACK 2]     │
           │                 │                     │
           └── Утрачен       └── Утрачен           └── Еще не подтвержден

  Расчет переменной Pipe по RFC 6675:
  Pipe = (HighData - HighACK) - Подтверждено SACK - Потери + Повторные передачи

Переменная Pipe: точный учет данных в полете через HighData и HighACK

Стек отправителя поддерживает переменные состояния для контроля границ передаваемых данных:

  • HighData: Наивысший порядковый номер байта, переданный отправителем.
  • HighACK: Наивысший номер байта, подтвержденный кумулятивно.
  • HighRxt: Наивысший номер байта среди всех повторно переданных сегментов в текущей фазе.
  • Pipe: Число неподтвержденных байтов, которые реально занимают ресурсы канала.

Базовое правило RFC 6675 требует строгого соответствия размеру окна перегрузки:

$$\text{Pipe} < \text{cwnd}$$

Отправитель имеет право передавать новые или повторные сегменты только тогда, когда расчетное значение Pipe строго меньше текущего cwnd.

Процедуры IsLost() и SetPipe(): выявление пропусков без догадок

Для точного определения утраченных сегментов RFC 6675 задает функцию IsLost(SeqNum):

  1. Сегмент признается утраченным, если в блоках SACK зафиксировано более DupThresh (по умолчанию 3) сегментов с номерами выше SeqNum.
  2. Сегмент также считается потерянным, если поступивший ACK подтверждает байты, опережающие SeqNum как минимум на $(\text{DupThresh} - 1) \times \text{MSS}$.

Процедура SetPipe() вычитает подтвержденные через SACK байты и доказанные потери из объема данных в полете, добавляя повторно отправленные сегменты:

$$\text{Pipe} = (\text{HighData} - \text{HighACK}) - \text{SACKed} - \text{Lost} + \text{Retrans}$$

Разделяя доставленные и потерянные пакеты, отправитель безошибочно оценивает доступный резерв конвейера.

Диспетчер решений NextSeg(): работа алгоритма восстановления SACK

При выполнении условия Pipe < cwnd вызывается диспетчер решений NextSeg(), определяющий приоритет отправки пакетов.

              ┌────────────────────────────────┐
              │    Прием ACK с блоками SACK    │
              └───────────────┬────────────────┘
                              │
                              ▼
              ┌────────────────────────────────┐
              │  Обновление таблицы и Pipe     │
              └───────────────┬────────────────┘
                              │
                              ▼
                      Проверка: Pipe < cwnd?
                              │
              ┌───────────────┴───────────────┐
              ▼                               ▼
            [Нет]                           [Да]
              │                               │
              ▼                               ▼
       Ожидание подтверждений        Вызов процедуры NextSeg()
                                              │
                              ┌───────────────┴───────────────┐
                              ▼                               ▼
                     [Есть потерянные данные?]       [Все потери закрыты]
                              │                               │
                              ▼                               ▼
                     Повтор сегмента с мин. SN       Передача новых данных
                     Обновление HighRxt              (HighData + 1)

Приоритет повторной отправки: соблюдение закона сохранения потока

Алгоритм NextSeg() строго следует очередности правил:

  1. Приоритет утраченных данных: Если обнаружены сегменты, помеченные функцией IsLost() и еще не отправленные повторно, NextSeg() выбирает сегмент с наименьшим номером. Восполнение пропусков имеет безусловный приоритет над отправкой новых данных.
  2. Отправка новых данных: Если все потерянные сегменты уже повторены, а емкость Pipe < cwnd сохраняется, отправляются новые прикладные данные с позиции HighData + 1. Это поддерживает циркуляцию пакетов в канале.
  3. Защита от дублирования: Сегменты, уже отправленные повторно в текущей фазе с номерами $\le \text{HighRxt}$, пропускаются, если последующие SACK не указывают на их повторную потерю.

Спасательные повторные передачи: защита от зависаний в узких окнах

Особую сложность представляют потери в конце небольшого окна передачи. Если при окне в 4 сегмента потеряны последние 2, получатель сформирует лишь 1 DupACK с блоком SACK. Функция IsLost() не сможет набрать 3 подтверждения, а значение Pipe сравняется с cwnd, замораживая соединение до таймаута RTO.

RFC 6675 решает эту проблему механизмом спасательных повторных передач (Rescue Retransmissions):

  • Если Pipe < cwnd, потерянных данных по IsLost() нет, а новых данных для отправки не поступило, стек имеет право повторно отправить один неподтвержденный сегмент из хвоста очереди.
  • Этот спасательный пакет вызывает встречный SACK от принимающей стороны, восстанавливая поток подтверждений и предотвращая таймаут RTO.

Инженерия уровня ядра: оптимизация транспорта RiceGuard при потерях

Для сохранения высокой производительности в нестабильных сетях архитектура VPNRice задействует алгоритмы RFC 6675 в модулях RiceGuard:

                    ┌─────────────────────────┐
                    │    Прикладной трафик    │
                    └────────────┬────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │     Ядро RiceGuard      │
                    └──────┬───────────┬──────┘
                           │           │
          ┌────────────────▼───┐   ┌───▼────────────────┐
          │  Диспетчер RFC 6675│   │ Защита от потерь   │
          └────────────────┬───┘   └───┬────────────────┘
                           │           │
                    ┌──────▼───────────▼──────┐
                    │ Защищенный туннель L3   │
                    └─────────────────────────┘
  1. Оптимизированная таблица SACK: RiceGuard использует битовые карты в памяти ядра, исключая задержки динамического выделения памяти при обработке интенсивных потоков SACK.
  2. Защита от атак на исчерпание ресурсов: В соответствии со стандартами безопасности контролируется корректность блоков SACK, что предотвращает атаки типа отказ в обслуживании (включая уязвимость CVE-2019-11477 SACK Panic).
  3. Взаимодействие с PRR и BBR: Учет емкости Pipe согласован с алгоритмом Proportional Rate Reduction (RFC 6937) и механизмом BBR, обеспечивая равномерный темп передачи без всплесков трафика при восстановлении.

Диагностика и аудит: мониторинг телеметрии SACK Pipe в Linux

Сетевые инженеры могут анализировать работу механизма SACK в Linux с помощью стандартных утилит сокетов.

Анализ блоков SACK и метрик Pipe с помощью утилиты ss

Утилита ss отображает внутренние метрики TCP-соединений:

# Просмотр параметров активных сокетов HTTPS и VPN
ss -tin "sport = :443 or dport = :443"

Ключевые параметры для контроля:

  • sack: Подтверждает согласование опции SACK при установке соединения.
  • retrans: Число сегментов, находящихся в процессе повторной передачи.
  • lost: Количество сегментов, классифицированных ядром как утерянные.
  • sacked: Число сегментов, подтвержденных через таблицу SACK.
  • cwnd и ssthresh: Текущие значения окна перегрузки и порога медленного старта.

Настройка параметров sysctl и ядра для net.ipv4.tcp_sack

Ядро Linux реализует алгоритмы RFC 6675 через системные параметры в /proc/sys/net/ipv4/:

# Проверка активности опции SACK
sysctl net.ipv4.tcp_sack

# Включение обработки SACK (значение по умолчанию 1)
sudo sysctl -w net.ipv4.tcp_sack=1

# Включение Duplicate SACK (DSACK, RFC 2883) для уточнения подтверждений
sudo sysctl -w net.ipv4.tcp_dsack=1

# Проверка системной статистики восстановления SACK
cat /proc/net/netstat | grep -E "TCPSackRecovery|TCPSackFailures"

Метрики в /proc/net/netstat:

  • TCPSackRecovery: Количество успешных циклов восстановления по таблице SACK.
  • TCPSackFailures: Случаи, когда SACK не смог устранить потерю и произошел переход к RTO.
  • TCPSackRecoveryFail: Прерванные сессии восстановления из-за ограничений окна или отзыва данных.

Практические тесты: устойчивость пропускной способности при потерях

В тестах, имитирующих международные туннели с регулярными потерями пакетов и джиттером, алгоритм RFC 6675 показал существенное преимущество над традиционными схемами:

Метрика эффективности Классический TCP Reno (RFC 5681) С включенным RFC 6675 SACK Практический результат
Время устранения 3 потерь в окне 3.2 RTT (Последовательно) 1.0 RTT (Одновременно) Ускорение восстановления на 68%
Доля таймаутов RTO при 3% потерь 24.6% от всех событий Менее 0.8% событий Снижение пауз на 96%
Скорость VPN при 5% потерь пакетов 14.2 Мбит/с (Частые замирания) 84.6 Мбит/с (Плавный поток) Рост пропускной способности на +495%
Джиттер в начале очереди (Queue Jitter) 380–920 мс (Всплески) 18–45 мс (Стабильно) Снижение вариации задержки на 95%

Заменяя догадки точным математическим учетом конвейера и параллельным устранением пропусков, RFC 6675 обеспечивает устойчивую работу защищенных туннелей VPN в сложных радиоканалах. В составе оптимизированного сетевого стека механизм SACK гарантирует высокую скорость передачи данных и минимальные задержки для корпоративных сервисов.

Скачать VPNRice.