213 lines
19 KiB
Markdown
213 lines
19 KiB
Markdown
# Montana — Network State Snapshot v2.0.0
|
||
|
||
**Дата:** 2026-05-27
|
||
**Предыдущий:** v1.1.0 (`../NETWORK-STATE-RUNBOOK.md`)
|
||
**Статус:** ВНУТРЕННИЙ — содержит IP, ключи, токены. НЕ публиковать (правило no-IP-in-public, [[reference_metzdowd]]).
|
||
**Назначение:** полное рабочее состояние сети + точные процедуры восстановления + все известные причины падений на момент крупной переделки VPN-слоя (Москва-мастер, stream-demux, anti-block DNS, failover-watchdog, фикс маршрутизации городов).
|
||
|
||
> Версионирование слепка — ТОЛЬКО по явной команде автора ([[feedback_network_snapshot_on_command]]).
|
||
|
||
---
|
||
|
||
## 0. Что изменилось v1.1.0 → v2.0.0 (крупно)
|
||
|
||
1. **Москва стала мастер-точкой входа** (`de.montana.quest → 176.124.208.93`), домашний РФ-вход. Раньше мастером был Франкфурт.
|
||
2. **Moscow :443 = nginx stream ssl_preread демультиплекс** (SNI googletagmanager → xray :8444 Reality; montana.quest/hub → nginx-web :8443). Совмещает VPN-вход и сайт/подписку на одном порту.
|
||
3. **Автоматический failover точки входа (3-зеркало)**: цепочка `[Москва#1 → Франкфурт#2 → Хельсинки#3]`, `montana-entry-watchdog` на всех трёх, health = Reality-рукопожатие (не TCP), идемпотентная запись `de.montana.quest`.
|
||
4. **Anti-block DNS**: в публичном DNS виден только `de`→Москва + веб; `cdn` (светил все IP) и пер-узловые имена удалены; `mess` за Cloudflare; оркестратор больше не публикует IP узлов.
|
||
5. **Фикс маршрутизации городов**: каждый город привязан к своему cascade-выходу (front-independent); Москва убрана как город-выход (только вход).
|
||
6. **Test-mode в установщике** (фикс-мнемоника / кастомный генезис / d-override) + тестовая сеть `montana-testnet-01`.
|
||
|
||
---
|
||
|
||
## 1. Топология (6 узлов)
|
||
|
||
| alias | IP | хостинг | роль (VPN) | роль (протокол montana-node) |
|
||
|-------|----|---------|-----------|------------------------------|
|
||
| moscow | 176.124.208.93 | Timeweb (RU) | **МАСТЕР-ВХОД** (фронт #1), сайт+подписка+хаб | Active (singleton genesis) |
|
||
| frankfurt | 89.19.208.158 | Timeweb (DE) | фронт #2 (резерв) + exit DE | CandidateVdf |
|
||
| helsinki | 91.132.142.42 | THE.Hosting (FI) | фронт #3 (резерв) + exit FI | CandidateVdf |
|
||
| vilnius | 149.154.185.5 | LT | exit LT (docker) | CandidateVdf (test-режим в процессе) |
|
||
| yerevan | 149.154.184.205 | WorkTitans (AM) | exit AM (docker) | CandidateVdf (test-режим в процессе) |
|
||
| nicosia | 45.9.13.170 | VMmanager (CY) | exit CY (docker) | CandidateVdf |
|
||
|
||
SSH: `montana-moscow`/`my-timeweb` (через джамп `-J montana-frankfurt` если прямой ssh недоступен), `montana-frankfurt`, `montana-finland`, `montana-vilnius`/`montana-lithuania`, Армения `-i ~/.ssh/montana-frankfurt root@149.154.184.205`, `montana-nicosia`.
|
||
|
||
Удалённые ранее: Amsterdam, Almaty, SPb, Novosibirsk, US/NYC (newyork запись удалена из DNS 2026-05-27).
|
||
|
||
---
|
||
|
||
## 2. Архитектура входа и failover
|
||
|
||
**Принцип:** домашний РФ-вход (Москва) → заграничный выход в выбранной стране. Клиент всегда коннектится на фиксированный `de.montana.quest`; сеть привязывает его к живому фронту и переключает при падении — пользователь ничего не делает.
|
||
|
||
```
|
||
клиент → de.montana.quest:443 (= текущий мастер, сейчас Москва)
|
||
→ nginx stream demux: SNI googletagmanager → xray :8444
|
||
→ Reality, routing по cascade-UUID → <city>-out → exit-страна
|
||
```
|
||
|
||
**Цепочка failover (приоритет):** `moscow:176.124.208.93 → frankfurt:89.19.208.158 → helsinki:91.132.142.42`.
|
||
- `montana-entry-watchdog` (systemd) на всех трёх фронтах: `/opt/montana-entry-watchdog.sh`.
|
||
- Health = Reality-рукопожатие: `openssl s_client -connect <ip>:443 -servername www.googletagmanager.com` → cert содержит `google` (Reality steal googletagmanager). НЕ голый TCP (nginx/левый сервис прошёл бы TCP-проверку — это роняло сеть).
|
||
- Логика: первый живой в цепочке владеет `de.montana.quest`; пишут идемпотентно (только при отличии cur≠target) → без гонок/флапа. Гистерезис `FAIL_THRESHOLD=3` × `CHECK_INTERVAL=15с`. TTL записи 60с.
|
||
- CF-запись `de.montana.quest` id `be42d2634fe62b7c1fd1f54058f24e27`.
|
||
- **`de.montana.quest` авто-управляемая — вручную НЕ ставить** (watchdog перепишет).
|
||
- Старый `/opt/montana-failover.sh` на Франкфурте — disabled, устарел (был дефектный TCP-health + master=Москва).
|
||
|
||
---
|
||
|
||
## 3. Moscow :443 — stream-demux (вход + сайт на одном порту)
|
||
|
||
```
|
||
nginx stream (:443, ssl_preread) /etc/nginx/stream.d/montana-demux.conf
|
||
map $ssl_preread_server_name:
|
||
www.googletagmanager.com → 127.0.0.1:8444 (xray Reality, VPN)
|
||
default (montana.quest/hub/www) → 127.0.0.1:8443 (nginx-web: сайт, /vpn/sub, hub)
|
||
xray: listen 127.0.0.1:8444, Reality serverNames=[googletagmanager], dest=googletagmanager:443
|
||
(сильная маскировка), универсальный ключ, 8 cascade-клиентов + outbounds
|
||
nginx-web: 127.0.0.1:8443 (montana.quest cert + hub.montana.quest cert; /vpn/sub → :5008)
|
||
```
|
||
**xray Restart=always — при падении возможен зомби на :8444 (`bind: address already in use`).** Лечить: `systemctl stop xray; pkill -9 xray; fuser -k 8444/tcp; sleep 2; systemctl reset-failed xray; systemctl start xray`.
|
||
|
||
---
|
||
|
||
## 4. Маршрутизация городов (cascade, front-independent)
|
||
|
||
Каждый город → `de.montana.quest` (= текущий фронт) + свой cascade-UUID → фронт роутит на `<city>-out` → выход в стране. **Москва — только вход, НЕ выход** (нет города «Москва» в подписке).
|
||
|
||
| Город в подписке | host | cascade-UUID | outbound на фронте | Выход |
|
||
|---|---|---|---|---|
|
||
| 🇫🇮 Хельсинки Монтана | de.montana.quest | `094f9073-aff0-4c07-a4af-6ca4c924f6a9` | helsinki-out | 91.132.142.42 / FI |
|
||
| 🇩🇪 Франкфурт Монтана | de.montana.quest | `75e281f1-b702-5eb9-ba5c-8a5d38fa3c31` | frankfurt-out→direct | 89.19.208.158 / DE |
|
||
| 🇦🇲 Ереван Монтана | de.montana.quest | `43ba0c0e-c1e3-4e30-8ae8-c2e68d24d7c7` | armenia-out | 149.154.184.205 / AM |
|
||
| 🇱🇹 Вильнюс Монтана | de.montana.quest | `fc8a174d-f42b-4945-8548-ab5c9f448f81` | vilnius-out | 149.154.185.5 / LT |
|
||
| 🇨🇾 Никосия Монтана | de.montana.quest | `dad79315-0b80-5eca-9703-afee839e0131` | nicosia-out | 45.9.13.170 / CY |
|
||
|
||
ВАЖНО (урок v2.0.0): универсальный UUID `e6d355e2…` = «прямой выход НА ФРОНТЕ». Привязывать город к нему НЕЛЬЗЯ (выйдет там, где сейчас фронт = Москва). Город всегда → свой cascade-UUID.
|
||
|
||
---
|
||
|
||
## 5. Ключи Reality
|
||
|
||
**Universal Montana key** (клиентский вход + exit-узлы helsinki/frankfurt/vilnius/yerevan/nicosia):
|
||
- UUID `e6d355e2-2d79-4c96-a373-3b0e6b6f4b0d`
|
||
- PBK `EkTs2aGKnFNgFZ0f7wgft2sJp3VjwFQqIrwkZKM4gD8`
|
||
- SID `302805bc0c25e504`, SNI `www.googletagmanager.com`
|
||
- privateKey `cL7D6FCqH5nWcQlHCKH9uNr-RNwCt5peRAqt8tl9mXs` (секрет; на каждом узле + pre-stage `/etc/montana-vpn/privkey`)
|
||
|
||
**Cascade-UUID на фронте** (reality-entry клиенты, flow="", роутятся к exit-outbound):
|
||
- montana-universal `e6d355e2…` (→direct на фронте), yerevan `43ba0c0e…`→armenia-out, helsinki `094f9073…`→helsinki-out, vilnius `fc8a174d…`→vilnius-out, nicosia `dad79315…`→nicosia-out, frankfurt `75e281f1…`→frankfurt-out, nyc `db053bb3…`, cascade-verify `25e1779b…`
|
||
|
||
**Moscow own-key** (PBK `svxjTnEZxk6aStkaHSYd2b-br3Pe4yqGcNrugokjEgg`, SID `f976f81b29f78c1f`, SNI montana.quest, privkey `iMWS9kMDTBsvRqXMdjXdoRg50DgB3ZRjvJEZ2LxPm3g`) — оставлен в коде sub-gen (MOSCOW_CASCADE_KEYS / OWN_KEY) но **город Москва на выход отключён**; используется только если фронт = montana.quest.
|
||
|
||
Токены: orchestrator admin `/etc/montana/orchestrator-admin-token` (Moscow); CF API `cfut_k2tVV55op71oquxocV1OHvL9ut32WrXqFzFqQF8M1b53e6ee` (зона `2bc47161267258960d48bedfdf476f1a`), также Keychain `cloudflare-api-token`/`montana-quest`.
|
||
|
||
---
|
||
|
||
## 6. DNS (Cloudflare, зона montana.quest) — anti-block минимизация
|
||
|
||
Видно снаружи ТОЛЬКО:
|
||
```
|
||
de.montana.quest → 176.124.208.93 (вход, авто-управляется watchdog, TTL 60, DNS-only)
|
||
montana.quest / www / hub → 176.124.208.93 (веб/подписка/хаб, Москва уже видна через de)
|
||
mess.montana.quest → CF-proxied (origin Франкфурт 89.19.208.158 скрыт; origin-rule port 8443)
|
||
messenger-api.montana.quest → CF-proxied
|
||
```
|
||
**Удалены (2026-05-27):** `cdn.montana.quest` (multi-A, светил ВСЕ 5 IP), `entry/frankfurt/helsinki/moscow/newyork.montana.quest` (пер-узловые IP). Бэкап-фронты и выходы НЕ в постоянном DNS — появляются в `de` лишь при failover. Censor видит один домашний RU-IP.
|
||
|
||
CF origin-rule: phase `http_request_origin`, `(http.host eq "mess.montana.quest") → origin.port=8443`. Зона SSL = strict.
|
||
|
||
---
|
||
|
||
## 7. Control-plane (Moscow)
|
||
|
||
- **orchestrator** `/opt/montana-orchestrator/server.py`, systemd `montana-orchestrator`, :5020. Registry `/var/lib/montana-orchestrator/nodes.json`. **DNS-passive** (2026-05-27): в `/register` и `watchdog_loop` отключены `cf_add` (cdn re-leak) и `apply_failover` (управлял `de`→Frankfurt, конфликтовал с entry-watchdog → флаппинг `de`). Хранит реестр для sub-gen + auto-cascade provision (см. §8).
|
||
- **sub-генератор** `/opt/montana-vpn-balance/app.py`, gunicorn :5008, `/vpn/sub`. Читает `/nodes` из оркестратора. CASCADE dict (yerevan/helsinki/vilnius/frankfurt → cascade-UUID), `if alias=="moscow": continue` (Москва не выход). Заголовки `profile-title: base64:TW9udGFuYQ==` (=Montana) + `profile-update-interval: 12`. Rust :5009 (`mt-vpn-balance`) присутствует но nginx /vpn/sub → :5008.
|
||
- **Front-provision** (на Франкфурте) `/usr/local/sbin/montana-cascade-add <alias> <ip>` — идемпотентно добавляет cascade client+outbound+routing на фронт (бэкап→`xray -test`→один рестарт только при изменении). Оркестратор вызывает его по SSH при регистрации узла из BLOCKED_CIDRS (`149.154.0.0/16`).
|
||
- nginx: stream demux :443 (см §3) + http :80/:8443; efir_org/hub отдельными vhost. Бэкапы конфигов `/root/nginx-baks/`, `*.bak-*`.
|
||
|
||
---
|
||
|
||
## 8. Авто-каскад при регистрации ([[project_montana_auto_cascade]])
|
||
|
||
Оркестратор `/register`: `needs_cascade = ip_in_blocked(ip) or not moscow_reachable`. BLOCKED_CIDRS=`['149.154.0.0/16']` (РКН-residential). Если да → SSH Франкфурт `montana-cascade-add` → пишет в registry `cascade_front`/`cascade_uuid`/`cascade_reason` + `moscow_reachable`/`moscow_rtt_ms`. Узлы в публичный DNS НЕ публикуются.
|
||
|
||
---
|
||
|
||
## 9. Протокольный слой (montana-node, :8444 Noise_PQ XX)
|
||
|
||
**Транспортный мэш РАБОТАЕТ** (heartbeat между узлами, Noise_PQ XX). **Консенсус НЕ единый** — каждый узел отдельная singleton-генезис-цепочка (общий генезис `network_name="montana"`, но окна расходятся, NodeTable=1 у каждого). Москва Active (sole proposer), остальные CandidateVdf (грызут admission-VDF τ₂=20160 окон ≈ 14 дней). Причина не-сходимости: нет M7 fast-sync client (отставший узел не догоняет cemented-голову) + DEV-012 multi-confirmer. Genesis-manifest: `network_name` + `peers[]` (label/multiaddr/peer_id/account_id_hex/node_id_hex/bootstrap). peer_id в манифесте косметика (dial по multiaddr). Москва :8444 ufw открыт 2026-05-27 (был закрыт → Москва была изолирована).
|
||
|
||
**VPN-слой полностью отдельно от консенсуса** (xray :443 vs montana-node :8444; разные процессы; узел можно перезапускать не трогая VPN).
|
||
|
||
---
|
||
|
||
## 10. Test-mode сети ([[установщик]])
|
||
|
||
Установщик (`Code/docker/runtime/entrypoint.sh` + `docker-compose.yml`, запушено `ab8910d`):
|
||
- `MONTANA_MNEMONIC` → `init --mnemonic` (фикс-identity).
|
||
- `MONTANA_GENESIS_MANIFEST_B64` → кастомный генезис (тестовая когорта) вместо запечённого.
|
||
- `MONTANA_D_TEST_OVERRIDE` → `start --d-test-override N` (малый D → окна за мс → admission за ~минуту).
|
||
|
||
Тестовая сеть `montana-testnet-01` (2 bootstrap):
|
||
- armenia: account `c543c4151b69a7a9…`, node `1c77621274ee2f66…`, mnemonic «typical lift fork extra awesome gauge gauge senior brain social two more resource soap runway eagle alter famous cause push mystery sleep have couple»
|
||
- vilnius: account `597913015db153b0…`, node `4b125501fcf21d4e…`, mnemonic «frame transfer rug post crumble furnace barely theme square play endless december lucky season inspire rough food sister candy lunar list hollow cart icon»
|
||
- На момент слепка: Вильнюс+Армения в полной переустановке (cargo build), консенсус-тест не завершён.
|
||
|
||
---
|
||
|
||
## 11. Все известные причины падений + восстановление
|
||
|
||
### 11.1 Frankfurt xray restart-race / xray зомби на :8444 (Moscow)
|
||
`bind: address already in use`, `Start request repeated too quickly`. Лечить: `systemctl stop xray; pkill -9 xray; fuser -k <port>/tcp; sleep 2; reset-failed; start`.
|
||
|
||
### 11.2 `de.montana.quest` указывает на Москву, но :443 не Reality
|
||
Если на Москве :443 = nginx без stream-demux или xray с dest=google (не local) → клиенты получают не Reality → нет интернета / TLS-ошибка подписки. Должно быть: nginx stream demux (§3). Проверка: `openssl s_client -connect 127.0.0.1:443 -servername www.googletagmanager.com` → google cert; `-servername montana.quest` → montana.quest cert.
|
||
|
||
### 11.3 Город выходит не в своей стране (напр. Франкфурт → Москва)
|
||
Причина: город привязан к универсальному UUID (`e6d355e2`, direct-на-фронте) вместо своего cascade-UUID. Лечить: в sub-gen город → его cascade-UUID (§4). Урок: смена мастер-фронта обнажает такие маппинги.
|
||
|
||
### 11.4 `de` флаппит между узлами
|
||
Причина: два писателя DNS (entry-watchdog ↔ оркестратор apply_failover). Лечить: оркестратор DNS-passive (apply_failover/cf_add отключены); `de` пишет только entry-watchdog.
|
||
|
||
### 11.5 cdn / пер-узловые DNS возвращаются (утечка IP флота)
|
||
Причина: оркестратор `watchdog_loop` cf_add. Лечить: cf_add отключён (§7). Удалить записи через CF API.
|
||
|
||
### 11.6 Узел недостижим у РФ-провайдера (IP в 149.154.0.0/16)
|
||
Завернуть каскадом через достижимый фронт (авто, §8). Долгосрочно: чистый IP / T1-T3 транспорты.
|
||
|
||
### 11.7 docker build OOM/провал
|
||
RAM<1.5G→swap; фиксы glibc/context/volume уже в install-docker.sh.
|
||
|
||
### 11.8 hub/mess отдают чужой сертификат
|
||
Причина: vhost слушает только 127.0.0.1:8443, на :443 дефолтный блок. Лечить: добавить `listen 443 ssl` блоку (hub) ИЛИ stream-demux маршрутизирует по SNI на :8443 (Moscow). mess за CF (origin-rule :8443).
|
||
|
||
---
|
||
|
||
## 12. Health-check команды
|
||
|
||
```
|
||
# вход жив + Reality
|
||
dig +short de.montana.quest @1.1.1.1
|
||
echo | openssl s_client -connect de.montana.quest:443 -servername www.googletagmanager.com 2>/dev/null | grep 'Verify return'
|
||
# подписка
|
||
curl -s -o /dev/null -w '%{http_code}' https://montana.quest/vpn/sub # 200
|
||
# города выходят правильной страной (xray-client на любом фронте, dial 176.124.208.93:443 + cascade-UUID → ifconfig.io/ip)
|
||
# watchdog
|
||
for s in montana-moscow montana-frankfurt montana-finland; do ssh $s 'systemctl is-active montana-entry-watchdog'; done
|
||
# DNS-утечки (должно быть пусто кроме de+web)
|
||
curl -s -H "Authorization: Bearer <CF>" ".../dns_records?per_page=100"
|
||
# протокол
|
||
docker exec montana-node /usr/local/bin/montana-node status --data-dir /var/lib/montana
|
||
```
|
||
|
||
---
|
||
|
||
## 13. Задеплоено vs pending
|
||
|
||
- **Прод (живое):** Москва-мастер вход + stream-demux; failover-watchdog 3 фронта; cascade-выходы 5 стран; anti-block DNS; sub-gen :5008 (профиль Montana); auto-cascade; mess за CF.
|
||
- **В коде, НЕ задеплоено как единый консенсус:** многоузловой apply_proposal (DEV-012), M7 fast-sync client → протокол работает singleton-цепочками.
|
||
- **В процессе:** консенсус-тест на Вильнюс/Армения (montana-testnet-01, d-override).
|
||
- **Не построено:** T1-T3 транспорты, mt-egress relay (VPN как протокол-native), прикладной слой.
|