Спецификация протокола Hysteria 2
Hysteria — это TCP- и UDP-прокси на основе QUIC, разработанный для скорости, безопасности и устойчивости к цензуре. Этот документ описывает протокол, используемый Hysteria начиная с версии 2.0.0, иногда внутренне называемый протоколом «v4». Далее мы будем называть его «протокол» или «протокол Hysteria».
Терминология
Ключевые слова «ДОЛЖЕН» (MUST), «НЕ ДОЛЖЕН» (MUST NOT), «ТРЕБУЕТСЯ» (REQUIRED), «СЛЕДУЕТ» (SHALL), «НЕ СЛЕДУЕТ» (SHALL NOT), «РЕКОМЕНДУЕТСЯ» (SHOULD), «НЕ РЕКОМЕНДУЕТСЯ» (SHOULD NOT), «РЕКОМЕНДОВАНО» (RECOMMENDED), «МОЖЕТ» (MAY) и «НЕОБЯЗАТЕЛЬНО» (OPTIONAL) в этом документе должны интерпретироваться в соответствии с RFC 2119.
Базовый протокол и формат передачи
Протокол Hysteria ДОЛЖЕН быть реализован поверх стандартного транспортного протокола QUIC RFC 9000 с расширением ненадёжных дейтаграмм.
Все многобайтовые числа используют формат Big Endian.
Все целые числа переменной длины («varint») кодируются/декодируются в соответствии с определением QUIC (RFC 9000).
Аутентификация и маскировка под HTTP/3
Одной из ключевых особенностей протокола Hysteria является то, что для третьей стороны без надлежащих учётных данных аутентификации (будь то посредник или активный зонд) прокси-сервер Hysteria ведёт себя как стандартный веб-сервер HTTP/3. Кроме того, зашифрованный трафик между клиентом и сервером неотличим от обычного HTTP/3-трафика.
Поэтому сервер Hysteria ДОЛЖЕН реализовывать HTTP/3-сервер (в соответствии с RFC 9114) и обрабатывать HTTP-запросы как любой стандартный веб-сервер. Для предотвращения обнаружения активными зондами типичных паттернов ответов серверов Hysteria, реализации РЕКОМЕНДУЕТСЯ советовать пользователям либо размещать реальный контент, либо настроить обратный прокси для других сайтов.
Реальный клиент Hysteria при подключении ДОЛЖЕН отправить серверу следующий HTTP/3-запрос:
:method: POST
:path: /auth
:host: hysteria
Hysteria-Auth: [string]
Hysteria-CC-RX: [uint]
Hysteria-Padding: [string]
Hysteria-Auth: Учётные данные аутентификации.
Hysteria-CC-RX: Максимальная скорость приёма клиента в байтах в секунду. Значение 0 означает «неизвестно».
Hysteria-Padding: Случайная строка заполнения переменной длины.
Сервер Hysteria ДОЛЖЕН распознать этот специальный запрос и, вместо попытки отдать контент или перенаправить его на вышестоящий сайт, ДОЛЖЕН аутентифицировать клиента, используя предоставленную информацию. В случае успешной аутентификации сервер ДОЛЖЕН отправить следующий ответ (HTTP-статус 233):
:status: 233 HyOK
Hysteria-UDP: [true/false]
Hysteria-CC-RX: [uint/"auto"]
Hysteria-Padding: [string]
Hysteria-UDP: Поддерживает ли сервер ретрансляцию UDP.
Hysteria-CC-RX: Максимальная скорость приёма сервера в байтах в секунду. Значение 0 означает неограниченно; «auto» означает, что сервер отказывается предоставлять значение и просит клиент использовать управление перегрузкой для самостоятельного определения скорости.
Hysteria-Padding: Случайная строка заполнения переменной длины.
Подробнее об использовании значений Hysteria-CC-RX см. раздел «Управление перегрузкой».
Hysteria-Padding является необязательным и предназначен только для обфускации паттерна запроса/ответа. Обе стороны РЕКОМЕНДУЕТСЯ его игнорировать.
В случае неудачной аутентификации сервер ДОЛЖЕН либо вести себя как стандартный веб-сервер, который не понимает запрос, либо, в случае обратного прокси, перенаправить запрос на вышестоящий сайт и вернуть ответ клиенту.
Клиент ДОЛЖЕН проверить код статуса для определения успешности аутентификации. Если код статуса отличается от 233, клиент ДОЛЖЕН считать аутентификацию неудачной и отключиться от сервера.
После (и только после) прохождения клиентом аутентификации сервер ДОЛЖЕН считать это QUIC-соединение прокси-соединением Hysteria. Затем он ДОЛЖЕН начать обработку прокси-запросов от клиента, как описано в следующем разделе.
Прокси-запросы
TCP
Для каждого TCP-соединения клиент ДОЛЖЕН создать новый двунаправленный поток QUIC и отправить следующее сообщение TCPRequest:
[varint] 0x401 (TCPRequest ID)
[varint] Длина адреса
[bytes] Строка адреса (host:port)
[varint] Длина заполнения
[bytes] Случайное заполнение
Сервер ДОЛЖЕН ответить сообщением TCPResponse:
[uint8] Статус (0x00 = OK, 0x01 = Error)
[varint] Длина сообщения
[bytes] Строка сообщения
[varint] Длина заполнения
[bytes] Случайное заполнение
Если статус OK, сервер ДОЛЖЕН начать перенаправление данных между клиентом и указанным TCP-адресом до тех пор, пока одна из сторон не закроет соединение. Если статус Error, сервер ДОЛЖЕН закрыть поток QUIC.
UDP
UDP-пакеты ДОЛЖНЫ быть инкапсулированы в следующий формат UDPMessage и отправлены через ненадёжную дейтаграмму QUIC (как от клиента к серверу, так и от сервера к клиенту):
[uint32] Session ID
[uint16] Packet ID
[uint8] Fragment ID
[uint8] Fragment count
[varint] Длина адреса
[bytes] Строка адреса (host:port)
[bytes] Полезная нагрузка
Клиент ДОЛЖЕН использовать уникальный Session ID для каждой UDP-сессии. Сервер РЕКОМЕНДУЕТСЯ назначать уникальный UDP-порт для каждого Session ID, если он не имеет другого механизма различения пакетов от разных сессий (например, симметричный NAT, различные исходящие IP-адреса и т.д.).
Протокол не предоставляет явного способа закрытия UDP-сессии. Хотя клиент может сохранять и повторно использовать Session ID бесконечно, сервер РЕКОМЕНДУЕТСЯ освобождать и переназначать порт, связанный с Session ID, после периода бездействия или по другим критериям. Если клиент отправляет UDP-пакет с Session ID, который больше не распознаётся сервером, сервер ДОЛЖЕН рассматривать его как новую сессию и назначить новый порт.
Если сервер не поддерживает ретрансляцию UDP, он РЕКОМЕНДУЕТСЯ молча отбрасывать все полученные от клиента UDP-сообщения.
Фрагментация
Из-за ограничений, налагаемых каналом ненадёжных дейтаграмм QUIC, любой UDP-пакет, превышающий максимальный размер дейтаграммы QUIC, ДОЛЖЕН быть либо фрагментирован, либо отброшен.
Для фрагментированных пакетов каждый фрагмент ДОЛЖЕН нести одинаковый уникальный Packet ID. Fragment ID, начиная с 0, указывает индекс из общего Fragment Count. Как сервер, так и клиент ДОЛЖНЫ дождаться прибытия всех фрагментов фрагментированного пакета перед их обработкой. Если один или несколько фрагментов пакета потеряны, весь пакет ДОЛЖЕН быть отброшен.
Для нефрагментированных пакетов Fragment Count ДОЛЖЕН быть установлен в 1. В этом случае значения Packet ID и Fragment ID не имеют значения.
Управление перегрузкой
Уникальная особенность Hysteria — возможность установки скорости tx/rx (загрузка/скачивание) на стороне клиента. Во время аутентификации клиент отправляет свою скорость rx серверу через заголовок Hysteria-CC-RX. Сервер может использовать это для определения скорости передачи клиенту, и наоборот, возвращая свою скорость rx клиенту через тот же заголовок.
Три особых случая:
- Если клиент отправляет 0, он не знает свою скорость rx. Сервер ДОЛЖЕН использовать алгоритм управления перегрузкой (например, BBR, Cubic) для настройки скорости передачи.
- Если сервер отвечает 0, ограничений полосы пропускания нет. Клиент МОЖЕТ передавать с любой скоростью.
- Если сервер отвечает «auto», он предпочитает не указывать скорость. Клиент ДОЛЖЕН использовать алгоритм управления перегрузкой для настройки скорости передачи.
Обфускация «Salamander»
Протокол Hysteria поддерживает необязательный уровень обфускации под кодовым названием «Salamander».
«Salamander» инкапсулирует все QUIC-пакеты в следующий формат:
Для каждого QUIC-пакета обфускатор ДОЛЖЕН вычислить хеш BLAKE2b-256 случайно сгенерированного 8-байтового salt, добавленного к предварительно согласованному ключу пользователя.
Затем хеш используется для обфускации полезной нагрузки с помощью следующего алгоритма:
Деобфускатор ДОЛЖЕН использовать те же алгоритмы для вычисления хеша с солью и деобфускации полезной нагрузки. Любой некорректный пакет ДОЛЖЕН быть отброшен.