mirror of
https://github.com/MatsuriDayo/NekoBoxForAndroid.git
synced 2025-12-19 14:40:06 +08:00
implement front proxy & selector
This commit is contained in:
parent
c2f39c3bbc
commit
b6a5f334ba
@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user