montana/Монтана-Протокол/Код/montana-vpn/docs/architecture.md

137 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Архитектура Montana VPN — как собрана Helsinki
Этот документ описывает референсное развёртывание Montana VPN на Helsinki VPS (`91.132.142.42`, Ubuntu 24.04). Все компоненты, конфиги и обоснования живые — то есть так оно реально работает на момент `2026-05-02`.
## Слои стека
```
┌──────────────────────────────────────────────────┐
│ клиент (v2rayN / Hiddify / Streisand / sing-box) │
└───────────────────────┬──────────────────────────┘
│ TCP :443 (TLS Reality)
│ outer SNI = www.googletagmanager.com
│ inner = VLESS + xtls-rprx-vision
┌──────────────────────────────────────────────────┐
│ Helsinki VPS (Ubuntu 24.04, x86_64) │
│ │
│ ┌─────────────────────────────────────────┐ │
│ │ ufw (default deny) │ │
│ │ 22/tcp — SSH │ │
│ │ 80/tcp — nginx decoy (камуфляж) │ │
│ │ 443/tcp — xray Reality VPN │ │
│ └─────────────────────────────────────────┘ │
│ │
│ :80 ──► nginx (sites-enabled/decoy) │
│ └── /var/www/decoy/index.html │
│ "It works! Default server page." │
│ │
│ :443 ──► xray 26.2.6 (xtls/xray-core) │
│ └── inbound vless+reality+vision │
│ └── outbound freedom (direct) │
│ │
│ fail2ban — ssh brute-force protection │
│ crowdsec — IP-reputation feed + auto-ban │
│ sysctl — fq_codel + BBR (anti-bufferbloat) │
│ │
└──────────────────────────────────────────────────┘
│ outbound (direct)
Internet
```
## Почему именно VLESS + Reality + Vision
**VLESS** — минимальный V2Ray-протокол: только authentication по UUID, без обфускации поверх. Лёгкий, нет накладных расходов.
**Reality** — TLS-stealing handshake. Серверу не нужен собственный домен с валидным сертификатом. Вместо этого Reality на лету проксирует TLS-handshake к **чужому реальному сайту** (`www.googletagmanager.com:443`), и атакующий снаружи видит TLS-fingerprint Google. Только клиент с правильным `private_key`/`short_id` распознаёт что это «свой» сервер и переходит в туннельный режим. Атакующий без ключа — получает реальный ответ Google.
Это закрывает класс атак «active probing»: Russian/Iranian/Chinese DPI-боты **не отличают** Reality-эндпоинт от обычного proxy к Google.
**xtls-rprx-vision flow** — оптимизация для TCP-trafic. После handshake передаёт payload без дополнительного TLS-обёртывания, что устраняет известный fingerprint «TLS-in-TLS» и снижает CPU-нагрузку.
## Helsinki: реальные параметры
| Параметр | Значение |
|---|---|
| OS | Ubuntu 24.04 LTS, kernel 6.8.0-79-generic |
| Arch | x86_64 |
| RAM | 961 MiB (узел Montana + xray поместятся) |
| Disk | 25 GiB, ~5 GiB used |
| xray | v26.2.6 (commit `12ee51e`, go1.25.7) |
| nginx | 1.24.0 (Ubuntu) |
| Reality dest SNI | `www.googletagmanager.com:443` |
| Listen | `0.0.0.0:443` (TCP) |
| Decoy | `0.0.0.0:80` nginx static |
| Outbound | `freedom` (direct, exit-IP = Helsinki) |
| Routing | blackhole для bittorrent + private IPs |
| DNS | DoH `1.1.1.1` |
| systemd hardening | `User=nobody`, `NoNewPrivileges=true`, `CapabilityBoundingSet=CAP_NET_BIND_SERVICE,CAP_NET_ADMIN` |
| sysctl | `net.core.default_qdisc = fq_codel` (anti-bufferbloat) |
| ufw | default deny incoming, allow 22/80/443 |
| fail2ban | SSH bantime 1h→1w incremental, maxretry=3 |
| crowdsec | community IP-reputation, auto-ban 4h |
## Почему `www.googletagmanager.com` как dest SNI
Reality dest должен:
1. Разрешать TLS 1.3 + ECH (он разрешает оба)
2. Иметь `X25519` key exchange (Reality использует X25519)
3. Возвращать valid TLS handshake без strict-SNI блокировок
4. Быть глобально доступным (Google CDN)
5. Иметь высокий traffic baseline — чтобы появление новой связи не выделялось
`www.googletagmanager.com` подходит по всем пунктам. Альтернативы: `www.cloudflare.com`, `www.microsoft.com`, `www.icloud.com`. Не использовать сайты которые могут быть заблокированы в стране клиента (например Twitter).
## Почему nginx :80 с decoy
Активный пробер шлёт первым делом HTTP GET на :80. Если сервер отвечает 503 / connection refused / SSH banner — это **аномалия**, флаг для последующего более глубокого зондирования :443.
Decoy `index.html` со стандартным «It works!» от Apache/nginx — выглядит как **неактивированный VPS дефолтной конфигурации**. Не привлекает внимание ботов, ищущих «брошенные» VPS под web-defacement.
## Почему `freedom` outbound, а не каскад
В первоначальной концепции (моя память) Helsinki был **front** для Frankfurt origin — `nginx stream-proxy :443 → 89.19.208.158:443`. На практике Helsinki был перестроен в **самостоятельный exit-node** с прямым `freedom` outbound: проще, быстрее (один RTT вместо двух), exit-IP финский (хорошая юрисдикция).
Каскадная схема имеет смысл когда:
- Нужно скрыть **реальный** IP origin от клиента (zero-trust trust front)
- Front в дружественной юрисдикции, origin в любой
- Клиент компрометирован → видит только front-IP
Прямой exit-node имеет смысл когда:
- Юрисдикция уже хорошая (Финляндия)
- Caller хочет минимальную latency
- Нет требования двойной анонимизации
Helsinki — второе.
## Почему статический Xray `User=nobody`, не root
Xray должен слушать `:443` (privileged port). По старой Unix-модели это требует root. Современная модель: запускать как `nobody` + `AmbientCapabilities=CAP_NET_BIND_SERVICE`. Принцип least-privilege: компрометация xray-процесса даёт `nobody`, а не root.
Drop-in `/etc/systemd/system/xray.service.d/10-donot_touch_single_conf.conf` — стандартный paranoid-блок официального xray installer. Перезаписывает `ExecStart` чтобы гарантировать что запускается **именно** `/usr/local/etc/xray/config.json`, а не глобальный `/etc/xray/...` или конфиг из current-dir.
## Почему `fq_codel` + BBR
VPN-трафик идёт через шифрованный TCP. Default Linux qdisc (`pfifo_fast`) при перегрузке буфера вызывает bufferbloat — RTT прыгает с 30ms до 500ms+. `fq_codel` (Fair Queue + Controlled Delay) держит buffer drain time на уровне ~5ms даже под нагрузкой.
`tcp_congestion_control=bbr` — Google's BBR. На lossy-каналах (Wi-Fi клиента, сотовая связь) BBR даёт 2-5× throughput vs CUBIC default.
Для VPN с ~10-20 одновременными клиентами это разница «работает идеально» vs «ютуб тормозит».
## Что НЕ делает этот стек
- **Не маскирует timing-correlation.** Если глобальный наблюдатель видит трафик клиента → Helsinki + трафик Helsinki → target — он может корреляцией восстановить «кто куда ходил».
- **Не защищает от компрометации клиентского устройства.** Если RAT на клиенте — VPN бесполезен.
- **Не log-free.** xray пишет access.log + error.log. Можно отключить (`"loglevel": "none"`) — оставлено для оператора.
- **Не двойной hop.** Clean exit-IP, не Tor.
## Связь с протоколом Montana
Узел Montana (singleton mode без сетевого слоя) **не использует** этот VPN. Узел тикает VDF локально и пишет state в `/var/lib/montana/`, портов наружу не открывает.
VPN и узел Montana — два **независимых** systemd-сервиса на одном хосте. Можно поднять оба (`scripts/install-vps-full.sh`), либо только один (`montana-vpn/install.sh` или `scripts/install-vps.sh`). Конфликтов по ресурсам нет — узел single-thread, xray async-IO.
После M6 (когда у узла появится сетевой слой) — узел получит свой порт (например `:9501`), и можно будет (опционально) пускать его за тот же Reality-эндпоинт через xray inbound на отдельном порту. Сейчас это не предусмотрено архитектурно.