# Mirror Tool Асинхронный TCP mirror и панель управления маршрутами для A/D CTF. Один Rust-бинарник включает сетевое ядро, веб-интерфейс и клиент ForcAD. Python, Node и внешние CDN для работы не нужны. Клиент общается с одним **основным upstream**. Входящий поток можно одновременно отправлять выбранным **репликам**; их ответы вычитываются и игнорируются. Смена маршрута применяется к новым соединениям. Открытая сессия сохраняет свой upstream до завершения. ## Запуск Нужен актуальный stable Rust (проверенные версии записаны в `docs/verification.md`). ```sh cargo build --release --locked cp mirror.example.toml mirror.toml # Отредактируйте команды, адреса и порты. Пример использует тестовую сеть RFC 5737. read -s MIRROR_PASSWORD export MIRROR_PASSWORD ./target/release/mirror-tool --config mirror.toml check ./target/release/mirror-tool --config mirror.toml ``` Пароль должен содержать минимум 12 символов. Панель по умолчанию доступна на `http://127.0.0.1:6700`. Для удалённого доступа используйте VPN/SSH-туннель либо HTTPS reverse proxy; при HTTPS установите `secure_cookie = true`. TLS для управляющей панели внутри бинарника не реализован. Переменные окружения с паролем и конфигурацию игры не коммитьте. В панели: выберите основной маршрут, включите нужные реплики и запустите сервис. Радиокнопка меняет primary, флажок выбирает реплику. Настройки сохраняются после успешного ответа сервера; они переживают рестарт. Сервис можно добавить с произвольными портом слушателя и upstream-портом, команду — с IPv4/IPv6-адресом. Авто режим выбирает только из назначенных реплик. ## Доставка и перегрузка | Режим | При медленной реплике | При недоступной/сломавшейся реплике | |---|---|---| | `isolate` (по умолчанию) | Ограниченная очередь; при заполнении реплика отключается для этой сессии. Primary продолжает работать. | Ошибка и отключение учитываются в статистике; primary независим. | | `strict` | Чтение клиента замедляется до скорости всех реплик (backpressure). | Сессия завершается с ошибкой; продолжать без обязательной реплики нельзя. | Произвольные фрагменты потока не выбрасываются с продолжением передачи, как в старой версии. Но TCP mirror **не является репликацией БД**: серверы могут генерировать разные идентификаторы, challenge/nonce, ответы и побочные эффекты. TLS-поток нельзя содержательно зеркалировать в независимые TLS-сессии. Проверяйте совместимость конкретного протокола и checker перед игрой. Абсолютные «0 потерь / 0 накладных расходов» недостижимы при сбоях сети и медленных репликах. Strict сохраняет порядок и полную передачу при здоровых участниках ценой скорости/доступности; isolate может доставить отключённой реплике только префикс. Счётчики байтов показывают успешные локальные socket writes, а не фиксацию данных приложением или БД. Незавершённый запрос никогда не переигрывается на другом upstream. ## ForcAD и автоматический выбор ```toml [scoreboard] kind = "forcad" url = "http://scoreboard.example:8080" poll_interval_ms = 3000 stale_after_ms = 15000 ``` ForcAD: постоянная сессия Engine.IO v4 / Socket.IO polling, namespace `/game_events`, ping/pong и переподключение с ограниченным backoff. `init_scoreboard` задаёт метаданные и состояние; `update_scoreboard` обновляет состояние без потери метаданных. Поддерживается формат upstream [pomo-mondreganto/ForcAD](https://github.com/pomo-mondreganto/ForcAD), проверенный на `6ce3407`. События сопоставляются по точному имени сервиса и имени/IP команды. Настройте имена как на scoreboard. Произвольные несовместимые форки автоматически не угадываются. Для adplatf укажите `kind = "adplatf"`: используется HTTP `/state`. URL не должен содержать пароль, query или fragment. Доступный endpoint должен отдавать публичную таблицу без отдельной аутентификации. Автопереключение: 1. TCP-пробы выполняются параллельно с ограничением; отдельно считаются последовательные сбои и успешные проверки. 2. Причина переключения — устойчивый TCP-сбой primary либо свежий checker CORRUPT/MUMBLE/DOWN. CHECK_FAILED и неизвестный статус не принимаются за неисправность сервиса. 3. Кандидат должен быть назначенной репликой с недавней успешной TCP-проверкой и пройденным порогом восстановления. Свежий плохой checker исключает кандидата; UP получает приоритет. При равенстве используются `stolen`, затем ID. 4. Работает cooldown, включая ручную смену primary. Самопроизвольного возврата на прежний сервер нет. Старый primary становится репликой. 5. Устаревший или отключённый scoreboard не влияет на выбор; при отсутствии пригодного кандидата маршрут сохраняется. Активные сессии не мигрируют. Встроенный proxy выбирает upstream; системные маршруты, nftables и iptables не меняются. Исходный IP клиента на upstream не сохраняется. UDP и PROXY protocol не реализованы. ## Ресурсы и остановка Tokio multiplexes соединения без subprocess/thread на сессию. Общий `max_connections` ограничивает активные сессии всех сервисов; лишние подключения закрываются и учитываются как `rejected`. Очереди реплик ограничены `replica_queue_chunks`, общий бюджет их разделяемых payload — `max_buffered_bytes` (256 MiB по умолчанию). В этот бюджет не входят рабочие буферы чтения и TCP-буферы ОС. Рабочие буферы ограничены числом сессий/реплик; не выставляйте лимиты выше доступной памяти и fd. Путь без реплик переиспользует буфер; зеркальный путь передаёт разделяемые `Bytes` без отдельной копии на реплику. Счётчики используют атомарные операции. TCP_NODELAY и TCP keepalive включены; connect timeout не превращается в короткий idle timeout. Stop отключает приём новых соединений, сохраняя открытые. Удаление остановленного сервиса ждёт `drain_timeout_ms`, затем отменяет остаток. SIGINT/SIGTERM закрывает слушатели, даёт всем сессиям общий срок дренирования, затем отменяет и дожидается принадлежащих им задач. Isolate также ограничивает время отправки остатка репликам после завершения основного обмена. Strict в нормальном режиме может ожидать медленную реплику неограниченно; общая остановка остаётся ограниченной. ## Конфигурация и состояние `mirror.toml` задаёт настройки процесса и начальную топологию. После первого изменения сохранённый `state_file` содержит авторитетный список команд/сервисов; TOML продолжает задавать сеть, мониторинг и scoreboard. Путь состояния отсчитывается от каталога TOML. Чтобы применить начальную топологию заново, остановите процесс и **переместите** state-файл в резервную копию. Файл не исполняется, версия и содержимое валидируются. Запись состояния: временный файл на той же ФС → fsync → atomic rename; права `0600`. Сбой сохранения вызывает попытку отката runtime. Публикация topology сериализована с ручными и автоматическими изменениями. Runtime и ФС не образуют распределённую транзакцию: клиент, принятый в коротком окне до неудачного сохранения, может сохранить временно применённый маршрут. Необходимость отката выводится как ошибка. Старый `config.py` не выполняется и не импортируется. Перенесите `TEAMS` в `[[teams]]`, `TASKS` в `[[services]]`, ForcAD URL в `[scoreboard]`. `PASSWORD` заменён переменной окружения, `SECRET_KEY` не нужен: сервер хранит случайные сессии в памяти. Старые cookie после перезапуска недействительны. API описан в [docs/api.md](docs/api.md). Для Linux есть [systemd unit](deploy/mirror-tool.service): создайте отдельного пользователя `mirror-tool`, разместите бинарник в `/usr/local/bin/`, конфиг и закрытый `mirror.env` в `/etc/mirror-tool/`; в TOML задайте `state_file = "/var/lib/mirror-tool/state.json"`. Unit даёт только CAP_NET_BIND_SERVICE для портов ниже 1024; при ненужности удалите capability. При увеличении drain timeout увеличьте `TimeoutStopSec`. Unit поставляется как шаблон, автоматически не устанавливается. ## Разработка и проверка ```sh cargo fmt --check cargo clippy --all-targets --locked -- -D warnings cargo test --all-targets --locked node --check web/app.js cargo run --release --example benchmark -- 64 ``` Benchmark сравнивает direct loopback с proxy в том же процессе, проверяет каждый байт, выполняет три чередующихся прогона и 500 latency-проб. Это воспроизводимый локальный baseline, не прогноз скорости реальной сети. Результаты и практические ограничения: [docs/verification.md](docs/verification.md). Для ручного теста UI на изолированном loopback: `cargo build`, затем `python3 scripts/ui_fixture.py target/debug/mirror-tool`. Это необязательная тестовая утилита; её публичный тестовый пароль указан в начале файла, конфиг/состояние временные. Не используйте fixture для реального доступа. Структура: `config` — валидация, `engine` — TCP, `scoreboard` — протоколы таблиц, `control` — health/выбор/изменения, `store` — сохранение, `auth`/`web` — управление, `web/` — встроенный интерфейс. `agent.md` и журналы фиксируют ход работы.