🤖 Роудмап: интеграция AmneziaWG для Android

Текущее состояние iOS-интеграции (справочно)

iOS-интеграция AmneziaWG уже работает и состоит из 6 компонентов:

[Diagram]
Компонент Файл Роль
Vendor plugin plugins/withAmneziaWGVendor.js Клонирует amneziawg-apple в vendor/
NE target plugin plugins/withWgnext.js Создаёт Network Extension, SPM, Go bridge
Native bridge plugin plugins/withRNWireGuardModule.js Копирует ObjC модуль в ios/
ObjC bridge modules/wireguard/RNWireGuardModule.m NativeModule: initialize, connect, disconnect, getStatus, getNetworkStats
Tunnel provider WGNEXT_SOURCE/PacketTunnelProvider.swift Читает AWG-параметры (jc, jmin, jmax, s1, s2, h1-h4), запускает WireGuardAdapter
TypeScript адаптер src/services/vpn/WireGuardProvider.ts Пробрасывает obfuscation params, маппит состояния

[!IMPORTANT] Текущий WireGuardProvider.ts содержит проверку Platform.OS !== 'ios' и не поддерживает Android.


Архитектура Android-интеграции (целевая)

[Diagram]

Фаза 1: Подготовка vendor-модуля (3–5 дней)

1.1 Клонирование amneziawg-android

Создать Expo config plugin plugins/withAmneziaWGAndroid.js по аналогии с withAmneziaWGVendor.js:

1.2 Подключение tunnel-модуля как Gradle subproject

Подключить vendor/amneziawg-android/tunnel как Gradle-модуль:

⚠️ Проблема 1: tunnel использует Kotlin DSL (build.gradle.kts), а проект — Groovy DSL (build.gradle)

Решение: Gradle поддерживает смешанные DSL. Подключение include ':tunnel' работает независимо от DSL формата субмодуля. Нужно только убедиться, что gradle.properties содержит amneziawgPackageName=com.vpnapp.mobile.tunnel (требуется tunnel'ом).

⚠️ Проблема 2: tunnel-модуль собирает Go через CMake → требует Go toolchain

Решение: На CI/локальной машине нужен Go 1.19+ (тот же Go, что используется для iOS). CMake автоматически скачивает зависимости через go mod. Для EAS Build — добавить Go в eas.json через "build": { "android": { "image": "ubuntu-22.04-jdk-17-ndk-25" } } и предустановить Go.

⚠️ Проблема 3: CMake требует NDK для кросс-компиляции Go → arm64/armeabi/x86_64

Решение: Expo prebuild уже настраивает NDK. Нужно убедиться что ndkVersion в android/build.gradle совместим с CMakeLists.txt из tunnel. Текущий NDK в проекте должен работать (проверить версию ≥ r25).

1.3 Конфигурация Expo plugin для автоматизации

Создать plugins/withAmneziaWGAndroid.js который при prebuild:

  1. Клонирует репозиторий (если нужно)
  2. Модифицирует android/settings.gradle — добавляет :tunnel
  3. Модифицирует android/app/build.gradle — добавляет implementation project(':tunnel')
  4. Добавляет amneziawgPackageName в gradle.properties

Фаза 2: Android VPN Service (3–5 дней)

2.1 Создание VpnService

Создать AmneziaWGVpnService.java / .kt, который:

⚠️ Проблема 4: на Android нужно запрашивать VpnService.prepare() — это показывает системный диалог, требующий Activity context

Решение: В RNWireGuardModule вызывать VpnService.prepare(activity), получая Activity через getCurrentActivity(). Если prepare() возвращает Intent — стартовать его через startActivityForResult и обрабатывать onActivityResult. Для React Native использовать ActivityEventListener.

⚠️ Проблема 5: Android 13+ требует разрешение POST_NOTIFICATIONS для foreground service notification

Решение: Запрашивать POST_NOTIFICATIONS runtime permission перед первым подключением. Добавить в native module метод requestNotificationPermission(), или интегрировать с expo-notifications.

2.2 AndroidManifest.xml

Добавить через Expo config plugin plugins/withAndroidVpnPermissions.js:

<!-- Permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />

<!-- VpnService declaration -->
<service
    android:name=".AmneziaWGVpnService"
    android:permission="android.permission.BIND_VPN_SERVICE"
    android:exported="false">
    <intent-filter>
        <action android:name="android.net.VpnService" />
    </intent-filter>
</service>

⚠️ Проблема 6: Expo prebuild перезаписывает AndroidManifest.xml при каждом запуске

Решение: Использовать withAndroidManifest из expo/config-plugins — это мержит изменения корректно, аналогично withEntitlementsPlist на iOS. Не редактировать AndroidManifest.xml вручную.


Фаза 3: React Native Native Module для Android (5–7 дней)

3.1 Создание RNWireGuardModule.java/kt

Создать Android-версию native module с тем же API, что и iOS:

Метод Описание
initialize() Проверяет/запрашивает VPN permission, инициализирует GoBackend
connect(config: ReadableMap) Парсит конфиг (включая AWG params), строит tunnel config, стартует VpnService
disconnect() Останавливает VpnService
getStatus() Возвращает {isConnected, tunnelState}
getNetworkStats() Возвращает {rxBytes, txBytes} через TrafficStats или tunnel API

Отправка событий — через RCTDeviceEventEmitter:

reactApplicationContext
    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
    .emit("VPNStateWG", params)

3.2 Регистрация модуля

Создать RNWireGuardPackage.java/kt → зарегистрировать в MainApplication через autolink или вручную.

⚠️ Проблема 7: React Native New Architecture (TurboModules) vs Old Architecture (Bridge)

Решение: Текущий проект (newArchEnabled: true в app.json) использует новую архитектуру. Рекомендуется реализовать модуль совместимым с обеими архитектурами. Начать с Bridge-based модуля (ReactContextBaseJavaModule), который работает в обоих режимах — New Arch fallback.

⚠️ Проблема 8: передача AWG obfuscation-параметров из JS → Java → Go tunnel

Решение: На iOS параметры передаются через providerConfiguration dict → InterfaceConfiguration свойства. На Android — через tunnel API. Нужно изучить, какие API tunnel-модуля amneziawg-android принимают AWG параметры. В amneziawg-android конфиг парсится из .conf файла — вероятно, нужно будет программно создать Config объект с AWG-расширениями. Fallback: если tunnel API не экспортирует AWG setters напрямую — генерировать .conf строку на лету и парсить её через Config.parse().

3.3 Исходные файлы

Исходные файлы поместить в modules/wireguard/android/:

modules/wireguard/android/
├── RNWireGuardModule.kt
├── RNWireGuardPackage.kt
└── AmneziaWGVpnService.kt

По аналогии с iOS (источники в modules/wireguard/), Expo plugin будет копировать файлы в android/app/src/main/java/com/vpnapp/mobile/.


Фаза 4: Обновление TypeScript слоя (1–2 дня)

4.1 WireGuardProvider.ts

Убрать проверку Platform.OS !== 'ios':

- if (Platform.OS !== 'ios') {
-     console.warn('[WireGuardProvider] Currently only supported on iOS with native bridge');
-     return;
- }
+ // Поддерживается iOS и Android

buildNativeConfig() уже корректно формирует объект с AWG-параметрами — этот код универсален для обеих платформ.

4.2 Различия в mapping состояний

На iOS статусы приходят из NEVPNStatus (числовые коды 0-6). На Android — из tunnel API (свои состояния: UP, DOWN, TOGGLE). Нужно маппить оба формата в VpnState.

⚠️ Проблема 9: разные форматы событий с iOS и Android native modules

Решение: Нативные модули обеих платформ должны отправлять события в одинаковом формате: { state: "0"|"1"|"2"|"3"|"-1" }. Маппинг в унифицированный формат делать на нативной стороне, а не в TypeScript.


Фаза 5: Expo Config Plugins (2–3 дня)

5.1 Новые плагины

Plugin Файл Назначение
VPN permissions plugins/withAndroidVpnPermissions.js Добавляет permissions + VpnService в manifest
Vendor clone plugins/withAmneziaWGAndroid.js Клонирует amneziawg-android, патчит Gradle
Native module copy plugins/withRNWireGuardModule.js (modify) Добавить Android-ветку: копировать .kt файлы

5.2 Обновление app.json

"plugins": [
    "./plugins/withAmneziaWGAndroid.js",
    "./plugins/withAndroidVpnPermissions.js",
    // существующие iOS плагины продолжают работать
]

⚠️ Проблема 10: порядок плагинов важен — vendor должен клонироваться ДО того, как Gradle попытается resolve tunnel module

Решение: В app.json плагин withAmneziaWGAndroid.js должен идти ПЕРЕД другими Android-специфичными плагинами. Expo выполняет плагины в порядке объявления. Vendor-клонирование происходит в withDangerousMod (synchronous), поэтому к моменту Gradle sync файлы уже будут на месте.


Фаза 6: Тестирование и отладка (3–5 дней)

6.1 Checklist

6.2 Известные проблемы тестирования

⚠️ Проблема 11: VPN туннель не работает на Android эмуляторе

Решение: Тестировать ТОЛЬКО на реальном устройстве. Android эмулятор не поддерживает VPN tunneling из-за ограничений виртуальной сети. Использовать adb logcat для дебага.

⚠️ Проблема 12: Go compilation занимает 5-15 минут при первом билде

Решение: Это нормально — Go модули (wireguard-go + amneziawg patches) компилируются для всех ABI (arm64-v8a, armeabi-v7a, x86_64). Последующие билды используют кеш. Для ускорения dev-билдов можно временно ограничить ABI в build.gradle:

ndk {
    abiFilters 'arm64-v8a' // только для тестового устройства
}

Сводная таблица проблем и решений

# Проблема Критичность Решение
1 Kotlin DSL vs Groovy DSL 🟡 Низкая Gradle поддерживает смешанные DSL
2 Go toolchain на CI 🟡 Средняя Установить Go 1.19+ на CI, аналогично iOS
3 NDK для Go cross-compilation 🟡 Средняя Проверить совместимость NDK версии (≥ r25)
4 VPN permission dialog (Activity) 🔴 Высокая ActivityEventListener + startActivityForResult
5 POST_NOTIFICATIONS (Android 13+) 🟡 Средняя Runtime permission request
6 Manifest при prebuild 🟢 Низкая withAndroidManifest из expo/config-plugins
7 New Architecture vs Bridge 🟡 Средняя Bridge-based модуль (совместим с обоими)
8 AWG params → Go tunnel 🔴 Высокая Config.parse() или прямые setters в tunnel API
9 Разные форматы событий iOS/Android 🟡 Средняя Унифицировать на нативной стороне
10 Порядок плагинов 🟢 Низкая Vendor plugin первым в массиве
11 Эмулятор не поддерживает VPN 🟡 Средняя Только реальное устройство
12 Долгая Go-компиляция 🟢 Низкая abiFilters для dev-билдов

Оценка трудозатрат

Фаза Описание Срок Зависимости
1 Vendor + Gradle 3–5 дн Go toolchain, NDK
2 VpnService 3–5 дн Фаза 1
3 Native Module 5–7 дн Фаза 2
4 TypeScript 1–2 дн Фаза 3
5 Expo Plugins 2–3 дн Параллельно с Фазами 2-3
6 Тестирование 3–5 дн Все фазы
Итого ~3–4 недели

Сравнение iOS и Android интеграций

Аспект iOS Android
Vendor amneziawg-apple (SPM) amneziawg-android/tunnel (Gradle)
Go bridge Aggregate Target + Makefile CMake + NDK
Tunnel API WireGuardAdapter (Swift) GoBackend / Backend (Java)
VPN framework NetworkExtension + NEPacketTunnelProvider VpnService (Android)
Permission Capabilities + Entitlements BIND_VPN_SERVICE + system dialog
Native bridge ObjC (RCTEventEmitter) Kotlin (ReactContextBaseJavaModule)
Process model Separate App Extension process Same app process (VpnService)
Package manager SPM (local) Gradle subproject