Skip to content

Совместимость проводных форматов

Этот документ описывает побайтовые проводные форматы прокси-протоколов, поддерживаемых 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| ...               |
+----------+---------+---------+----------+---------+-------------------+
ПолеРазмерОписание
Version1 байтВерсия протокола, всегда 0
UUID16 байтUUID пользователя в двоичной форме
Addons Length1 байтДлина protobuf-кодированных дополнений (0, если нет)
AddonsпеременныйProtobuf-дополнения (содержит поле Flow для XTLS)
Command1 байт0x01 = TCP, 0x02 = UDP, 0x03 = MUX
DestinationпеременныйФормат адреса SOCKS5 (см. ниже)

Заголовок ответа

+----------+---------+
| Version  | Addons  |
| 1 byte   | varint+ |
+----------+---------+
ПолеРазмерОписание
Version1 байтВсегда 0
Addons Length1 байтДлина 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. Клиент:

  1. Читает TLS-записи из внутреннего соединения
  2. Дополняет короткие записи для скрытия паттернов длины записей
  3. Переходит к необработанному копированию после завершения 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-GCM
  • 0x04: ChaCha20-Poly1305
  • 0x05: 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/.

Получение ключа

go
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 для мультиплексирования соединений, поддерживая три протокола.

Выбор протокола

json
{
  "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 — это пользовательский режим управления перегрузкой, который принудительно устанавливает фиксированную скорость отправки:

json
{
  "brutal": {
    "enabled": true,
    "up_mbps": 100,
    "down_mbps": 100
  }
}

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

UDP-over-TCP (UoT)

Используется Shadowsocks, когда сервер не поддерживает нативную ретрансляцию UDP:

json
{
  "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: Совместим

Распространённые подводные камни

  1. Временная метка VMess: Аутентификация использует текущую Unix-метку времени в секундах. Расхождение часов более 120 секунд вызовет ошибки аутентификации. Используйте NTP
  2. UUID VLESS: Должен быть ровно 16 байт в двоичной форме. Парсится из стандартного строкового формата UUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
  3. Ключ Trojan: Используется SHA-224 (не SHA-256). Выход hex-кодируется, давая ровно 56 ASCII-байт
  4. Nonce Shadowsocks: Начинается с 0 и увеличивается последовательно. Nonce обычно 12 байт (96 бит) со счётчиком в первых байтах (little-endian для большинства реализаций)
  5. Временные метки Shadowsocks 2022: Должны быть в пределах 30 секунд от времени сервера. Используйте NTP
  6. Байт типа адреса SOCKS5: Должен быть 0x01 (IPv4), 0x03 (домен) или 0x04 (IPv6). Тип 0x00 недопустим
  7. Кодирование порта: Всегда big-endian uint16 во всех протоколах
  8. Требование TLS: VLESS и Trojan всегда должны использоваться с TLS в рабочей среде. Без TLS ключ/UUID передаётся открытым текстом