Recordizer: batching и границы записей
Что PACKET_MUX_V1 делает до шифрования, как согласуется после AUTH и как безопасно настраивать batching, размеры записей, fragmentation и reassembly.
Recordizer меняет связь «IP-пакет ↔ запись»
В legacy data plane один внутренний IPv4/IPv6-пакет обычно превращался в одну зашифрованную запись. Это не раскрывает содержимое, но создаёт устойчивую связь размеров и моментов отправки. Согласуемый PACKET_MUX_V1 работает до шифрования:
- несколько внутренних пакетов могут попасть в одну зашифрованную запись;
- один внутренний пакет может пересечь границы нескольких записей;
- целевой размер записи и deadline batching варьируются в заданных пределах;
- mux-заголовки находятся внутри AEAD и не видны на проводе.
Где именно он включён
внутренний IPv4/IPv6-пакет
→ PACKET_MUX recordizer
→ padding / traffic normalization
→ шифрование PacketCodec
→ carrier (plain / fake-TLS / Reality-H2 / obfs-WS / UDP QUIC shape)
→ carrier/path fragmentation при необходимости
Целевой record size — порог сборки, а не обещание допаддить каждую запись до этого размера. Deadline может отправить меньшую запись. Padding и traffic normalization выполняются после recordizer и могут дополнительно изменить внешний размер.
Политикой владеет сервер
Параметры obf.recordizer.* не передаются пользователю и не добавляются в qeli://. Сервер отправляет политику внутри аутентифицированного AUTH-контракта; изменение применяется после переподключения.
| Политика | Поведение | Rollout |
|---|---|---|
off | Legacy mapping: один внутренний пакет примерно соответствует одной записи. | Откат или диагностика. |
prefer | Включает PACKET_MUX_V1 только если клиент объявил capability; старый клиент остаётся на legacy data plane. | Рекомендуемый первый этап и дефолт релизных шаблонов. |
required | Несовместимый клиент отклоняется до выдачи адреса. | Только после обновления всего парка. |
Рекомендуемые значения 0.8.1
obf.recordizer.policy = prefer
obf.recordizer.batch.delay_min_ms = 2
obf.recordizer.batch.delay_max_ms = 8
obf.recordizer.batch.max_packets = 16
obf.recordizer.batch.max_queue_bytes = 262144
obf.recordizer.record.max_payload_bytes = 0
obf.recordizer.record.small_min_ratio = 0.25
obf.recordizer.record.small_max_ratio = 0.875
obf.recordizer.record.full_probability = 0.72
obf.recordizer.fragment.enabled = true
obf.recordizer.fragment.reassembly_timeout_ms = 3000
obf.recordizer.fragment.max_inflight_packets = 64
obf.recordizer.fragment.max_reassembly_bytes = 4194304
obf.recordizer.fragment.max_fragments_per_packet = 64
Batching, размер и reassembly
| Группа | Ключи | Смысл и ограничения |
|---|---|---|
| Deadline | delay_min_msdelay_max_ms | Случайное окно flush, запускаемое первым пакетом очереди. 0/0 отправляет без ожидания; max не меньше min. |
| Очередь | max_packetsmax_queue_bytes | Максимум mux-фреймов и жёсткий предел памяти на направление. Queue bytes: 64…4194304. |
| Payload ceiling | max_payload_bytes | 0 автоматически выбирает безопасный plaintext по активному carrier/path; явное значение — 64…MAX_TUNNEL_MTU и всё равно ограничивается budget. |
| Морфология | small_min_ratiosmall_max_ratiofull_probability | Неполная цель выбирается в диапазоне 0 < min ≤ max ≤ 1; probability находится в 0…1 и определяет шанс полной цели. |
| Fragment | fragment.enabled | Разрешает одному внутреннему пакету пересекать записи. Не отключайте, если не гарантировано помещение пакета целиком. |
| Reassembly | reassembly_timeout_msmax_inflight_packetsmax_reassembly_bytesmax_fragments_per_packet | Ограничивают время, количество packet ID, общую память и число частей одного пакета; защищают каждое направление от исчерпания ресурсов. |
Три безопасные отправные точки
| Сценарий | Изменения от дефолта | Компромисс |
|---|---|---|
| Чувствительный к задержке | delay=0…2 ms, max_packets=4 | Меньше batching latency, слабее изменение группировки. |
| Потеряющий UDP | delay=0…2 ms, max_packets=2, full_probability=0.85 | Меньше amplification потери одной крупной записи. |
| Экспериментальная морфология | delay=3…12 ms, full_probability=0.55 | Больше вариативность, но выше задержка/overhead; сначала проверяйте на стенде. |
Что происходит с другими функциями
- Работает со всеми TCP/UDP режимами, IPv4/IPv6/dual-stack и TUN/TAP. На TAP Ethernet header снимается и восстанавливается на краю; произвольные L2-фреймы не переносятся.
- Reality/H2 batching и recordizer delay могут складываться. Изменяйте одно измерение за раз.
- На UDP слишком крупный batch усиливает эффект потери datagram — используйте короткое окно и малый
max_packets. - AWG junk работает до handshake; recordizer — только после AUTH. QUIC shape по-прежнему не является настоящим QUIC.
obf.fragmentation.*дробит выбранные handshake/carrier writes и не заменяетobf.recordizer.fragment.*.- UDP DATA_FRAG/PMTU — внешняя фрагментация уже зашифрованной записи; recordizer fragmentation делит внутренний пакет до PacketCodec.
Системные вызовы пачками — не новый формат записи
После формирования Recordizer-датаграмм Linux и Android отправляют до 32 готовых элементов одним sendmmsg; уже ожидающие входящие датаграммы читаются через recvmmsg. Дополнительного coalescing delay нет, wire format и параметры obf.recordizer.* не меняются.
| Same-window A/B | До | После | Изменение |
|---|---|---|---|
| Upload | 320,8 Мбит/с | 694,7 Мбит/с | +116,5% |
| Download | 359,8 Мбит/с | 697,5 Мбит/с | +93,9% |
Проверка и откат
- Обновите сервер, задайте
policy=prefer, выполнитеqeli check-configи перезапустите профиль. - Переподключите клиент и найдите в журнале маркер
Packet recordizer: PACKET_MUX_V1 active. - Проверьте двусторонний IPv4/IPv6, DNS, reconnect/roaming, PMTU и длинную нагрузку на каждом используемом transport.
- После обновления всего парка при необходимости включите
required.
obf.recordizer.policy = off, перезапустите профиль/службу и переподключите сессии. Изменение не применяется к уже работающей сессии без reconnect.