Add process-based routing support
Introduce process field for routing rules and wire it through UI, config parsing, and runtime resolution. Adds a PackageUidResolver utility to map package names to UIDs (with caching) and converts user-entered package names into UIDs when building v2ray routing rules. Updates V2rayConfig types, RoutingEditActivity layout/bindings/strings to accept process input, and adjusts getRouting to receive Context. Also adds extra logging and a defensive check in V2RayServiceManager when resolving connection owner UID.
This commit is contained in:
@@ -4,6 +4,7 @@ data class RulesetItem(
|
||||
var remarks: String? = "",
|
||||
var ip: List<String>? = null,
|
||||
var domain: List<String>? = null,
|
||||
var process: List<String>? = null,
|
||||
var outboundTag: String = "",
|
||||
var port: String? = null,
|
||||
var network: String? = null,
|
||||
|
||||
@@ -422,8 +422,9 @@ data class V2rayConfig(
|
||||
|
||||
data class RulesBean(
|
||||
var type: String = "field",
|
||||
var ip: ArrayList<String>? = null,
|
||||
var domain: ArrayList<String>? = null,
|
||||
var ip: List<String>? = null,
|
||||
var domain: List<String>? = null,
|
||||
var process: List<String>? = null,
|
||||
var outboundTag: String? = null,
|
||||
var balancerTag: String? = null,
|
||||
var port: String? = null,
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.service.V2RayProxyOnlyService
|
||||
import com.v2ray.ang.service.V2RayVpnService
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.PackageUidResolver
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -385,12 +386,21 @@ object V2RayServiceManager {
|
||||
else -> return -1L
|
||||
}
|
||||
|
||||
if (destIP.isBlank() || destPort == 0L) {
|
||||
Log.d(AppConfig.TAG, "ProcessFinder: Find $network connection from $srcIP:$srcPort to :$destPort, (no dest)")
|
||||
return -1L
|
||||
}
|
||||
|
||||
return try {
|
||||
cm.getConnectionOwnerUid(
|
||||
val uid = cm.getConnectionOwnerUid(
|
||||
proto,
|
||||
InetSocketAddress(srcIP, srcPort.toInt()),
|
||||
InetSocketAddress(destIP, destPort.toInt())
|
||||
).toLong()
|
||||
Log.d(AppConfig.TAG, "ProcessFinder: Find $network connection from $srcIP:$srcPort to $destIP:$destPort, uid=$uid")
|
||||
//Log.d(AppConfig.TAG, "ProcessFinder: Find $network connection from $srcIP:$srcPort to $destIP:$destPort, uid=$uid,${PackageUidResolver.uidToPackageName(uid.toString())}")
|
||||
|
||||
uid
|
||||
} catch (e: Exception) {
|
||||
-1L
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.v2ray.ang.fmt.VmessFmt
|
||||
import com.v2ray.ang.fmt.WireguardFmt
|
||||
import com.v2ray.ang.util.HttpUtil
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.PackageUidResolver
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
object V2rayConfigManager {
|
||||
@@ -200,7 +201,7 @@ object V2rayConfigManager {
|
||||
getOutbounds(v2rayConfig, config) ?: return result
|
||||
getMoreOutbounds(v2rayConfig, config.subscriptionId)
|
||||
|
||||
getRouting(v2rayConfig)
|
||||
getRouting(context, v2rayConfig)
|
||||
|
||||
getFakeDns(v2rayConfig)
|
||||
|
||||
@@ -257,7 +258,7 @@ object V2rayConfigManager {
|
||||
outboundsList.addAll(v2rayConfig.outbounds)
|
||||
v2rayConfig.outbounds = ArrayList(outboundsList)
|
||||
|
||||
getRouting(v2rayConfig)
|
||||
getRouting(context, v2rayConfig)
|
||||
|
||||
getFakeDns(v2rayConfig)
|
||||
|
||||
@@ -452,7 +453,7 @@ object V2rayConfigManager {
|
||||
* @param v2rayConfig The V2ray configuration object to be modified
|
||||
* @return true if routing configuration was successful, false otherwise
|
||||
*/
|
||||
private fun getRouting(v2rayConfig: V2rayConfig): Boolean {
|
||||
private fun getRouting(context: Context, v2rayConfig: V2rayConfig): Boolean {
|
||||
try {
|
||||
|
||||
v2rayConfig.routing.domainStrategy =
|
||||
@@ -461,7 +462,7 @@ object V2rayConfigManager {
|
||||
|
||||
val rulesetItems = MmkvManager.decodeRoutingRulesets()
|
||||
rulesetItems?.forEach { key ->
|
||||
getRoutingUserRule(key, v2rayConfig)
|
||||
getRoutingUserRule(context, key, v2rayConfig)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(AppConfig.TAG, "Failed to configure routing", e)
|
||||
@@ -476,7 +477,7 @@ object V2rayConfigManager {
|
||||
* @param item The ruleset item to add
|
||||
* @param v2rayConfig The V2ray configuration object to be modified
|
||||
*/
|
||||
private fun getRoutingUserRule(item: RulesetItem?, v2rayConfig: V2rayConfig) {
|
||||
private fun getRoutingUserRule(context: Context, item: RulesetItem?, v2rayConfig: V2rayConfig) {
|
||||
try {
|
||||
if (item == null || !item.enabled) {
|
||||
return
|
||||
@@ -497,6 +498,14 @@ object V2rayConfigManager {
|
||||
rule.ip = updatedIpList
|
||||
}
|
||||
|
||||
// Convert process package names to UIDs
|
||||
rule.process?.let { processList ->
|
||||
if (processList.isNotEmpty()) {
|
||||
val uids = PackageUidResolver.packageNamesToUids(context, processList)
|
||||
rule.process = uids.ifEmpty { null }
|
||||
}
|
||||
}
|
||||
|
||||
v2rayConfig.routing.rules.add(rule)
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -42,6 +42,7 @@ class RoutingEditActivity : BaseActivity() {
|
||||
binding.chkLocked.isChecked = rulesetItem.locked == true
|
||||
binding.etDomain.text = Utils.getEditable(rulesetItem.domain?.joinToString(","))
|
||||
binding.etIp.text = Utils.getEditable(rulesetItem.ip?.joinToString(","))
|
||||
binding.etProcess.text = Utils.getEditable(rulesetItem.process?.joinToString(","))
|
||||
binding.etPort.text = Utils.getEditable(rulesetItem.port)
|
||||
binding.etProtocol.text = Utils.getEditable(rulesetItem.protocol?.joinToString(","))
|
||||
binding.etNetwork.text = Utils.getEditable(rulesetItem.network)
|
||||
@@ -65,6 +66,7 @@ class RoutingEditActivity : BaseActivity() {
|
||||
locked = binding.chkLocked.isChecked
|
||||
domain = binding.etDomain.text.toString().nullIfBlank()?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
|
||||
ip = binding.etIp.text.toString().nullIfBlank()?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
|
||||
process = binding.etProcess.text.toString().nullIfBlank()?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
|
||||
protocol = binding.etProtocol.text.toString().nullIfBlank()?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
|
||||
port = binding.etPort.text.toString().nullIfBlank()
|
||||
network = binding.etNetwork.text.toString().nullIfBlank()
|
||||
|
||||
@@ -25,7 +25,7 @@ class RoutingSettingRecyclerAdapter(
|
||||
val ruleset = rulesets[position]
|
||||
|
||||
holder.itemRoutingSettingBinding.remarks.text = ruleset.remarks
|
||||
holder.itemRoutingSettingBinding.domainIp.text = (ruleset.domain ?: ruleset.ip ?: ruleset.port)?.toString()
|
||||
holder.itemRoutingSettingBinding.domainIp.text = (ruleset.domain ?: ruleset.ip ?: ruleset.process ?: ruleset.port)?.toString()
|
||||
holder.itemRoutingSettingBinding.outboundTag.text = ruleset.outboundTag
|
||||
holder.itemRoutingSettingBinding.chkEnable.isChecked = ruleset.enabled
|
||||
holder.itemRoutingSettingBinding.imgLocked.isVisible = ruleset.locked == true
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.v2ray.ang.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.Log
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
object PackageUidResolver {
|
||||
|
||||
private const val TAG = "PackageUidResolver"
|
||||
|
||||
// In-process cache to avoid resolving the same package UID repeatedly.
|
||||
private val packageUidCache = ConcurrentHashMap<String, String>()
|
||||
|
||||
val packageUidMap: Map<String, String>
|
||||
get() = packageUidCache
|
||||
|
||||
fun packageNamesToUids(context: Context, packageNames: List<String>): List<String> {
|
||||
return packageNames.mapNotNull { pkg ->
|
||||
packageUidCache[pkg] ?: resolveUid(context, pkg)?.also { uid ->
|
||||
packageUidCache[pkg] = uid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun uidsToPackageNames(uids: List<String>): List<String> {
|
||||
if (uids.isEmpty()) return emptyList()
|
||||
return packageUidCache.filterValues { it in uids }.keys.toList()
|
||||
}
|
||||
|
||||
fun uidToPackageName(uid: String): String? {
|
||||
return packageUidCache.entries.firstOrNull { it.value == uid }?.key
|
||||
}
|
||||
|
||||
private fun resolveUid(context: Context, packageName: String): String? {
|
||||
return try {
|
||||
val uid = context.packageManager.getPackageUid(packageName, 0).toString()
|
||||
Log.d(TAG, "Package: $packageName -> UID: $uid")
|
||||
uid
|
||||
} catch (_: PackageManager.NameNotFoundException) {
|
||||
Log.w(TAG, "Package not found: $packageName")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user