mirror of
https://github.com/mrvasil/ctf-ad-mirror-tool-rust.git
synced 2026-10-23 15:31:01 +00:00
93 lines
14 KiB
Markdown
93 lines
14 KiB
Markdown
# 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` и журналы фиксируют ход работы.
|