|
|
@@ -19,7 +19,7 @@
|
|
|
* *
|
|
|
******************************************************************************/
|
|
|
|
|
|
-package io.nekohasekai.sagernet.fmt.v2ray
|
|
|
+package io.nekohasekai.sagernet.fmt
|
|
|
|
|
|
import cn.hutool.core.lang.Validator
|
|
|
import io.nekohasekai.sagernet.BuildConfig
|
|
|
@@ -31,13 +31,14 @@ import io.nekohasekai.sagernet.fmt.http.HttpBean
|
|
|
import io.nekohasekai.sagernet.fmt.shadowsocks.ShadowsocksBean
|
|
|
import io.nekohasekai.sagernet.fmt.socks.SOCKSBean
|
|
|
import io.nekohasekai.sagernet.fmt.trojan.TrojanBean
|
|
|
+import io.nekohasekai.sagernet.fmt.v2ray.*
|
|
|
import io.nekohasekai.sagernet.fmt.v2ray.V2RayConfig.*
|
|
|
import io.nekohasekai.sagernet.ktx.Logs
|
|
|
import io.nekohasekai.sagernet.ktx.formatObject
|
|
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
|
import java.util.*
|
|
|
import kotlin.collections.HashMap
|
|
|
-
|
|
|
+import kotlin.collections.LinkedHashMap
|
|
|
|
|
|
const val TAG_SOCKS = "in"
|
|
|
const val TAG_HTTP = "http"
|
|
|
@@ -50,7 +51,7 @@ const val TAG_DNS_OUT = "dns-out"
|
|
|
|
|
|
class V2rayBuildResult(
|
|
|
var config: V2RayConfig,
|
|
|
- var index: HashMap<Int, ProxyEntity>,
|
|
|
+ var index: LinkedList<LinkedHashMap<Int, ProxyEntity>>,
|
|
|
var requireWs: Boolean,
|
|
|
)
|
|
|
|
|
|
@@ -69,6 +70,10 @@ fun buildV2RayConfig(proxy: ProxyEntity): V2rayBuildResult {
|
|
|
}
|
|
|
|
|
|
val proxies = proxy.resolveChain().asReversed()
|
|
|
+ val extraRules = SagerDatabase.rulesDao.enabledRules()
|
|
|
+ val extraProxies = SagerDatabase.proxyDao.getEntities(extraRules.mapNotNull { rule ->
|
|
|
+ rule.outbound.takeIf { it > 0 }
|
|
|
+ }.toHashSet().toList()).map { it.id to it.resolveChain() }.toMap()
|
|
|
|
|
|
val bind = if (DataStore.allowAccess) "0.0.0.0" else "127.0.0.1"
|
|
|
val remoteDns = DataStore.remoteDNS.split(",")
|
|
|
@@ -76,7 +81,7 @@ fun buildV2RayConfig(proxy: ProxyEntity): V2rayBuildResult {
|
|
|
val enableLocalDNS = DataStore.enableLocalDNS
|
|
|
val routeChina = DataStore.routeChina
|
|
|
val trafficSniffing = DataStore.trafficSniffing
|
|
|
- val indexMap = hashMapOf<Int, ProxyEntity>()
|
|
|
+ val indexMap = LinkedList<LinkedHashMap<Int, ProxyEntity>>()
|
|
|
var requireWs = false
|
|
|
|
|
|
return V2RayConfig().apply {
|
|
|
@@ -241,412 +246,468 @@ fun buildV2RayConfig(proxy: ProxyEntity): V2rayBuildResult {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- var pastExternal = false
|
|
|
- lateinit var pastOutbound: OutboundObject
|
|
|
+ var currentPort = socksPort + 10
|
|
|
+ fun requirePort() = currentPort++
|
|
|
|
|
|
- proxies.forEachIndexed { index, proxyEntity ->
|
|
|
- Logs.d("Index $index, proxyEntity: ")
|
|
|
- Logs.d(formatObject(proxyEntity))
|
|
|
+ fun buildChain(tagInbound: String, profileList: List<ProxyEntity>) {
|
|
|
+ var pastExternal = false
|
|
|
+ lateinit var pastOutbound: OutboundObject
|
|
|
+ val chainMap = LinkedHashMap<Int, ProxyEntity>()
|
|
|
+ indexMap.add(chainMap)
|
|
|
|
|
|
- val bean = proxyEntity.requireBean()
|
|
|
- indexMap[index] = proxyEntity
|
|
|
- val localPort = socksPort + 10 + index
|
|
|
- val outbound = OutboundObject()
|
|
|
+ profileList.forEachIndexed { index, proxyEntity ->
|
|
|
+ Logs.d("Index $index, proxyEntity: ")
|
|
|
+ Logs.d(formatObject(proxyEntity))
|
|
|
|
|
|
- if (proxyEntity.needExternal()) {
|
|
|
- if (!pastExternal) {
|
|
|
- outbound.apply {
|
|
|
- protocol = "socks"
|
|
|
- settings = LazyOutboundConfigurationObject(this,
|
|
|
- SocksOutboundConfigurationObject().apply {
|
|
|
- servers = listOf(
|
|
|
- SocksOutboundConfigurationObject.ServerObject()
|
|
|
- .apply {
|
|
|
- address = "127.0.0.1"
|
|
|
- port = localPort
|
|
|
- }
|
|
|
- )
|
|
|
- })
|
|
|
- tag = if (index == 0) TAG_AGENT else "${proxyEntity.id}"
|
|
|
- if (index > 0) {
|
|
|
- pastOutbound.proxySettings =
|
|
|
- OutboundObject.ProxySettingsObject().apply {
|
|
|
- tag = "${proxyEntity.id}"
|
|
|
- transportLayer = true
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- pastOutbound = outbound
|
|
|
- outbounds.add(outbound)
|
|
|
- }
|
|
|
+ val bean = proxyEntity.requireBean()
|
|
|
+ val outbound = OutboundObject()
|
|
|
|
|
|
- pastExternal = true
|
|
|
- return@forEachIndexed
|
|
|
- } else {
|
|
|
- outbound.apply {
|
|
|
- val keepAliveInterval = DataStore.tcpKeepAliveInterval
|
|
|
- val needKeepAliveInterval = keepAliveInterval !in intArrayOf(0, 15)
|
|
|
-
|
|
|
- if (bean is SOCKSBean) {
|
|
|
- protocol = "socks"
|
|
|
- settings = LazyOutboundConfigurationObject(this,
|
|
|
- SocksOutboundConfigurationObject().apply {
|
|
|
- servers = listOf(
|
|
|
- SocksOutboundConfigurationObject.ServerObject()
|
|
|
- .apply {
|
|
|
- address = bean.serverAddress
|
|
|
- port = bean.serverPort
|
|
|
- if (!bean.username.isNullOrBlank()) {
|
|
|
- users =
|
|
|
- listOf(SocksOutboundConfigurationObject.ServerObject.UserObject()
|
|
|
- .apply {
|
|
|
- user = bean.username
|
|
|
- pass = bean.password
|
|
|
- })
|
|
|
+ if (proxyEntity.needExternal()) {
|
|
|
+ val localPort = requirePort()
|
|
|
+ chainMap[localPort] = proxyEntity
|
|
|
+ if (!pastExternal) {
|
|
|
+ outbound.apply {
|
|
|
+ protocol = "socks"
|
|
|
+ settings = LazyOutboundConfigurationObject(this,
|
|
|
+ SocksOutboundConfigurationObject().apply {
|
|
|
+ servers = listOf(
|
|
|
+ SocksOutboundConfigurationObject.ServerObject()
|
|
|
+ .apply {
|
|
|
+ address = "127.0.0.1"
|
|
|
+ port = localPort
|
|
|
}
|
|
|
- }
|
|
|
- )
|
|
|
- })
|
|
|
- if (bean.tls || needKeepAliveInterval) {
|
|
|
- streamSettings = StreamSettingsObject().apply {
|
|
|
- network = "tcp"
|
|
|
- if (bean.tls) {
|
|
|
- security = "tls"
|
|
|
- if (bean.sni.isNotBlank()) {
|
|
|
- tlsSettings = TLSObject().apply {
|
|
|
- serverName = bean.sni
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if (needKeepAliveInterval) {
|
|
|
- sockopt =
|
|
|
- StreamSettingsObject.SockoptObject().apply {
|
|
|
- tcpKeepAliveInterval = keepAliveInterval
|
|
|
- }
|
|
|
- }
|
|
|
+ )
|
|
|
+ })
|
|
|
+ tag = if (index == 0) tagInbound else {
|
|
|
+ "$tagInbound-${proxyEntity.id}"
|
|
|
}
|
|
|
- }
|
|
|
- } else if (bean is HttpBean) {
|
|
|
- protocol = "http"
|
|
|
- settings = LazyOutboundConfigurationObject(this,
|
|
|
- HTTPOutboundConfigurationObject().apply {
|
|
|
- servers = listOf(
|
|
|
- HTTPOutboundConfigurationObject.ServerObject()
|
|
|
- .apply {
|
|
|
- address = bean.serverAddress
|
|
|
- port = bean.serverPort
|
|
|
- if (!bean.username.isNullOrBlank()) {
|
|
|
- users =
|
|
|
- listOf(HTTPInboundConfigurationObject.AccountObject()
|
|
|
- .apply {
|
|
|
- user = bean.username
|
|
|
- pass = bean.password
|
|
|
- })
|
|
|
- }
|
|
|
- }
|
|
|
- )
|
|
|
- })
|
|
|
- if (bean.tls || needKeepAliveInterval) {
|
|
|
- streamSettings = StreamSettingsObject().apply {
|
|
|
- network = "tcp"
|
|
|
- if (bean.tls) {
|
|
|
- security = "tls"
|
|
|
- if (bean.sni.isNotBlank()) {
|
|
|
- tlsSettings = TLSObject().apply {
|
|
|
- serverName = bean.sni
|
|
|
- }
|
|
|
+ if (index > 0) {
|
|
|
+ pastOutbound.proxySettings =
|
|
|
+ OutboundObject.ProxySettingsObject().apply {
|
|
|
+ tag = "$tagInbound-${proxyEntity.id}"
|
|
|
+ transportLayer = true
|
|
|
}
|
|
|
- }
|
|
|
- if (needKeepAliveInterval) {
|
|
|
- sockopt =
|
|
|
- StreamSettingsObject.SockoptObject().apply {
|
|
|
- tcpKeepAliveInterval = keepAliveInterval
|
|
|
- }
|
|
|
- }
|
|
|
}
|
|
|
}
|
|
|
- } else if (bean is StandardV2RayBean) {
|
|
|
- if (bean is VMessBean) {
|
|
|
- protocol = "vmess"
|
|
|
+ pastOutbound = outbound
|
|
|
+ outbounds.add(outbound)
|
|
|
+ }
|
|
|
+
|
|
|
+ pastExternal = true
|
|
|
+ return@forEachIndexed
|
|
|
+ } else {
|
|
|
+ outbound.apply {
|
|
|
+ val keepAliveInterval = DataStore.tcpKeepAliveInterval
|
|
|
+ val needKeepAliveInterval = keepAliveInterval !in intArrayOf(0, 15)
|
|
|
+
|
|
|
+ if (bean is SOCKSBean) {
|
|
|
+ protocol = "socks"
|
|
|
settings = LazyOutboundConfigurationObject(this,
|
|
|
- VMessOutboundConfigurationObject().apply {
|
|
|
- vnext = listOf(
|
|
|
- VMessOutboundConfigurationObject.ServerObject()
|
|
|
+ SocksOutboundConfigurationObject().apply {
|
|
|
+ servers = listOf(
|
|
|
+ SocksOutboundConfigurationObject.ServerObject()
|
|
|
.apply {
|
|
|
address = bean.serverAddress
|
|
|
port = bean.serverPort
|
|
|
- users = listOf(
|
|
|
- VMessOutboundConfigurationObject.ServerObject.UserObject()
|
|
|
- .apply {
|
|
|
- id = bean.uuidOrGenerate()
|
|
|
- alterId = bean.alterId
|
|
|
- security =
|
|
|
- bean.encryption.takeIf { it.isNotBlank() }
|
|
|
- ?: "auto"
|
|
|
- level = 8
|
|
|
- }
|
|
|
- )
|
|
|
+ if (!bean.username.isNullOrBlank()) {
|
|
|
+ users =
|
|
|
+ listOf(SocksOutboundConfigurationObject.ServerObject.UserObject()
|
|
|
+ .apply {
|
|
|
+ user = bean.username
|
|
|
+ pass = bean.password
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
)
|
|
|
})
|
|
|
- } else if (bean is VLESSBean) {
|
|
|
- protocol = "vless"
|
|
|
+ if (bean.tls || needKeepAliveInterval) {
|
|
|
+ streamSettings = StreamSettingsObject().apply {
|
|
|
+ network = "tcp"
|
|
|
+ if (bean.tls) {
|
|
|
+ security = "tls"
|
|
|
+ if (bean.sni.isNotBlank()) {
|
|
|
+ tlsSettings = TLSObject().apply {
|
|
|
+ serverName = bean.sni
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (needKeepAliveInterval) {
|
|
|
+ sockopt =
|
|
|
+ StreamSettingsObject.SockoptObject().apply {
|
|
|
+ tcpKeepAliveInterval = keepAliveInterval
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (bean is HttpBean) {
|
|
|
+ protocol = "http"
|
|
|
settings = LazyOutboundConfigurationObject(this,
|
|
|
- VLESSOutboundConfigurationObject().apply {
|
|
|
- vnext = listOf(
|
|
|
- VLESSOutboundConfigurationObject.ServerObject()
|
|
|
+ HTTPOutboundConfigurationObject().apply {
|
|
|
+ servers = listOf(
|
|
|
+ HTTPOutboundConfigurationObject.ServerObject()
|
|
|
.apply {
|
|
|
address = bean.serverAddress
|
|
|
port = bean.serverPort
|
|
|
- users = listOf(
|
|
|
- VLESSOutboundConfigurationObject.ServerObject.UserObject()
|
|
|
- .apply {
|
|
|
- id = bean.uuidOrGenerate()
|
|
|
- encryption = bean.encryption
|
|
|
- level = 8
|
|
|
- }
|
|
|
- )
|
|
|
+ if (!bean.username.isNullOrBlank()) {
|
|
|
+ users =
|
|
|
+ listOf(HTTPInboundConfigurationObject.AccountObject()
|
|
|
+ .apply {
|
|
|
+ user = bean.username
|
|
|
+ pass = bean.password
|
|
|
+ })
|
|
|
+ }
|
|
|
}
|
|
|
)
|
|
|
})
|
|
|
- }
|
|
|
-
|
|
|
- streamSettings = StreamSettingsObject().apply {
|
|
|
- network = bean.type
|
|
|
- if (bean.security.isNotBlank()) {
|
|
|
- security = bean.security
|
|
|
- }
|
|
|
- if (security == "tls") {
|
|
|
- tlsSettings = TLSObject().apply {
|
|
|
- if (bean.sni.isNotBlank()) {
|
|
|
- serverName = bean.sni
|
|
|
+ if (bean.tls || needKeepAliveInterval) {
|
|
|
+ streamSettings = StreamSettingsObject().apply {
|
|
|
+ network = "tcp"
|
|
|
+ if (bean.tls) {
|
|
|
+ security = "tls"
|
|
|
+ if (bean.sni.isNotBlank()) {
|
|
|
+ tlsSettings = TLSObject().apply {
|
|
|
+ serverName = bean.sni
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- if (bean.alpn.isNotBlank()) {
|
|
|
- alpn = bean.alpn.split(",")
|
|
|
+ if (needKeepAliveInterval) {
|
|
|
+ sockopt =
|
|
|
+ StreamSettingsObject.SockoptObject().apply {
|
|
|
+ tcpKeepAliveInterval = keepAliveInterval
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- when (network) {
|
|
|
- "tcp" -> {
|
|
|
- tcpSettings = TcpObject().apply {
|
|
|
- if (bean.headerType == "http") {
|
|
|
- header = TcpObject.HeaderObject().apply {
|
|
|
- type = "http"
|
|
|
- if (bean.host.isNotBlank() || bean.path.isNotBlank()) {
|
|
|
- request =
|
|
|
- TcpObject.HeaderObject.HTTPRequestObject()
|
|
|
+ } else if (bean is StandardV2RayBean) {
|
|
|
+ if (bean is VMessBean) {
|
|
|
+ protocol = "vmess"
|
|
|
+ settings = LazyOutboundConfigurationObject(this,
|
|
|
+ VMessOutboundConfigurationObject().apply {
|
|
|
+ vnext = listOf(
|
|
|
+ VMessOutboundConfigurationObject.ServerObject()
|
|
|
+ .apply {
|
|
|
+ address = bean.serverAddress
|
|
|
+ port = bean.serverPort
|
|
|
+ users = listOf(
|
|
|
+ VMessOutboundConfigurationObject.ServerObject.UserObject()
|
|
|
.apply {
|
|
|
- headers = mutableMapOf()
|
|
|
- if (bean.host.isNotBlank()) {
|
|
|
- headers["Host"] = TcpObject.HeaderObject.StringOrListObject().apply {
|
|
|
- valueY = bean.host.split(",")
|
|
|
- .map { it.trim() }
|
|
|
- }
|
|
|
- }
|
|
|
- if (bean.path.isNotBlank()) {
|
|
|
- path = bean.path.split(",")
|
|
|
- }
|
|
|
+ id = bean.uuidOrGenerate()
|
|
|
+ alterId = bean.alterId
|
|
|
+ security =
|
|
|
+ bean.encryption.takeIf { it.isNotBlank() }
|
|
|
+ ?: "auto"
|
|
|
+ level = 8
|
|
|
}
|
|
|
+ )
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ )
|
|
|
+ })
|
|
|
+ } else if (bean is VLESSBean) {
|
|
|
+ protocol = "vless"
|
|
|
+ settings = LazyOutboundConfigurationObject(this,
|
|
|
+ VLESSOutboundConfigurationObject().apply {
|
|
|
+ vnext = listOf(
|
|
|
+ VLESSOutboundConfigurationObject.ServerObject()
|
|
|
+ .apply {
|
|
|
+ address = bean.serverAddress
|
|
|
+ port = bean.serverPort
|
|
|
+ users = listOf(
|
|
|
+ VLESSOutboundConfigurationObject.ServerObject.UserObject()
|
|
|
+ .apply {
|
|
|
+ id = bean.uuidOrGenerate()
|
|
|
+ encryption = bean.encryption
|
|
|
+ level = 8
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+ )
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ streamSettings = StreamSettingsObject().apply {
|
|
|
+ network = bean.type
|
|
|
+ if (bean.security.isNotBlank()) {
|
|
|
+ security = bean.security
|
|
|
}
|
|
|
- "kcp" -> {
|
|
|
- kcpSettings = KcpObject().apply {
|
|
|
- mtu = 1350
|
|
|
- tti = 50
|
|
|
- uplinkCapacity = 12
|
|
|
- downlinkCapacity = 100
|
|
|
- congestion = false
|
|
|
- readBufferSize = 1
|
|
|
- writeBufferSize = 1
|
|
|
- header = KcpObject.HeaderObject().apply {
|
|
|
- type = bean.headerType
|
|
|
+ if (security == "tls") {
|
|
|
+ tlsSettings = TLSObject().apply {
|
|
|
+ if (bean.sni.isNotBlank()) {
|
|
|
+ serverName = bean.sni
|
|
|
}
|
|
|
- if (bean.mKcpSeed.isNotBlank()) {
|
|
|
- seed = bean.mKcpSeed
|
|
|
+ if (bean.alpn.isNotBlank()) {
|
|
|
+ alpn = bean.alpn.split(",")
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- "ws" -> {
|
|
|
- wsSettings = WebSocketObject().apply {
|
|
|
- headers = mutableMapOf()
|
|
|
|
|
|
- if (bean.host.isNotBlank()) {
|
|
|
- headers["Host"] = bean.host
|
|
|
+ when (network) {
|
|
|
+ "tcp" -> {
|
|
|
+ tcpSettings = TcpObject().apply {
|
|
|
+ if (bean.headerType == "http") {
|
|
|
+ header = TcpObject.HeaderObject().apply {
|
|
|
+ type = "http"
|
|
|
+ if (bean.host.isNotBlank() || bean.path.isNotBlank()) {
|
|
|
+ request =
|
|
|
+ TcpObject.HeaderObject.HTTPRequestObject()
|
|
|
+ .apply {
|
|
|
+ headers = mutableMapOf()
|
|
|
+ if (bean.host.isNotBlank()) {
|
|
|
+ headers["Host"] =
|
|
|
+ TcpObject.HeaderObject.StringOrListObject()
|
|
|
+ .apply {
|
|
|
+ valueY =
|
|
|
+ bean.host.split(
|
|
|
+ ","
|
|
|
+ )
|
|
|
+ .map { it.trim() }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (bean.path.isNotBlank()) {
|
|
|
+ path = bean.path.split(",")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+ }
|
|
|
+ "kcp" -> {
|
|
|
+ kcpSettings = KcpObject().apply {
|
|
|
+ mtu = 1350
|
|
|
+ tti = 50
|
|
|
+ uplinkCapacity = 12
|
|
|
+ downlinkCapacity = 100
|
|
|
+ congestion = false
|
|
|
+ readBufferSize = 1
|
|
|
+ writeBufferSize = 1
|
|
|
+ header = KcpObject.HeaderObject().apply {
|
|
|
+ type = bean.headerType
|
|
|
+ }
|
|
|
+ if (bean.mKcpSeed.isNotBlank()) {
|
|
|
+ seed = bean.mKcpSeed
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ "ws" -> {
|
|
|
+ wsSettings = WebSocketObject().apply {
|
|
|
+ headers = mutableMapOf()
|
|
|
|
|
|
- path = bean.path.takeIf { it.isNotBlank() } ?: "/"
|
|
|
-
|
|
|
- if (bean.wsMaxEarlyData > 0) {
|
|
|
- maxEarlyData = bean.wsMaxEarlyData
|
|
|
+ if (bean.host.isNotBlank()) {
|
|
|
+ headers["Host"] = bean.host
|
|
|
+ }
|
|
|
|
|
|
- val pathUrl = "http://localhost$path".toHttpUrlOrNull()
|
|
|
- if (pathUrl != null) {
|
|
|
- pathUrl.queryParameter("ed")?.let {
|
|
|
- path = pathUrl.newBuilder()
|
|
|
- .removeAllQueryParameters("ed")
|
|
|
- .build()
|
|
|
- .toString()
|
|
|
- .substringAfter("http://localhost")
|
|
|
- earlyDataHeaderName = "Sec-WebSocket-Protocol"
|
|
|
+ path = bean.path.takeIf { it.isNotBlank() } ?: "/"
|
|
|
+
|
|
|
+ if (bean.wsMaxEarlyData > 0) {
|
|
|
+ maxEarlyData = bean.wsMaxEarlyData
|
|
|
+
|
|
|
+ val pathUrl =
|
|
|
+ "http://localhost$path".toHttpUrlOrNull()
|
|
|
+ if (pathUrl != null) {
|
|
|
+ pathUrl.queryParameter("ed")?.let {
|
|
|
+ path = pathUrl.newBuilder()
|
|
|
+ .removeAllQueryParameters("ed")
|
|
|
+ .build()
|
|
|
+ .toString()
|
|
|
+ .substringAfter("http://localhost")
|
|
|
+ earlyDataHeaderName =
|
|
|
+ "Sec-WebSocket-Protocol"
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if (bean.wsUseBrowserForwarder) {
|
|
|
- useBrowserForwarding = true
|
|
|
- requireWs = true
|
|
|
+ if (bean.wsUseBrowserForwarder) {
|
|
|
+ useBrowserForwarding = true
|
|
|
+ requireWs = true
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- "http", "h2" -> {
|
|
|
- network = "h2"
|
|
|
+ "http", "h2" -> {
|
|
|
+ network = "h2"
|
|
|
|
|
|
- httpSettings = HttpObject().apply {
|
|
|
- if (bean.host.isNotBlank()) {
|
|
|
- host = bean.host.split(",")
|
|
|
- }
|
|
|
+ httpSettings = HttpObject().apply {
|
|
|
+ if (bean.host.isNotBlank()) {
|
|
|
+ host = bean.host.split(",")
|
|
|
+ }
|
|
|
|
|
|
- path = bean.path.takeIf { it.isNotBlank() } ?: "/"
|
|
|
+ path = bean.path.takeIf { it.isNotBlank() } ?: "/"
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- "quic" -> {
|
|
|
- quicSettings = QuicObject().apply {
|
|
|
- security =
|
|
|
- bean.quicSecurity.takeIf { it.isNotBlank() } ?: "none"
|
|
|
- key = bean.quicKey
|
|
|
- header = QuicObject.HeaderObject().apply {
|
|
|
- type =
|
|
|
- bean.headerType.takeIf { it.isNotBlank() } ?: "none"
|
|
|
+ "quic" -> {
|
|
|
+ quicSettings = QuicObject().apply {
|
|
|
+ security =
|
|
|
+ bean.quicSecurity.takeIf { it.isNotBlank() }
|
|
|
+ ?: "none"
|
|
|
+ key = bean.quicKey
|
|
|
+ header = QuicObject.HeaderObject().apply {
|
|
|
+ type =
|
|
|
+ bean.headerType.takeIf { it.isNotBlank() }
|
|
|
+ ?: "none"
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- "grpc" -> {
|
|
|
- grpcSettings = GrpcObject().apply {
|
|
|
- serviceName = bean.grpcServiceName
|
|
|
+ "grpc" -> {
|
|
|
+ grpcSettings = GrpcObject().apply {
|
|
|
+ serviceName = bean.grpcServiceName
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- if (needKeepAliveInterval) {
|
|
|
- sockopt = StreamSettingsObject.SockoptObject().apply {
|
|
|
- tcpKeepAliveInterval = keepAliveInterval
|
|
|
+ if (needKeepAliveInterval) {
|
|
|
+ sockopt = StreamSettingsObject.SockoptObject().apply {
|
|
|
+ tcpKeepAliveInterval = keepAliveInterval
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- }
|
|
|
- } else if (bean is ShadowsocksBean) {
|
|
|
- protocol = "shadowsocks"
|
|
|
- settings = LazyOutboundConfigurationObject(this,
|
|
|
- ShadowsocksOutboundConfigurationObject().apply {
|
|
|
- servers = listOf(
|
|
|
- ShadowsocksOutboundConfigurationObject.ServerObject()
|
|
|
- .apply {
|
|
|
- address = bean.serverAddress
|
|
|
- port = bean.serverPort
|
|
|
- method = bean.method
|
|
|
- password = bean.password
|
|
|
- }
|
|
|
- )
|
|
|
- if (needKeepAliveInterval) {
|
|
|
- streamSettings = StreamSettingsObject().apply {
|
|
|
- sockopt =
|
|
|
- StreamSettingsObject.SockoptObject().apply {
|
|
|
- tcpKeepAliveInterval = keepAliveInterval
|
|
|
+ }
|
|
|
+ } else if (bean is ShadowsocksBean) {
|
|
|
+ protocol = "shadowsocks"
|
|
|
+ settings = LazyOutboundConfigurationObject(this,
|
|
|
+ ShadowsocksOutboundConfigurationObject().apply {
|
|
|
+ servers = listOf(
|
|
|
+ ShadowsocksOutboundConfigurationObject.ServerObject()
|
|
|
+ .apply {
|
|
|
+ address = bean.serverAddress
|
|
|
+ port = bean.serverPort
|
|
|
+ method = bean.method
|
|
|
+ password = bean.password
|
|
|
}
|
|
|
+ )
|
|
|
+ if (needKeepAliveInterval) {
|
|
|
+ streamSettings = StreamSettingsObject().apply {
|
|
|
+ sockopt =
|
|
|
+ StreamSettingsObject.SockoptObject().apply {
|
|
|
+ tcpKeepAliveInterval = keepAliveInterval
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
+ })
|
|
|
+ } else if (bean is TrojanBean) {
|
|
|
+ protocol = "trojan"
|
|
|
+ settings = LazyOutboundConfigurationObject(this,
|
|
|
+ TrojanOutboundConfigurationObject().apply {
|
|
|
+ servers = listOf(
|
|
|
+ TrojanOutboundConfigurationObject.ServerObject()
|
|
|
+ .apply {
|
|
|
+ address = bean.serverAddress
|
|
|
+ port = bean.serverPort
|
|
|
+ password = bean.password
|
|
|
+ level = 8
|
|
|
+ }
|
|
|
+ )
|
|
|
}
|
|
|
- })
|
|
|
- } else if (bean is TrojanBean) {
|
|
|
- protocol = "trojan"
|
|
|
- settings = LazyOutboundConfigurationObject(this,
|
|
|
- TrojanOutboundConfigurationObject().apply {
|
|
|
- servers = listOf(
|
|
|
- TrojanOutboundConfigurationObject.ServerObject()
|
|
|
- .apply {
|
|
|
- address = bean.serverAddress
|
|
|
- port = bean.serverPort
|
|
|
- password = bean.password
|
|
|
- level = 8
|
|
|
- }
|
|
|
- )
|
|
|
- }
|
|
|
- )
|
|
|
- streamSettings = StreamSettingsObject().apply {
|
|
|
- network = "tcp"
|
|
|
- security = "tls"
|
|
|
- if (bean.sni.isNotBlank()) {
|
|
|
- tlsSettings = TLSObject().apply {
|
|
|
- serverName = bean.sni
|
|
|
+ )
|
|
|
+ streamSettings = StreamSettingsObject().apply {
|
|
|
+ network = "tcp"
|
|
|
+ security = "tls"
|
|
|
+ if (bean.sni.isNotBlank()) {
|
|
|
+ tlsSettings = TLSObject().apply {
|
|
|
+ serverName = bean.sni
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- if (needKeepAliveInterval) {
|
|
|
- sockopt = StreamSettingsObject.SockoptObject().apply {
|
|
|
- tcpKeepAliveInterval = keepAliveInterval
|
|
|
+ if (needKeepAliveInterval) {
|
|
|
+ sockopt = StreamSettingsObject.SockoptObject().apply {
|
|
|
+ tcpKeepAliveInterval = keepAliveInterval
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- }
|
|
|
- if (index == 0 && proxyEntity.needCoreMux() && DataStore.enableMux) {
|
|
|
- mux = OutboundObject.MuxObject().apply {
|
|
|
- enabled = true
|
|
|
- concurrency = DataStore.muxConcurrency
|
|
|
+ if (index == 0 && proxyEntity.needCoreMux() && DataStore.enableMux) {
|
|
|
+ mux = OutboundObject.MuxObject().apply {
|
|
|
+ enabled = true
|
|
|
+ concurrency = DataStore.muxConcurrency
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- tag = if (index == 0) TAG_AGENT else "${proxyEntity.id}"
|
|
|
- if (pastExternal) {
|
|
|
- inbounds.add(InboundObject().apply {
|
|
|
- tag = "${proxyEntity.id}-in"
|
|
|
- listen = "127.0.0.1"
|
|
|
- port = localPort
|
|
|
- protocol = "socks"
|
|
|
- settings = LazyInboundConfigurationObject(this,
|
|
|
- SocksInboundConfigurationObject().apply {
|
|
|
- auth = "noauth"
|
|
|
- udp = true
|
|
|
- userLevel = 8
|
|
|
- })
|
|
|
- if (trafficSniffing) {
|
|
|
- sniffing = InboundObject.SniffingObject().apply {
|
|
|
- enabled = true
|
|
|
- destOverride = listOf("http", "tls")
|
|
|
- metadataOnly = false
|
|
|
+ tag = if (index == 0) tagInbound else "$tagInbound-${proxyEntity.id}"
|
|
|
+ if (pastExternal) {
|
|
|
+ val localPort = requirePort()
|
|
|
+ chainMap[localPort] = proxyEntity
|
|
|
+ inbounds.add(InboundObject().apply {
|
|
|
+ tag = "$tagInbound-${proxyEntity.id}-in"
|
|
|
+ listen = "127.0.0.1"
|
|
|
+ port = localPort
|
|
|
+ protocol = "socks"
|
|
|
+ settings = LazyInboundConfigurationObject(this,
|
|
|
+ SocksInboundConfigurationObject().apply {
|
|
|
+ auth = "noauth"
|
|
|
+ udp = true
|
|
|
+ userLevel = 8
|
|
|
+ })
|
|
|
+ if (trafficSniffing) {
|
|
|
+ sniffing = InboundObject.SniffingObject().apply {
|
|
|
+ enabled = true
|
|
|
+ destOverride = listOf("http", "tls")
|
|
|
+ metadataOnly = false
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- })
|
|
|
- routing.rules.add(RoutingObject.RuleObject().apply {
|
|
|
- type = "field"
|
|
|
- inboundTag = listOf("${proxyEntity.id}-in")
|
|
|
- outboundTag = "${proxyEntity.id}"
|
|
|
- })
|
|
|
- } else if (index > 0) {
|
|
|
- pastOutbound.proxySettings =
|
|
|
- OutboundObject.ProxySettingsObject().apply {
|
|
|
- tag = "${proxyEntity.id}"
|
|
|
- transportLayer = true
|
|
|
- }
|
|
|
+ })
|
|
|
+ routing.rules.add(RoutingObject.RuleObject().apply {
|
|
|
+ type = "field"
|
|
|
+ inboundTag = listOf("$tagInbound-${proxyEntity.id}-in")
|
|
|
+ outboundTag = "$tagInbound-${proxyEntity.id}"
|
|
|
+ })
|
|
|
+ } else if (index > 0) {
|
|
|
+ pastOutbound.proxySettings =
|
|
|
+ OutboundObject.ProxySettingsObject().apply {
|
|
|
+ tag = "$tagInbound-${proxyEntity.id}"
|
|
|
+ transportLayer = true
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- pastExternal = false
|
|
|
- pastOutbound = outbound
|
|
|
- outbounds.add(outbound)
|
|
|
+ pastExternal = false
|
|
|
+ pastOutbound = outbound
|
|
|
+ outbounds.add(outbound)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /* if (proxies.size > 1) {
|
|
|
- val outNode = proxies.last()
|
|
|
- if (!outNode.needExternal()) {
|
|
|
- routing.rules.add(RoutingObject.RuleObject().apply {
|
|
|
- type = "field"
|
|
|
- inboundTag = listOf("${outNode.id}-in")
|
|
|
- outboundTag = TAG_DIRECT
|
|
|
- })
|
|
|
- }
|
|
|
- }*/
|
|
|
+ buildChain(TAG_AGENT, proxies)
|
|
|
+ if (extraProxies.isNotEmpty()) {
|
|
|
+ extraProxies.forEach { (id, entities) ->
|
|
|
+ buildChain("$TAG_AGENT-$id", entities)
|
|
|
+ }
|
|
|
+ routing.rules.add(RoutingObject.RuleObject().apply {
|
|
|
+ type = "field"
|
|
|
+ inboundTag = extraProxies.keys.map { "$TAG_AGENT-$it" }
|
|
|
+ outboundTag = TAG_DIRECT
|
|
|
+ })
|
|
|
+ }
|
|
|
+ extraRules.forEach { rule ->
|
|
|
+ routing.rules.add(RoutingObject.RuleObject().apply {
|
|
|
+ type = "field"
|
|
|
+ if (rule.domains.isNotBlank()) {
|
|
|
+ domain = rule.domains.split("\n")
|
|
|
+ }
|
|
|
+ if (rule.ip.isNotBlank()) {
|
|
|
+ ip = rule.ip.split("\n")
|
|
|
+ }
|
|
|
+ if (rule.port.isNotBlank()) {
|
|
|
+ port = rule.port
|
|
|
+ }
|
|
|
+ if (rule.sourcePort.isNotBlank()) {
|
|
|
+ sourcePort = rule.sourcePort
|
|
|
+ }
|
|
|
+ if (rule.network.isNotBlank()) {
|
|
|
+ network = rule.network
|
|
|
+ }
|
|
|
+ if (rule.source.isNotBlank()) {
|
|
|
+ source = rule.source.split("\n")
|
|
|
+ }
|
|
|
+ if (rule.protocol.isNotBlank()) {
|
|
|
+ protocol = rule.protocol.split("\n")
|
|
|
+ }
|
|
|
+ if (rule.attrs.isNotBlank()) {
|
|
|
+ attrs = rule.attrs
|
|
|
+ }
|
|
|
+ outboundTag = when (val outId = rule.outbound) {
|
|
|
+ 0L -> TAG_AGENT
|
|
|
+ -1L -> TAG_DIRECT
|
|
|
+ -2L -> TAG_BLOCK
|
|
|
+ else -> "$TAG_AGENT-$outId"
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
if (requireWs) {
|
|
|
browserForwarder = BrowserForwarderObject().apply {
|