Files

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 Установка сессии

  1. Клиент: TLS 1.3 (uTLS, fingerprint chrome, ALPN h2) к серверу:443.
  2. HTTP/2: клиент открывает один POST на один из paths.
  3. Тело запроса (клиент→сервер) — поток кадров. Первый кадр = AUTH: [len][0xFE][key].
  4. Сервер читает AUTH:
    • ключ верный → отвечает 200 (заголовки + flush), дальше тело ответа — поток кадров сервер→клиент;
    • ключ неверный/нет → 404 + закрытие.
  5. После 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 — struct StealthConfig + 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. Ссылки