All checks were successful
Published content check / validate (push) Successful in 3s
Добавить новый раздел о стандартах FIDO, статьи о VLESS Encryption и слоях VLESS, а также несжатую иллюстрацию диагностики MTProxy. Обновить связанные материалы и направить ссылки на исходники новых статей в Forgejo.
141 lines
19 KiB
Markdown
141 lines
19 KiB
Markdown
---
|
||
date: 2026-07-17
|
||
tags:
|
||
- xray
|
||
- xhttp
|
||
- splithttp
|
||
- transport
|
||
- cdn
|
||
- censorship
|
||
aliases:
|
||
- XHTTP
|
||
- SplitHTTP
|
||
- Xray XHTTP transport
|
||
link: https://github.com/XTLS/Xray-core/tree/main/transport/internet/splithttp
|
||
---
|
||
|
||
# 📡 XHTTP: транспорт Xray, притворяющийся обычным веб-трафиком
|
||
|
||
> [!info] О чём заметка
|
||
> Подробный разбор транспорта **XHTTP** (раньше назывался **SplitHTTP**) в [[xray/project-x|Xray-core]] — как он заворачивает прокси-трафик в обычные HTTP-запросы, чтобы пройти сквозь CDN вроде Cloudflare, и в чём разница между его режимами `packet-up`, `stream-up` и `stream-one`. Разбор основан на чтении исходного кода `transport/internet/splithttp/` (внутреннее имя пакета осталось `splithttp`, публичное название — XHTTP). XHTTP — это транспорт (как переносить), он ортогонален протоколу [[xray/vless|VLESS]] и слою безопасности [[xray/reality|REALITY]].
|
||
|
||
## TL;DR
|
||
|
||
- **XHTTP** — транспорт, который упаковывает поток прокси в обычные HTTP GET/POST-запросы. Для CDN и наблюдателя это выглядит как штатное веб-приложение.
|
||
- «Split» в старом названии = **разделение направлений**: скачивание (download) и отправка (upload) идут разными HTTP-запросами, потому что HTTP по своей природе полудуплексный, и промежуточные узлы (CDN, nginx) плохо переносят долгие двунаправленные потоки.
|
||
- Три режима: **`packet-up`** (аплинк дробится на кучу коротких POST — самый CDN-дружелюбный), **`stream-up`** (аплинк одним длинным POST, даунлинк отдельным GET), **`stream-one`** (всё в одном дуплексном запросе — для REALITY напрямую, без CDN).
|
||
- **XMUX** мультиплексирует много логических сессий в одно физическое соединение и ротирует соединения — как настоящий браузер.
|
||
- Маскировка усиливается паддингом, заголовками «под fetch/gRPC/SSE» и анти-буферными заголовками, отключающими кэширование на CDN.
|
||
|
||
## Зачем нужен XHTTP: пройти сквозь CDN
|
||
|
||
Основная задача XHTTP — прогнать прокси-трафик **через CDN** (сеть доставки контента, например Cloudflare). Смысл в том, что цензору очень дорого блокировать CDN целиком: за одним IP Cloudflare стоят миллионы легальных сайтов. Если прокси-трафик неотличим от обычных запросов к сайту за CDN, заблокировать его точечно почти невозможно.
|
||
|
||
Но CDN — капризная среда: она понимает только валидный HTTP, не любит долгие двунаправленные соединения, буферизует и кэширует ответы. Поэтому нельзя просто пустить туда произвольный TCP-туннель. XHTTP устроен так, чтобы выглядеть как настоящее веб-приложение, которое грузит данные POST-запросами и получает ответ GET-запросом.
|
||
|
||
Проще говоря: XHTTP переодевает прокси-туннель в костюм обычного сайта, да так тщательно, что даже придирчивый охранник-CDN пропускает его как своего.
|
||
|
||
## «Split»: почему upload и download — разные запросы
|
||
|
||
Один логический дуплексный туннель (VLESS поверх транспорта) в коде представлен структурой `splitConn` — это соединение с **раздельными** `reader` (входящий поток) и `writer` (исходящий). Название «Split» отражает главную идею: два направления физически разнесены на разные HTTP-запросы.
|
||
|
||
Причина в природе HTTP: тело запроса естественно течёт вверх (клиент → сервер), тело ответа — вниз. Полноценный одновременный дуплекс в одном HTTP-обмене возможен только по HTTP/2 или HTTP/3 и плохо переживает промежуточные прокси. Поэтому:
|
||
|
||
- **Download (сервер → клиент)** — один длинный GET, тело ответа которого стримится бесконечно.
|
||
- **Upload (клиент → сервер)** — либо одно длинное тело POST, либо множество коротких POST (зависит от режима).
|
||
|
||
Сессию на сервере склеивает общий `sessionId`: GET-запрос даунлинка и все POST-запросы аплинка с одинаковым `sessionId` относятся к одному туннелю.
|
||
|
||
## Три режима работы
|
||
|
||
Режим задаётся полем `mode`. Значение `auto` (по умолчанию) раскрывается так: обычно → `packet-up`; при включённом [[xray/reality|REALITY]] → `stream-one`; а если при REALITY задан отдельный канал скачивания (`downloadSettings`) → `stream-up`.
|
||
|
||
### packet-up — самый CDN-дружелюбный
|
||
|
||
Аплинк нарезается на **множество коротких независимых POST-запросов**, каждый несёт кусок данных с порядковым номером `seq`. Даунлинк — отдельный длинный GET.
|
||
|
||
Почему это лучше всего проходит через CDN: короткие POST + один GET не требуют полного дуплекса, который многие CDN не поддерживают. Но есть сложность — независимые POST приходят на сервер **в произвольном порядке** (через CDN они могут идти по разным бэкенд-соединениям). Поэтому каждый POST помечен монотонным `seq`, а сервер восстанавливает исходный порядок.
|
||
|
||
За пересборку отвечает `upload_queue.go` — очередь с приоритетом (min-heap по `seq`): пакеты выдаются наверх строго по возрастанию номера, «опоздавшие» копятся в куче и ждут недостающий. Если куча разрастается сверх лимита (`scMaxBufferedPosts`, по умолчанию 30) — соединение рвётся, чтобы потерянный пакет не съел всю память.
|
||
|
||
На стороне клиента есть важная оптимизация — **батчинг**: множество мелких `Write` склеиваются в буфере в один POST покрупнее (иначе пропускная способность резко падает). Между POST выдерживается пауза `scMinPostsIntervalMs` (по умолчанию 30 мс) — заодно сбивает фингерпринт по таймингу.
|
||
|
||
### stream-up — аплинк потоком
|
||
|
||
Аплинк идёт **одним длинным потоковым POST** (тело не закрывается, данные текут), даунлинк — отдельным GET. Может использовать разные адреса и транспорты для отправки и скачивания (через `downloadSettings`). Это разделение «загружаю через один канал, качаю через другой».
|
||
|
||
### stream-one — всё в одном запросе
|
||
|
||
И аплинк, и даунлинк идут в рамках **одного HTTP-обмена**: тело запроса — вверх, тело ответа — вниз, одновременно. Это истинный дуплекс, требующий HTTP/2 или HTTP/3. Идеален для REALITY напрямую (без CDN), где полный дуплекс гарантирован. `sessionId` здесь не нужен — сессия и есть один запрос.
|
||
|
||
| Режим | Uplink | Downlink | Где хорош |
|
||
|---|---|---|---|
|
||
| `packet-up` | много коротких POST с `seq` | отдельный длинный GET | через CDN, HTTP/1.1 |
|
||
| `stream-up` | одно длинное тело POST | отдельный GET | раздельные каналы up/down |
|
||
| `stream-one` | тело одного запроса | тело того же ответа | REALITY напрямую, h2/h3 |
|
||
|
||
## XMUX: мультиплексирование как у браузера
|
||
|
||
**XMUX** управляет тем, сколько HTTP-запросов туннеля мультиплексируется в одно нижележащее (TCP/QUIC) соединение и когда соединения переиспользуются или закрываются. Смысл — вести себя как настоящий браузер: тот держит несколько долгих HTTP/2-соединений и гоняет по ним много параллельных запросов, периодически их обновляя.
|
||
|
||
Ключевые лимиты (все задаются диапазонами и рандомизируются):
|
||
|
||
- **`maxConcurrency`** — сколько запросов одновременно активны на одном соединении.
|
||
- **`maxConnections`** — сколько соединений держать в пуле.
|
||
- **`cMaxReuseTimes`** — сколько раз одно соединение можно переиспользовать.
|
||
- **`hMaxRequestTimes`** — сколько всего запросов провести через соединение, прежде чем сменить его.
|
||
- **`hMaxReusableSecs`** — временной лимит жизни соединения.
|
||
|
||
Когда соединение исчерпало лимит запросов или время жизни — клиент переключается на свежее. Ротация соединений экономит TLS-рукопожатия и одновременно мешает фингерпринтить прокси по аномально долгоживущим соединениям.
|
||
|
||
## Маскировка: паддинг и «правильные» заголовки
|
||
|
||
XHTTP старательно имитирует легитимный HTTP-трафик несколькими способами:
|
||
|
||
- **XPadding** — случайный заполнитель (`xPaddingBytes`, по умолчанию 100–1000 байт) размывает характерные размеры запросов и ответов. Есть даже режим `tokenish`, который подгоняет паддинг под нужную длину *после* сжатия заголовков HPACK/QPACK, чтобы на проводе он занимал ровно заданное число байт. По умолчанию паддинг прячется в query-параметр `x_padding` внутри заголовка `Referer`.
|
||
- **Заголовки под браузер** — запросы по умолчанию имитируют браузерный `fetch`.
|
||
- **Маскировка под gRPC** — потоковые запросы помечаются `Content-Type: application/grpc`.
|
||
- **Маскировка под SSE** — даунлинк-ответ помечается `Content-Type: text/event-stream` (Server-Sent Events).
|
||
- **Анти-буферные заголовки** — `X-Accel-Buffering: no` и `Cache-Control: no-store` отключают буферизацию и кэширование на nginx/CDN. Это критично: иначе CDN накопил бы стрим в кэше и сломал интерактивность.
|
||
|
||
Метаданные (`sessionId`, `seq`) и даже саму полезную нагрузку XHTTP умеет раскладывать по разным частям запроса — в путь URL, query, заголовки или cookie (настраивается через `sessionIDPlacement`, `seqPlacement`, `uplinkDataPlacement`). При размещении данных в заголовках/cookie они кодируются base64url и режутся на чанки. Это даёт гибкость под требования конкретного CDN.
|
||
|
||
## Как XHTTP сочетается с TLS и REALITY
|
||
|
||
XHTTP — это транспорт уровня приложения (HTTP), а шифрование задаётся отдельно, в общих настройках потока (`streamSettings`), а не внутри самого XHTTP. Слой безопасности — обычный TLS (через uTLS с фингерпринтом браузера) или [[xray/reality|REALITY]].
|
||
|
||
Выбор версии HTTP зависит от слоя безопасности: при REALITY — всегда HTTP/2; без TLS — HTTP/1.1; иначе по согласованному ALPN (`h3` → HTTP/3, иначе HTTP/2). Именно поэтому при REALITY автоматический режим — `stream-one` (полный дуплекс гарантирован), а через произвольный CDN по HTTP/1.1 безопаснее `packet-up`.
|
||
|
||
> [!note] XHTTP и Vision: раньше не совмещались, теперь совмещаются через VLESS Encryption
|
||
> Долгое время правило звучало просто: [[xray/xtls-vision|XTLS-Vision]] работает только поверх прямого TLS/REALITY по TCP, поэтому в связке с XHTTP flow `xtls-rprx-vision` не использовался, и приходилось выбирать один подход к маскировке из двух. С сентября 2025 это изменилось: если включить [[xray/vless-encryption|VLESS Encryption]], Vision становится доступен поверх XHTTP и других транспортов — документация Xray описывает XTLS как доступный либо при `TCP + TLS/REALITY`, либо при VLESS Encryption без ограничений на транспорт. Оговорка: ядерный `splice` через XHTTP всё равно не включится — он работает только поверх голого TCP. Остальное Vision делает исправно: и набивку внутреннего рукопожатия, и отказ от повторного шифрования уже зашифрованного TLS 1.3, так что выигрыш по процессору сохраняется и здесь. Рекомендация автора Xray для связок через CDN звучит как «VLESS Encryption + XTLS Vision + XHTTP XMUX». Разбор комбинаций — в [[xray/vless-stack-map|карте слоёв VLESS-стека]].
|
||
|
||
## Ключевые параметры конфига
|
||
|
||
| Параметр | Что это |
|
||
|---|---|
|
||
| `mode` | Режим: `auto` / `packet-up` / `stream-up` / `stream-one` |
|
||
| `path` | Базовый URL-путь запросов |
|
||
| `host` | Значение заголовка `Host` / SNI |
|
||
| `headers` | Дополнительные HTTP-заголовки |
|
||
| `scMaxEachPostBytes` | Макс. размер тела одного upload-POST (по умолчанию 1 000 000) |
|
||
| `scMinPostsIntervalMs` | Мин. пауза между POST (по умолчанию 30 мс) |
|
||
| `scMaxBufferedPosts` | Глубина очереди пересборки (по умолчанию 30) |
|
||
| `xPaddingBytes` | Диапазон длины паддинга (по умолчанию 100–1000) |
|
||
| `noGRPCHeader` / `noSSEHeader` | Отключить маскировку под gRPC / SSE |
|
||
| `xmux` | Настройки мультиплексирования (см. выше) |
|
||
| `downloadSettings` | Отдельный транспорт/адрес для канала скачивания |
|
||
|
||
## 📚 См. также
|
||
|
||
- [[xray/vless|Протокол VLESS]] — протокол, который переносится поверх XHTTP
|
||
- [[xray/vless-encryption|VLESS Encryption]] — шифрование самого VLESS: прячет UUID и адреса назначения от CDN и открывает Vision поверх XHTTP
|
||
- [[xray/vless-stack-map|Слои VLESS-стека]] — матрица: какие сочетания транспорта, `security`, `flow` и `encryption` работают
|
||
- [[xray/reality|REALITY]] — слой безопасности, с которым XHTTP работает через CDN
|
||
- [[xray/xtls-vision|XTLS и Vision]] — альтернативный подход (прямой TLS вместо HTTP-маскировки)
|
||
- [[xray/project-x|Project X (Xray-core)]] — обзор проекта и всех технологий
|
||
- 🔗 [transport/internet/splithttp](https://github.com/XTLS/Xray-core/tree/main/transport/internet/splithttp) — исходный код транспорта
|
||
|
||
---
|
||
|
||
> [!quote] 🤖 Эти статьи открыты — можно обучать на них ИИ
|
||
> При желании вы можете натренировать ИИ на наших статьях. Исходное форматирование и скачивание всего репозитория одним zip-архивом доступны в Forgejo: [исходник этой заметки](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main/xray/xhttp.md) · [весь репозиторий](https://git.zapret.moe/zapretdiscordyoutube/todo/src/branch/main).
|