Архитектура VPN-сети Montana
Версия: 2026-05-18
Поток подключения клиента
┌──────────────┐ 1. DNS resolve cdn.montana.quest
│ iPhone Happ │ ─────────────────────────────────────┐
└──────┬───────┘ │
│ ▼
│ ┌──────────────────────────┐
│ │ Cloudflare DNS multi-A │
│ │ TTL=120, не proxied │
│ │ → FI 91.132.142.42 │
│ │ → FRA 89.19.208.158 │
│ │ → US 86.104.72.12 │
│ └────────┬─────────────────┘
│ │
│ 2. TLS handshake (Reality) ───────────┘
│ SNI=www.googletagmanager.com
│ pbk=EkTs2aGKnFNgFZ0f7wgft2sJp3VjwFQqIrwkZKM4gD8
▼
┌────────────────────────────────────────────────────┐
│ xray :443 на одном из узлов (любом — один keypair) │
│ vless + xtls-rprx-vision + reality │
│ user=xray:xray, Restart=always, StartLimitBurst │
│ outbound: freedom (direct internet) │
└────────────────────────────────────────────────────┘
Поток управления (health watchdog)
┌────────────────────────────────────────────────┐
│ montana-orchestrator.service на Moscow │
│ /opt/montana-orchestrator/server.py :5020 │
│ │
│ Flask: │
│ POST /register {ip, alias, …, secret} │ ← admin
│ POST /deregister {ip, secret} │ ← узел при shutdown
│ GET /pool — CF records + state │ ← аудитор
│ GET /health — кол-во live │ ← мониторинг
│ │
│ Background thread (каждые 30 сек): │
│ for ip in registry∪pool: │
│ ok = tls_probe(ip:443, SNI) │
│ if ok && success≥2 && not in_pool: │
│ cf_api POST /dns_records │
│ if !ok && fails≥2 && in_pool: │
│ cf_api DELETE /dns_records/<id> │
└────────────────────────────────────────────────┘
│
▼
Cloudflare API
Компоненты
| Узел |
Хост |
Роль |
Сервисы |
| Helsinki |
91.132.142.42 |
VPN-backend |
xray |
| Frankfurt |
89.19.208.158 |
VPN-backend |
xray |
| New York |
86.104.72.12 |
VPN-backend |
xray |
| Moscow |
176.124.208.93 |
orchestrator + landing + p2p (НЕ VPN) |
nginx, montana-orchestrator, montana-node |
Криптографические параметры
| Параметр |
Значение |
Где |
| Reality protocol |
vless + xtls-rprx-vision |
inbound config |
| Server SNI / dest |
www.googletagmanager.com:443 |
realitySettings.dest |
| Universal UUID |
e6d355e2-2d79-4c96-a373-3b0e6b6f4b0d |
clients[0].id |
| Reality publicKey |
EkTs2aGKnFNgFZ0f7wgft2sJp3VjwFQqIrwkZKM4gD8 |
URL pbk= |
| Reality shortId |
302805bc0c25e504 |
URL sid= |
| Reality privateKey |
(хранится только на узлах) |
/usr/local/etc/xray/config.json (600) |
Сетевые потоки
| От |
До |
Порт |
Назначение |
| Любой клиент |
VPN-узел |
443/tcp |
Reality VPN |
| Moscow orchestrator |
VPN-узел |
443/tcp |
watchdog TLS-probe |
| Moscow orchestrator |
api.cloudflare.com |
443/tcp |
DNS API |
| Узел |
Любой destination |
443/tcp etc. |
freedom outbound |
| Любой p2p-узел |
Любой узел |
8444/tcp |
montana-node Bootstrap+VDF |
| Admin |
Moscow montana.quest/vpn/node/register |
443/tcp |
POST с secret |
Решения и обоснования
- Universal keypair на всех backends — Reality требует, чтобы клиент знал
publicKey сервера. Multi-A pool с разными keypair невозможен без подписки с несколькими vless://. Принято: один keypair с trade-off shared-secret (см. THREAT-MODEL.md T-2).
- DNS multi-A вместо подписки с несколькими URL — даёт «один ключ распределяет» без логики на клиенте; iOS native resolver сам ротирует ANSWER.
- TTL 120s — баланс между скоростью переключения при отказе и нагрузкой на DNS.
- Watchdog interval 30s, threshold 2 — реакция за 60–120 секунд при минимальной нагрузке на узлы (≈ 1 probe в минуту с одной точки).
- orchestrator на Moscow, не на VPN-узле — изоляция управления от data-plane; падение orchestrator не роняет существующие подключения.
- Moscow исключена из VPN pool — на :443 nginx (efir.org), :2053 — отдельный xray не для публики.