Some checks failed
Published content check / validate (push) Failing after 3s
Обновить правила репозитория и подписи исходников в 99 заметках, не затрагивая пользовательские незакоммиченные файлы. Сделать Forgejo Actions содержательным: проверять опубликованный commit, а не пустое рабочее дерево после checkout.
151 lines
30 KiB
Markdown
151 lines
30 KiB
Markdown
---
|
||
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 9110–9114 про 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).
|