Optimize V2RayVpnService add Tun2SocksManager

This commit is contained in:
2dust
2025-08-02 16:20:41 +08:00
parent fa12878258
commit 10df1b44ea
3 changed files with 141 additions and 91 deletions

View File

@@ -163,6 +163,7 @@ object AppConfig {
/** Give a good name to this, IDK*/ /** Give a good name to this, IDK*/
const val VPN = "VPN" const val VPN = "VPN"
const val VPN_MTU = 1500
// Google API rule constants // Google API rule constants
const val GOOGLEAPIS_CN_DOMAIN = "domain:googleapis.cn" const val GOOGLEAPIS_CN_DOMAIN = "domain:googleapis.cn"

View File

@@ -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)
}
}
}

View File

@@ -5,8 +5,6 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.net.Network import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.NetworkRequest import android.net.NetworkRequest
@@ -19,26 +17,18 @@ import android.util.Log
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.v2ray.ang.AppConfig import com.v2ray.ang.AppConfig
import com.v2ray.ang.AppConfig.LOOPBACK import com.v2ray.ang.AppConfig.LOOPBACK
import com.v2ray.ang.AppConfig.VPN_MTU
import com.v2ray.ang.BuildConfig import com.v2ray.ang.BuildConfig
import com.v2ray.ang.handler.MmkvManager import com.v2ray.ang.handler.MmkvManager
import com.v2ray.ang.handler.SettingsManager import com.v2ray.ang.handler.SettingsManager
import com.v2ray.ang.util.MyContextWrapper import com.v2ray.ang.util.MyContextWrapper
import com.v2ray.ang.util.Utils 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 import java.lang.ref.SoftReference
class V2RayVpnService : VpnService(), ServiceControl { class V2RayVpnService : VpnService(), ServiceControl {
companion object {
private const val VPN_MTU = 1500
private const val TUN2SOCKS = "libtun2socks.so"
}
private lateinit var mInterface: ParcelFileDescriptor private lateinit var mInterface: ParcelFileDescriptor
private var isRunning = false private var isRunning = false
private lateinit var process: Process private var tun2SocksManager: Tun2SocksManager? = null
/**destroy /**destroy
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e * 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. * Starts the tun2socks process with the appropriate parameters.
*/ */
private fun runTun2socks() { private fun runTun2socks() {
Log.i(AppConfig.TAG, "Start run $TUN2SOCKS") tun2SocksManager = Tun2SocksManager(
val socksPort = SettingsManager.getSocksPort() context = applicationContext,
val vpnConfig = SettingsManager.getCurrentVpnInterfaceAddressConfig() vpnInterface = mInterface,
val cmd = arrayListOf( isRunningProvider = { isRunning },
File(applicationContext.applicationInfo.nativeLibraryDir, TUN2SOCKS).absolutePath, restartCallback = { runTun2socks() }
"--netif-ipaddr", vpnConfig.ipv4Router, ).also {
"--netif-netmask", "255.255.255.252", it.startTun2Socks()
"--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
}
} }
} }
@@ -375,12 +299,8 @@ class V2RayVpnService : VpnService(), ServiceControl {
} }
} }
try { tun2SocksManager?.stopTun2Socks()
Log.i(AppConfig.TAG, "$TUN2SOCKS destroy") tun2SocksManager = null
process.destroy()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to destroy $TUN2SOCKS process", e)
}
V2RayServiceManager.stopCoreLoop() V2RayServiceManager.stopCoreLoop()