todo/sing-box/wire-protocol-explained.md
loop-uh 08b4491545
Some checks failed
Published content check / validate (push) Failing after 3s
Завершить переезд базы знаний на Forgejo
Обновить правила репозитория и подписи исходников в 99 заметках, не затрагивая пользовательские незакоммиченные файлы. Сделать Forgejo Actions содержательным: проверять опубликованный commit, а не пустое рабочее дерево после checkout.
2026-08-07 08:27:28 +03:00

30 KiB
Raw Permalink Blame History

date tags aliases
2026-07-18
протоколы
основы
sing-box
для-новичка
Что такое wire-протокол
Двойная реализация протокола
Почему разные программы понимают друг друга
wire format простыми словами

🧵 Что такое «wire-протокол» и почему один протокол реализуют дважды

[!info] О чём заметка Объяснение с нуля, но без упрощений: что такое протокол «на проводе» (wire format), почему две программы, написанные разными людьми и не видевшие кода друг друга, всё равно понимают друг друга, как устроена культура интернет-стандартов, которая это обеспечивает, и зачем вообще писать вторую реализацию одного протокола вместо копирования чужого кода. Заметка нужна как фундамент к разбору sing-box/protocols-origin, где показано, что ядра sing-box и Xray-core совместимы по VLESS/Trojan/REALITY, но общего кода почти не имеют.

TL;DR

  • Протокол — это не программа, а договорённость: какие байты, в каком порядке и в ответ на что летят по сети между двумя программами.
  • «Wire format» (формат на проводе) — точное описание этих байтов. Слово «wire» (провод) подчёркивает: важно только то, что видно на проводе, а не то, что творится внутри программ.
  • Две программы совместимы, если выкладывают на провод одинаковые байты и одинаково реагируют на входящие — даже если написаны на разных языках разными людьми. Совместимость проверяется на границе, а не по коду.
  • Поэтому один протокол можно реализовать дважды независимо (это называют реимплементацией) — ради другого языка, другой лицензии, своей архитектуры или чтобы не наследовать чужие баги. Это не то же самое, что «форк» — копия чужого кода.
  • Вы каждый день пользуетесь этим принципом: любой браузер открывает любой сайт, любая почта пишет любой почте, любой архиватор открывает чужой ZIP.

Сначала — что такое протокол вообще

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

Ключевая мысль, которую легко упустить новичку: протокол — это не код и не программа. Это договорённость, документ, спецификация. А программа, которая эти правила соблюдает, — это уже реализация протокола. Одну и ту же договорённость могут соблюдать сколько угодно разных программ.

[!example] Аналогия: язык Протокол — это язык, на котором две программы разговаривают. Спецификация протокола — учебник грамматики этого языка. Реализация — конкретный человек, который на этом языке говорит. Людей, знающих английский, миллионы, у каждого свой мозг и свой характер — но язык один, поэтому они понимают друг друга. Точно так же: у двух программ «мозги» (внутренний код) могут быть совершенно разные, но если обе «говорят на VLESS», они друг друга поймут.

Где аналогия ломается: живые языки допускают неточность и «поймёшь по контексту», а сетевой протокол — нет. Перепутал один байт — и собеседник не понял вообще ничего (соединение рвётся). Протокол ближе к строгому машинному языку, чем к живой речи.

«Wire format»: смотрим только на провод

Слово wire (провод, кабель) в термине «wire format / wire protocol» означает буквально: нас интересует только то, что реально бежит по сетевому кабелю между двумя программами — конкретная последовательность байтов. Что программа думает внутри, на каком языке написана, как хранит данные в памяти — за пределами провода это никого не касается.

Проще говоря: wire-протокол описывает поведение снаружи и молчит про устройство внутри. Именно поэтому «внутренности» у двух совместимых программ могут отличаться радикально.

[!example] Аналогия: шахматы по переписке Гроссмейстер из Бразилии и любитель из Японии играют партию по почте. Думают они по-разному, на разных языках, один считает варианты в уме, другой двигает фигуры на доске. Но записывают ходы они одинаково — общей шахматной нотацией (e2-e4). И этого достаточно, чтобы сыграть партию.

Здесь нотация ходов = wire-протокол, а мышление игрока = реализация (внутренний код). Аналогия точная сразу в трёх местах: есть формат сообщения (запись хода), есть очерёдность (ходят по очереди — это то, что в протоколах называют «машиной состояний»), и есть правило «некорректный ход отклоняется» — ровно как программа отвергает неправильный пакет.

Как это выглядит в реальных байтах

Чтобы «одинаковые байты на проводе» перестали быть абстракцией — вот реальный пример. Протокол Trojan (один из способов обойти блокировки) описывает заголовок запроса так: сначала 56 байт (пароль в виде хеша), потом разделитель \r\n, потом 1 байт команды, потом адрес назначения, снова \r\n, и дальше сами данные.

Теперь самое важное: и sing-box, и Xray-core — два независимых проекта — порождают на проводе ровно эту последовательность. Вот их код рядом (упрощённо):

sing-box (transport/trojan/protocol.go):        Xray-core (proxy/trojan/protocol.go):
  header.Write(key[:])       // 56 байт           buffer.Write(c.Account.Key)   // 56 байт
  header.Write(CRLF)         // \r\n              buffer.Write(crlf)            // \r\n
  header.WriteByte(CommandTCP) // 1 байт          buffer.WriteByte(command)     // 1 байт
  SocksaddrSerializer.WriteAddrPort(...)          addrParser.WriteAddressPort(...)
  header.Write(CRLF)         // \r\n              buffer.Write(crlf)            // \r\n

Код разный: разные имена функций, разные библиотеки, разный стиль. А байты на выходе — одни и те же: пароль(56) + \r\n + команда(1) + адрес + \r\n + данные. Именно поэтому Trojan-клиент от sing-box без проблем подключается к Trojan-серверу на Xray, и наоборот. Не потому что у них общий код — а потому что оба следуют одному описанию байтов.

Тот же принцип — на более сложном протоколе xray/vless. Его заголовок устроен так: 1 байт версии (0x00), затем 16 «сырых» байт UUID пользователя (именно 16 байт, а не 36-символьная строка с дефисами), 1 байт длины дополнительных полей (addons), сами addons, 1 байт команды, адрес с портом, данные. И снова оба ядра пишут ровно эту раскладку — но по-разному в коде: Xray-core собирает addons «правильным» способом через библиотеку protobuf (proto.Marshal), а sing-box кодирует те же байты вручную, разбирая protobuf-поля по номерам (case 0x0A: — это поле flow). Подход к коду противоположный, результат на проводе идентичный. Пустой VLESS-заголовок для TCP получается длиной 1 + 16 + 1 + 1 = 19 байт плюс адрес — и у того, и у другого.

[!note] Мелкая, но показательная деталь В обоих проектах длина пароля-хеша «зашита» числом 56 (это SHA224, записанный шестнадцатеричными символами: 28 байт хеша → 56 символов). Оба автора независимо написали в коде именно 56 — потому что так сказано в спецификации Trojan. Отклонись один из них хоть на байт — совместимость сломалась бы. Разбор таких зашитых чисел — в sing-box/hardcoded-defaults.

Откуда берётся сама спецификация

Раз протокол — это документ-договорённость, встаёт вопрос: где этот документ и кто его пишет? Вариантов несколько, и они различаются по надёжности:

  • Формальный стандарт (RFC). Самые фундаментальные интернет-протоколы (HTTP, электронная почта SMTP, TLS) описаны в открытых документах RFC (Request for Comments), которые публикует организация IETF. Название скромное («запрос комментариев»), но принятые RFC — фактически законы интернета: открытые, бесплатные, максимально однозначные.
  • Документ автора (README, markdown-файл). У большинства протоколов обхода блокировок (xray/vless, Shadowsocks, Trojan) спецификация — это не RFC, а просто файл с описанием в репозитории автора. Менее формально, но работает.
  • Эталонная (reference) реализация. Иногда отдельного документа нет вообще, и «спецификация» — это код самого автора: читаешь его исходник и повторяешь поведение. Минус: непонятно, где в поведении задумка, а где случайность или баг, который тоже придётся скопировать, чтобы остаться совместимым.
  • Реверс-инжиниринг. Если протокол закрыт, его восстанавливают, наблюдая за трафиком (перехват в Wireshark, анализ байтов, проверка гипотез). Трудоёмко и хрупко: автор поменяет протокол — всё ломается. В теме приватности это работает в обе стороны: и разработчики восстанавливают закрытые протоколы сервисов, и цензоры-DPI реверс-инжинирят протоколы обхода, чтобы научиться их узнавать по тому самому VLESS/dpi-tls-june-2026.

Где лежат спецификации протоколов обхода блокировок

Полезно понимать, что у знакомых по этому vault протоколов уровень формализации очень разный — от почти-RFC до «источник истины — это код автора». Это напрямую влияет на то, насколько легко и надёжно написать совместимую реализацию.

Протокол Есть ли текстовая спецификация Что служит источником истины
Shadowsocks 2022 Да, в RFC-подобном стиле (со словами MUST/SHOULD), репозиторий Shadowsocks-NET/shadowsocks-specs Спецификация — код следует за ней
TUIC Да, файл SPEC.md в репозитории, байтовый формат расписан Спецификация
Hysteria2 Да, раздел Protocol в документации, нормативный стиль Спецификация (детали — по коду apernet/hysteria)
Trojan Да, страница «The Trojan Protocol» с диаграммами, но короткая Спецификация + код для пограничных случаев
VMess Да, но описательная, «вслед за кодом» Код v2ray/Xray
xray/vless Есть, но сама помечена «не обязательно авторитетная» Код Xray-core
xray/reality Нет, только README с идеями Код — REALITY это форк crypto/tls, читать надо исходники

Главный вывод: отсутствие RFC не означает отсутствия стандарта. Для многих протоколов обхода работает модель «эталонная реализация как спецификация» (reference implementation as specification): практический источник истины — это код автора, а текстовое описание либо появилось позже и догоняет код (VLESS, VMess), либо не существует вовсе (REALITY). Крайние точки спектра — Shadowsocks 2022 (спецификация первична, реализации следуют за ней) и REALITY (никакого документа, только Go-исходники).

Зачем писать вторую реализацию, а не скопировать код

Если протокол уже кем-то реализован, почему бы просто не взять готовый код? Причины, по которым команды пишут свою реализацию с нуля:

  • Другой язык или платформа. Оригинал написан на Go, а нужен клиент на Kotlin для Android, на Swift для iPhone, на Rust ради безопасности. Чужой код не перенесёшь между языками — а протокол переносится легко: читаешь спецификацию, пишешь заново.
  • Лицензия. Копирование кода тянет за собой его лицензию (например, GPL с её требованиями). Независимая реализация по описанию протокола — юридически чистый способ получить совместимость под своей лицензией.
  • Своя архитектура и скорость. Свой код встраивается в собственную структуру проекта и оптимизируется под свои сценарии. Классический пример из мира игр: серверы Minecraft вроде Paper быстрее официального именно потому, что переписали внутренности, сохранив протокол.
  • Контроль и доверие. Свой код можно полностью проверить (аудит) и не унаследовать чужие ошибки или закладки. Для инструментов приватности это принципиально.
  • Проверка самой спецификации. Если протокол удалось независимо реализовать дважды и обе реализации совместимы — значит, описание полное и однозначное. У IETF наличие двух независимых совместимых реализаций исторически было признаком зрелости стандарта.

[!tip] Реимплементация ≠ форк Важно не путать два слова. Форк — это копия чужого кода, которую дальше правят под себя (так xray/project-x произошёл от v2ray-core). Реимплементация — это новый код, написанный с нуля по спецификации, без копирования оригинала. sing-box относительно Xray — это в основном реимплементация протоколов (свой код, совместимый по байтам), а не форк. Подробный разбор, что именно в sing-box реимплементировано, а что всё-таки скопировано, — в sing-box/protocols-origin.

Несколько реализаций одного протокола — это норма индустрии

Принцип «один протокол — много независимых реализаций» — не экзотика из мира VPN, а фундамент всего интернета. Вы пользуетесь им каждый день:

  • Веб (HTTP). Серверы nginx, Apache, Caddy, IIS и клиенты Chrome, Firefox, curl написаны разными командами на разных языках, ни один не заглядывал в код другого — но любой браузер открывает любой сайт, потому что все следуют одному контракту (документы RFC 91109114 про HTTP). nginx написал человек, никогда не видевший исходников Chrome, — и всё работает.
  • Почта (SMTP). Письмо из Gmail спокойно доходит до Outlook или почтового сервера провайдера. Серверы Postfix, Exim, Microsoft Exchange написаны разными компаниями, а «разговор» между ними задан спецификацией ещё 1982 года (RFC 821, ныне RFC 5321).
  • DNS. BIND, Unbound, PowerDNS, Knot — независимые реализации системы доменных имён. Причём здесь множественность реализаций — ещё и осознанная политика надёжности: корневые серверы интернета намеренно работают на разном софте, чтобы одна ошибка в одной программе не положила весь DNS сразу.
  • Торренты, архивы. qBittorrent, Transmission, Deluge качают один торрент друг у друга; WinRAR, 7-Zip и встроенный распаковщик Windows открывают один ZIP — всем достаточно одинаково понимать формат, код у каждого свой.

Во всех случаях совместимость держится не на общем коде, а на общем описании того, что летит по проводу.

Культура, которая это придумала: «rough consensus and running code»

За этим стоит целая инженерная традиция. Интернет-стандарты пишет IETF (Internet Engineering Task Force) — открытая организация без формального членства, где участвовать может любой через рабочие группы и почтовые рассылки. Её знаменитый девиз сформулировал инженер Дэвид Кларк из MIT в 1992 году: «мы отвергаем королей, президентов и голосование; мы верим в грубый консенсус и работающий код». Смысл в том, что стандарт признаётся зрелым не по авторитету и не голосованием, а тогда, когда существует несколько независимых реализаций, которые реально работают вместе. То есть двойная независимая реализация — это не побочный эффект, а прямо встроенный в культуру интернета критерий качества стандарта.

Есть и правило, объясняющее, почему разные реализации уживаются, — принцип Постела (Джон Постел, спецификация TCP 1980 года): «будь строг к тому, что отправляешь, и снисходителен к тому, что принимаешь». Отправляй строго по спецификации, но принимай входящее терпимо, прощая мелкие вольности чужих реализаций.

Даже авторы протокола пишут его несколько раз

Показательный случай — WireGuard (современный VPN-протокол). Его придумал Джейсон Доненфельд, и он же ведёт несколько независимых кодовых баз одного протокола на разных языках: реализацию внутри ядра Linux на C, кроссплатформенную wireguard-go на Go (именно она работает внутри официальных приложений для Windows, macOS, Android, iOS), плюс отдельные реализации под ядра Windows и BSD. Одна договорённость (описание протокола в научной статье автора) — и несколько воплощений, потому что контракт первичен, а код вторичен и подстраивается под платформу. Кстати, именно wireguard-go (в форке автора sing-box/sing-box-extended) лежит в основе поддержки WireGuard и amnezia-2-0/reference в sing-box.

Ещё пример из мира, знакомого не только программистам: серверы Minecraft. Официальный сервер закрыт, но энтузиасты годами документировали его сетевой протокол, и по этому описанию с нуля, без единой строчки чужого кода, написаны совместимые серверы (Glowstone на Java, Cuberite на C++). Обычный клиент подключается к ним, потому что они говорят те же байты. Важно не путать их с модификациями вроде Paper — те как раз являются форками официального кода, а не независимыми реализациями.

Контрпример: что бывает, когда спецификации нет

Ценность открытого контракта лучше всего видна там, где его не было. Классическая история — Samba, свободная реализация протокола общего доступа к файлам Windows (SMB). Её автор начал работу в 1992 году, восстанавливая протокол реверс-инжинирингом трафика, потому что Microsoft документацию не публиковала и меняла протокол без предупреждения. Итог — десятилетия догоняющей разработки и постоянные баги совместимости. Развязка пришла не технически, а юридически: после антимонопольного дела Еврокомиссии Microsoft в 2007 году обязали открыть документацию протоколов — и разработка Samba радикально упростилась.

Мораль: несколько независимых реализаций получаются дёшево и надёжно, когда есть опубликованный контракт. Без него совместимость всё равно достижима — через реверс-инжиниринг, — но ценой долгой борьбы и бесконечного хвоста мелких несовместимостей. Именно поэтому для протоколов обхода блокировок так важно, есть ли у них внятная спецификация (см. таблицу выше): xray/reality без документа реализовать заметно тяжелее, чем Shadowsocks 2022 с его RFC-подобным описанием.

📚 См. также

  • sing-box/protocols-origin — применение всего этого на практике: sing-box vs Xray-core, что реимплементировано, а что скопировано
  • sing-box/hardcoded-defaults — те самые «зашитые в код числа» (вроде 56 байт пароля Trojan), от которых зависит байтовая совместимость
  • sing-box/sing-box-extended — прокси-платформа, на примере которой всё разбиралось
  • xray/project-x — пример настоящего форка (копии кода), в отличие от реимплементации
  • xray/vless — конкретный wire-протокол, реализованный независимо в обоих ядрах
  • VLESS/dpi-tls-june-2026 — обратная сторона: цензор смотрит на те же байты на проводе

[!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: исходник этой заметки · весь репозиторий.