From b6a5f334ba073028a8c12bdf26b39ac703831396 Mon Sep 17 00:00:00 2001 From: arm64v8a <48624112+arm64v8a@users.noreply.github.com> Date: Sat, 18 Mar 2023 13:22:02 +0900 Subject: [PATCH] implement front proxy & selector --- .../io/nekohasekai/sagernet/bg/BaseService.kt | 31 +++++++++-- .../sagernet/bg/proto/ProxyInstance.kt | 13 ++++- .../nekohasekai/sagernet/fmt/ConfigBuilder.kt | 52 ++++++++++++++----- libcore/box.go | 18 ++++++- 4 files changed, 96 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt index cb63e25..4681068 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/BaseService.kt @@ -47,7 +47,7 @@ class BaseService { val receiver = broadcastReceiver { _, intent -> when (intent.action) { Intent.ACTION_SHUTDOWN -> service.persistStats() - Action.RELOAD -> service.forceLoad() + Action.RELOAD -> service.reload() Action.SWITCH_WAKE_LOCK -> runOnDefaultDispatcher { service.switchWakeLock() } else -> service.stopRunner() } @@ -82,7 +82,7 @@ class BaseService { cb.updateWakeLockStatus(data?.proxy?.service?.wakeLock != null) } - val boardcastMutex = Mutex() + private val boardcastMutex = Mutex() suspend fun broadcast(work: (ISagerNetServiceCallback) -> Unit) { boardcastMutex.withLock { @@ -143,10 +143,23 @@ class BaseService { fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null - fun forceLoad() { + fun reload() { if (DataStore.selectedProxy == 0L) { stopRunner(false, (this as Context).getString(R.string.profile_empty)) } + if (canReloadSelector()) { + var tag = "" + data.proxy!!.config.trafficMap.forEach { (t, list) -> + if (list.map { it.id }.contains(DataStore.selectedProxy)) { + tag = t + } + } + if (tag.isNotBlank()) { + val success = data.proxy!!.box.selectOutbound(tag) + Logs.d("selectOutbound $tag $success") + } + return + } val s = data.state when { s == State.Stopped -> startRunner() @@ -155,6 +168,18 @@ class BaseService { } } + fun canReloadSelector(): Boolean { + if ((data.proxy?.config?.selectorGroupId ?: -1L) < 0) return false + val ent = SagerDatabase.proxyDao.getById(DataStore.selectedProxy) ?: return false + val tmpBox = ProxyInstance(ent) + tmpBox.buildConfigTmp() + if (tmpBox.lastSelectorGroupId == data.proxy?.lastSelectorGroupId) { + return true + // TODO if profile changed? + } + return false + } + suspend fun startProcesses() { data.proxy!!.launch() } diff --git a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt index de70f12..d0b4a31 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/bg/proto/ProxyInstance.kt @@ -8,18 +8,27 @@ import io.nekohasekai.sagernet.ktx.runOnIoDispatcher import kotlinx.coroutines.runBlocking import moe.matsuri.nb4a.utils.JavaUtil -class ProxyInstance(profile: ProxyEntity, val service: BaseService.Interface) : +class ProxyInstance(profile: ProxyEntity, var service: BaseService.Interface? = null) : BoxInstance(profile) { + var lastSelectorGroupId = -1L + // for TrafficLooper private var looper: TrafficLooper? = null override fun buildConfig() { super.buildConfig() + lastSelectorGroupId = super.config.selectorGroupId + // Logs.d(config.config) if (BuildConfig.DEBUG) Logs.d(JavaUtil.gson.toJson(config.trafficMap)) } + // only use this in temporary instance + fun buildConfigTmp() { + buildConfig() + } + override suspend fun init() { super.init() pluginConfigs.forEach { (_, plugin) -> @@ -32,7 +41,7 @@ class ProxyInstance(profile: ProxyEntity, val service: BaseService.Interface) : box.setAsMain() super.launch() runOnIoDispatcher { - looper = TrafficLooper(service.data, this) + looper = service?.let { TrafficLooper(it.data, this) } looper?.start() } } diff --git a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt index afd575c..2064e90 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/fmt/ConfigBuilder.kt @@ -54,6 +54,7 @@ class ConfigBuildResult( var mainEntId: Long, var trafficMap: Map>, val alerts: List>, + val selectorGroupId: Long, ) { data class IndexEntity(var chain: LinkedHashMap) } @@ -84,16 +85,19 @@ fun buildConfig( listOf(), proxy.id, // mapOf(TAG_PROXY to listOf(proxy)), // - listOf() + listOf(), + -1L ) } } val trafficMap = HashMap>() val globalOutbounds = ArrayList() + val group = SagerDatabase.groupDao.getById(proxy.groupId) var optionsToMerge = "" fun ProxyEntity.resolveChain(): MutableList { + val frontProxy = group?.frontProxy?.let { SagerDatabase.proxyDao.getById(it) } val bean = requireBean() if (bean is ChainBean) { val beans = SagerDatabase.proxyDao.getEntities(bean.proxies) @@ -103,18 +107,26 @@ fun buildConfig( val item = beansMap[proxyId] ?: continue beanList.addAll(item.resolveChain()) } - return beanList.asReversed() + return if (frontProxy == null) { + beanList.asReversed() + } else { + beanList.add(0, frontProxy) + beanList.asReversed() + } + } + return if (frontProxy == null) { + mutableListOf(this) + } else { + mutableListOf(this, frontProxy) } - return mutableListOf(this) } - val proxies = proxy.resolveChain() val extraRules = if (forTest) listOf() else SagerDatabase.rulesDao.enabledRules() val extraProxies = - if (forTest) mapOf() else SagerDatabase.proxyDao.getEntities(extraRules.mapNotNull { rule -> + (if (forTest) mapOf() else SagerDatabase.proxyDao.getEntities(extraRules.mapNotNull { rule -> rule.outbound.takeIf { it > 0 && it != proxy.id } - }.toHashSet().toList()).associate { it.id to it.resolveChain() } - + }.toHashSet().toList()).associate { it.id to it.resolveChain() }).toMutableMap() + val buildSelector = !forTest && group?.isSelector == true val uidListDNSRemote = mutableListOf() val uidListDNSDirect = mutableListOf() val domainListDNSRemote = mutableListOf() @@ -444,8 +456,23 @@ fun buildConfig( return chainTagOut } - val tagProxy = buildChain(0, proxies) + // build outbounds val tagMap = mutableMapOf() + if (buildSelector) { + val list = group?.id?.let { SagerDatabase.proxyDao.getByGroup(it) } + list?.forEach { + tagMap[it.id] = buildChain(it.id, it.resolveChain()) + } + outbounds.add(0, Outbound_SelectorOptions().apply { + type = "selector" + tag = TAG_PROXY + default_ = tagMap[proxy.id] + outbounds = tagMap.values.toList() + }.asMap()) + } else { + buildChain(0, proxy.resolveChain()) + } + // build outbounds from route item extraProxies.forEach { (key, entities) -> tagMap[key] = buildChain(key, entities) } @@ -521,10 +548,10 @@ fun buildConfig( } outbound = when (val outId = rule.outbound) { - 0L -> tagProxy + 0L -> TAG_PROXY -1L -> TAG_BYPASS -2L -> TAG_BLOCK - else -> if (outId == proxy.id) tagProxy else tagMap[outId] + else -> if (outId == proxy.id) TAG_PROXY else tagMap[outId] ?: throw Exception("invalid rule") } }) @@ -569,7 +596,7 @@ fun buildConfig( for (dns in remoteDns) { if (!dns.isIpAddress()) continue route.rules.add(Rule_DefaultOptions().apply { - outbound = tagProxy + outbound = TAG_PROXY ip_cidr = listOf(dns) }) } @@ -721,7 +748,8 @@ fun buildConfig( externalIndexMap, proxy.id, trafficMap, - alerts + alerts, + if (buildSelector) group!!.id else -1L ) } diff --git a/libcore/box.go b/libcore/box.go index d15f89d..ae011dd 100644 --- a/libcore/box.go +++ b/libcore/box.go @@ -24,6 +24,7 @@ import ( sblog "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/outbound" ) var mainInstance *BoxInstance @@ -70,7 +71,8 @@ type BoxInstance struct { cancel context.CancelFunc state int - v2api *boxapi.SbV2rayServer + v2api *boxapi.SbV2rayServer + selector *outbound.Selector ForTest bool } @@ -111,6 +113,13 @@ func NewSingBoxInstance(config string) (b *BoxInstance, err error) { platformFormatter := platformFormatter_.Interface().(*sblog.Formatter) platformFormatter.DisableColors = true + // selector + if proxy, ok := b.Router().Outbound("proxy"); ok { + if selector, ok := proxy.(*outbound.Selector); ok { + b.selector = selector + } + } + return b, nil } @@ -182,6 +191,13 @@ func (b *BoxInstance) QueryStats(tag, direct string) int64 { return b.v2api.QueryStats(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", tag, direct)) } +func (b *BoxInstance) SelectOutbound(tag string) bool { + if b.selector != nil { + return b.selector.SelectOutbound(tag) + } + return false +} + func UrlTest(i *BoxInstance, link string, timeout int32) (int32, error) { if i == nil { // test current