From 10df1b44ea95a8d1b43da670ca759beaee8f96db Mon Sep 17 00:00:00 2001 From: 2dust <31833384+2dust@users.noreply.github.com> Date: Sat, 2 Aug 2025 16:20:41 +0800 Subject: [PATCH] Optimize V2RayVpnService add Tun2SocksManager --- .../src/main/java/com/v2ray/ang/AppConfig.kt | 1 + .../com/v2ray/ang/service/Tun2SocksManager.kt | 129 ++++++++++++++++++ .../com/v2ray/ang/service/V2RayVpnService.kt | 102 ++------------ 3 files changed, 141 insertions(+), 91 deletions(-) create mode 100644 V2rayNG/app/src/main/java/com/v2ray/ang/service/Tun2SocksManager.kt diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt index 05369040..18364096 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/AppConfig.kt @@ -163,6 +163,7 @@ object AppConfig { /** Give a good name to this, IDK*/ const val VPN = "VPN" + const val VPN_MTU = 1500 // Google API rule constants const val GOOGLEAPIS_CN_DOMAIN = "domain:googleapis.cn" diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/service/Tun2SocksManager.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/service/Tun2SocksManager.kt new file mode 100644 index 00000000..bc6ae975 --- /dev/null +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/service/Tun2SocksManager.kt @@ -0,0 +1,129 @@ +package com.v2ray.ang.service + +import android.content.Context +import android.net.LocalSocket +import android.net.LocalSocketAddress +import android.os.ParcelFileDescriptor +import android.util.Log +import com.v2ray.ang.AppConfig +import com.v2ray.ang.AppConfig.VPN_MTU +import com.v2ray.ang.handler.MmkvManager +import com.v2ray.ang.handler.SettingsManager +import com.v2ray.ang.util.Utils +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.File + +/** + * Manages the tun2socks process that handles VPN traffic + */ +class Tun2SocksManager( + private val context: Context, + private val vpnInterface: ParcelFileDescriptor, + private val isRunningProvider: () -> Boolean, + private val restartCallback: () -> Unit +) { + companion object { + private const val TUN2SOCKS = "libtun2socks.so" + } + + private lateinit var process: Process + + /** + * Starts the tun2socks process with the appropriate parameters. + */ + fun startTun2Socks() { + Log.i(AppConfig.TAG, "Start run $TUN2SOCKS") + val socksPort = SettingsManager.getSocksPort() + val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig() + val cmd = arrayListOf( + File(context.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath, + "--netif-ipaddr", vpnConfig.ipv4Router, + "--netif-netmask", "255.255.255.252", + "--socks-server-addr", "${AppConfig.LOOPBACK}:${socksPort}", + "--tunmtu", VPN_MTU.toString(), + "--sock-path", "sock_path", + "--enable-udprelay", + "--loglevel", "notice" + ) + + if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) { + cmd.add("--netif-ip6addr") + cmd.add(vpnConfig.ipv6Router) + } + if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) { + val localDnsPort = Utils.parseInt( + MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), + AppConfig.PORT_LOCAL_DNS.toInt() + ) + cmd.add("--dnsgw") + cmd.add("${AppConfig.LOOPBACK}:${localDnsPort}") + } + Log.i(AppConfig.TAG, cmd.toString()) + + try { + val proBuilder = ProcessBuilder(cmd) + proBuilder.redirectErrorStream(true) + process = proBuilder + .directory(context.filesDir) + .start() + Thread { + Log.i(AppConfig.TAG, "$TUN2SOCKS check") + process.waitFor() + Log.i(AppConfig.TAG, "$TUN2SOCKS exited") + if (isRunningProvider()) { + Log.i(AppConfig.TAG, "$TUN2SOCKS restart") + restartCallback() + } + }.start() + Log.i(AppConfig.TAG, "$TUN2SOCKS process info: $process") + + sendFd() + } catch (e: Exception) { + Log.e(AppConfig.TAG, "Failed to start $TUN2SOCKS process", e) + } + } + + /** + * Sends the file descriptor to the tun2socks process. + * Attempts to send the file descriptor multiple times if necessary. + */ + private fun sendFd() { + val fd = vpnInterface.fileDescriptor + val path = File(context.filesDir, "sock_path").absolutePath + Log.i(AppConfig.TAG, "LocalSocket path: $path") + + CoroutineScope(Dispatchers.IO).launch { + var tries = 0 + while (true) try { + Thread.sleep(50L shl tries) + Log.i(AppConfig.TAG, "LocalSocket sendFd tries: $tries") + LocalSocket().use { localSocket -> + localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM)) + localSocket.setFileDescriptorsForSend(arrayOf(fd)) + localSocket.outputStream.write(42) + } + break + } catch (e: Exception) { + Log.e(AppConfig.TAG, "Failed to send file descriptor, try: $tries", e) + if (tries > 5) break + tries += 1 + } + } + } + + /** + * Stops the tun2socks process + */ + fun stopTun2Socks() { + try { + Log.i(AppConfig.TAG, "$TUN2SOCKS destroy") + if (::process.isInitialized) { + process.destroy() + } + } catch (e: Exception) { + Log.e(AppConfig.TAG, "Failed to destroy $TUN2SOCKS process", e) + } + } +} diff --git a/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayVpnService.kt b/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayVpnService.kt index 79ee528b..9c34dd36 100644 --- a/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayVpnService.kt +++ b/V2rayNG/app/src/main/java/com/v2ray/ang/service/V2RayVpnService.kt @@ -5,8 +5,6 @@ import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.ConnectivityManager -import android.net.LocalSocket -import android.net.LocalSocketAddress import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest @@ -19,26 +17,18 @@ import android.util.Log import androidx.annotation.RequiresApi import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig.LOOPBACK +import com.v2ray.ang.AppConfig.VPN_MTU import com.v2ray.ang.BuildConfig import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.util.MyContextWrapper import com.v2ray.ang.util.Utils -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import java.io.File import java.lang.ref.SoftReference class V2RayVpnService : VpnService(), ServiceControl { - companion object { - private const val VPN_MTU = 1500 - private const val TUN2SOCKS = "libtun2socks.so" - } - private lateinit var mInterface: ParcelFileDescriptor private var isRunning = false - private lateinit var process: Process + private var tun2SocksManager: Tun2SocksManager? = null /**destroy * Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e @@ -281,79 +271,13 @@ class V2RayVpnService : VpnService(), ServiceControl { * Starts the tun2socks process with the appropriate parameters. */ private fun runTun2socks() { - Log.i(AppConfig.TAG, "Start run $TUN2SOCKS") - val socksPort = SettingsManager.getSocksPort() - val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig() - val cmd = arrayListOf( - File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath, - "--netif-ipaddr", vpnConfig.ipv4Router, - "--netif-netmask", "255.255.255.252", - "--socks-server-addr", "$LOOPBACK:${socksPort}", - "--tunmtu", VPN_MTU.toString(), - "--sock-path", "sock_path",//File(applicationContext.filesDir, "sock_path").absolutePath, - "--enable-udprelay", - "--loglevel", "notice" - ) - - if (MmkvManager.decodeSettingsBool(AppConfig.PREF_PREFER_IPV6)) { - cmd.add("--netif-ip6addr") - cmd.add(vpnConfig.ipv6Router) - } - if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED)) { - val localDnsPort = Utils.parseInt(MmkvManager.decodeSettingsString(AppConfig.PREF_LOCAL_DNS_PORT), AppConfig.PORT_LOCAL_DNS.toInt()) - cmd.add("--dnsgw") - cmd.add("$LOOPBACK:${localDnsPort}") - } - Log.i(AppConfig.TAG, cmd.toString()) - - try { - val proBuilder = ProcessBuilder(cmd) - proBuilder.redirectErrorStream(true) - process = proBuilder - .directory(applicationContext.filesDir) - .start() - Thread { - Log.i(AppConfig.TAG, "$TUN2SOCKS check") - process.waitFor() - Log.i(AppConfig.TAG, "$TUN2SOCKS exited") - if (isRunning) { - Log.i(AppConfig.TAG, "$TUN2SOCKS restart") - runTun2socks() - } - }.start() - Log.i(AppConfig.TAG, "$TUN2SOCKS process info : ${process.toString()}") - - sendFd() - } catch (e: Exception) { - Log.e(AppConfig.TAG, "Failed to start $TUN2SOCKS process", e) - } - } - - /** - * Sends the file descriptor to the tun2socks process. - * Attempts to send the file descriptor multiple times if necessary. - */ - private fun sendFd() { - val fd = mInterface.fileDescriptor - val path = File(applicationContext.filesDir, "sock_path").absolutePath - Log.i(AppConfig.TAG, "LocalSocket path : $path") - - CoroutineScope(Dispatchers.IO).launch { - var tries = 0 - while (true) try { - Thread.sleep(50L shl tries) - Log.i(AppConfig.TAG, "LocalSocket sendFd tries: $tries") - LocalSocket().use { localSocket -> - localSocket.connect(LocalSocketAddress(path, LocalSocketAddress.Namespace.FILESYSTEM)) - localSocket.setFileDescriptorsForSend(arrayOf(fd)) - localSocket.outputStream.write(42) - } - break - } catch (e: Exception) { - Log.e(AppConfig.TAG, "Failed to send file descriptor, try: $tries", e) - if (tries > 5) break - tries += 1 - } + tun2SocksManager = Tun2SocksManager( + context = applicationContext, + vpnInterface = mInterface, + isRunningProvider = { isRunning }, + restartCallback = { runTun2socks() } + ).also { + it.startTun2Socks() } } @@ -375,12 +299,8 @@ class V2RayVpnService : VpnService(), ServiceControl { } } - try { - Log.i(AppConfig.TAG, "$TUN2SOCKS destroy") - process.destroy() - } catch (e: Exception) { - Log.e(AppConfig.TAG, "Failed to destroy $TUN2SOCKS process", e) - } + tun2SocksManager?.stopTun2Socks() + tun2SocksManager = null V2RayServiceManager.stopCoreLoop()