Initial commit: Hermes skill networking-proxy

This commit is contained in:
estorozhenko
2026-09-06 13:51:04 +00:00
commit be86a3ff2b
8 changed files with 1389 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
# SSH Tunnel: Telegram API via WireGuard + SOCKS5
## Контекст
Telegram Bot API заблокирован на сервере (Россия, DPI). Решение: SSH SOCKS5 туннель через VPS01 по WireGuard.
## Схема подключения
```
bigbox (este сервер)
├── wg0 — 10.8.0.2/24
├── VPS01 — 10.8.0.1 (WireGuard IP), 5.129.217.146 (внешний)
├── Остальные: VPS02 (87.242.100.206), VPS03 (80.209.240.167)
└── SSH SOCKS5: 127.0.0.1:1080
через root@10.8.0.1 (WireGuard адрес VPS01)
```
## Текущая конфигурация (2026-07-15)
**Режим: SOCKS5 (-D).** Ранее был TCP forward (-L 127.0.0.1:8444:api.telegram.org:443), переключено на SOCKS5 для поддержки TELEGRAM_PROXY и удалённого DNS.
### WireGuard
- Интерфейс: `wg0`
- Адрес bigbox: `10.8.0.2/24`
- Маска: `/24`
- Сервис: `wg-quick@wg0.service` — автостарт после загрузки
- Jump host: VPS01 (5.129.217.146, root, key timewebVPS)
### SSH SOCKS5 туннель
```ini
ExecStart=/usr/bin/ssh \
-i /mnt/yandex-disk/.ssh_box/timewebVPS \
-D 127.0.0.1:1080 \
-N \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-o ExitOnForwardFailure=yes \
root@10.8.0.1
```
- Сервис: `telegram-tunnel.service`
- Файл: `/etc/systemd/system/telegram-tunnel.service`
- Ключ: `/mnt/yandex-disk/.ssh_box/timewebVPS` (совпадает с `~/.ssh/timewebVPS`, хранится на Yandex.Disk davfs)
- Автостарт: включён (`systemctl enable telegram-tunnel.service`)
### Hermes Gateway
- Сервис: `hermes-gateway.service`
- В `.env` (`~/.hermes/.env`):
- `TELEGRAM_BOT_TOKEN=8620910071:...`
- `TELEGRAM_PROXY=socks5://127.0.0.1:1080`
- Gateway читает `TELEGRAM_PROXY` через `resolve_proxy_url()` и использует socks5
- В venv должен быть `aiohttp-socks` (установлен)
- DNS резолвится на VPS01 (обход блокировки DNS)
### SSH конфиг (`~/.ssh/config`)
```
Host vps01
Hostname 5.129.217.146
User root
IdentityFile ~/.ssh/timewebVPS
IdentitiesOnly yes
ForwardAgent yes
```
### Проверка после перезагрузки
```bash
# 1. WireGuard
ip addr show wg0
# 2. SOCKS5 туннель
systemctl status telegram-tunnel
ss -tlnp | grep 1080
# 3. Gateway
systemctl status hermes-gateway
# 4. Telegram API доступен через SOCKS5
TOKEN=$(grep -a TELEGRAM_BOT_TOKEN ~/.hermes/.env | grep -v "^#" | cut -d= -f2-)
curl -s --socks5 127.0.0.1:1080 "https://api.telegram.org/bot${TOKEN}/getMe"
# 5. IP выходит через VPS01
curl -s --socks5 127.0.0.1:1080 https://httpbin.org/ip
# 6. Логи туннеля
journalctl -u telegram-tunnel --since "1 hour ago"
```
## Порядок старта после перезагрузки
1. Сеть → `network-online.target`
2. Yandex.Disk davfs → `/etc/fstab` (ключи на FUSE)
3. WireGuard → `wg-quick@wg0.service`
4. SSH SOCKS5 туннель → `telegram-tunnel.service`
5. Hermes Gateway → `hermes-gateway.service`
## Заметка в Obsidian
Создана: mozg → homelab/hermes/Gateway Telegram tunnel.md
## История изменений
- 2026-07-15: Переход с TCP forward (-L 8444) на SOCKS5 (-D 1080)
- Причина: поддержка TELEGRAM_PROXY, удалённый DNS, единый порт для нескольких сервисов
- Добавлено: `TELEGRAM_PROXY=socks5://127.0.0.1:1080` в `.env`
+97
View File
@@ -0,0 +1,97 @@
# Subscription Distribution for v2rayNG — Session Example
## Context
VPS (80.209.240.167, debian, `vps03.nixg.ru`), XRay с одним UUID на все протоколы. Рабочий протокол: VLESS+WebSocket без TLS на порту 50002. Reality порты (443, 8443, 50000) заблокированы DPI (TLS fingerprint). Shadowsocks нестабилен.
## Цель
Раздать конфиг WS 50002 на несколько Android-устройств членов семьи через subscription.
## Шаги
### 1. Установить Caddy
```bash
apt-get update && apt-get install -y caddy
systemctl status caddy # active (running)
```
### 2. Создать файл subscription
```bash
cat > /etc/caddy/subscription.txt << 'EOF'
vless://cf4d32e5-9c19-460e-a5de-f881b01dcfe1@vps03.nixg.ru:50002?encryption=none&security=none&type=ws&host=discord.com&path=%2F#XRay+WS+50002
EOF
```
### 3. Скопировать в document root Caddy
```bash
cp /etc/caddy/subscription.txt /usr/share/caddy/subscription
```
### 4. Настроить Caddyfile (HTTP только, порт 80)
```caddyfile
:80 {
root * /usr/share/caddy
file_server
}
```
### 5. Перезагрузить и проверить
```bash
systemctl stop caddy
# правим Caddyfile
systemctl start caddy
curl -s http://localhost/subscription
# → vless://... (должен вернуть ссылку)
```
## Генерация share-ссылки
Формат для v2rayNG (VLESS+WS без TLS):
```
vless://UUID@host:port?encryption=none&security=none&type=ws&host=discord.com&path=%2F#remark
```
- `path=%2F` — это URL-encoded `/` (корневой путь)
- `host=discord.com` — заголовок Host в HTTP Upgrade
- `#remark` — имя профиля в v2rayNG
## Проблема: Caddy не может занять 443
XRay Reality уже слушает 443. Caddy пытается повесить HTTPS на 443 и падает:
```
listen tcp :443: bind: address already in use
```
**Решение:** раздавать subscription через HTTP (порт 80). v2rayNG не требует HTTPS для subscription.
## Добавление в v2rayNG
1. Открыть v2rayNG
2. Меню (⋮) → **Subscription Group**
3. `+` → ввести URL: `http://vps03.nixg.ru/subscription`
4. ✅ → **Update** → появится профиль **XRay WS 50002**
5. Тапнуть на профиль, чтобы подключиться
## Добавление нескольких конфигов
Просто дописать каждую share-ссылку с новой строки в файл `/usr/share/caddy/subscription`:
```
vless://...@...:50002?...#WS+50002
vless://...@...:443?...#Reality+443
ss://...@...:50003?...#Shadowsocks
```
## Важные детали
- **UUID общий для семьи** — если не нужно разграничение доступа, хватит одного UUID на всех
- **Обновление конфигов** — отредактировать файл на сервере → v2rayNG обновит при следующем poll (или нажать Update вручную)
- **HTTP vs HTTPS** — subscription не содержит секретов (ссылки уже есть у пользователя), HTTP безопасен для этой задачи
+133
View File
@@ -0,0 +1,133 @@
# TLS Fingerprint — DPI Detection Technique
## Источник
Статья на Хабре: [Конец удобства? Почему MTProxy начал ломаться](https://habr.com/ru/articles/1018672/) (2 апреля 2026, s4q)
## Суть техники
DPI (Deep Packet Inspection) анализирует TLS ClientHello на предмет узнаваемого отпечатка (fingerprint). Отпечаток складывается из:
- Порядка cipher suites
- Списка TLS extensions
- Supported groups (elliptic curves)
- Signature algorithms
- Версии TLS
- Других параметров ClientHello
У каждого TLS-клиента (браузер, Telegram, curl) — уникальная комбинация этих параметров. DPI сравнивает её с базой известных fingerprint'ов и принимает решение: пропустить, деградировать (RST-инъекция) или дропнуть пакеты.
## Применимость к XRay Reality
XRay Reality принципиально отличается от MTProxy/Fake-TLS тем, что **не имитирует TLS, а использует настоящий TLS-сервер** (reddit.com, cloudflare.com и т.д.) как маскировку. Reality:
1. Принимает ClientHello от клиента
2. Проверяет REALITY-подпись (publicKey + shortId + время)
3. Если подпись невалидна — проксирует соединение на реальный сервер (dest), как обычный HTTPS
4. Если подпись валидна — расшифровывает VLESS-трафик
**Разница с MTProxy:** MTProxy использует Fake-TLS — сам отправляет поддельный ServerHello, не обращаясь к реальному серверу. У Reality — настоящий TLS-сервер на backend'е, поэтому поведение трафика после рукопожатия неотличимо от браузерного.
## Почему это важно для диагностики
Когда в логах XRay появляется `REALITY: failed to read client hello`, это может быть следствием:
1. **Неверный publicKey/shortId в клиенте** — DPI не при чём, чисто конфигурационная проблема
2. **DPI обрезает ClientHello на уровне сети** — пакет приходит повреждённым/обрезанным
3. **Клиент отправляет ClientHello с нестандартными параметрами** — например, из-за ошибки в реализации TLS
### Случай из статьи: баг в encrypted_client_hello Telegram
В коде генерации `encrypted_client_hello` у Telegram нашли ошибку:
- Формируется нестандартное TLS-расширение с **неверным ID** и **некорректной длиной**
- Из-за этого ClientHello Telegram легко матчится ТСПУ
- Патч подготовлен, но на момент статьи не смерджен в официальный tdesktop
**Этот случай — иллюстрация того, как даже небольшая ошибка в TLS-реализации клиента делает его узнаваемым для DPI.** Применительно к XRay: если клиент (v2rayNG, XRay-core на Android) отправляет ClientHello, который отличается от нормального браузерного, DPI может отфильтровать соединение ещё до того, как XRay сервер успеет проверить REALITY-подпись.
## Как DPI взаимодействует с XRay Reality
### Вариант A — Дроп после ClientHello
```mermaid
sequenceDiagram
Client->>DPI: TCP SYN
DPI->>Server: TCP SYN
Server->>DPI: TCP SYN-ACK
DPI->>Client: TCP SYN-ACK
Client->>DPI: TCP ACK
Client->>DPI: TLS ClientHello
DPI->>DPI: Анализ fingerprint
Note over DPI: ClientHello не похож на браузерный?<br/>Дропаем/инжектим RST
DPI->>Client: RST (поддельный)
```
**Проявление:** `failed to read client hello` в логах сервера, при этом TCP SYN успешно доходит.
### Вариант B — Деградация после установки соединения
DPI пропускает ClientHello, но после установки REALITY-сессии анализирует поведение трафика. Если трафик не похож на HTTPS — начинает дропать пакеты.
**Проявление:** соединение устанавливается, работает несколько секунд/минут, потом обрывается.
### Дополнительные механизмы DPI из статьи
Статья описывает несколько техник DPI, которые выходят за рамки простого fingerprint-матчинга:
1. **Поведенческий анализ после соединения:** DPI может пропустить ClientHello, установить TLS-сессию, но затем анализировать **паттерны трафика** (размеры пакетов, частоту, направление). Если трафик не похож на HTTPS-сёрфинг (например, идёт постоянный поток данных без видимых HTTP-запросов), DPI начинает дропать или деградировать соединение.
2. **Деградация (throttling) вместо блокировки:** Вместо RST или дропа пакетов DPI может **замедлять** соединение — искусственно вносить задержки, ограничивать пропускную способность. Пользователь видит «тормозит, но работает», что сложнее диагностировать, чем полный обрыв.
3. **Адаптивное обучение:** DPI не имеет статической базы fingerprint'ов. Он может **обучаться** новым сигнатурам в реальном времени. Если новый fingerprint появился и DPI его пропустил (например, потому что трафика мало), через некоторое время он может начать его блокировать. **Это объясняет паттерн «работало вчера, перестало сегодня»** — DPI потребовалось время, чтобы накопить статистику и обучить классификатор.
4. **Корреляция по нескольким признакам:** DPI может комбинировать fingerprint + SNI + IP + порт + поведение. Если хотя бы один признак совпадает с известной сигнатурой прокси — соединение помечается как подозрительное.
### Баг в encrypted_client_hello Telegram (из статьи)
В коде генерации `encrypted_client_hello` у Telegram нашли ошибку:
- Формируется нестандартное TLS-расширение с **неверным ID** и **некорректной длиной**
- Из-за этого ClientHello Telegram легко матчится ТСПУ
- Патч подготовлен, но на момент статьи не смерджен в официальный tdesktop
**Этот случай — иллюстрация того, как даже небольшая ошибка в TLS-реализации клиента делает его узнаваемым для DPI.** Применительно к XRay: если клиент (v2rayNG, XRay-core на Android) отправляет ClientHello, который отличается от нормального браузерного, DPI может отфильтровать соединение ещё до того, как XRay сервер успеет проверить REALITY-подпись.
## Меры противодействия
1. **Использовать Reality вместо Fake-TLS/MTProxy** — Reality уже имеет встроенную маскировку под реальный HTTPS
2. **Менять `fingerprint` в клиенте** — `chrome` / `firefox` / `random`. Если провайдер знает fingerprint конкретного браузера, смена маски может помочь
3. **Менять `dest` на сервере** — если DPI блокирует fingerprint клиента для конкретного сайта, смена `reddit.com` на `cloudflare.com` или `microsoft.com` может обойти блокировку (потому что DPI может сверять fingerprint + dest)
4. **Добавлять второй порт** — 8443, 2053, 2096 — на разных портах DPI может работать по-разному
5. **Encrypted Client Hello (ECH)** — скрывает часть параметров TLS-рукопожатия, но пока не везде поддерживается и может блокироваться отдельно
6. **Обновлять клиент** — fingerprint клиента может меняться с версиями v2rayNG/XRay-core
## Эмпирические наблюдения: когда меры не помогают (сессия 2026-07-15)
В ходе диагностики на Ростелеком (домашний WiFi) был задокументирован случай, когда **ни одна из стандартных мер не дала результата**:
| Мера | Что пробовали | Результат |
|------|--------------|-----------|
| Смена порта | 443 → 8443 → 50000 | `failed to read client hello` на всех |
| Смена dest | reddit.com → microsoft.com → discord.com | `failed to read client hello` на всех |
| Смена fingerprint | chrome → random | Без изменений |
| Смена shortId | Не пробовали | — |
**Контекст:** пользователь Ростелеком, домашний WiFi. Вечером предыдущего дня соединение работало. На следующее утро/день — перестало на всех портах и всех маскирующих доменах. Сервер XRay 26.7.11, клиент Xray-core 26.6.27 (встроен в v2rayNG 2.2.6). Параметры: flow=xtls-rprx-vision, fingerprint=chrome (позже random), encryption=none.
**Вывод:** DPI на конкретном провайдере (Ростелеком) может детектить XRay Reality **независимо от порта и маскирующего домена**. Fingerprint клиента (v2rayNG отправляет ClientHello с характерными параметрами XRay-core) является достаточным признаком для блокировки. Это означает, что DPI обучился на fingerprint XRay-core, а не на конкретном сайте или порте.
**Ограничение эксперимента:** Не пробовались другие транспорты (gRPC, WebSocket, QUIC), не пробовался Trojan или Shadowsocks на тех же портах. Возможно, блокировка специфична именно для VLESS+Reality+Vision — следующая попытка должна сменить протокол.
### Что пробовать в такой ситуации (от простого к сложному)
1. **Сменить transport** — Reality → WebSocket + TLS (через nginx reverse proxy) → другая сигнатура ClientHello
2. **Сменить протокол** — VLESS → Trojan → другой fingerprint XRay-core
3. **Сменить `fingerprint` на `random`** в клиенте — тогда XRay-core будет генерировать случайный ClientHello каждый раз, что делает fingerprint-анализ бесполезным
4. **Добавить CDN/Cloudflare перед сервером** — если IP провайдера попал в «серые списки», CDN скроет реальный IP
5. **Try AmneziaWG** — использует другой механизм обфускации, не основанный на TLS fingerprint
6. **Try Shadowsocks + v2ray-plugin (WebSocket)** — другая транспортировка, другой fingerprint
### Ключевой вывод
Статья про MTProxy — это не про MTProxy. Это про то, что **DPI перешёл на новый уровень анализа**. Fingerprint-фильтрация ClientHello стала реальностью. XRay Reality спроектирован с учётом этого (использует настоящий TLS-сервер), но не застрахован полностью: если ClientHello от клиента выглядит подозрительно, DPI может заблокировать его ещё до REALITY-проверки.
**Урок на будущее:** fingerprint клиента XRay-core (даже с `chrome`/`random`) может быть распознан DPI независимо от порта и dest. В такой ситуации нужно менять не параметры Reality, а транспорт/протокол целиком.
@@ -0,0 +1,210 @@
# Session reference — Диагностика XRay Reality 2026-07-14
## Контекст сессии
- **Сервер:** VPS vps03 (80.209.240.167), XRay Reality на портах 443 и 8443
- **Клиент:** Android, v2rayNG (версия не уточнена)
- **Оператор:** мобильный, IP клиента 46.159.34.212
- **Проблема:** Яндекс.Диск якобы работает через VPN, браузер — нет
- **Реальное положение дел (выяснилось позже):** Пользователь отключал VPN, когда проверял Яндекс.Диск. VPN был реально выключен. Классический самообман.
## Хронология диагностики
### Фаза 1 — Подтверждение работоспособности сервера
1. `systemctl status xray` → active ✅
2. `ss -tlnp | grep 443/8443` → LISTEN ✅
3. `journalctl -u xray --since ... | grep <user-ip>` → видно `REALITY: invalid connection from 46.159.34.212: failed to read client hello`
4. `curl -v https://google.com` с сервера → работает ✅ (через IPv4, IPv6 недоступен)
5. `curl -v -4 https://google.com` с сервера → TLS handshake OK ✅
**Вывод:** Сервер жив, конфиг валиден, outbound freedom работает.
### Фаза 2 — Анализ логов
```
accepted udp:46.159.34.212:XXXXX:53 [direct] ← DNS проходит
REALITY: invalid connection from 46.159.34.212:XXXXX: failed to read client hello ← TCP не проходит
```
**Ключевое наблюдение:** DNS-запросы (UDP:53) видны в логах как `accepted udp:...:53 [direct]`, а TCP-соединения — только как `REALITY: failed to read client hello`. Ни одного `accepted tcp:` из этого IP не было.
## Полезные наблюдения
### `curl` зависает, а не таймаутит
Обычно curl на Android при недоступности пишет `curl: (28) Connection timed out after X ms`.
В этой сессии curl просто висел бесконечно — не было ни ответа, ни таймаута. Это нестандартное поведение, указывающее на то, что TCP SYN достигает сервера, но дальнейшее взаимодействие обрывается.
### `REALITY: failed to read client hello` — что это значит
Ошибка означает:
1. TCP трёхстороннее рукопожатие **состоялось** (SYN → SYN-ACK → ACK прошли)
2. После ACK сервер ожидает TLS ClientHello для проверки REALITY
3. Клиент отправляет что-то, что непохоже на ClientHello, или отправляет ClientHello с неверными параметрами
Возможные причины (по вероятности):
1. **Клиент использует V2Ray-core вместо XRay-core** — REALITY — эксклюзив XRay. V2Ray-core не умеет REALITY, соединение приходит но TLS-рукопожатие неправильное.
2. **Неверный publicKey или shortId** — REALITY проверяет их на уровне ClientHello.
3. **Профиль v2rayNG не выбран** — телефон шлёт трафик на порт 443, но не через v2rayNG.
### Стратегия доменов IPIfNonMatch
Пользователь заметил, что смена `Domain Strategy` с `ASIS` на `IPIfNonMatch` один раз помогла, но потом перестала.
**IPIfNonMatch** — v2rayNG резолвит домен в IP, и только потом применяет правила маршрутизации. Если DNS на телефоне возвращает корректный IP, это может помочь обойти проблемы с DNS-перехватом.
**Почему могло помочь один раз:**
- DNS-кеш на телефоне был пуст, первый запрос вернул правильный IP
- После кеширования — запросы пошли по старому пути
### IPv4 vs IPv6
При проверке `curl https://google.com` на сервере:
- IPv6 адреса google.com резолвятся, но `connect fails: Network is unreachable`
- IPv4 (`-4`) работают нормально
Это значит, что на VPS нет IPv6-связности. Если клиент резолвит google.com в IPv6 и пытается идти через XRay — соединение может не установиться.
### Продолжение сессии — Обновление сервера и новая ошибка
#### Фаза 3 — `proxy/vless/encoding: invalid request version`
После обновления сервера с 26.3.27 до 26.7.11 (клиент: Xray-core 26.6.27) ошибка `REALITY: failed to read client hello` сменилась на:
```
rejected proxy/vless/encoding: invalid request version
```
**Значение:** REALITY-рукопожатие прошло успешно, но VLESS-протокол на клиенте использует другой формат запроса, чем сервер.
**Корень:** версия Xray на сервере (26.3.27) и на клиенте (26.6.27) различаются. Между ними изменился VLESS-протокол. При этом `install-release.sh` не обновляет сервер, потому что проверяет только GitHub `latest` релиз (который всё ещё 26.3.27), а клиент (v2rayNG) имеет более новую сборку.
**Как обновить сервер вручную:**
```bash
# Найти последнюю версию
curl -sL https://api.github.com/repos/XTLS/Xray-core/releases | grep -E '"tag_name"' | head -5
# Скачать и распаковать
curl -sL -o /tmp/xray.zip "https://github.com/XTLS/Xray-core/releases/download/v26.7.11/Xray-linux-64.zip"
unzip -o /tmp/xray.zip xray -d /usr/local/bin/
chmod +x /usr/local/bin/xray
systemctl restart xray
```
**Важно:** `unzip` кладёт файлы рядом с xray (geoip.dat, geosite.dat). Если извлекать всё (`unzip -o` целиком) — перезаписываются актуальные geoip/geosite свежими из архива (что может быть ок, но лучше извлекать только бинарник).
#### Результат
После обновления сервера до 26.7.11 (новее клиента 26.6.27) ошибка `invalid request version` должна исчезнуть. Если остаётся — обновить и клиент (v2rayNG → последняя версия).
### Продолжение сессии 2026-07-15 — Порто-независимая блокировка XRay Reality на Ростелеком
#### Контекст
- **Провайдер:** Ростелеком (домашний WiFi)
- **Клиент:** Android, v2rayNG 2.2.6 (Xray-core 26.6.27, Lib v38)
- **Сервер:** XRay 26.7.11, Reality, VLESS+XTLS+Vision
- **Параметры:** flow=xtls-rprx-vision, fingerprint=chrome (потом random), encryption=none
#### Симптом
Вечером предыдущего дня соединение работало. На следующее утро/день:
- "Internet check failed" в v2rayNG
- `curl` зависает без ответа (не таймаут, не connection refused)
- В логах сервера: `failed to read client hello` с IP клиента
#### Попробованные меры
| Мера | Что изменили | Результат |
|------|-------------|-----------|
| Порт | 443 → 8443 → 50000 | `failed to read client hello` на всех |
| dest | reddit.com → microsoft.com → discord.com | `failed to read client hello` на всех |
| serverNames | reddit.com → microsoft.com → discord.com | `failed to read client hello` (сначала `server name mismatch`, после исправления — `failed to read client hello`) |
| fingerprint | chrome → random | Без изменений |
| Всё вместе | 50000 + discord.com + random | Без изменений |
Ни одно изменение не дало ни одного успешного `accepted tcp:` в логах. Все соединения обрывались на `failed to read client hello`.
#### Вывод
DPI на Ростелекоме научился распознавать **ClientHello от XRay-core** независимо от:
- Порта назначения (443, 8443, 50000 — все под фильтром)
- SNI (reddit.com, microsoft.com, discord.com — все под фильтром)
- Настройки `fingerprint` (chrome и random — одинаково под фильтром)
**Эскалация:** В такой ситуации менять параметры Reality бесполезно. Нужно менять транспорт/протокол:
1. WebSocket + TLS через nginx reverse proxy
2. Trojan protocol
3. AmneziaWG
4. Shadowsocks + v2ray-plugin (WebSocket)
#### Ссылка на статью
Статья [Конец удобства? Почему MTProxy начал ломаться](https://habr.com/ru/articles/1018672/) (s4q, 2 апр 2026) описывает похожую ситуацию — TLS fingerprint DPI на уровне ClientHello. Хотя статья про MTProxy, механизм детекта тот же: DPI анализирует ClientHello и принимает решение до установки TLS-сессии. Разница: MTProxy использует Fake-TLS (поддельный ServerHello), Reality использует настоящий сервер. Но fingerprinter самого ClientHello может быть распознан независимо от того, что происходит после.
#### Решение: WebSocket plain (без TLS)
**Шаг 1 — Добавить WS inbound на сервере:**
```json
{
"port": 50002,
"protocol": "vless",
"settings": {
"clients": [
{ "id": "cf4d32e5-9c19-460e-a5de-f881b01dcfe1" }
],
"decryption": "none"
},
"streamSettings": {
"network": "ws",
"security": "none",
"wsSettings": {
"path": "/",
"headers": { "Host": "discord.com" }
}
},
"sniffing": { "enabled": false }
}
```
**Ключевые моменты:**
- `security: "none"` — **нет TLS**. WS идёт через HTTP Upgrade, ClientHello не отправляется.
- `flow` не указывается — для WS без TLS flow не нужен.
- `sniffing: false` — для WS без TLS сниффинг не работает (нечего сниффать).
- `Host: "discord.com"` в заголовках — для маскировки HTTP-запроса.
**Шаг 2 — Проверить что порт слушается:**
```
ss -tlnp | grep 50002
```
**Шаг 3 — Настроить клиент (v2rayNG):**
| Поле | Значение |
|------|----------|
| address | IP сервера |
| port | 50002 |
| id | как на сервере |
| flow | (пусто) |
| encryption | none |
| network | ws |
| security | none (не TLS!) |
| ws path | / |
| ws Host | discord.com |
**Результат:** Соединение установилось, 2ip.io показывает IP сервера (80.209.240.167). ✅
**Почему это работает:** WS без TLS не отправляет ClientHello. DPI не с чем сравнивать fingerprint. HTTP Upgrade-запрос не имеет характерных признаков прокси-трафика. VLESS шифрует содержимое внутри WS, так что конфиденциальность сохраняется.
**Trade-off:** WS без TLS виден как HTTP-трафик (не TLS), поэтому не маскируется под HTTPS. Пройдёт DPI, но может быть замечен провайдером как не-TLS трафик на нестандартном порту. Для дополнительной защиты можно использовать TLS-версию WS через nginx reverse proxy.
#### Выводы по сессии
1. **Самое вероятное объяснение (первоначальное):** v2rayNG на телефоне использует V2Ray-core (старый), а не XRay-core. REALITY не поддерживается старым core. Решение: обновить v2rayNG до 1.8.13+ (там встроен XRay-core).
2. **Фактическая проблема:** версионное несоответствие Xray между сервером (26.3.27) и клиентом (26.6.27). `install-release.sh` не обновляет до не-latest версий, требуется ручное обновление.
3. **Самое тривиальное объяснение:** VPN был выключен при проверке Яндекс.Диска (подтверждено пользователем).
4. **Для будущей диагностики:** Всегда проверять реальное состояние VPN на клиенте, не полагаться на слова пользователя. Лучший индикатор — логи сервера.
5. **Новый паттерн:** `proxy/vless/encoding: invalid request version` → обновить сервер Xray до версии >= клиентской.
+251
View File
@@ -0,0 +1,251 @@
# Session reference — XRay Reality настройка 2026-07-14
## Окружение
- **US VPS:** vps03.nixg.ru (80.209.240.167), Debian 13 (trixie), root доступ
- **RU VPS:** cloud.ru (vps02, 87.242.100.206), пользователь estorozhenko
- **Клиент:** Android, v2rayNG (v2.2.6)
- **SSH ключи:** /mnt/yandex-disk/.ssh_box/hostkeyVPS
- **Маскировка:** reddit.com (SNI), fingerprint chrome
- **Пинг US→RU:** ~130ms
## Баг heredoc — подробности
**Проблема:** При записи JSON-конфига через SSH с heredoc:
```bash
ssh root@host "cat > /path/config.json << 'EOF'
{ \"key\": \"value\" }
EOF\"
```
Все кавычки в JSON теряются — на сервере оказывается:
```json
{
key: value
}
```
XRay выдаёт ошибку: `invalid character 'l' looking for beginning of object key string` (ожидал кавычку, получил 'l' от `log`).
**Причина:** Двойная обработка shell'ом: сначала локальный shell разбирает кавычки, потом передаёт через SSH, и quoted heredoc (`'EOF'`) не спасает — кавычки съедаются на одном из уровней.
**Проверенное решение — scp:**
```bash
# 1. Напиши локально
write_file(path="/tmp/xray_config.json", content="...")
# 2. Скопируй через SCP
scp -i /path/key -o StrictHostKeyChecking=no /tmp/xray_config.json root@host:/usr/local/etc/xray/config.json
# 3. Перезапусти
ssh -i /path/key root@host "systemctl restart xray"
```
**Альтернатива — Python json.dump() over SSH:**
```bash
ssh root@host "python3 -c '
import json
config = { ... }
with open(\"/usr/local/etc/xray/config.json\", \"w\") as f:
json.dump(config, f, indent=2)
'"
```
Но с этим тоже были проблемы экранирования — scp надёжнее.
## Полный конфиг сервера (рабочий)
```json
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "<uuid>",
"flow": "xtls-rprx-vision"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": false,
"dest": "reddit.com:443",
"xver": 0,
"serverNames": [
"reddit.com",
"www.reddit.com"
],
"privateKey": "<privateKey>",
"shortIds": [
"<shortId>"
]
}
},
"sniffing": {
"enabled": true,
"destOverride": ["http", "tls", "quic"]
}
}
],
"outbounds": [
{
"protocol": "freedom",
"tag": "direct"
},
{
"protocol": "blackhole",
"tag": "block"
}
]
}
```
## Рабочая ссылка для импорта в v2rayNG
Порт 443 (основной):
```
vless://cf4d32e5-9c19-460e-a5de-f881b01dcfe1@80.209.240.167:443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=reddit.com&fp=chrome&pbk=_ZuQgpScINJVuTRhifNX7cy9MikcNxQNq1vLOZa9ZBE&sid=f8701c8463b82974&type=tcp&headerType=none#XRay-Reality-US
```
Порт 8443 (если 443 блокируется оператором):
```
vless://cf4d32e5-9c19-460e-a5de-f881b01dcfe1@80.209.240.167:8443?encryption=none&flow=xtls-rprx-vision&security=reality&sni=reddit.com&fp=chrome&pbk=_ZuQgpScINJVuTRhifNX7cy9MikcNxQNq1vLOZa9ZBE&sid=f8701c8463b82974&type=tcp&headerType=none#XRay-Reality-US-8443
```
## Диагностика "не подключается"
Когда клиент пишет "connect deadline exceeded" / "Сбой проверки интернет-соединения":
```bash
# 1. Сервер жив?
systemctl status xray
ss -tlnp | grep -E '443|8443'
# 2. Конфиг валиден?
xray run -test -config /usr/local/etc/xray/config.json
# 3. Публичный ключ совпадает?
xray x25519 -i <privateKey> # сравни с тем, что в клиенте
# 4. Сервер выходит в интернет?
curl -s -o /dev/null -w '%{http_code}' https://reddit.com
# 5. Внешняя доступность порта?
# с другого хоста:
nc -w 5 80.209.240.167 443 && echo "OK" || echo "BLOCKED"
# 6. Логи xray — есть ли accepted?
journalctl -u xray --since '30 min ago' --no-pager | grep 'accepted'
```
Если всё выше ок — проблема **на стороне мобильного оператора** (блокировка порта, а не IP).
## Multi-port стратегия
При блокировке 443 порта мобильным оператором — добавить второй inbound на другой порт. Просто скопировать блок `inbounds[0]`, заменив `port`. Перезапустить xray.
Дополнить ссылку для клиента: заменить `@IP:443` на `@IP:8443`.
## ICMP не работает через VLESS+Reality (важно для диагностки)
XRay VLESS с flow=`xtls-rprx-vision` не проксирует **ICMP** (ping/traceroute).
Проксируется только **TCP** и **UDP**.
Когда пользователь пишет "VPN подключился, но пинг не идёт / сайты не грузятся":
- **ping 8.8.8.8** → ❌ не работает через XRay (ICMP)
- **curl https://google.com** → ✅ должно работать (TCP)
- **curl -s ifconfig.me** → ✅ покажет внешний IP сервера
- **nslookup google.com** → ✅ работает (UDP:53)
- **Открыть сайт в браузере** → ✅ должно работать
**Сценарии:**
1. ping не идёт, но curl работает → **VPN в порядке**, это норма
2. ping не идёт и curl не работает → проблема с TCP-маршрутизацией (см. "Диагностика" ниже)
3. tcpdump показывает трёхстороннее рукопожатие (SYN→SYN-ACK→ACK) → соединение с сервером есть, проблему искать в DNS или outbound-маршрутизации
**Live-диагностика через tcpdump (новые соединения):**
```bash
# На сервере — мониторинг порта 443
tcpdump -i any -n port 443 -c 10 -t
# Пользователь в это время пробует открыть сайт
# SYNs не видно → телефон не шлёт трафик (проблема с настройкой клиента)
# Видно SYN→SYN-ACK→ACK → рукопожатие есть, проблема в XRay routing
# Только FIN от старых сессий → старые сессии закрываются, новых нет
```
## Ротация ключей Reality (при смене конфига или утечке)
Периодическая смена ключей Reality повышает безопасность. Полный цикл:
```bash
# 1. Сгенерировать новую пару
xray x25519
# Вывод:
# PrivateKey: SEnziJbX3vDlWEQeGHHYXWa5DP3WXJrydkUJbUCh2FQ
# Password (PublicKey): 7PS-NrEqmF9_AcXrxjladd9zEAIxeE2LpfCAMsEQ_As
# 2. Сгенерировать новый shortId (8 байт hex)
openssl rand -hex 8
# Вывод: b11c31731d6d43d8
# 3. Обновить privateKey в serverNames и shortId в конфиге сервера
# 4. Перезапустить xray
systemctl restart xray
# 5. Передать клиенту:
# - PublicKey из шага 1
# - shortId из шага 2
# (UUID и адрес не меняются)
```
## Режим debug в логах XRay
При проблемах с соединением временно включить debug:
```json
"log": {
"loglevel": "debug"
}
```
После отладки вернуть `"warning"` — debug пишет очень много.
В debug видны:
- `accepted tcp:...` — входящие соединения
- `accepted udp:...:53 [direct]` — DNS-запросы (если есть → клиент умеет резолвить)
- `drain...` — завершение сессий
- Ошибки рукопожатия и таймауты
## Полезные команды
```bash
# Сгенерировать UUID
python3 -c 'import uuid; print(uuid.uuid4())'
# Сгенерировать ключевую пару Reality
xray x25519
# Получить public key из private key
xray x25519 -i <privateKey>
# Прослушиваемые порты
ss -tlnp | grep xray
# Логи xray
journalctl -u xray --since '1 hour ago' --no-pager
```
## Возможные проблемы
1. **XRay запущен, но порт 443 не отображается** — сначала проверь ss. Если там только SSH (22) и exim (25), проверь, что XRay стартовал без ошибок. Причина часто — битый JSON (см. баг heredoc).
2. **Connection refused при curl** — это нормально с локальной машины, если нет маршрута до сервера (там порт 443 открыт только на интерфейсе *:443). Проверять через `nc`.
3. **uuidgen: command not found** — на минимальных Debian-образах uuidgen отсутствует. Использовать `python3 -c 'import uuid; print(uuid.uuid4())'`.
4. **Client "connect deadline exceeded" при работающем сервере** — почти всегда блокировка порта оператором. Сменить порт или попробовать с Wi-Fi.