base android logic

This commit is contained in:
2026-03-12 19:10:16 +07:00
parent 2f7462e299
commit dfc24b8da3
5 changed files with 123 additions and 135 deletions
@@ -32,6 +32,7 @@ android {
}
dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.15.2")
implementation("net.java.dev.jna:jna:5.13.0@aar")
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.appcompat:appcompat:1.6.0")
@@ -5,12 +5,16 @@
<application>
<service
android:name="app.netrunner.vpn.VpnPlugin"
android:name=".VpnService"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
<meta-data
android:name="android.net.VpnService.SUPPORTS_USER_CONTROL"
android:value="true" />
</service>
</application>
</manifest>
@@ -2,191 +2,178 @@ package app.netrunner.vpn
import android.app.Activity
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.Invoke
import app.tauri.annotation.InvokeArg
import app.tauri.annotation.Command
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.Session
import uniffi.netrunner_client.NoHandle
import android.net.VpnService as AndroidVpnService
import app.netrunner.vpn.TunInterface
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.io.Closeable
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
// ------------------------------------------------
// DATA
// ------------------------------------------------
@Parcelize
data class TunnelConfig(
val address: String,
val prefix: Int,
val dns: String,
val mtu: Int
data class TunnelConfig @JsonCreator constructor(
@JsonProperty("address") val address: String,
@JsonProperty("prefix") val prefix: Int,
@JsonProperty("dns") val dns: String,
@JsonProperty("mtu") val mtu: Int
) : Parcelable
// ------------------------------------------------
// VPN SERVICE
// ------------------------------------------------
class TunInterface : Closeable {
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")
}
class TunVpnService : VpnService() {
private var activeSession: Session? = null
private val tunInterface = TunInterface()
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)
}
}
private var vpnInterface: ParcelFileDescriptor? = null
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" -> {
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)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra("config")
}
if (config == null) {
android.util.Log.e("VPN_DEBUG", "VpnService: ERROR - TunnelConfig is NULL! Check if parcelable passed correctly.")
} else {
android.util.Log.d("VPN_DEBUG", "VpnService: Config received: $config. Calling controller.startVpn()...")
controller?.startVpn(config, ip ?: "", port)
if (config != null) {
startVpn(config, ip, port)
}
}
"STOP_VPN" -> {
android.util.Log.d("VPN_DEBUG", "VpnService: Stopping VPN...")
controller?.stopVpn()
stopVpn()
stopSelf()
}
else -> {
android.util.Log.w("VPN_DEBUG", "VpnService: Unknown action received: '$action'")
}
}
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() {
controller?.stopVpn()
stopVpn()
super.onDestroy()
}
}
@InvokeArg
class VpnArgs {
var config: TunnelConfig? = null
var ip: String = ""
var port: Int = 0
}
// ------------------------------------------------
// TAURI PLUGIN
// ------------------------------------------------
@TauriPlugin
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
fun startVpn(invoke: Invoke) {
android.util.Log.d("VPN_DEBUG", "Plugin: startVpn invoked")
val args = invoke.parseArgs(VpnArgs::class.java)
val intent = Intent(activity, VpnService::class.java).apply {
val args = invoke.parseArgs(VpnArgs::class.java)
val config = args.config
if (config == null) {
invoke.reject("Config is null")
return
}
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"
putExtra("ip", args.ip)
putExtra("port", args.port)
putExtra("config", args.config)
putExtra("config", args.config)
}
android.util.Log.d("VPN_DEBUG", "Plugin: Starting VpnService...")
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()
activity.startService(intent)
}
@Command
fun stopVpn(invoke: Invoke) {
val intent = Intent(activity, VpnService::class.java).apply {
val intent = Intent(activity, TunVpnService::class.java).apply {
action = "STOP_VPN"
}
activity.startService(intent)
invoke.resolve()
}
}