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

151 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
date: 2026-07-18
tags:
- протоколы
- основы
- sing-box
- для-новичка
aliases:
- Что такое wire-протокол
- Двойная реализация протокола
- Почему разные программы понимают друг друга
- wire format простыми словами
---
# 🧵 Что такое «wire-протокол» и почему один протокол реализуют дважды
> [!info] О чём заметка
> Объяснение с нуля, но без упрощений: что такое протокол «на проводе» (wire format), почему две программы, написанные разными людьми и не видевшие кода друг друга, всё равно понимают друг друга, как устроена культура интернет-стандартов, которая это обеспечивает, и зачем вообще писать вторую реализацию одного протокола вместо копирования чужого кода. Заметка нужна как фундамент к разбору [[sing-box/protocols-origin|«Откуда в sing-box код протоколов»]], где показано, что ядра 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|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|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\|VLESS]] | Есть, но сама помечена «не обязательно авторитетная» | **Код** Xray-core |
| [[xray/reality\|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|Xray-core]] произошёл от 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|sing-box-extended]]) лежит в основе поддержки WireGuard и [[amnezia-2-0/reference|Amnezia 2.0]] в sing-box.
Ещё пример из мира, знакомого не только программистам: серверы **Minecraft**. Официальный сервер закрыт, но энтузиасты годами документировали его сетевой протокол, и по этому описанию с нуля, без единой строчки чужого кода, написаны совместимые серверы (Glowstone на Java, Cuberite на C++). Обычный клиент подключается к ним, потому что они говорят те же байты. Важно не путать их с модификациями вроде Paper — те как раз являются форками официального кода, а не независимыми реализациями.
### Контрпример: что бывает, когда спецификации нет
Ценность открытого контракта лучше всего видна там, где его не было. Классическая история — **Samba**, свободная реализация протокола общего доступа к файлам Windows (SMB). Её автор начал работу в 1992 году, восстанавливая протокол реверс-инжинирингом трафика, потому что Microsoft документацию не публиковала и меняла протокол без предупреждения. Итог — десятилетия догоняющей разработки и постоянные баги совместимости. Развязка пришла не технически, а юридически: после антимонопольного дела Еврокомиссии Microsoft в 2007 году обязали открыть документацию протоколов — и разработка Samba радикально упростилась.
Мораль: несколько независимых реализаций получаются дёшево и надёжно, **когда есть опубликованный контракт**. Без него совместимость всё равно достижима — через реверс-инжиниринг, — но ценой долгой борьбы и бесконечного хвоста мелких несовместимостей. Именно поэтому для протоколов обхода блокировок так важно, есть ли у них внятная спецификация (см. таблицу выше): [[xray/reality|REALITY]] без документа реализовать заметно тяжелее, чем Shadowsocks 2022 с его RFC-подобным описанием.
## 📚 См. также
- [[sing-box/protocols-origin|Откуда код протоколов в sing-box]] — применение всего этого на практике: sing-box vs Xray-core, что реимплементировано, а что скопировано
- [[sing-box/hardcoded-defaults|Хардкод-константы и дефолты]] — те самые «зашитые в код числа» (вроде 56 байт пароля Trojan), от которых зависит байтовая совместимость
- [[sing-box/sing-box-extended|sing-box-extended]] — прокси-платформа, на примере которой всё разбиралось
- [[xray/project-x|Project X / Xray-core]] — пример настоящего форка (копии кода), в отличие от реимплементации
- [[xray/vless|Протокол VLESS]] — конкретный wire-протокол, реализованный независимо в обоих ядрах
- [[VLESS/dpi-tls-june-2026|Как DPI узнаёт протокол по почерку]] — обратная сторона: цензор смотрит на те же байты на проводе
---
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: [исходник этой заметки](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/sing-box/wire-protocol-explained.md) · [весь репозиторий](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main).