# Архитектура 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 на отдельном порту. Сейчас это не предусмотрено архитектурно.