Add decoy SNI plumbing, bundle wintun.dll, fix flag icons, add docs

- Прокидываем sni (decoy-хост) по обновлённому UDL через весь стек:
  guest-js -> Tauri-команда -> desktop/mobile -> Kotlin spawnSession.
  Список серверов пока статичный (backend еще не отдает sni), но логика
  похода в реальный /api/v1/nodes оставлена под флагом на будущее.
- Бандлим wintun.dll в Windows-инсталлятор через bundle.resources в
  tauri.windows.conf.json - раньше он копировался только для
  локального cargo run/tauri dev, но не попадал в NSIS/MSI.
- Чиним падение мобильной сборки: гитигнорённые UniFFI Kotlin-биндинги
  отстали от обновленного UDL (не хватало параметра sni).
- Флаги стран - локальные SVG вместо react-country-flag в CDN-режиме:
  тот бился об CSP и не грузился офлайн.
- Добавляем дерево README.md по структуре проекта и точечные
  комментарии в неочевидных местах кода.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Kirill
2026-07-02 19:29:29 +07:00
parent 16760c16ea
commit c852315d34
27 changed files with 464 additions and 57 deletions
+40 -1
View File
@@ -1 +1,40 @@
# Tauri Plugin vpn
# `tauri-plugin-vpn`
Кастомный Tauri-плагин — единственная точка входа к VPN-движку из фронтенда.
Сам движок (`netrunner_client`, UniFFI-крейт из приватного репозитория
`netrunner-core`) сюда не входит — плагин лишь адаптирует его под интерфейс
Tauri-команд и различия desktop/mobile.
## Слои
```
guest-js/index.ts — JS API, которым пользуется фронтенд (invoke Tauri-команд)
src/commands.rs — #[command]-обвязка (start_vpn, stop_vpn, ...)
src/lib.rs — trait VpnInterface + выбор desktop/mobile реализации
src/desktop.rs ├─ вызывает netrunner_client::SessionManager напрямую
src/mobile.rs └─ шлёт команды в нативный Android/iOS плагин
src/models.rs — общие DTO (TunnelConfig, StartVpnRequest, ...)
android/ — Kotlin-часть, см. android/README.md
```
## Rust-файлы
| Файл | Назначение |
|---|---|
| `src/lib.rs` | `trait VpnInterface<R>` — общий контракт (`start_vpn`, `stop_vpn`, `check_actual_vpn_status`, `get_installed_apps`, `launch_app`). `Vpn<R>` хранит `Box<dyn VpnInterface<R>>` и реализует `Deref` на него — благодаря этому `commands.rs` вызывает `vpn.start_vpn(...)`, не зная, десктопная это реализация или мобильная. `init()` в `setup()` выбирает `desktop::init` / `mobile::init` по `#[cfg(desktop)]`/`#[cfg(mobile)]`. |
| `src/commands.rs` | Tauri-команды. `start_vpn` держит `AtomicBool is_running` с `compare_exchange` — защита от повторного запуска сессии при повторном вызове (двойной клик, React double-invoke), а не мьютекс, т.к. проверка/установка должны быть одной атомарной операцией. |
| `src/desktop.rs` | Оборачивает `netrunner_client::SessionManager::spawn_session(remote_address, sni, tun_fd, cache_dir, killswitch, excluded_apps, excluded_domains)` — порядок аргументов **обязан** совпадать с `.udl`-описанием в `netrunner-core`. `tun_fd = None` (на десктопе TUN создаётся самим движком, fd не нужен). Эмитит `status-change`/`traffic-stats` через `AppHandle::emit`, статистика — в фоновом потоке с опросом раз в секунду. |
| `src/mobile.rs` | Тонкий мост: сериализует те же параметры в `StartVpnRequest` (camelCase) и шлёт через `PluginHandle::run_mobile_plugin` в Kotlin/Swift. |
| `src/models.rs` | DTO с `#[serde(rename_all = "camelCase")]` — формат обмена с JS/Kotlin-стороной. |
## `guest-js/index.ts`
Обёртка над `invoke("plugin:vpn|...")`. `startVpn(config, ip, port, sni)`
`sni` обязателен и приходит от выбранного узла (`ServerNode.sni` во
фронтенде) — это decoy-домен, под который маскируется TLS ClientHello именно
для этого сервера; **не константа**, для каждого сервера свой.
## Android
См. [android/README.md](android/README.md) — там же задокументирован
известный источник поломки мобильной сборки (гитигнорённые UniFFI-биндинги).