| deploy | ||
| docs | ||
| examples | ||
| scripts | ||
| src | ||
| tests | ||
| web | ||
| .gitignore | ||
| agent.md | ||
| AGENTS.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| findings.md | ||
| mirror.example.toml | ||
| progress.md | ||
| README.md | ||
| task_plan.md | ||
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 для управляющей панели внутри бинарника не реализован. Переменные окружения с паролем и конфигурацию игры не коммитьте.
В панели: выберите основной маршрут, включите нужные реплики и запустите сервис. Радиокнопка меняет primary, флажок выбирает реплику. Настройки сохраняются после успешного ответа сервера; они переживают рестарт. Сервис можно добавить с произвольными портом слушателя и upstream-портом, команду — с IPv4/IPv6-адресом. Авто режим выбирает только из назначенных реплик.
Доставка и перегрузка
| Режим | При медленной реплике | При недоступной/сломавшейся реплике |
|---|---|---|
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 должен отдавать публичную таблицу без отдельной аутентификации.
Автопереключение:
- TCP-пробы выполняются параллельно с ограничением; отдельно считаются последовательные сбои и успешные проверки.
- Причина переключения — устойчивый TCP-сбой primary либо свежий checker CORRUPT/MUMBLE/DOWN. CHECK_FAILED и неизвестный статус не принимаются за неисправность сервиса.
- Кандидат должен быть назначенной репликой с недавней успешной TCP-проверкой и пройденным порогом восстановления. Свежий плохой checker исключает кандидата; UP получает приоритет. При равенстве используются
stolen, затем ID. - Работает cooldown, включая ручную смену primary. Самопроизвольного возврата на прежний сервер нет. Старый primary становится репликой.
- Устаревший или отключённый 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.
Для 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
Benchmark сравнивает direct loopback с proxy в том же процессе, проверяет каждый байт, выполняет три чередующихся прогона и 500 latency-проб. Это воспроизводимый локальный baseline, не прогноз скорости реальной сети. Результаты и практические ограничения: 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 и журналы фиксируют ход работы.