Skip to content

Protocols and Transports.ru

Iman edited this page Sep 6, 2026 · 2 revisions

Протоколы и транспорты

Вики Caspian

Руководство перенесено из README. Даты измерений сохранены; перенос документации не означает нового запуска тестов. English | فارسی | Русский | 中文

Что можно вставить и что будет отклонено

Конфиг приносите вы. Ниже то, что коробка принимает, взятое из самого кода, который это принятие выполняет, а не из списка пожеланий. Каждая строка измерена против internal/link и закреплённой версии движка.

Работает Отклоняется
Share-ссылки vless:// vmess:// ss:// socks:// trojan:// hysteria2:// hy2:// tuic:// ssr:// wireguard:// anytls:// naive+https:// hysteria:// (версия 1)
Вставленные документы YAML для Clash и Clash.Meta, сырой xray JSON, список ссылок по одной в строке, блок подписки в base64 адрес подписки, документ Clash, завёрнутый в base64, массив JSON, текст, первая строка которого является комментарием
Транспорты raw (он же tcp), ws, grpc, httpupgrade, xhttp (он же splithttp), kcp и mkcp h2, h3, http, quic, gun
Безопасность none, tls, reality xtls (старого образца), allowInsecure
flow в VLESS xtls-rprx-vision, xtls-rprx-vision-udp443 или ничего любое другое значение

h2 и h3 в том столбце с отклонёнными, это ИМЕНА транспортов. Сами HTTP/2 и HTTP/3 переносятся: type=xhttp с security=tls, а какой именно, решает ALPN в TLS. Смотрите HTTP/2 и HTTP/3 переносятся, просто под другим именем.

Шесть вещей удивляют людей, поэтому они здесь, а не в сноске:

Используется только ПЕРВАЯ ссылка. Вставьте сорок серверов, и настроен будет один; панель говорит, сколько она нашла. ss:// и socks:// требуют пользовательские данные в форме base64, а простое написание method:password@host отклоняется. REALITY работает только поверх raw, xhttp и grpc, поэтому сочетание с WebSocket движок отклоняет прямо в момент вставки, а не спустя время при подключении. security= здесь должен быть в нижнем регистре, хотя самому движку это безразлично, и TLS заглавными буквами возвращается вам как none. Параметр plugin= в ссылке ss:// игнорируется без каких-либо сообщений. И адрес подписки отклоняется, потому что панель ничего не запрашивает из интернета, а это намеренное свойство, а не недостающая функция.

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

Протоколы и транспорты

Share-ссылка несёт в себе три разные вещи, и их полезно держать порознь: сам прокси-протокол, транспорт, который его переносит, и слой шифрования, обёрнутый вокруг этого транспорта. Ссылка VLESS поверх WebSocket с TLS и ссылка VLESS поверх обычного TCP с REALITY, это один и тот же протокол, доходящий до сервера одного и того же типа двумя разными путями. Ломаются они по-разному.

Прокси-протоколы, это те семь схем, что перечислены выше. VLESS используется в большинстве примеров этого документа, потому что REALITY построен именно под него. Ничто в устройстве не привязано конкретно к нему. Парсер выдаёт описание, internal/xcfg собирает вокруг него документ для движка, а остальная часть коробки не знает, какой протокол она несёт.

Транспорты приходят из xray-core и названы во вендоренном парсере:

  • tcp, он же raw
  • ws, для WebSocket
  • httpupgrade
  • xhttp, протокол, ранее называвшийся SplitHTTP. Разбираются оба написания
  • grpc
  • kcp и mkcp, для mKCP

h2, http, h3 и quic в этом списке отсутствуют. Версия движка, которая здесь закреплена, их удалила, поэтому ссылка, запрашивающая один из них, отклоняется, а не переносится, и TestRemovedTransportsAreRefusedWithASentence в internal/link держит этот отказ на месте.

Насколько понятно читается отказ, зависит от того, каким путём пришли данные. Документ Clash, называющий такой транспорт, получает фразу про транспорт. Тот же транспорт в параметре type= в share-ссылке приходит как обобщённое «nothing in the pasted text was a proxy link this box understands», что верно и бесполезно. TestRemovedTransportInAURIIsReportedLessWell закрепляет эту разницу, так что она является известным пробелом, а не сюрпризом.

HTTP/2 и HTTP/3 переносятся, просто под другим именем

Отказ в type=h2 или type=quic не означает, что коробка не умеет на них говорить. Он означает, что написание переехало. XHTTP заменил оба, и версию HTTP он выбирает по ALPN в TLS, а не по имени транспорта:

Что вам нужно Что писать
HTTP/3, то есть QUIC type=xhttp с security=tls, alpn=h3 и mode=stream-one
HTTP/2 type=xhttp с security=tls и любым ALPN, который не равен ровно h3
QUIC, без XHTTP ссылка hysteria2://, под которой лежит QUIC и которой нужен alpn=h3

Ключи доходят до движка нетронутыми: internal/xcfg несёт outbound как непрозрачный JSON и никогда его не разбирает, поэтому alpn, mode, xmux и блок настройки QUIC приходят ровно в том виде, в каком их вставили.

Четыре детали решают, получите вы h3 или молча получите что-то другое:

alpn должен содержать ровно одно значение, и это значение должно быть h3. Написание alpn=h3,h2 без всякого предупреждения даёт вам HTTP/2, потому что список любой другой длины движок принимает за запрос версии 2. REALITY, где бы он ни присутствовал, навязывает HTTP/2, поэтому REALITY и h3 взаимно исключают друг друга, и их сочетание даёт вам h2, а не ошибку. mode надо задать явно, потому что значение по умолчанию разрешается в packet-up, а не в ту форму stream-one, которую движок называет заменой QUIC. А downloadSettings, для раздельной загрузки вверх и вниз, отклоняется вместе с mode: stream-one; для такого сочетания нужен stream-up.

Одно столкновение в терминах стоит проговорить прямо, потому что читается оно как противоречие: type=h3 отклоняется, а alpn=h3 обязателен. Это разные поля. Первое называет транспорт, которого больше нет; второе называет протокол, о котором договариваются внутри TLS.

Эти конфигурации коробка принимает и проверяет. Их ещё ни разу не прогоняли отсюда против живого сервера, поэтому считайте эту строку возможностью движка, а не тем, что этот проект видел работающим.

Слой безопасности, это reality, tls или none.

Не всякое сочетание одинаково полезно. REALITY обычно сочетают с обычным TCP, потому что весь его метод состоит в заимствовании TLS-рукопожатия настоящего сайта, так что оборачивание его в ещё один слой TLS сводит смысл на нет. WebSocket, HTTPUpgrade и XHTTP существуют, чтобы выглядеть как обычный веб-трафик для того, кто рассматривает соединение, и их обычно сочетают с TLS по той же причине, по которой это делает обычный сайт. WebSocket с security=none, это единственная конфигурация, о которой стоит подумать дважды. На проводе это открытый текст, и она уместна, только когда шифрование уже обеспечивает что-то другое, например CDN, терминирующий TLS перед сервером.

Три разных утверждения, которые не смешиваются

Различие ниже, это самое важное в этом документе. Прочитайте заголовки столбцов раньше строк.

Утверждение На чём оно держится Чего оно стоит
Парсер это принимает internal/link и закоммиченный золотой документ для движка Документ стабилен. Никуда не звонили
Оно переносит байты test/tunnel, настоящий сервер xray-core на loopback Трафик прошёл через протокол. Ни адреса выхода, ни устройства, ни интернета
Оно доказано целиком test/hardware, настоящий телефон на хотспоте Настоящий трафик вышел из коробки, и адрес выхода был зафиксирован и назван

Что переносило байты через настоящий сервер

Добавлено пакетом test/tunnel. Каждая схема, которую принимает парсер, прогоняется целиком против настоящего экземпляра xray-core, собранного из зависимости этого же модуля и загруженного тем же загрузчиком, которым пользуется internal/engine. Клиентская сторона, это продуктовый путь без изменений: link.Parse, затем xcfg.Build, затем engine.Engine.Start. Ни один конфиг не написан вручную.

протокол транспорт безопасность переносит HTTP-запрос
VLESS tcp (raw) none да
VMess tcp (raw) none да
Shadowsocks, aes-256-gcm tcp (raw) none да
SOCKS tcp (raw) none да
Trojan tcp (raw) TLS, закреплённый по отпечатку да
Hysteria2, и псевдоним hy2 QUIC TLS, закреплённый по отпечатку да

Четыре контрольных механизма не дают пройти запросу, который прошёл мимо туннеля, и все четыре реально выполняются, а не утверждаются в тексте. Клиенту никогда не сообщают, где находится источник: ему дают имя в зоне .invalid и порт приманки. Это имя невозможно разрешить, и набор тестов говорит об этом вслух, если какой-нибудь резолвер на машине всё же на него ответит. Источник проверяет, куда запрос был адресован, а не только то, что он пришёл. Приманка считает попадания по себе, и туннелированный запрос не должен добавить ни одного. TestEveryCarriageProofCanFail и TestTheProofRejectsARequestThatDidNotGoThroughTheTunnel и делают эти механизмы доказательством, а не намерением.

Читайте каждую строку узко. Все строки, кроме Hysteria2, работают поверх сырого TCP. Ни одна строка не задействует REALITY, серверной стороне которого нужна настоящая цель для рукопожатия. Shadowsocks взят только с aes-256-gcm, потому что шифры 2022 года идут другим путём в коде. Каждая строка несёт запрос по TCP, а UDP associate выключен. Всё происходит на loopback, поэтому адрес выхода не фиксируется и зафиксирован быть не может.

TestEveryProtocolTheParserAcceptsIsDrivenEndToEnd читает список принимаемых схем прямо из исходников internal/link, поэтому восьмую схему нельзя добавить, не добавив здесь строку.

Что действительно доказано на железе

Таблица ниже, это то, что прошло настоящим трафиком с зафиксированным адресом выхода. Это не то, что принимает парсер, и не то, что переносит набор тестов на loopback.

протокол транспорт безопасность доказано целиком
VLESS tcp (raw) REALITY да, на трёх разных серверах
VLESS ws (WebSocket) none, плюс VLESS Encryption да
VLESS ws (WebSocket) TLS да, через CDN
VLESS httpupgrade TLS да, через CDN
VLESS xhttp TLS да
VMess, Trojan, Shadowsocks, SOCKS, Hysteria2 любой любая нет

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

Строка, которая не доказана, не является утверждением, что она сломана. Это утверждение, что никто не видел, как пакет вышел с дальнего конца, а это другое дело и единственное, что этот проект считает доказательством. Документ для движка, который порождает каждый транспорт, ЗАКРЕПЛЁН как золотой файл, поэтому изменение в том, как он собирается, проявляется как diff. Это доказывает, что документ стабилен, и ничего не говорит о том, соединяется ли транспорт.

Почему строка без transport security всё равно зашифрована

Столбец security выше говорит о слое, ОБЁРНУТОМ ВОКРУГ транспорта, и none там не означает «без шифрования». Он означает «без TLS и без REALITY». Об этом стоит сказать точно, потому что прочтение наоборот пугало бы, а слишком щедрое прочтение было бы хуже.

Сам по себе VLESS не несёт шифрования. Это протокол без состояния, который ожидает, что конфиденциальность обеспечит слой под ним, обычно REALITY или TLS. Ссылка VLESS поверх WebSocket с security=none и ничем больше БЫЛА БЫ открытым текстом на проводе, и адрес выхода был бы доказан при том, что каждый пакет читался бы всем, кто есть на пути.

Безопасной эту строку делает VLESS Encryption, передаваемое в параметре encryption= в ссылке. Это гибридный обмен ключами, ML-KEM-768 для стойкости к квантовым вычислениям в сочетании с X25519, применяемый на самом уровне VLESS, а не под ним. Поэтому трафик зашифрован, и зашифрован тем, что спроектировано оставаться стойким против того, кто записывает его сегодня, а квантовый компьютер получит позже. Ссылка, несущая encryption=none И security=none, не имеет ни того, ни другого, и именно это сочетание надо отклонять.

Это НЕ Noise Protocol Framework (noiseprotocol.org). Ни это устройство, ни вендоренный парсер share-ссылок, ни движок не реализуют Noise. Слово «noise» встречается в конфигурации xray-core по совсем другому поводу, для набивки трафика случайными байтами, чтобы изменить его форму на проводе, а это обфускация, а не рукопожатие. Конфиденциальность этой строке даёт именно VLESS Encryption, и название важно, потому что эти две вещи дают разные гарантии.

ИЗМЕРЕНО, а не предположено, 2026-08-30. Этот пакет не пересобирает outbound поле за полем. Он заново сериализует то, что выдал парсер, и настройки протокола едут внутри как непрозрачный блок. Именно поэтому параметр выживает. И именно поэтому ничего бы не сломалось, если бы он выживать перестал: ни одно поле не пропало бы, ни один тип не изменился бы, и ни один другой тест ничего бы не заметил, пока туннель нёс бы трафик пользователя в открытом виде со всеми проверками зелёными. TestVLESSEncryptionSurvivesIntoTheEngineDocument в internal/link, это страж, и его видели падающим ровно на таком тихом понижении, прежде чем оставить в наборе.

Имя в сертификате, которое не совпало, и исправление на стороне клиента

Один результат стоит записать, потому что это сбой, который устройство совершенно правильно отказывается заминать. Две конфигурации указывали на собственный адрес сервера, но несли TLS-имя стоящего перед ним CDN. Движок сообщил:

transport/internet/httpupgrade: failed to dial request ...
  tls: failed to verify certificate: x509: certificate is valid for
  <the apex>, not <the cdn subdomain>

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

И причина, и исправление находятся на стороне клиента, менять на сервере ничего не нужно. Share-ссылка несёт два имени, про которые считают, что они обязаны совпадать, а это не так:

sni   имя, по которому TLS проверяет сертификат
host  имя, по которому сервер маршрутизирует запрос, HTTP-заголовок

В неработавших ссылках имя CDN стояло в ОБОИХ. Через CDN так работает, потому что у CDN есть сертификат на это имя. При обращении прямо к источнику так работать не может, потому что у источника есть сертификат только на основной домен. Поставьте в sni то имя, которое действительно есть в сертификате, а в host оставьте имя, по которому маршрутизирует сервер:

sni=example.com          host=cdn.example.com

ИЗМЕРЕНО 2026-08-30. Две ссылки, падавшие с ошибкой сертификата выше, после этого одного изменения обе подключились. Адреса выхода были зафиксированы из двух независимых источников и сопоставлены с их собственными серверами, а проверки на утечку DNS и на fail-closed прошли в том же прогоне.

Поэтому если транспорт отказывает только при обращении прямо к источнику, сравните sni с альтернативными именами субъекта в сертификате источника, прежде чем подозревать транспорт. openssl s_client -connect <address>:443 -servername <name> печатает то, что сервер на самом деле предъявляет.

Панель принимает вставленную ссылку, а не изображение

Перетаскивание изображения с QR-кодом описано в дизайне, раздел 5.2, и не реализовано. internal/panel/qr, это только кодировщик, и ни один обработчик в internal/panel не читает multipart-загрузку. QR-код, который панель всё же выдаёт, это тот, который телефон сканирует для подключения к хотспоту. internal/panel/view.go строит его через qr.Encode и qr.WiFiJoin, поэтому здесь нет ни графической библиотеки, ни удалённого сервиса.

English: HTTP/2, HTTP/3 | English | فارسی: HTTP/2, HTTP/3 | فارسی | Русский: HTTP/2, HTTP/3 | Русский | 中文: HTTP/2, HTTP/3 | 中文

Clone this wiki locally