base android logic
This commit is contained in:
@@ -3,9 +3,5 @@
|
|||||||
"identifier": "default",
|
"identifier": "default",
|
||||||
"description": "Capability for the main window",
|
"description": "Capability for the main window",
|
||||||
"windows": ["main"],
|
"windows": ["main"],
|
||||||
"permissions": [
|
"permissions": ["core:default", "vpn:allow-vpn-control"]
|
||||||
"core:default",
|
|
||||||
"vpn:allow-start-tunnel",
|
|
||||||
"vpn:allow-stop-tunnel"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")
|
||||||
implementation("net.java.dev.jna:jna:5.13.0@aar")
|
implementation("net.java.dev.jna:jna:5.13.0@aar")
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
implementation("androidx.appcompat:appcompat:1.6.0")
|
||||||
|
|||||||
@@ -5,12 +5,16 @@
|
|||||||
|
|
||||||
<application>
|
<application>
|
||||||
<service
|
<service
|
||||||
android:name="app.netrunner.vpn.VpnPlugin"
|
android:name=".VpnService"
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.net.VpnService"/>
|
<action android:name="android.net.VpnService"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.net.VpnService.SUPPORTS_USER_CONTROL"
|
||||||
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -2,191 +2,178 @@ package app.netrunner.vpn
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.net.VpnService
|
||||||
|
import android.os.ParcelFileDescriptor
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
import app.tauri.plugin.Plugin
|
import app.tauri.plugin.Plugin
|
||||||
import app.tauri.plugin.Invoke
|
import app.tauri.plugin.Invoke
|
||||||
import app.tauri.annotation.InvokeArg
|
import app.tauri.annotation.InvokeArg
|
||||||
import app.tauri.annotation.Command
|
import app.tauri.annotation.Command
|
||||||
import app.tauri.annotation.TauriPlugin
|
import app.tauri.annotation.TauriPlugin
|
||||||
import app.tauri.annotation.Permission
|
|
||||||
import android.Manifest
|
|
||||||
import app.netrunner.vpn.VpnController
|
|
||||||
import android.os.ParcelFileDescriptor
|
|
||||||
import android.content.Context
|
|
||||||
import uniffi.netrunner_client.SessionManager
|
import uniffi.netrunner_client.SessionManager
|
||||||
import uniffi.netrunner_client.Session
|
import uniffi.netrunner_client.Session
|
||||||
import uniffi.netrunner_client.NoHandle
|
|
||||||
import android.net.VpnService as AndroidVpnService
|
import com.fasterxml.jackson.annotation.JsonCreator
|
||||||
import app.netrunner.vpn.TunInterface
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import android.os.Parcelable
|
|
||||||
import kotlinx.parcelize.Parcelize
|
// ------------------------------------------------
|
||||||
import java.io.Closeable
|
// DATA
|
||||||
|
// ------------------------------------------------
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class TunnelConfig(
|
data class TunnelConfig @JsonCreator constructor(
|
||||||
val address: String,
|
@JsonProperty("address") val address: String,
|
||||||
val prefix: Int,
|
@JsonProperty("prefix") val prefix: Int,
|
||||||
val dns: String,
|
@JsonProperty("dns") val dns: String,
|
||||||
val mtu: Int
|
@JsonProperty("mtu") val mtu: Int
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
|
// ------------------------------------------------
|
||||||
|
// VPN SERVICE
|
||||||
|
// ------------------------------------------------
|
||||||
|
|
||||||
class TunInterface : Closeable {
|
class TunVpnService : VpnService() {
|
||||||
|
|
||||||
private var vpnInterface: ParcelFileDescriptor? = null
|
|
||||||
|
|
||||||
fun establish(builder: AndroidVpnService.Builder, config: TunnelConfig): Int {
|
|
||||||
// Принудительно закрываем текущий, если вдруг вызываем повторно
|
|
||||||
close()
|
|
||||||
|
|
||||||
val descriptor = builder
|
|
||||||
.addAddress(config.address, config.prefix)
|
|
||||||
.addRoute("0.0.0.0", 0)
|
|
||||||
.setMtu(config.mtu)
|
|
||||||
.establish()
|
|
||||||
|
|
||||||
vpnInterface = descriptor
|
|
||||||
return descriptor?.fd ?: -1
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
try {
|
|
||||||
vpnInterface?.close()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
} finally {
|
|
||||||
vpnInterface = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class VpnController(private val service: VpnService) {
|
|
||||||
init {
|
|
||||||
System.loadLibrary("netrunner_client")
|
|
||||||
}
|
|
||||||
|
|
||||||
private var activeSession: Session? = null
|
private var activeSession: Session? = null
|
||||||
private val tunInterface = TunInterface()
|
private var vpnInterface: ParcelFileDescriptor? = null
|
||||||
|
|
||||||
fun startVpn(config: TunnelConfig, ip: String, port: Int) {
|
|
||||||
val builder = service.Builder()
|
|
||||||
|
|
||||||
val fd = tunInterface.establish(builder, config)
|
|
||||||
|
|
||||||
if (fd != -1) {
|
|
||||||
val manager = SessionManager(NoHandle)
|
|
||||||
android.util.Log.d("VPN_DEBUG", "Starting session with FD: $fd")
|
|
||||||
activeSession = manager.start("$ip:$port", fd)
|
|
||||||
} else {
|
|
||||||
android.util.Log.d("VPN_DEBUG", "Starting session with FD: $fd")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stopVpn() {
|
|
||||||
activeSession?.stop()
|
|
||||||
activeSession = null
|
|
||||||
tunInterface.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class VpnService : AndroidVpnService() {
|
|
||||||
private var controller: VpnController? = null
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
android.util.Log.d("VPN_DEBUG", "VpnService: onCreate() executed")
|
|
||||||
try {
|
|
||||||
controller = VpnController(this)
|
|
||||||
android.util.Log.d("VPN_DEBUG", "VpnService: VpnController initialized successfully")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
android.util.Log.e("VPN_DEBUG", "VpnService: CRITICAL - VpnController init failed", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
val action = intent?.action
|
|
||||||
android.util.Log.d("VPN_DEBUG", "VpnService: onStartCommand() action='$action'")
|
|
||||||
|
|
||||||
when (action) {
|
when (intent?.action) {
|
||||||
|
|
||||||
"START_VPN" -> {
|
"START_VPN" -> {
|
||||||
val ip = intent.getStringExtra("ip")
|
|
||||||
val port = intent.getIntExtra("port", -1)
|
|
||||||
android.util.Log.d("VPN_DEBUG", "VpnService: Extracting extras -> IP: '$ip', Port: $port")
|
|
||||||
|
|
||||||
val config = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
|
val ip = intent.getStringExtra("ip") ?: ""
|
||||||
|
val port = intent.getIntExtra("port", 0)
|
||||||
|
|
||||||
|
val config = if (android.os.Build.VERSION.SDK_INT >= 33) {
|
||||||
intent.getParcelableExtra("config", TunnelConfig::class.java)
|
intent.getParcelableExtra("config", TunnelConfig::class.java)
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
intent.getParcelableExtra("config")
|
intent.getParcelableExtra("config")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config == null) {
|
if (config != null) {
|
||||||
android.util.Log.e("VPN_DEBUG", "VpnService: ERROR - TunnelConfig is NULL! Check if parcelable passed correctly.")
|
startVpn(config, ip, port)
|
||||||
} else {
|
|
||||||
android.util.Log.d("VPN_DEBUG", "VpnService: Config received: $config. Calling controller.startVpn()...")
|
|
||||||
controller?.startVpn(config, ip ?: "", port)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"STOP_VPN" -> {
|
"STOP_VPN" -> {
|
||||||
android.util.Log.d("VPN_DEBUG", "VpnService: Stopping VPN...")
|
stopVpn()
|
||||||
controller?.stopVpn()
|
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
else -> {
|
|
||||||
android.util.Log.w("VPN_DEBUG", "VpnService: Unknown action received: '$action'")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startVpn(config: TunnelConfig, ip: String, port: Int) {
|
||||||
|
|
||||||
|
val builder = Builder()
|
||||||
|
.addAddress(config.address, config.prefix)
|
||||||
|
.addRoute("0.0.0.0", 0)
|
||||||
|
.setMtu(config.mtu)
|
||||||
|
|
||||||
|
vpnInterface = builder.establish()
|
||||||
|
|
||||||
|
val fd = vpnInterface?.detachFd() ?: return
|
||||||
|
|
||||||
|
try {
|
||||||
|
val manager = SessionManager()
|
||||||
|
activeSession = manager.start("$ip:$port", fd)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
android.util.Log.e("VPN_DEBUG", "manager.start failed", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopVpn() {
|
||||||
|
activeSession?.stop()
|
||||||
|
activeSession = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
vpnInterface?.close()
|
||||||
|
} catch (_: Exception) {}
|
||||||
|
|
||||||
|
vpnInterface = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
controller?.stopVpn()
|
stopVpn()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------
|
||||||
@InvokeArg
|
// TAURI PLUGIN
|
||||||
class VpnArgs {
|
// ------------------------------------------------
|
||||||
var config: TunnelConfig? = null
|
|
||||||
var ip: String = ""
|
|
||||||
var port: Int = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@TauriPlugin
|
@TauriPlugin
|
||||||
class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
class VpnPlugin(private val activity: Activity) : Plugin(activity) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
System.loadLibrary("netrunner_client")
|
||||||
|
}
|
||||||
|
|
||||||
|
@InvokeArg
|
||||||
|
class VpnArgs {
|
||||||
|
var config: TunnelConfig? = null
|
||||||
|
var ip: String = ""
|
||||||
|
var port: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private var pendingArgs: VpnArgs? = null
|
||||||
|
|
||||||
@Command
|
@Command
|
||||||
fun startVpn(invoke: Invoke) {
|
fun startVpn(invoke: Invoke) {
|
||||||
android.util.Log.d("VPN_DEBUG", "Plugin: startVpn invoked")
|
|
||||||
val args = invoke.parseArgs(VpnArgs::class.java)
|
val args = invoke.parseArgs(VpnArgs::class.java)
|
||||||
|
|
||||||
|
val config = args.config
|
||||||
|
if (config == null) {
|
||||||
|
invoke.reject("Config is null")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val intent = Intent(activity, VpnService::class.java).apply {
|
val permissionIntent = VpnService.prepare(activity)
|
||||||
|
|
||||||
|
if (permissionIntent != null) {
|
||||||
|
|
||||||
|
pendingArgs = args
|
||||||
|
activity.startActivityForResult(permissionIntent, 100)
|
||||||
|
|
||||||
|
invoke.resolve() // не reject
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
startVpnService(args)
|
||||||
|
|
||||||
|
invoke.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startVpnService(args: VpnArgs) {
|
||||||
|
|
||||||
|
val intent = Intent(activity, TunVpnService::class.java).apply {
|
||||||
action = "START_VPN"
|
action = "START_VPN"
|
||||||
putExtra("ip", args.ip)
|
putExtra("ip", args.ip)
|
||||||
putExtra("port", args.port)
|
putExtra("port", args.port)
|
||||||
putExtra("config", args.config)
|
putExtra("config", args.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
android.util.Log.d("VPN_DEBUG", "Plugin: Starting VpnService...")
|
activity.startService(intent)
|
||||||
val result = activity.startService(intent)
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
android.util.Log.e("VPN_DEBUG", "Plugin: startService returned null (Service not found?)")
|
|
||||||
} else {
|
|
||||||
android.util.Log.d("VPN_DEBUG", "Plugin: startService triggered successfully")
|
|
||||||
}
|
|
||||||
invoke.resolve()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Command
|
@Command
|
||||||
fun stopVpn(invoke: Invoke) {
|
fun stopVpn(invoke: Invoke) {
|
||||||
val intent = Intent(activity, VpnService::class.java).apply {
|
|
||||||
|
val intent = Intent(activity, TunVpnService::class.java).apply {
|
||||||
action = "STOP_VPN"
|
action = "STOP_VPN"
|
||||||
}
|
}
|
||||||
|
|
||||||
activity.startService(intent)
|
activity.startService(intent)
|
||||||
|
|
||||||
invoke.resolve()
|
invoke.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ export function VpnControl() {
|
|||||||
|
|
||||||
// Подготавливаем конфигурацию (замените на нужные вам значения)
|
// Подготавливаем конфигурацию (замените на нужные вам значения)
|
||||||
const config: TunnelConfig = {
|
const config: TunnelConfig = {
|
||||||
address: "10.0.0.2",
|
address: "10.0.0.1",
|
||||||
prefix: 24,
|
prefix: 24,
|
||||||
dns: "8.8.8.8",
|
dns: "8.8.8.8",
|
||||||
mtu: 1400,
|
mtu: 1400,
|
||||||
|
|||||||
Reference in New Issue
Block a user