Раздел: Security & Network
Идентификатор:vpn· Экран:ui/vpn/(9 файлов, ~2500 строк) +vpn/(10 файлов)
Снимок: 2026-10-15
1. Зачем и что это
Ключевая инженерная особенность — надёжность: туннель не «отваливается молча», а переподключается сам по заданной политике, а backend (runner) ходит мимо туннеля через прямой SOCKS5-прокси.
Подробный разбор надёжности и граблей — docs/tech/vpn-reliability.md.
2. Как попасть
Хаб инструментов → секция Security & Network → карточка «VPN» (hub_vpn_title, подпись «Трафик устройства через Hysteria2 — глобально или по приложению», тайл — «VPN»).
| Откуда ещё | Путь |
|---|---|
| Мост | app_navigate с screen: "vpn" |
Маршрут: MainRouter.kt:326 (VpnScreen).
3. Экран: элементы и действия
| Секция | Элемент | Что делает |
|---|---|---|
| Шапка | Терминальная шапка VPN со статусом и суффиксом | vpnStatusColor, terminalMetaSuffixColor |
| Карточка статуса | VpnStatusCard | Состояние, попытки, причина реконнекта, кнопка Retry |
HealthLine | Строка здоровья | Результат проверки туннеля |
// server | ServerSection | Список серверов, выбор, шторка |
// mode | ModeSection | ALL (весь трафик) или PER_APP (по приложениям) |
// options | OptionsSection | Автостарт, сервер по умолчанию, выбор приложений |
ConnectBar | Нижняя панель | Подключиться / отключиться |
| Экран серверов | VpnServerListScreen | Список, дефолтный, удаление |
| Экран добавления | VpnAddServerScreen | Ручной ввод или вставка ссылки |
| Экран правки | VpnEditServerScreen | Изменение параметров сервера |
| Экран выбора приложений | VpnAppSelectionScreen | Поиск, чипы «Все / Выбранные» (SelectChip), секция // selected, меню списка (системные приложения, порядок, «снять все»), лоадер первой загрузки; выбор применяется на живом туннеле |
Экран выбора приложений держит общий вертикальный ритм приложения: поиск отстоит от терминальной шапки на 12dp, верх первого заголовка — FirstSectionTopPadding, шаг секций — SectionHeaderContentSpacing + sectionHeaderSlot.
4. Возможности подробно
Схема туннеля
приложения → TUN (10.0.88.88/16) → tun2socks (JNI) → SOCKS5 127.0.0.1:1080
→ hysteria2 → сервер:443 (QUIC)
терминал (RemoteServer) → SOCKS5 127.0.0.1:1080 (тот же SOCKS5 hysteria2, внутрь туннеля)
Сервис живёт в отдельном процессе :vpn (Y83VpnService). Состояние передаётся broadcast com.y83.ws.vpn.STATE_CHANGED (connected / reconnecting / disconnected / error), статистика — раз в 3 секунды.
Политика переподключения
VpnReconnectPolicy: попытки 1 / 2 / 4 / 8 / 16 секунд, затем медленный режим раз в 60 секунд. Сервис сам по себе не останавливается. Сброс счётчика — успех, ручной vpn_retry или validated-смена сети. FATAL (нет бинаря или сервера) — единственный случай, когда ставится error и сервис останавливается.
Мягкий реконнект перезапускает только процесс hysteria2: TUN и tun2socks остаются поднятыми, поэтому утечки трафика нет и соединения не рвутся повторно. Жёсткий пересбор TUN происходит после трёх неудач подряд.
Готовность туннеля
После старта hysteria2 приложение ждёт не просто запуска процесса, а готовности туннеля: TunnelProbe проверяет SOCKS5-порт и делает реальный HTTP-запрос через туннель (до 8 секунд). Только после этого ставится connected. Это исправляет старую проблему шторма рестартов, когда connected выставлялся сразу после exec().
Реакция на сеть
VpnNetworkMonitor ловит validated-события с дебаунсом 1.5 секунды, затем пробует туннель: жив — ничего не делаем (WiFi-флап без обрыва), мёртв — мягкий реконнект. Если validated-сети нет — состояние Reconnecting(reason=no_network), попыты не тратятся.
Перед запуском hysteria2 приложение ждёт validated-сеть и резолв хоста: пакет исключён из VPN, поэтому резолв идёт по обычной сети (убирает «no such host»).
Режимы работы
VpnMode:
ALL— весь трафик устройства через VPN (по умолчанию);PER_APP— только выбранные приложения, список хранится в prefs (vpn_selected_apps).
Режим пишется в vpn_mode (PREF_VPN_MODE) и читается менеджером при старте.
Смена режима и набора приложений применяется на живом туннеле: VpnManager после записи префов шлёт в сервис ACTION_APPLY_POLICY с дебаунсом 600 мс, сервис глушит tun2socks, пересоздаёт TUN с новым набором (establishTun) и поднимает tun2socks на новом fd — hysteria2 и время сессии не трогаются. Пустой набор в PER_APP не применяется: экран не пускает в подключение без выбранных приложений, а живой туннель остаётся на прежнем наборе и показывает предупреждение.
О факте применения сервис сообщает broadcast'ом com.y83.ws.vpn.POLICY_APPLIED (is_per_app, app_count): главный экран показывает плашку «Режим применён: …», а на время пересбора TUN в статус-карточке горит строка «применяются настройки…» (VpnManager.policyPending). Плашка не оптимистичная — она появляется по подтверждению, поэтому не врёт, если пересбор TUN не удался (тогда работает штатный реконнект с причиной policy_apply_failed).
Выбор приложений (per-app)
Экран VpnAppSelectionScreen держит три вещи, за которые отвечает чистая логика VpnAppSelection.kt (проверяется JVM-тестами):
- Фильтр и группы —
filterInstalledApps(имя или пакет),userInstalledApps/systemInstalledApps,selectedInstalledAppsдля секции// selected,unselectedInstalledApps— строки групп без выбранного. - Область списка —
visibleInstalledApps: системные приложения по умолчанию скрыты (тумблер «Показывать системные» в меню ⋮, prefsvpn_apps_show_system), поэтому чип «Все» считает пользовательские приложения, а не 563 пакета. - Порядок —
sortInstalledApps: по имени (по умолчанию) или по недавним (lastUpdateTimeизPackageManager, prefsvpn_apps_sort). - Массовые действия только по видимым —
selectVisibleдобавляет видимое к набору (выбранное вне фильтра не теряется),deselectVisibleснимает только видимое; «снять все» уносит весь набор через диалог подтверждения. - Чистка набора —
pruneUninstalled: пакет, которого больше нет на устройстве, нельзя снять в списке, поэтому он убирается при загрузке списка с сообщением «Убрано удалённых приложений: N».
Инвариант: секция // selected строится по поиску, но не подчиняется тумблеру системных — иначе выбранное системное приложение нельзя снять. Массовые действия считают то, что показано: в фильтре «Выбранные» — выбранные строки. Строка живёт ровно в одной секции: выбранное вырезано из групп (unselectedInstalledApps), иначе один пакет попадал в LazyColumn дважды и экран падал на дубликате ключа (Key … was already used).
Список читает loadInstalledApps в IO-корутине VM (метки, иконки, lastUpdateTime) — это пара секунд. Пока список пуст и проход идёт (appsLoading), шапка показывает «загрузка приложений…», а тело — лоадер VpnAppsLoadingState: без них экран встречал пользователя счётчиком «выбрано 0 / 0 приложений» и пустым списком, что читалось как поломка. Повторный заход перечитывает список молча (загруженное остаётся на экране, параллельный проход не стартует), лоадер гасится и при ошибке чтения.
Серверы
VpnServerEntity: имя, протокол (hysteria2), хост, порт, пароль, allowInsecure, SNI, пароль обфускации, флаг «по умолчанию». Серверы хранятся в Room; пароль и пароль обфускации — поля SecretString, в базе это шифротекст (AES-GCM на ключе AndroidKeystore, см. data/secrets), открытый текст только в памяти.
Добавление возможно вставкой ссылки: VpnLinkParser.parse понимает hysteria2:// и hy2://, вытаскивает хост, порт (по умолчанию 443), логин, пароль, SNI и параметры обфускации. Неподдерживаемый формат даёт «Unsupported link format».
Автостарт и перезапуск
- Автостарт: prefs
vpn_auto_start+vpn_auto_server_id— при заходе в приложение (MainActivity→VpnManager.autoStartIfNeeded) туннель поднимается сам. Включение тумблера сразу фиксирует выбранный сервер, а если его нет — берётся дефолтный (раньше автозапуск молча ничего не делал); - автозапуск не срабатывает повторно, если VPN уже
connecting/connected/reconnecting: лишнийACTION_CONNECTперезапускал живой hysteria2 и рвал сессию; - база серверов поднимается самим геттером (
App.vpnServerDao()→agentRepository()): на холодном старте автозапуск обращается к ней первым, а раньшеagentDb!!падал сNullPointerException— и туннель не поднимался; START_STICKY+ префыvpn_desired_on/vpn_last_server_id: если систему перезапустили, VPN поднимается сам. Эти префы и зеркало политики (per_app+ набор приложений) лежат в отдельном файлеvpn_service_state, который пишет только процесс:vpn— общий файл настроек сервис не трогает, чтобы не затирать ключи главного процесса;- always-on VPN включается в системных настройках — код только переживает перезапуск, сам в системе ничего не меняет.
SSH и VPN
Терминал приложения ходит через SOCKS5 1080 hysteria2, то есть как обычный трафик внутри туннеля (SshPtyTransport, флаг useSocksProxy); без VPN — напрямую. Отдельного прокси мимо туннеля в приложении нет.
5. Данные: БД, файлы, настройки
| Что | Где лежит | Как меняется | Кто читает |
|---|---|---|---|
| Серверы | Room (VpnServerEntity; пароли — шифротекстом) | Экраны серверов | Y83VpnService |
| Режим | prefs vpn_mode | Секция // mode | VpnManager |
| Выбранные приложения | prefs vpn_selected_apps | Экран выбора приложений | Сервис |
| Системные в списке | prefs vpn_apps_show_system | Меню ⋮ экрана выбора | Экран |
| Порядок списка | prefs vpn_apps_sort | Меню ⋮ экрана выбора | Экран |
| Автостарт | prefs vpn_auto_start, vpn_auto_server_id | Секция // options | VpnManager |
| Желаемое состояние | prefs файла vpn_service_state: vpn_desired_on, vpn_last_server_id | START_STICKY-восстановление | Сервис |
| Политика сервиса | prefs файла vpn_service_state: vpn_service_per_app, vpn_service_apps | Запись при connect/apply policy | Сервис (восстановление) |
| Конфиг hysteria2 | Генерируется на лету | HysteriaConfigGenerator | Процесс hysteria2 |
| Бинарь | jniLibs/arm64-v8a/libhysteria.so | Только сборка | Сервис |
6. Backend: REST/роуты
Неприменимо: VPN работает целиком на устройстве и не ходит в backend.
7. Мост и MCP
| Команда | Что делает |
|---|---|
vpn_state | Состояние, попытка, причина |
vpn_connect | Подключиться |
vpn_disconnect | Отключиться |
vpn_restart | Мягкий рестарт hysteria2 |
vpn_retry | Сброс попыток и немедленное подключение |
vpn_apps | Режим, число и список выбранных пакетов (PER_APP) |
app_log {file: "vpn"} | Лог процесса :vpn |
Команды относятся к пространству vpn_* реестра BridgeCommands.kt. Логи процесса :vpn пишутся в filesDir/log-vpn.txt (зеркало в /sdcard/Download/y83ws/log-vpn.txt), main — в log.txt: два процесса больше не пишут в один файл.
8. Настройки, конфиг, квоты и лимиты
| Параметр | Значение | Где задаётся |
|---|---|---|
| Попытки реконнекта | 1/2/4/8/16 с, затем 60 с | VpnReconnectPolicy |
| Дебаунс сетевого события | 1.5 с | VpnNetworkMonitor |
| Таймаут пробы туннеля | до 8 с | TunnelProbe |
| Интервал статистики | 3 с | Сервис |
| Жёсткий пересбор TUN | после 3 неудач | Политика |
| Порт hysteria2 SOCKS | 1080 | Конфиг |
| TUN-подсеть | 10.0.88.88/16 | Конфиг сервиса |
| Архитектура | только arm64 | Нет armeabi-v7a бинаря hysteria |
9. Ловушки и ограничения
- Нужен arm64. Бинаря hysteria под armeabi-v7a нет — устройство должно быть arm64, иначе бинарь не запустится.
- Обновлять hysteria2 только через
jniLibs/arm64-v8a/libhysteria.soсо сверкой sha256 сhashes.txtрелиза. - Kill switch сделан через TUN, а не
ip rule. Настройка правил и маршрутов требует root и не работает; роль kill switch выполняет сам поднятый TUN. - Во время реконнекта приложения видят «нет сети», а не утечку трафика. Это сознательный компромисс: TUN остаётся поднятым.
- Always-on VPN — только в системных настройках. Приложение переживает перезапуск сервиса, но само в системе ничего не меняет.
- Логи VPN в отдельном файле. Искать их надо через
dev_log {source:"app", file:"vpn"}, а не в общемlog.txt. - Сервер по умолчанию и автостарт — разные вещи. Можно задать дефолтный сервер, но не включить автостарт, и тогда туннель не поднимется сам.
- Правка или удаление активного сервера переподключает туннель. Сохранение настроек уходит в
VpnManager.onServerUpdated(мягкий реконнект тем же сервером), удаление активного — вonServerDeleted(туннель гасится, id автозапуска снимается); без этого hysteria2 работал бы по старым host/паролю. - Ссылка должна быть
hysteria2://илиhy2://. Другие форматы парсер не понимает.
10. Источники правды
app/src/main/java/com/y83/ws/ui/vpn/VpnScreen.kt— главный экран (статус, режим, опции).app/src/main/java/com/y83/ws/ui/vpn/VpnViewModel.kt— состояние, команды, серверы.app/src/main/java/com/y83/ws/ui/vpn/VpnServerListScreen.kt,VpnAddServerScreen.kt,VpnEditServerScreen.kt— серверы.app/src/main/java/com/y83/ws/ui/vpn/VpnAppSelectionScreen.kt— выбор приложений (PER_APP).app/src/main/java/com/y83/ws/ui/vpn/VpnAppRows.kt— строки, счётчики, пустые состояния и тулбар экрана выбора приложений.app/src/main/java/com/y83/ws/ui/vpn/VpnAppSelection.kt— чистая логика выбора (фильтр, группы, массовые действия, чистка набора).app/src/main/res/values/strings_vpn.xml— строки раздела VPN (RU, пара —values-en).app/src/main/java/com/y83/ws/vpn/Y83VpnService.kt— сервис в процессе:vpn.app/src/main/java/com/y83/ws/vpn/VpnReconnectPolicy.kt— политика попыток.app/src/main/java/com/y83/ws/vpn/VpnNetworkMonitor.kt,TunnelProbe.kt,NetworkState.kt— сеть и готовность.app/src/main/java/com/y83/ws/vpn/HysteriaConfigGenerator.kt,Tun2socksWrapper.kt— генерация конфига и JNI.app/src/main/java/com/y83/ws/data/vpn/VpnLinkParser.kt— разбор ссылки.app/src/main/java/com/y83/ws/data/db/VpnServerEntity.kt— модель сервера.docs/tech/vpn-reliability.md— надёжность, инварианты, диагностика.