Совместимость проводных форматов
Этот документ описывает побайтовые проводные форматы прокси-протоколов, поддерживаемых sing-box, предназначенный для разработчиков, которым необходимо обеспечить совместимость с существующими экземплярами sing-box (и Xray-core).
Проводной формат VLESS
VLESS реализован в библиотеке sing-vmess/vless. sing-box делегирует всю обработку проводного формата этой библиотеке.
Заголовок запроса
+----------+---------+---------+----------+---------+-------------------+
| Version | UUID | Addons | Command | Dest | Payload |
| 1 byte | 16 bytes| varint+ | 1 byte | variable| ... |
+----------+---------+---------+----------+---------+-------------------+| Поле | Размер | Описание |
|---|---|---|
| Version | 1 байт | Версия протокола, всегда 0 |
| UUID | 16 байт | UUID пользователя в двоичной форме |
| Addons Length | 1 байт | Длина protobuf-кодированных дополнений (0, если нет) |
| Addons | переменный | Protobuf-дополнения (содержит поле Flow для XTLS) |
| Command | 1 байт | 0x01 = TCP, 0x02 = UDP, 0x03 = MUX |
| Destination | переменный | Формат адреса SOCKS5 (см. ниже) |
Заголовок ответа
+----------+---------+
| Version | Addons |
| 1 byte | varint+ |
+----------+---------+| Поле | Размер | Описание |
|---|---|---|
| Version | 1 байт | Всегда 0 |
| Addons Length | 1 байт | Длина protobuf-дополнений (обычно 0) |
После заголовка ответа данные передаются двунаправленно как необработанный TCP-поток.
Формат адреса SOCKS5
Используется в поле назначения:
Type 0x01 (IPv4): [1 byte type] [4 bytes addr] [2 bytes port big-endian]
Type 0x03 (FQDN): [1 byte type] [1 byte len] [N bytes domain] [2 bytes port big-endian]
Type 0x04 (IPv6): [1 byte type] [16 bytes addr] [2 bytes port big-endian]Кодирование пакетов
VLESS поддерживает три режима кодирования UDP-пакетов:
1. Простой (без кодирования)
Каждый UDP-пакет отправляется как отдельное VLESS-соединение с командой 0x02. Неэффективно, но просто.
2. PacketAddr
Использует магический FQDN sp.packet-addr.v2fly.arpa как назначение VLESS. Каждый UDP-пакет внутри соединения обрамляется как:
[SOCKS5 addr] [2 bytes payload length big-endian] [payload]3. XUDP (по умолчанию)
Использует кодирование XUDP в стиле VMess внутри VLESS-соединения с командой 0x03 (MUX). Каждый пакет обрамляется с управлением сессиями, аналогичным VMess XUDP.
Flow: xtls-rprx-vision
При настройке flow: "xtls-rprx-vision" VLESS использует прямую пересылку TLS. Клиент:
- Читает TLS-записи из внутреннего соединения
- Дополняет короткие записи для скрытия паттернов длины записей
- Переходит к необработанному копированию после завершения TLS-рукопожатия
Это требует осведомлённости о TLS на уровне прокси.
Проводной формат VMess
VMess реализован в библиотеке sing-vmess.
Получение ключей
Request Key: MD5(UUID bytes)
Request IV: MD5(timestamp + UUID bytes)Временная метка — текущее Unix-время в секундах, кодированное как big-endian int64, с 30-секундным окном допуска для аутентификации.
Заголовок запроса (AlterId = 0, AEAD)
Современный формат AEAD (без alterId / alterId = 0):
Аутентификация:
[16 bytes: HMAC-MD5 временной метки с использованием UUID в качестве ключа]
Зашифрованный заголовок (AES-128-GCM):
[1 byte: version (1)]
[16 bytes: IV тела запроса]
[16 bytes: ключ тела запроса]
[1 byte: аутентификация ответа V]
[1 byte: флаги опций]
bit 0: chunk stream (всегда установлен)
bit 1: повторное использование соединения (устарело)
bit 2: маскирование чанков (global_padding)
bit 3: аутентифицированная длина
bit 4: заполнение
[1 byte: длина заполнения P (верхние 4 бита) + безопасность (нижние 4 бита)]
[1 byte: зарезервировано (0)]
[1 byte: команда (0x01=TCP, 0x02=UDP)]
[2 bytes: порт big-endian]
[address: тип + адрес]
[P bytes: случайное заполнение]
[4 bytes: хеш FNV1a заголовка]Значения безопасности (нижние 4 бита):
0x00: Устаревший (AES-128-CFB)0x03: AES-128-GCM0x04: ChaCha20-Poly13050x05: None (открытый текст)0x06: Zero (без шифрования, режим аутентифицированной длины)
Заголовок ответа
[1 byte: аутентификация ответа V (должна совпадать с запросом)]
[1 byte: флаги опций]
[1 byte: команда (0)]
[1 byte: длина команды (0)]Обрамление данных (Chunk Stream)
Каждый чанк данных:
Без аутентифицированной длины:
[2 bytes: длина big-endian] [зашифрованная полезная нагрузка]
С аутентифицированной длиной:
[2 bytes: зашифрованная длина] [зашифрованная полезная нагрузка]
(длина сама шифруется отдельным AEAD)Маскирование чанков применяет XOR к длине с хешем, полученным из IV, что затрудняет анализ длины.
Кодирование пакетов (XUDP)
VMess поддерживает XUDP для UDP-over-TCP. XUDP использует мультиплексирование на основе сессий, где каждое UDP-«соединение» получает ID сессии:
[2 bytes: session ID]
[1 byte: status (new/keep/end)]
[1 byte: padding length]
[address: destination]
[2 bytes: payload length]
[payload]
[padding]Проводной формат Trojan
Trojan — это простой протокол с аутентификацией по паролю. Реализация sing-box находится в transport/trojan/.
Получение ключа
func Key(password string) [56]byte {
hash := sha256.New224() // SHA-224, выдаёт 28 байт
hash.Write([]byte(password))
hex.Encode(key[:], hash.Sum(nil)) // 28 байт -> 56 hex-символов
return key
}Ключ — это hex-кодированный хеш SHA-224 пароля, дающий 56-байтовую ASCII-строку.
TCP-запрос
[56 bytes: hex-ключ]
[2 bytes: CRLF (\r\n)]
[1 byte: команда]
0x01 = TCP (CommandTCP)
0x03 = UDP (CommandUDP)
0x7F = MUX (CommandMux)
[variable: адрес SOCKS5 (назначение)]
[2 bytes: CRLF (\r\n)]
[полезная нагрузка...]Пример (TCP к example.com:443):
6162636465666768... (56 байт hex-ключ)
0D 0A (CRLF)
01 (команда TCP)
03 0B 65 78 61 6D 70 6C 65 2E 63 6F 6D 01 BB (SOCKS5: домен "example.com" порт 443)
0D 0A (CRLF)
[TCP-нагрузка следует немедленно]Сервер отвечает необработанными данными — заголовка ответа нет.
Обрамление UDP-пакетов
После начального рукопожатия (с CommandUDP) каждый UDP-пакет обрамляется как:
[variable: адрес SOCKS5 (назначение пакета)]
[2 bytes: длина полезной нагрузки big-endian]
[2 bytes: CRLF (\r\n)]
[байты полезной нагрузки]Для начального пакета рукопожатия назначение появляется дважды — один раз в заголовке рукопожатия и один раз в обрамлении пакета:
[56 bytes: ключ] [CRLF]
[0x03: команда UDP]
[SOCKS5 addr: начальное назначение] <-- назначение рукопожатия
[CRLF]
[SOCKS5 addr: назначение пакета] <-- назначение первого пакета (обычно совпадает)
[2 bytes: длина полезной нагрузки]
[CRLF]
[полезная нагрузка]Последующие пакеты в рамках того же соединения используют только обрамление пакета (без префикса ключ/команда).
Проводной формат Shadowsocks
sing-box использует библиотеку sing-shadowsocks2.
Шифры AEAD (aes-128-gcm, aes-256-gcm, chacha20-ietf-poly1305)
Получение ключа
Key = HKDF-SHA1(password=password, salt=nil, info="ss-subkey")
или
Key = EVP_BytesToKey(password, key_size) // устаревшийTCP-поток
[salt: key_size байт случайных]
[зашифрованный адрес SOCKS5 + начальная полезная нагрузка]
[зашифрованные чанки...]Каждый зашифрованный чанк:
[2 bytes зашифрованная длина + 16 bytes тег AEAD]
[полезная нагрузка + 16 bytes тег AEAD]Длина — big-endian uint16 с максимальным значением 0x3FFF (16383 байта).
Шифрование использует AEAD с подключом для каждой сессии, полученным через HKDF:
Subkey = HKDF-SHA1(key=PSK, salt=salt, info="ss-subkey")Nonce начинается с 0 и увеличивается на 1 для каждой операции AEAD (длина и полезная нагрузка используют отдельные инкременты nonce).
UDP-пакет
[salt: key_size байт]
[зашифрованный: адрес SOCKS5 + полезная нагрузка + тег AEAD]Каждый UDP-пакет использует свежую случайную соль и, следовательно, свежий подключ.
Шифры AEAD 2022 (Shadowsocks 2022)
Шифры 2022 используют другое обрамление с защитой от повторов и временными метками.
Формат ключа
Base64-кодированные необработанные байты ключа:
2022-blake3-aes-128-gcm: 16-байтовый ключ2022-blake3-aes-256-gcm: 32-байтовый ключ2022-blake3-chacha20-poly1305: 32-байтовый ключ
TCP-заголовок
Request Salt: [key_size байт случайных]
Фиксированный заголовок (зашифрованный):
[1 byte: тип (0=клиент, 1=сервер)]
[8 bytes: временная метка big-endian (Unix epoch)]
[2 bytes: длина соли запроса]
[N bytes: соль запроса (для корреляции ответа)]
Переменный заголовок (зашифрованный, отдельный nonce):
[1 byte: тип адреса SOCKS5]
[variable: адрес SOCKS5]
[2 bytes: длина заполнения начальной полезной нагрузки]
[N bytes: заполнение]
[начальная полезная нагрузка]Многопользовательский режим (EIH)
Для многопользовательских серверов добавляются заголовки зашифрованной идентификации:
[N * 16 bytes: блоки EIH]Каждый блок — это AES-ECB(identity_subkey, salt[0:16] XOR PSK_hash[0:16]).
Формат мультиплекса (sing-mux)
sing-box использует библиотеку sing-mux для мультиплексирования соединений, поддерживая три протокола.
Выбор протокола
{
"multiplex": {
"enabled": true,
"protocol": "h2mux", // или "smux", "yamux"
"max_connections": 4,
"min_streams": 4,
"max_streams": 0,
"padding": false
}
}Рукопожатие sing-mux
Перед базовым протоколом мультиплексирования sing-mux добавляет согласование версии и протокола:
[1 byte: version]
[1 byte: protocol]
0x00 = smux
0x01 = yamux
0x02 = h2mux
[заполнение, если включено]Заголовок запроса потока
Каждый новый поток в мультиплексе начинается с:
[1 byte: network]
0x00 = TCP
0x01 = UDP
[SOCKS5 address: назначение]Для UDP-потоков каждый пакет дополнительно обрамляется с префиксом длины.
Заполнение
Когда padding: true включено, к рукопожатию и каждому потоку добавляется заполнение случайной длины для противодействия анализу трафика:
[2 bytes: длина заполнения big-endian]
[N bytes: случайное заполнение]Режим Brutal
Brutal — это пользовательский режим управления перегрузкой, который принудительно устанавливает фиксированную скорость отправки:
{
"brutal": {
"enabled": true,
"up_mbps": 100,
"down_mbps": 100
}
}Это переопределяет управление перегрузкой TCP фиксированной полосой пропускания, полезно для сетей с потерей пакетов, где обычный TCP слишком агрессивно снижает скорость.
UDP-over-TCP (UoT)
Используется Shadowsocks, когда сервер не поддерживает нативную ретрансляцию UDP:
{
"udp_over_tcp": true
}Формат кадра UoT v2
Каждый UDP-пакет обрамляется поверх TCP-потока как:
[2 bytes: общая длина кадра big-endian (включая адрес)]
[SOCKS5 address: назначение пакета]
[полезная нагрузка]Версия UoT согласовывается через пакет sing/common/uot. Версия 2 (текущая по умолчанию) использует вышеуказанный формат.
Сериализация адреса SOCKS5
Этот формат используется во всех протоколах для кодирования адресов назначения. Он следует формату адреса SOCKS5:
IPv4: [0x01] [4 bytes: адрес] [2 bytes: порт big-endian]
Domain: [0x03] [1 byte: длина] [N bytes: домен] [2 bytes: порт big-endian]
IPv6: [0x04] [16 bytes: адрес] [2 bytes: порт big-endian]sing-box использует M.SocksaddrSerializer из библиотеки sing, который реализует именно этот формат.
Замечания о совместимости
С Xray-core
- VMess: Полная совместимость. Используйте
security: "auto"или явный шифр. УстановитеalterId: 0для режима AEAD (обязателен для современного Xray) - VLESS: Полная совместимость. Кодирование пакетов XUDP является режимом по умолчанию и соответствует поведению Xray
- Trojan: Полная совместимость. Хеширование пароля (SHA-224 hex) идентично
- Shadowsocks: Полная совместимость для шифров AEAD и 2022
С Clash.Meta
- VMess: Совместим. Clash.Meta использует ту же библиотеку
sing-vmess - Trojan: Совместим
- Shadowsocks: Совместим
Распространённые подводные камни
- Временная метка VMess: Аутентификация использует текущую Unix-метку времени в секундах. Расхождение часов более 120 секунд вызовет ошибки аутентификации. Используйте NTP
- UUID VLESS: Должен быть ровно 16 байт в двоичной форме. Парсится из стандартного строкового формата UUID (
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) - Ключ Trojan: Используется SHA-224 (не SHA-256). Выход hex-кодируется, давая ровно 56 ASCII-байт
- Nonce Shadowsocks: Начинается с 0 и увеличивается последовательно. Nonce обычно 12 байт (96 бит) со счётчиком в первых байтах (little-endian для большинства реализаций)
- Временные метки Shadowsocks 2022: Должны быть в пределах 30 секунд от времени сервера. Используйте NTP
- Байт типа адреса SOCKS5: Должен быть
0x01(IPv4),0x03(домен) или0x04(IPv6). Тип0x00недопустим - Кодирование порта: Всегда big-endian uint16 во всех протоколах
- Требование TLS: VLESS и Trojan всегда должны использоваться с TLS в рабочей среде. Без TLS ключ/UUID передаётся открытым текстом