This commit is contained in:
arm64v8a 2023-03-30 19:16:27 +09:00
parent 3270d7e282
commit dfb81814d1
10 changed files with 219 additions and 168 deletions

View File

@ -255,7 +255,7 @@ data class ProxyEntity(
return with(requireBean()) {
StringBuilder().apply {
val config = buildConfig(this@ProxyEntity)
val config = buildConfig(this@ProxyEntity, forExport = true)
append(config.config)
if (!config.externalIndex.all { it.chain.isEmpty() }) {

View File

@ -26,9 +26,10 @@ import io.nekohasekai.sagernet.fmt.wireguard.buildSingBoxOutboundWireguardBean
import io.nekohasekai.sagernet.ktx.isIpAddress
import io.nekohasekai.sagernet.ktx.mkPort
import io.nekohasekai.sagernet.utils.PackageCache
import moe.matsuri.nb4a.DNS.applyDNSNetworkSettings
import moe.matsuri.nb4a.DNS.makeSingBoxRule
import moe.matsuri.nb4a.SingBoxOptions.*
import moe.matsuri.nb4a.applyDNSNetworkSettings
import moe.matsuri.nb4a.checkEmpty
import moe.matsuri.nb4a.makeSingBoxRule
import moe.matsuri.nb4a.plugin.Plugins
import moe.matsuri.nb4a.proxy.config.ConfigBean
import moe.matsuri.nb4a.proxy.shadowtls.ShadowTLSBean
@ -77,7 +78,7 @@ fun mergeJSON(j: String, to: MutableMap<String, Any>) {
}
fun buildConfig(
proxy: ProxyEntity, forTest: Boolean = false
proxy: ProxyEntity, forTest: Boolean = false, forExport: Boolean = false
): ConfigBuildResult {
if (proxy.type == TYPE_CONFIG) {
@ -98,6 +99,7 @@ fun buildConfig(
val trafficMap = HashMap<String, List<ProxyEntity>>()
val tagMap = HashMap<Long, String>()
val globalOutbounds = ArrayList<Long>()
val selectorNames = ArrayList<String>()
val group = SagerDatabase.groupDao.getById(proxy.groupId)
var optionsToMerge = ""
@ -116,6 +118,17 @@ fun buildConfig(
return mutableListOf(this)
}
fun selectorName(name_: String): String {
var name = name_
var count = 0
while (selectorNames.contains(name)) {
count++
name = "$name_-$count"
}
selectorNames.add(name)
return name
}
fun ProxyEntity.resolveChain(): MutableList<ProxyEntity> {
val frontProxy = group?.frontProxy?.let { SagerDatabase.proxyDao.getById(it) }
val landingProxy = group?.landingProxy?.let { SagerDatabase.proxyDao.getById(it) }
@ -133,8 +146,8 @@ fun buildConfig(
val extraProxies =
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 }
val buildSelector = !forTest && group?.isSelector == true
}.toHashSet().toList()).associateBy { it.id }
val buildSelector = !forTest && group?.isSelector == true && !forExport
val uidListDNSRemote = mutableListOf<Int>()
val uidListDNSDirect = mutableListOf<Int>()
val domainListDNSRemote = mutableListOf<String>()
@ -328,6 +341,11 @@ fun buildConfig(
tagOut = TAG_PROXY
}
// selector human readable name
if (buildSelector && index == 0) {
tagOut = selectorName(bean.displayName())
}
// chain rules
if (index > 0) {
// chain route/proxy rules
@ -495,26 +513,25 @@ fun buildConfig(
// apply user rules
for (rule in extraRules) {
val _uidList = rule.packages.map {
if (rule.packages.isNotEmpty()) {
PackageCache.awaitLoadSync()
}
val uidList2 = rule.packages.map {
if (!isVPN) {
alerts.add(0 to rule.displayName())
}
PackageCache[it]?.takeIf { uid -> uid >= 1000 }
}.toHashSet().filterNotNull()
if (rule.packages.isNotEmpty()) {
if (!isVPN) {
alerts.add(0 to rule.displayName())
continue
}
}
route.rules.add(Rule_DefaultOptions().apply {
if (rule.packages.isNotEmpty()) {
val ruleObj = Rule_DefaultOptions().apply {
if (uidList2.isNotEmpty()) {
PackageCache.awaitLoadSync()
user_id = _uidList
user_id = uidList2
}
var _domainList: List<String>? = null
var domainList2: List<String>? = null
if (rule.domains.isNotBlank()) {
_domainList = rule.domains.split("\n")
makeSingBoxRule(_domainList, false)
domainList2 = rule.domains.split("\n")
makeSingBoxRule(domainList2, false)
}
if (rule.ip.isNotBlank()) {
makeSingBoxRule(rule.ip.split("\n"), true)
@ -554,13 +571,13 @@ fun buildConfig(
// also bypass lookup
// cannot use other outbound profile to lookup...
if (rule.outbound == -1L) {
uidListDNSDirect += _uidList
if (_domainList != null) domainListDNSDirect += _domainList
uidListDNSDirect += uidList2
if (domainList2 != null) domainListDNSDirect += domainList2
} else if (rule.outbound == 0L) {
uidListDNSRemote += _uidList
if (_domainList != null) domainListDNSRemote += _domainList
uidListDNSRemote += uidList2
if (domainList2 != null) domainListDNSRemote += domainList2
} else if (rule.outbound == -2L) {
if (_domainList != null) domainListDNSBlock += _domainList
if (domainList2 != null) domainListDNSBlock += domainList2
}
outbound = when (val outId = rule.outbound) {
@ -570,7 +587,11 @@ fun buildConfig(
else -> if (outId == proxy.id) TAG_PROXY else tagMap[outId]
?: throw Exception("invalid rule")
}
})
}
if (!ruleObj.checkEmpty()) {
route.rules.add(ruleObj)
}
}
for (freedom in arrayOf(TAG_DIRECT, TAG_BYPASS)) outbounds.add(Outbound().apply {
@ -683,8 +704,9 @@ fun buildConfig(
// dns object user rules
if (enableDnsRouting) {
val dnsRuleObj = mutableListOf<DNSRule_DefaultOptions>()
if (uidListDNSRemote.isNotEmpty()) {
dns.rules.add(
dnsRuleObj.add(
DNSRule_DefaultOptions().apply {
user_id = uidListDNSRemote.toHashSet().toList()
server = if (useFakeDns) "dns-fake" else "dns-remote"
@ -692,7 +714,7 @@ fun buildConfig(
)
}
if (domainListDNSRemote.isNotEmpty()) {
dns.rules.add(
dnsRuleObj.add(
DNSRule_DefaultOptions().apply {
makeSingBoxRule(domainListDNSRemote.toHashSet().toList())
server = if (useFakeDns) "dns-fake" else "dns-remote"
@ -700,7 +722,7 @@ fun buildConfig(
)
}
if (uidListDNSDirect.isNotEmpty()) {
dns.rules.add(
dnsRuleObj.add(
DNSRule_DefaultOptions().apply {
user_id = uidListDNSDirect.toHashSet().toList()
server = "dns-direct"
@ -708,7 +730,7 @@ fun buildConfig(
)
}
if (domainListDNSDirect.isNotEmpty()) {
dns.rules.add(
dnsRuleObj.add(
DNSRule_DefaultOptions().apply {
makeSingBoxRule(domainListDNSDirect.toHashSet().toList())
server = "dns-direct"
@ -716,7 +738,7 @@ fun buildConfig(
)
}
if (domainListDNSBlock.isNotEmpty()) {
dns.rules.add(
dnsRuleObj.add(
DNSRule_DefaultOptions().apply {
makeSingBoxRule(domainListDNSBlock.toHashSet().toList())
server = "dns-block"
@ -724,6 +746,9 @@ fun buildConfig(
}
)
}
dnsRuleObj.forEach {
if (!it.checkEmpty()) dns.rules.add(it)
}
}
// Disable DNS for test
@ -774,6 +799,7 @@ fun buildConfig(
dns.rules.add(DNSRule_DefaultOptions().apply {
inbound = listOf("tun-in")
server = "dns-fake"
disable_cache = true
})
}

View File

@ -199,7 +199,7 @@ object RawUpdater : GroupUpdater() {
}
@Suppress("UNCHECKED_CAST")
suspend fun parseRaw(text: String): List<AbstractBean>? {
suspend fun parseRaw(text: String, fileName: String = ""): List<AbstractBean>? {
val proxies = mutableListOf<AbstractBean>()
@ -399,7 +399,10 @@ object RawUpdater : GroupUpdater() {
} else if (text.contains("[Interface]")) {
// wireguard
try {
proxies.addAll(parseWireGuard(text))
proxies.addAll(parseWireGuard(text).map {
if (fileName.isNotBlank()) it.name = fileName
it
})
return proxies
} catch (e: Exception) {
Logs.w(e)
@ -442,9 +445,7 @@ object RawUpdater : GroupUpdater() {
val bean = WireGuardBean().applyDefaultValues()
val localAddresses = iface.getAll("Address")
if (localAddresses.isNullOrEmpty()) error("Empty address in 'Interface' selection")
bean.localAddress = localAddresses.flatMap { it.split(",") }.let { address ->
address.joinToString("\n") { it.substringBefore("/") }
}
bean.localAddress = localAddresses.flatMap { it.split(",") }.joinToString("\n")
bean.privateKey = iface["PrivateKey"]
val peers = ini.getAll("Peer")
if (peers.isNullOrEmpty()) error("Missing 'Peer' selections")

View File

@ -225,51 +225,52 @@ class ConfigurationFragment @JvmOverloads constructor(
return super.onKeyDown(ketCode, event)
}
val importFile = registerForActivityResult(ActivityResultContracts.GetContent()) { file ->
if (file != null) runOnDefaultDispatcher {
try {
val fileName = requireContext().contentResolver.query(file, null, null, null, null)
?.use { cursor ->
cursor.moveToFirst()
cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
.let(cursor::getString)
private val importFile =
registerForActivityResult(ActivityResultContracts.GetContent()) { file ->
if (file != null) runOnDefaultDispatcher {
try {
val fileName =
requireContext().contentResolver.query(file, null, null, null, null)
?.use { cursor ->
cursor.moveToFirst()
cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)
.let(cursor::getString)
}
val proxies = mutableListOf<AbstractBean>()
if (fileName != null && fileName.endsWith(".zip")) {
// try parse wireguard zip
val zip =
ZipInputStream(requireContext().contentResolver.openInputStream(file)!!)
while (true) {
val entry = zip.nextEntry ?: break
if (entry.isDirectory) continue
val fileText = zip.bufferedReader().readText()
RawUpdater.parseRaw(fileText, entry.name)
?.let { pl -> proxies.addAll(pl) }
zip.closeEntry()
}
zip.closeQuietly()
} else {
val fileText =
requireContext().contentResolver.openInputStream(file)!!.use {
it.bufferedReader().readText()
}
RawUpdater.parseRaw(fileText, fileName ?: "")
?.let { pl -> proxies.addAll(pl) }
}
val proxies = mutableListOf<AbstractBean>()
if (fileName != null && fileName.endsWith(".zip")) {
// try parse wireguard zip
val zip =
ZipInputStream(requireContext().contentResolver.openInputStream(file)!!)
while (true) {
val entry = zip.nextEntry ?: break
if (entry.isDirectory) continue
val fileText = zip.bufferedReader().readText()
RawUpdater.parseRaw(fileText)?.let { pl -> proxies.addAll(pl) }
zip.closeEntry()
if (proxies.isEmpty()) onMainDispatcher {
snackbar(getString(R.string.no_proxies_found_in_file)).show()
} else import(proxies)
} catch (e: SubscriptionFoundException) {
(requireActivity() as MainActivity).importSubscription(Uri.parse(e.link))
} catch (e: Exception) {
Logs.w(e)
onMainDispatcher {
snackbar(e.readableMessage).show()
}
zip.closeQuietly()
} else {
val fileText = requireContext().contentResolver.openInputStream(file)!!.use {
it.bufferedReader().readText()
}
RawUpdater.parseRaw(fileText)?.let { pl -> proxies.addAll(pl) }
}
if (proxies.isEmpty()) onMainDispatcher {
snackbar(getString(R.string.no_proxies_found_in_file)).show()
} else import(proxies)
} catch (e: SubscriptionFoundException) {
(requireActivity() as MainActivity).importSubscription(Uri.parse(e.link))
} catch (e: Exception) {
Logs.w(e)
onMainDispatcher {
snackbar(e.readableMessage).show()
}
}
}
}
suspend fun import(proxies: List<AbstractBean>) {
val targetId = DataStore.selectedGroupForImport()

View File

@ -9,6 +9,7 @@ import android.webkit.*
import androidx.appcompat.widget.Toolbar
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.input.input
import io.nekohasekai.sagernet.BuildConfig
import io.nekohasekai.sagernet.R
import io.nekohasekai.sagernet.database.DataStore
import io.nekohasekai.sagernet.databinding.LayoutWebviewBinding
@ -32,6 +33,7 @@ class WebviewFragment : ToolbarFragment(R.layout.layout_webview), Toolbar.OnMenu
val binding = LayoutWebviewBinding.bind(view)
// webview
WebView.setWebContentsDebuggingEnabled(BuildConfig.DEBUG)
mWebView = binding.webview
mWebView.settings.domStorageEnabled = true
mWebView.settings.javaScriptEnabled = true

View File

@ -1,86 +0,0 @@
package moe.matsuri.nb4a
import io.nekohasekai.sagernet.database.DataStore
object DNS {
fun SingBoxOptions.DNSServerOptions.applyDNSNetworkSettings(isDirect: Boolean) {
if (isDirect) {
if (DataStore.dnsNetwork.contains("NoDirectIPv4")) this.strategy = "ipv6_only"
if (DataStore.dnsNetwork.contains("NoDirectIPv6")) this.strategy = "ipv4_only"
} else {
if (DataStore.dnsNetwork.contains("NoRemoteIPv4")) this.strategy = "ipv6_only"
if (DataStore.dnsNetwork.contains("NoRemoteIPv6")) this.strategy = "ipv4_only"
}
}
fun SingBoxOptions.DNSRule_DefaultOptions.makeSingBoxRule(list: List<String>) {
geosite = mutableListOf<String>()
domain = mutableListOf<String>()
domain_suffix = mutableListOf<String>()
domain_regex = mutableListOf<String>()
domain_keyword = mutableListOf<String>()
list.forEach {
if (it.startsWith("geosite:")) {
geosite.plusAssign(it.removePrefix("geosite:"))
} else if (it.startsWith("full:")) {
domain.plusAssign(it.removePrefix("full:"))
} else if (it.startsWith("domain:")) {
domain_suffix.plusAssign(it.removePrefix("domain:"))
} else if (it.startsWith("regexp:")) {
domain_regex.plusAssign(it.removePrefix("regexp:"))
} else if (it.startsWith("keyword:")) {
domain_keyword.plusAssign(it.removePrefix("keyword:"))
} else {
domain.plusAssign(it)
}
}
if (geosite?.isEmpty() == true) geosite = null
if (domain?.isEmpty() == true) domain = null
if (domain_suffix?.isEmpty() == true) domain_suffix = null
if (domain_regex?.isEmpty() == true) domain_regex = null
if (domain_keyword?.isEmpty() == true) domain_keyword = null
}
fun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List<String>, isIP: Boolean) {
if (isIP) {
ip_cidr = mutableListOf<String>()
geoip = mutableListOf<String>()
} else {
geosite = mutableListOf<String>()
domain = mutableListOf<String>()
domain_suffix = mutableListOf<String>()
domain_regex = mutableListOf<String>()
domain_keyword = mutableListOf<String>()
}
list.forEach {
if (isIP) {
if (it.startsWith("geoip:")) {
geoip.plusAssign(it.removePrefix("geoip:"))
} else {
ip_cidr.plusAssign(it)
}
return@forEach
}
if (it.startsWith("geosite:")) {
geosite.plusAssign(it.removePrefix("geosite:"))
} else if (it.startsWith("full:")) {
domain.plusAssign(it.removePrefix("full:"))
} else if (it.startsWith("domain:")) {
domain_suffix.plusAssign(it.removePrefix("domain:"))
} else if (it.startsWith("regexp:")) {
domain_regex.plusAssign(it.removePrefix("regexp:"))
} else if (it.startsWith("keyword:")) {
domain_keyword.plusAssign(it.removePrefix("keyword:"))
} else {
domain.plusAssign(it)
}
}
if (ip_cidr?.isEmpty() == true) ip_cidr = null
if (geoip?.isEmpty() == true) geoip = null
if (geosite?.isEmpty() == true) geosite = null
if (domain?.isEmpty() == true) domain = null
if (domain_suffix?.isEmpty() == true) domain_suffix = null
if (domain_regex?.isEmpty() == true) domain_regex = null
if (domain_keyword?.isEmpty() == true) domain_keyword = null
}
}

View File

@ -0,0 +1,106 @@
package moe.matsuri.nb4a
import io.nekohasekai.sagernet.database.DataStore
fun SingBoxOptions.DNSServerOptions.applyDNSNetworkSettings(isDirect: Boolean) {
if (isDirect) {
if (DataStore.dnsNetwork.contains("NoDirectIPv4")) this.strategy = "ipv6_only"
if (DataStore.dnsNetwork.contains("NoDirectIPv6")) this.strategy = "ipv4_only"
} else {
if (DataStore.dnsNetwork.contains("NoRemoteIPv4")) this.strategy = "ipv6_only"
if (DataStore.dnsNetwork.contains("NoRemoteIPv6")) this.strategy = "ipv4_only"
}
}
fun SingBoxOptions.DNSRule_DefaultOptions.makeSingBoxRule(list: List<String>) {
geosite = mutableListOf<String>()
domain = mutableListOf<String>()
domain_suffix = mutableListOf<String>()
domain_regex = mutableListOf<String>()
domain_keyword = mutableListOf<String>()
list.forEach {
if (it.startsWith("geosite:")) {
geosite.plusAssign(it.removePrefix("geosite:"))
} else if (it.startsWith("full:")) {
domain.plusAssign(it.removePrefix("full:"))
} else if (it.startsWith("domain:")) {
domain_suffix.plusAssign(it.removePrefix("domain:"))
} else if (it.startsWith("regexp:")) {
domain_regex.plusAssign(it.removePrefix("regexp:"))
} else if (it.startsWith("keyword:")) {
domain_keyword.plusAssign(it.removePrefix("keyword:"))
} else {
domain.plusAssign(it)
}
}
if (geosite?.isEmpty() == true) geosite = null
if (domain?.isEmpty() == true) domain = null
if (domain_suffix?.isEmpty() == true) domain_suffix = null
if (domain_regex?.isEmpty() == true) domain_regex = null
if (domain_keyword?.isEmpty() == true) domain_keyword = null
}
fun SingBoxOptions.DNSRule_DefaultOptions.checkEmpty(): Boolean {
if (geosite?.isNotEmpty() == true) return false
if (domain?.isNotEmpty() == true) return false
if (domain_suffix?.isNotEmpty() == true) return false
if (domain_regex?.isNotEmpty() == true) return false
if (domain_keyword?.isNotEmpty() == true) return false
if (user_id?.isNotEmpty() == true) return false
return true
}
fun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List<String>, isIP: Boolean) {
if (isIP) {
ip_cidr = mutableListOf<String>()
geoip = mutableListOf<String>()
} else {
geosite = mutableListOf<String>()
domain = mutableListOf<String>()
domain_suffix = mutableListOf<String>()
domain_regex = mutableListOf<String>()
domain_keyword = mutableListOf<String>()
}
list.forEach {
if (isIP) {
if (it.startsWith("geoip:")) {
geoip.plusAssign(it.removePrefix("geoip:"))
} else {
ip_cidr.plusAssign(it)
}
return@forEach
}
if (it.startsWith("geosite:")) {
geosite.plusAssign(it.removePrefix("geosite:"))
} else if (it.startsWith("full:")) {
domain.plusAssign(it.removePrefix("full:"))
} else if (it.startsWith("domain:")) {
domain_suffix.plusAssign(it.removePrefix("domain:"))
} else if (it.startsWith("regexp:")) {
domain_regex.plusAssign(it.removePrefix("regexp:"))
} else if (it.startsWith("keyword:")) {
domain_keyword.plusAssign(it.removePrefix("keyword:"))
} else {
domain.plusAssign(it)
}
}
if (ip_cidr?.isEmpty() == true) ip_cidr = null
if (geoip?.isEmpty() == true) geoip = null
if (geosite?.isEmpty() == true) geosite = null
if (domain?.isEmpty() == true) domain = null
if (domain_suffix?.isEmpty() == true) domain_suffix = null
if (domain_regex?.isEmpty() == true) domain_regex = null
if (domain_keyword?.isEmpty() == true) domain_keyword = null
}
fun SingBoxOptions.Rule_DefaultOptions.checkEmpty(): Boolean {
if (ip_cidr?.isNotEmpty() == true) return false
if (geoip?.isNotEmpty() == true) return false
if (geosite?.isNotEmpty() == true) return false
if (domain?.isNotEmpty() == true) return false
if (domain_suffix?.isNotEmpty() == true) return false
if (domain_regex?.isNotEmpty() == true) return false
if (domain_keyword?.isNotEmpty() == true) return false
if (user_id?.isNotEmpty() == true) return false
return true
}

View File

@ -2,7 +2,7 @@
source ../buildScript/init/env_ndk.sh
[ $rel ] || sed -i "s/buildDate .*/buildDate := \"`date +'%Y%m%d'`\"/g" date.go
[ $rel ] || sed -i "s/buildDate .*/buildDate := `date +'%Y%m%d'`/g" date.go
BUILD=".build"

View File

@ -1,14 +1,15 @@
package libcore
import (
"strconv"
"time"
)
var outdated string
func GetBuildTime() int64 {
buildDate := "20230327"
buildTime, _ := time.Parse("20060102", buildDate)
buildDate := 20230330
buildTime, _ := time.Parse("20060102", strconv.Itoa(buildDate))
return buildTime.Unix()
}

View File

@ -105,11 +105,11 @@ func verifyAPK() {
for sc.Scan() {
line := sc.Text()
if strings.HasSuffix(line, "/base.apk") {
apkPath = line[strings.Index(line, "/data/"):]
apkPath = line[strings.Index(line, "/"):]
break
}
}
//
certs, err := apkverifier.ExtractCerts(apkPath, nil)
if certs == nil || err != nil {
outdated = fmt.Sprintf("verifyAPK: no certificate: %v", err)
@ -128,7 +128,7 @@ func verifyAPK() {
}
if !ok {
outdated = fmt.Sprintf("verifyAPK: unknown signer")
outdated = "verifyAPK: unknown signer"
}
}