ctf-ad-mirror-tool-rust/README.md

18 KiB
Raw Blame History

Mirror Tool

Асинхронный TCP mirror и панель управления маршрутами для A/D CTF. Один Rust-бинарник включает сетевое ядро, веб-интерфейс и клиент ForcAD. Python, Node и внешние CDN для работы не нужны.

Клиент общается с одним основным upstream. Входящий поток можно одновременно отправлять выбранным репликам; их ответы вычитываются и игнорируются. Смена маршрута применяется к новым соединениям. Открытая сессия сохраняет свой upstream до завершения.

Запуск

Нужен актуальный stable Rust (проверенные версии записаны в docs/verification.md).

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 для управляющей панели внутри бинарника не реализован. Переменные окружения с паролем и конфигурацию игры не коммитьте.

Панель сохраняет оформление старого mirror_tool: горизонтальные колонки сервисов, список команд, кружок primary, выбор реплики нажатием на строку, робот авто режима, открытие сервиса и переключатель запуска. При выборе нового primary прежний становится репликой. Кнопка «+» открывает прежнюю форму: название, порт и необязательный IPv4/IPv6. Предварительно создавать команду для произвольного IP не нужно. У custom-прокси остаются поле IP и удаление в самой колонке.

Настройки сохраняются после успешного ответа сервера и переживают рестарт. Дополнительные настройки открываются нажатием на название сервиса: имя, адрес слушателя, upstream-порт и поведение при медленной реплике. Основная панель сохраняет прежние четыре счётчика соединений; байты и диагностика реплик доступны в подсказке над ними и API. Авто режим выбирает только из назначенных реплик.

Исходный CSS перенесён с нормализацией пробелов в концах строк; Bootstrap 5.1.3 и Font Awesome Free 6.0.0 той же версии включены в бинарник. Для работы панели Интернет не нужен. Небольшие изменения: клавиатурное управление, индикаторы устаревших данных, сохранение формы во время обновлений и адаптация узкого экрана. Лицензии встроенных зависимостей доступны на /licenses.txt и в web/vendor/.

Доставка и перегрузка

Режим При медленной реплике При недоступной/сломавшейся реплике
isolate (по умолчанию) Ограниченная очередь; при заполнении реплика отключается для этой сессии. Primary продолжает работать. Ошибка и отключение учитываются в статистике; primary независим.
strict Чтение клиента замедляется до скорости всех реплик (backpressure). Сессия завершается с ошибкой; продолжать без обязательной реплики нельзя.

Произвольные фрагменты потока не выбрасываются с продолжением передачи, как в старой версии. Но TCP mirror не является репликацией БД: серверы могут генерировать разные идентификаторы, challenge/nonce, ответы и побочные эффекты. TLS-поток нельзя содержательно зеркалировать в независимые TLS-сессии. Проверяйте совместимость конкретного протокола и checker перед игрой.

Абсолютные «0 потерь / 0 накладных расходов» недостижимы при сбоях сети и медленных репликах. Strict сохраняет порядок и полную передачу при здоровых участниках ценой скорости/доступности; isolate может доставить отключённой реплике только префикс. Счётчики байтов показывают успешные локальные socket writes, а не фиксацию данных приложением или БД. Незавершённый запрос никогда не переигрывается на другом upstream.

ForcAD и автоматический выбор

[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, проверенный на 6ce3407. События сопоставляются по точному имени сервиса и имени/IP команды. Настройте имена как на scoreboard. Произвольные несовместимые форки автоматически не угадываются.

Для adplatf укажите kind = "adplatf": используется HTTP /state. URL не должен содержать пароль, query или fragment. Доступный endpoint должен отдавать публичную таблицу без отдельной аутентификации.

Для прежней цветной полосы состояния своей команды задайте main_team_ip = "192.0.2.10" в начале TOML, до секций. Это аналог MAIN_TEAM_IP; полоса независима от выбранного primary. Без этого параметра она остаётся нейтральной. Имена команд окрашиваются по checker, чипы показывают потерянные флаги; сортировка сохраняет прежний порядок status → lost → имя.

Автопереключение:

  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 запрещает новые управляющие изменения, закрывает слушатели и ограничивает завершение процесса общим бюджетом drain_timeout_ms + 1100 ms с учётом планирования ОС. Недочитанный HTTP-запрос не задерживает процесс бесконечно. При исчерпании бюджета остаток прерывается; сохранение могло завершиться до обрыва HTTP-ответа. Isolate также ограничивает время отправки остатка репликам после завершения основного обмена. Strict в нормальном режиме может ожидать медленную реплику неограниченно; общая остановка остаётся ограниченной.

Конфигурация и состояние

Пошаговая инструкция для ForcAD и локального демо: docs/configuration.md. В панели кнопка «Команды» добавляет и переименовывает команды; ⓘ показывает состояние scoreboard и краткую справку.

mirror.toml задаёт настройки процесса и начальную топологию. После первого изменения сохранённый state_file содержит авторитетный список команд/сервисов; TOML продолжает задавать сеть, мониторинг и scoreboard. Путь состояния отсчитывается от каталога TOML. Чтобы применить начальную топологию заново, остановите процесс и переместите state-файл в резервную копию. Файл не исполняется, версия и содержимое валидируются.

Запись состояния: временный файл на той же ФС → fsync → atomic rename; права 0600. Сбой сохранения вызывает попытку отката runtime. Публикация topology сериализована с ручными и автоматическими изменениями. Runtime и ФС не образуют распределённую транзакцию: клиент, принятый в коротком окне до неудачного сохранения, может сохранить временно применённый маршрут. Необходимость отката выводится как ошибка. После допуска к транзакции она завершается независимо от HTTP-соединения: закрытие вкладки не оставляет сохранение/очистку наполовину. Если ответ потерян, обновите snapshot перед повтором — изменение могло сохраниться.

Старый config.py не выполняется и не импортируется. Перенесите TEAMS в [[teams]], TASKS в [[services]], ForcAD URL в [scoreboard], MAIN_TEAM_IP в верхнеуровневый main_team_ip. PASSWORD заменён переменной окружения, SECRET_KEY не нужен: сервер хранит случайные сессии в памяти. Старые cookie после перезапуска недействительны. API описан в docs/api.md.

Для Linux есть systemd unit: создайте отдельного пользователя 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 поставляется как шаблон, автоматически не устанавливается.

Разработка и проверка

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 none
cargo run --release --example benchmark -- 64 strict
cargo run --release --example benchmark -- 64 isolate

Benchmark сравнивает direct loopback с proxy в том же процессе, проверяет каждый байт, выполняет три чередующихся прогона и 500 latency-проб. Это воспроизводимый локальный baseline, не прогноз скорости реальной сети. Результаты и практические ограничения: docs/verification.md.

Для ручного теста UI на изолированном loopback: cargo build, затем python3 scripts/ui_fixture.py target/debug/mirror-tool. Это необязательная тестовая утилита; её публичный тестовый пароль указан в начале файла, конфиг/состояние по умолчанию временные. Флаг --data-dir data/demo сохраняет их между запусками, --port-offset 100 изолирует порты тестового стенда. Не используйте fixture для реального доступа.

Сравнение с прежними шаблонами: установите jinja2 в отдельный dev-venv и запустите python scripts/legacy_ui_reference.py ../ad_scripts/mirror_tool. Утилита проверяет сохранность исходных CSS-правил и рендерит старые страницы на синтетических loopback-данных, не читая config.py. Для дополнительной ForcAD-проверки есть scripts/verify_forcad_library.py, использующий настоящие версии Socket.IO/Engine.IO из upstream.

Структура: config — валидация, engine — TCP, scoreboard — протоколы таблиц, control — health/выбор/изменения, store — сохранение, auth/web — управление, web/ — встроенный интерфейс. agent.md и журналы фиксируют ход работы.