implement front proxy & selector

This commit is contained in:
arm64v8a 2023-03-18 13:22:02 +09:00
parent c2f39c3bbc
commit b6a5f334ba
4 changed files with 96 additions and 18 deletions

View File

@ -47,7 +47,7 @@ class BaseService {
val receiver = broadcastReceiver { _, intent -> val receiver = broadcastReceiver { _, intent ->
when (intent.action) { when (intent.action) {
Intent.ACTION_SHUTDOWN -> service.persistStats() Intent.ACTION_SHUTDOWN -> service.persistStats()
Action.RELOAD -> service.forceLoad() Action.RELOAD -> service.reload()
Action.SWITCH_WAKE_LOCK -> runOnDefaultDispatcher { service.switchWakeLock() } Action.SWITCH_WAKE_LOCK -> runOnDefaultDispatcher { service.switchWakeLock() }
else -> service.stopRunner() else -> service.stopRunner()
} }
@ -82,7 +82,7 @@ class BaseService {
cb.updateWakeLockStatus(data?.proxy?.service?.wakeLock != null) cb.updateWakeLockStatus(data?.proxy?.service?.wakeLock != null)
} }
val boardcastMutex = Mutex() private val boardcastMutex = Mutex()
suspend fun broadcast(work: (ISagerNetServiceCallback) -> Unit) { suspend fun broadcast(work: (ISagerNetServiceCallback) -> Unit) {
boardcastMutex.withLock { boardcastMutex.withLock {
@ -143,10 +143,23 @@ class BaseService {
fun onBind(intent: Intent): IBinder? = fun onBind(intent: Intent): IBinder? =
if (intent.action == Action.SERVICE) data.binder else null if (intent.action == Action.SERVICE) data.binder else null
fun forceLoad() { fun reload() {
if (DataStore.selectedProxy == 0L) { if (DataStore.selectedProxy == 0L) {
stopRunner(false, (this as Context).getString(R.string.profile_empty)) 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 val s = data.state
when { when {
s == State.Stopped -> startRunner() 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() { suspend fun startProcesses() {
data.proxy!!.launch() data.proxy!!.launch()
} }

View File

@ -8,18 +8,27 @@ import io.nekohasekai.sagernet.ktx.runOnIoDispatcher
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import moe.matsuri.nb4a.utils.JavaUtil 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) { BoxInstance(profile) {
var lastSelectorGroupId = -1L
// for TrafficLooper // for TrafficLooper
private var looper: TrafficLooper? = null private var looper: TrafficLooper? = null
override fun buildConfig() { override fun buildConfig() {
super.buildConfig() super.buildConfig()
lastSelectorGroupId = super.config.selectorGroupId
//
Logs.d(config.config) Logs.d(config.config)
if (BuildConfig.DEBUG) Logs.d(JavaUtil.gson.toJson(config.trafficMap)) if (BuildConfig.DEBUG) Logs.d(JavaUtil.gson.toJson(config.trafficMap))
} }
// only use this in temporary instance
fun buildConfigTmp() {
buildConfig()
}
override suspend fun init() { override suspend fun init() {
super.init() super.init()
pluginConfigs.forEach { (_, plugin) -> pluginConfigs.forEach { (_, plugin) ->
@ -32,7 +41,7 @@ class ProxyInstance(profile: ProxyEntity, val service: BaseService.Interface) :
box.setAsMain() box.setAsMain()
super.launch() super.launch()
runOnIoDispatcher { runOnIoDispatcher {
looper = TrafficLooper(service.data, this) looper = service?.let { TrafficLooper(it.data, this) }
looper?.start() looper?.start()
} }
} }

View File

@ -54,6 +54,7 @@ class ConfigBuildResult(
var mainEntId: Long, var mainEntId: Long,
var trafficMap: Map<String, List<ProxyEntity>>, var trafficMap: Map<String, List<ProxyEntity>>,
val alerts: List<Pair<Int, String>>, val alerts: List<Pair<Int, String>>,
val selectorGroupId: Long,
) { ) {
data class IndexEntity(var chain: LinkedHashMap<Int, ProxyEntity>) data class IndexEntity(var chain: LinkedHashMap<Int, ProxyEntity>)
} }
@ -84,16 +85,19 @@ fun buildConfig(
listOf(), listOf(),
proxy.id, // proxy.id, //
mapOf(TAG_PROXY to listOf(proxy)), // mapOf(TAG_PROXY to listOf(proxy)), //
listOf() listOf(),
-1L
) )
} }
} }
val trafficMap = HashMap<String, MutableList<ProxyEntity>>() val trafficMap = HashMap<String, MutableList<ProxyEntity>>()
val globalOutbounds = ArrayList<Long>() val globalOutbounds = ArrayList<Long>()
val group = SagerDatabase.groupDao.getById(proxy.groupId)
var optionsToMerge = "" var optionsToMerge = ""
fun ProxyEntity.resolveChain(): MutableList<ProxyEntity> { fun ProxyEntity.resolveChain(): MutableList<ProxyEntity> {
val frontProxy = group?.frontProxy?.let { SagerDatabase.proxyDao.getById(it) }
val bean = requireBean() val bean = requireBean()
if (bean is ChainBean) { if (bean is ChainBean) {
val beans = SagerDatabase.proxyDao.getEntities(bean.proxies) val beans = SagerDatabase.proxyDao.getEntities(bean.proxies)
@ -103,18 +107,26 @@ fun buildConfig(
val item = beansMap[proxyId] ?: continue val item = beansMap[proxyId] ?: continue
beanList.addAll(item.resolveChain()) 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 extraRules = if (forTest) listOf() else SagerDatabase.rulesDao.enabledRules()
val extraProxies = 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 } 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<Int>() val uidListDNSRemote = mutableListOf<Int>()
val uidListDNSDirect = mutableListOf<Int>() val uidListDNSDirect = mutableListOf<Int>()
val domainListDNSRemote = mutableListOf<String>() val domainListDNSRemote = mutableListOf<String>()
@ -444,8 +456,23 @@ fun buildConfig(
return chainTagOut return chainTagOut
} }
val tagProxy = buildChain(0, proxies) // build outbounds
val tagMap = mutableMapOf<Long, String>() val tagMap = mutableMapOf<Long, String>()
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) -> extraProxies.forEach { (key, entities) ->
tagMap[key] = buildChain(key, entities) tagMap[key] = buildChain(key, entities)
} }
@ -521,10 +548,10 @@ fun buildConfig(
} }
outbound = when (val outId = rule.outbound) { outbound = when (val outId = rule.outbound) {
0L -> tagProxy 0L -> TAG_PROXY
-1L -> TAG_BYPASS -1L -> TAG_BYPASS
-2L -> TAG_BLOCK -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") ?: throw Exception("invalid rule")
} }
}) })
@ -569,7 +596,7 @@ fun buildConfig(
for (dns in remoteDns) { for (dns in remoteDns) {
if (!dns.isIpAddress()) continue if (!dns.isIpAddress()) continue
route.rules.add(Rule_DefaultOptions().apply { route.rules.add(Rule_DefaultOptions().apply {
outbound = tagProxy outbound = TAG_PROXY
ip_cidr = listOf(dns) ip_cidr = listOf(dns)
}) })
} }
@ -721,7 +748,8 @@ fun buildConfig(
externalIndexMap, externalIndexMap,
proxy.id, proxy.id,
trafficMap, trafficMap,
alerts alerts,
if (buildSelector) group!!.id else -1L
) )
} }

View File

@ -24,6 +24,7 @@ import (
sblog "github.com/sagernet/sing-box/log" sblog "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/outbound"
) )
var mainInstance *BoxInstance var mainInstance *BoxInstance
@ -71,6 +72,7 @@ type BoxInstance struct {
state int state int
v2api *boxapi.SbV2rayServer v2api *boxapi.SbV2rayServer
selector *outbound.Selector
ForTest bool ForTest bool
} }
@ -111,6 +113,13 @@ func NewSingBoxInstance(config string) (b *BoxInstance, err error) {
platformFormatter := platformFormatter_.Interface().(*sblog.Formatter) platformFormatter := platformFormatter_.Interface().(*sblog.Formatter)
platformFormatter.DisableColors = true 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 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)) 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) { func UrlTest(i *BoxInstance, link string, timeout int32) (int32, error) {
if i == nil { if i == nil {
// test current // test current