9.3 KiB
Executable File
sdm-xray: stealth transport — спецификация для интеграции
Версия: v0.2.2. Статус: работает (клиент/сервер — форк Xray-core).
1. Что это
stealth — кастомный сетевой транспорт для Xray-core (поле network: "stealth" в
streamSettings). Каждая туннельная сессия = один долгоживущий HTTP/2 POST-стрим
(ALPN h2): тело запроса несёт кадры клиент→сервер, тело ответа — кадры сервер→клиент
(двунаправленно). Снаружи выглядит как обычный POST-запрос к API реального сайта.
Для зондов/DPI: сервер отдаёт реальный сайт (зеркало) на GET/HEAD запросы. Клиент автоматически ротирует пути, padding/pacing маскирует размеры и тайминги.
2. Quick-start
Сервер (минимальный конфиг)
{
"log": {"loglevel": "warning"},
"inbounds": [{
"listen": "0.0.0.0", "port": 443,
"protocol": "vless",
"settings": {"clients": [{"id": "<UUID>", "level": 0}], "decryption": "none"},
"streamSettings": {
"network": "stealth",
"stealthSettings": {
"key": "< hex-ключ >", "padMax": 2048, "padProb": 50,
"paceMs": 0, "paceJitterMs": 0,
"paths": ["/api/t04d12b46/", "/api/v2/telemetry/", "/collect/", "/ingest/"],
"ports": [443], "bps": 0,
"target": "https://www.iana.org",
"padProfile": "browser", "retries": 3
},
"security": "tls",
"tlsSettings": {"certificates": [{"certificateFile": "fullchain.pem", "keyFile": "privkey.pem"}]}
}
}],
"outbounds": [{"protocol": "freedom", "tag": "direct"}]
}
Клиент (минимальный конфиг)
{
"log": {"loglevel": "warning"},
"inbounds": [
{"listen": "127.0.0.1", "port": 10808, "protocol": "socks"},
{"listen": "127.0.0.1", "port": 10809, "protocol": "http"}
],
"outbounds": [{
"protocol": "vless",
"settings": {"vnext": [{"address": "<домен или IP>", "port": 443,
"users": [{"id": "<UUID>", "encryption": "none"}]}]},
"streamSettings": {
"network": "stealth",
"stealthSettings": {
"key": "< hex-ключ >", "padMax": 2048, "padProb": 50,
"paceMs": 0, "paceJitterMs": 0,
"paths": ["/api/t04d12b46/", "/api/v2/telemetry/", "/collect/", "/ingest/"],
"ports": [443], "bps": 0
},
"security": "tls",
"tlsSettings": {"serverName": "<домен>", "fingerprint": "chrome"}
}
}]
}
3. Конфигурация (streamSettings)
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
key |
string | — | Общий секрет (hex). Клиент и сервер должны совпадать |
paths |
[]string | ["/api/t04d12b46/"] |
Туннельные пути (ротация на каждое соединение) |
ports |
[]uint32 | [443] |
Порты (ротация, опция) |
padMax |
int64 | 2048 | Максимальный размер PAD-кадра |
padProb |
int64 | 50 | Вероятность PAD после DATA (%) |
paceMs |
int64 | 0 | Минимальный интервал между кадрами (мс) |
paceJitterMs |
int64 | 0 | Случайный довесок к paceMs (мс) |
bps |
int64 | 0 | Rate-limit (0 = выкл) |
target |
string | — | URL зеркала (сервер). Пустой = встроенная заглушка |
retries |
int64 | 3 | Количество ретраев при неудаче |
padProfile |
string | "browser" | "browser" (перекос к мелким) или "uniform" |
decoy |
bool | false | Фоновый трафик к CDN (клиент) |
decoyEverySecs |
int64 | 60 | Интервал decoy-запросов |
decoyTarget |
string | cloudflare CDN | URL для decoy |
path |
string | — | Один путь (fallback, если paths пуст) |
4. Wire-протокол (поверх TLS+HTTP/2)
4.1 Установка сессии
- Клиент: TLS 1.3 (uTLS, fingerprint chrome, ALPN h2) к серверу:443.
- HTTP/2: клиент открывает один POST на один из
paths. - Тело запроса (клиент→сервер) — поток кадров. Первый кадр = AUTH:
[len][0xFE][key]. - Сервер читает AUTH:
- ключ верный → отвечает
200(заголовки + flush), дальше тело ответа — поток кадров сервер→клиент; - ключ неверный/нет →
404+ закрытие.
- ключ верный → отвечает
- После 200 обе стороны обмениваются кадрами DATA/PAD двунаправленно.
4.2 Формат кадра
[4 байта, big-endian длина = 1 + len(payload)] [1 байт type] [payload]
| type | Значение | Смысл |
|---|---|---|
| DATA | 0x01 | Данные туннеля (VLESS-поток) |
| PAD | 0x02 | Padding (читатель пропускает) |
| AUTH | 0xFE | Первый кадр тела запроса (ключ) |
| CLOSE | 0xFF | Закрытие |
Лимит кадра: 64 КБ. PAD-кадры прозрачны для читателя (пропускаются).
4.3 Сервер: зеркало для зондов
Не-туннельные запросы (GET/HEAD) сервер реверсивно проксирует на target:
- Location переписывается на свой домен
- HTML href/src/content переписываются на свой домен
- CSS url() переписываются на свой домен
- POST на туннельные пути без AUTH → 404
- Пустой
target→ встроенная страница-заглушка
5. Безопасность
TLS (по умолчанию)
Сертификат: Let's Encrypt (домен) или self-signed (IP).
Self-signed режим
Для работы без домена: install.sh --selfsigned генерирует сертификат (RSA 2048, 10 лет).
Клиент доверяет по pinnedPeerCertSha256 — hex SHA256 от DER-кода сертификата.
Клиентский конфиг:
"tlsSettings": {
"serverName": "<IP VPS>",
"fingerprint": "chrome",
"pinnedPeerCertSha256": "<hex sha256 сертификата>"
}
Важно: pinnedPeerCertSha256 — hex, не base64. Поддерживает OpenSSL-колонки.
REALITY
Опционально: security: "reality" вместо security: "tls".
Сертификат не нужен — клиент доверяет по publicKey (X25519).
Таргет: www.apple.com (microsoft.com режет подключения).
6. Статистика
xray считает трафик по email пользователя. Если email пустой — счётчики не создаются. По умолчанию панель присваивает email = UUID (уникальный для каждого клиента).
Для включения статистики на сервере (добавляется автоматически при первом запросе):
"api": {"tag": "api", "listen": "127.0.0.1:10085", "services": ["StatsService"]},
"stats": {},
"policy": {"levels": {"0": {"statsUserUplink": true, "statsUserDownlink": true, "statsUserOnline": true}}}
7. Файлы транспорта
Новые (пакет транспорта):
transport/internet/stealth/config.proto(+config.pb.go)transport/internet/stealth/config.go— регистрацияtransport/internet/stealth/framing.go— кадрыtransport/internet/stealth/conn.go— net.Conn поверх HTTP/2 стримаtransport/internet/stealth/dialer.go— клиентtransport/internet/stealth/hub.go— сервер
Изменённые (в infra/conf):
infra/conf/transport_internet.go—case "stealth", REALITY-проверкаinfra/conf/transport_method.go— structStealthConfig+Build()
8. Требования к сборке
- Go 1.26
- golang.org/x/net/http2
- golang.org/x/time/rate
- refraction-networking/utls
- protoc + protoc-gen-go (для config.proto)
Регенерация protobuf:
protoc -I. --go_out=. --go_opt=paths=source_relative transport/internet/stealth/config.proto
9. Известные ограничения
- HTTP/2 — один поток, throughput ~140 Мбит (bulk), ~22 Мбит (веб)
- Клиент и сервер должны быть одной версии форка
pinnedPeerCertSha256— нестандартный параметр (поддерживается только нашим форком)- REALITY: таргет microsoft.com режет — использовать apple.com
10. Ссылки
- Репозиторий: https://git.my-handbook.ru/ded_ikar/sdm-xray
- Релизы: https://git.my-handbook.ru/ded_ikar/sdm-xray/releases
- Лицензия: MPL-2.0