mirror of
https://github.com/MatsuriDayo/NekoBoxForAndroid.git
synced 2025-12-18 22:20:06 +08:00
feat: generate ruleset
This commit is contained in:
parent
0c3c42aa74
commit
be1edbd5e4
@ -29,6 +29,7 @@ import libcore.Libcore
|
||||
import moe.matsuri.nb4a.NativeInterface
|
||||
import moe.matsuri.nb4a.utils.JavaUtil
|
||||
import moe.matsuri.nb4a.utils.cleanWebview
|
||||
import java.io.File
|
||||
import androidx.work.Configuration as WorkConfiguration
|
||||
|
||||
class SagerNet : Application(),
|
||||
@ -40,11 +41,11 @@ class SagerNet : Application(),
|
||||
application = this
|
||||
}
|
||||
|
||||
val nativeInterface = NativeInterface()
|
||||
private val nativeInterface = NativeInterface()
|
||||
|
||||
val externalAssets by lazy { getExternalFilesDir(null) ?: filesDir }
|
||||
val process = JavaUtil.getProcessName()
|
||||
val isMainProcess = process == BuildConfig.APPLICATION_ID
|
||||
val externalAssets: File by lazy { getExternalFilesDir(null) ?: filesDir }
|
||||
val process: String = JavaUtil.getProcessName()
|
||||
private val isMainProcess = process == BuildConfig.APPLICATION_ID
|
||||
val isBgProcess = process.endsWith(":bg")
|
||||
|
||||
override fun onCreate() {
|
||||
|
||||
@ -26,11 +26,8 @@ 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.Protocols
|
||||
import moe.matsuri.nb4a.*
|
||||
import moe.matsuri.nb4a.SingBoxOptions.*
|
||||
import moe.matsuri.nb4a.SingBoxOptionsUtil
|
||||
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
|
||||
@ -535,6 +532,9 @@ fun buildConfig(
|
||||
if (rule.ip.isNotBlank()) {
|
||||
makeSingBoxRule(rule.ip.listByLineOrComma(), true)
|
||||
}
|
||||
|
||||
generateRuleSet()
|
||||
|
||||
if (rule.port.isNotBlank()) {
|
||||
port = mutableListOf<Int>()
|
||||
port_range = mutableListOf<String>()
|
||||
@ -733,7 +733,7 @@ fun buildConfig(
|
||||
if (DataStore.bypassLanInCore) {
|
||||
route.rules.add(Rule_DefaultOptions().apply {
|
||||
outbound = TAG_BYPASS
|
||||
geoip = listOf("private")
|
||||
ip_is_private = true
|
||||
})
|
||||
}
|
||||
// block mcast
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package io.nekohasekai.sagernet.utils
|
||||
|
||||
import android.content.Context
|
||||
import io.nekohasekai.sagernet.ktx.app
|
||||
import libcore.Libcore
|
||||
import java.io.File
|
||||
|
||||
object GeoipUtils {
|
||||
fun generateRuleSet(context: Context = app.applicationContext, country: String) {
|
||||
|
||||
val filesDir = context.getExternalFilesDir(null) ?: context.filesDir
|
||||
|
||||
val ruleSetDir = filesDir.resolve("ruleSets")
|
||||
ruleSetDir.mkdirs()
|
||||
|
||||
val geositeFile = File(filesDir, "geoip.db")
|
||||
|
||||
val geoip = Libcore.newGeoip()
|
||||
if (!geoip.openGeosite(geositeFile.absolutePath)) {
|
||||
error("open geoip failed")
|
||||
}
|
||||
|
||||
geoip.convertGeoip(country, ruleSetDir.resolve("geoip-$country.srs").absolutePath)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package io.nekohasekai.sagernet.utils
|
||||
|
||||
import android.content.Context
|
||||
import io.nekohasekai.sagernet.ktx.app
|
||||
import libcore.Geosite
|
||||
import java.io.File
|
||||
|
||||
object GeositeUtils {
|
||||
fun generateRuleSet(context: Context = app.applicationContext, code: String) {
|
||||
|
||||
val filesDir = context.getExternalFilesDir(null) ?: context.filesDir
|
||||
|
||||
val ruleSetDir = filesDir.resolve("ruleSets")
|
||||
ruleSetDir.mkdirs()
|
||||
|
||||
val geositeFile = File(filesDir, "geosite.db")
|
||||
|
||||
val geosite = Geosite()
|
||||
if (!geosite.checkGeositeCode(geositeFile.absolutePath, code)) {
|
||||
error("code $code not found in geosite")
|
||||
}
|
||||
|
||||
geosite.convertGeosite(code, ruleSetDir.resolve("geosite-$code.srs").absolutePath)
|
||||
}
|
||||
}
|
||||
@ -968,12 +968,10 @@ public class SingBoxOptions {
|
||||
|
||||
public static class RouteOptions extends SingBoxOption {
|
||||
|
||||
public GeoIPOptions geoip;
|
||||
|
||||
public GeositeOptions geosite;
|
||||
|
||||
public List<Rule> rules;
|
||||
|
||||
public List<RuleSet> rule_set;
|
||||
|
||||
@SerializedName("final")
|
||||
public String final_;
|
||||
|
||||
@ -989,26 +987,6 @@ public class SingBoxOptions {
|
||||
|
||||
}
|
||||
|
||||
public static class GeoIPOptions extends SingBoxOption {
|
||||
|
||||
public String path;
|
||||
|
||||
public String download_url;
|
||||
|
||||
public String download_detour;
|
||||
|
||||
}
|
||||
|
||||
public static class GeositeOptions extends SingBoxOption {
|
||||
|
||||
public String path;
|
||||
|
||||
public String download_url;
|
||||
|
||||
public String download_detour;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class Rule extends SingBoxOption {
|
||||
|
||||
@ -1020,6 +998,20 @@ public class SingBoxOptions {
|
||||
|
||||
}
|
||||
|
||||
public static class RuleSet extends SingBoxOption {
|
||||
|
||||
public String type;
|
||||
|
||||
public String tag;
|
||||
|
||||
public String format;
|
||||
|
||||
public String path;
|
||||
|
||||
public String url;
|
||||
|
||||
}
|
||||
|
||||
public static class DefaultRule extends SingBoxOption {
|
||||
|
||||
// Generate note: Listable
|
||||
@ -1048,15 +1040,6 @@ public class SingBoxOptions {
|
||||
// Generate note: Listable
|
||||
public List<String> domain_regex;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> geosite;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> source_geoip;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> geoip;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> source_ip_cidr;
|
||||
|
||||
@ -1098,18 +1081,6 @@ public class SingBoxOptions {
|
||||
|
||||
}
|
||||
|
||||
public static class LogicalRule extends SingBoxOption {
|
||||
|
||||
public String mode;
|
||||
|
||||
public List<DefaultRule> rules;
|
||||
|
||||
public Boolean invert;
|
||||
|
||||
public String outbound;
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class DNSRule extends SingBoxOption {
|
||||
|
||||
@ -1155,9 +1126,6 @@ public class SingBoxOptions {
|
||||
// Generate note: Listable
|
||||
public List<String> geosite;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> source_geoip;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> source_ip_cidr;
|
||||
|
||||
@ -1203,22 +1171,6 @@ public class SingBoxOptions {
|
||||
|
||||
}
|
||||
|
||||
public static class LogicalDNSRule extends SingBoxOption {
|
||||
|
||||
public String mode;
|
||||
|
||||
public List<DefaultDNSRule> rules;
|
||||
|
||||
public Boolean invert;
|
||||
|
||||
public String server;
|
||||
|
||||
public Boolean disable_cache;
|
||||
|
||||
public Integer rewrite_ttl;
|
||||
|
||||
}
|
||||
|
||||
public static class ShadowsocksInboundOptions extends SingBoxOption {
|
||||
|
||||
// Generate note: nested type ListenOptions
|
||||
@ -4387,14 +4339,12 @@ public class SingBoxOptions {
|
||||
// Generate note: Listable
|
||||
public List<String> domain_regex;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> geosite;
|
||||
public List<String> rule_set;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> source_geoip;
|
||||
public Boolean source_ip_is_private;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> geoip;
|
||||
public Boolean rule_set_ipcidr_match_source;
|
||||
public Boolean ip_is_private;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> source_ip_cidr;
|
||||
@ -4437,18 +4387,6 @@ public class SingBoxOptions {
|
||||
|
||||
}
|
||||
|
||||
public static class Rule_LogicalOptions extends Rule {
|
||||
|
||||
public String mode;
|
||||
|
||||
public List<DefaultRule> rules;
|
||||
|
||||
public Boolean invert;
|
||||
|
||||
public String outbound;
|
||||
|
||||
}
|
||||
|
||||
public static class DNSRule_DefaultOptions extends DNSRule {
|
||||
|
||||
// Generate note: Listable
|
||||
@ -4483,9 +4421,6 @@ public class SingBoxOptions {
|
||||
// Generate note: Listable
|
||||
public List<String> geosite;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> source_geoip;
|
||||
|
||||
// Generate note: Listable
|
||||
public List<String> source_ip_cidr;
|
||||
|
||||
@ -4531,22 +4466,6 @@ public class SingBoxOptions {
|
||||
|
||||
}
|
||||
|
||||
public static class DNSRule_LogicalOptions extends DNSRule {
|
||||
|
||||
public String mode;
|
||||
|
||||
public List<DefaultDNSRule> rules;
|
||||
|
||||
public Boolean invert;
|
||||
|
||||
public String server;
|
||||
|
||||
public Boolean disable_cache;
|
||||
|
||||
public Integer rewrite_ttl;
|
||||
|
||||
}
|
||||
|
||||
public static class V2RayTransportOptions_HTTPOptions extends V2RayTransportOptions {
|
||||
|
||||
// Generate note: Listable
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package moe.matsuri.nb4a
|
||||
|
||||
import io.nekohasekai.sagernet.database.DataStore
|
||||
import io.nekohasekai.sagernet.utils.GeoipUtils
|
||||
import io.nekohasekai.sagernet.utils.GeositeUtils
|
||||
|
||||
object SingBoxOptionsUtil {
|
||||
|
||||
@ -70,12 +72,26 @@ fun SingBoxOptions.DNSRule_DefaultOptions.checkEmpty(): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun SingBoxOptions.Rule_DefaultOptions.generateRuleSet() {
|
||||
rule_set.forEach {
|
||||
when {
|
||||
it.startsWith("geoip") -> {
|
||||
GeoipUtils.generateRuleSet(country = it.removePrefix("geoip:"))
|
||||
}
|
||||
|
||||
it.startsWith("geosite") -> {
|
||||
GeositeUtils.generateRuleSet(code = it.removePrefix("geosite:"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List<String>, isIP: Boolean) {
|
||||
if (isIP) {
|
||||
ip_cidr = mutableListOf<String>()
|
||||
geoip = mutableListOf<String>()
|
||||
rule_set = mutableListOf<String>()
|
||||
} else {
|
||||
geosite = mutableListOf<String>()
|
||||
rule_set = mutableListOf<String>()
|
||||
domain = mutableListOf<String>()
|
||||
domain_suffix = mutableListOf<String>()
|
||||
domain_regex = mutableListOf<String>()
|
||||
@ -84,14 +100,15 @@ fun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List<String>, isIP:
|
||||
list.forEach {
|
||||
if (isIP) {
|
||||
if (it.startsWith("geoip:")) {
|
||||
geoip.plusAssign(it.removePrefix("geoip:"))
|
||||
rule_set.plusAssign(it)
|
||||
rule_set_ipcidr_match_source = true
|
||||
} else {
|
||||
ip_cidr.plusAssign(it)
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
if (it.startsWith("geosite:")) {
|
||||
geosite.plusAssign(it.removePrefix("geosite:"))
|
||||
rule_set.plusAssign(it)
|
||||
} else if (it.startsWith("full:")) {
|
||||
domain.plusAssign(it.removePrefix("full:").lowercase())
|
||||
} else if (it.startsWith("domain:")) {
|
||||
@ -106,15 +123,12 @@ fun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List<String>, isIP:
|
||||
}
|
||||
}
|
||||
ip_cidr?.removeIf { it.isNullOrBlank() }
|
||||
geoip?.removeIf { it.isNullOrBlank() }
|
||||
geosite?.removeIf { it.isNullOrBlank() }
|
||||
rule_set?.removeIf { it.isNullOrBlank() }
|
||||
domain?.removeIf { it.isNullOrBlank() }
|
||||
domain_suffix?.removeIf { it.isNullOrBlank() }
|
||||
domain_regex?.removeIf { it.isNullOrBlank() }
|
||||
domain_keyword?.removeIf { it.isNullOrBlank() }
|
||||
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
|
||||
@ -123,9 +137,8 @@ fun SingBoxOptions.Rule_DefaultOptions.makeSingBoxRule(list: List<String>, isIP:
|
||||
|
||||
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 (rule_set?.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
|
||||
|
||||
78
libcore/geoip.go
Normal file
78
libcore/geoip.go
Normal file
@ -0,0 +1,78 @@
|
||||
package libcore
|
||||
|
||||
import (
|
||||
"github.com/oschwald/maxminddb-golang"
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Geoip struct {
|
||||
geoipReader *maxminddb.Reader
|
||||
}
|
||||
|
||||
func (g *Geoip) OpenGeosite(path string) bool {
|
||||
geoipReader, err := maxminddb.Open(path)
|
||||
g.geoipReader = geoipReader
|
||||
if err != nil {
|
||||
log.Println("failed to open geoip file:", err)
|
||||
return false
|
||||
} else {
|
||||
log.Println("loaded geoip database")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *Geoip) ConvertGeoip(countryCode, outputPath string) {
|
||||
networks := g.geoipReader.Networks(maxminddb.SkipAliasedNetworks)
|
||||
countryMap := make(map[string][]*net.IPNet)
|
||||
var (
|
||||
ipNet *net.IPNet
|
||||
nextCountryCode string
|
||||
err error
|
||||
)
|
||||
for networks.Next() {
|
||||
ipNet, err = networks.Network(&nextCountryCode)
|
||||
if err != nil {
|
||||
log.Println("failed to get network:", err)
|
||||
return
|
||||
}
|
||||
countryMap[nextCountryCode] = append(countryMap[nextCountryCode], ipNet)
|
||||
}
|
||||
|
||||
ipNets := countryMap[strings.ToLower(countryCode)]
|
||||
|
||||
if len(ipNets) == 0 {
|
||||
log.Println("no networks found for country code:", countryCode)
|
||||
return
|
||||
}
|
||||
|
||||
var headlessRule option.DefaultHeadlessRule
|
||||
headlessRule.IPCIDR = make([]string, 0, len(ipNets))
|
||||
for _, cidr := range ipNets {
|
||||
headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String())
|
||||
}
|
||||
var plainRuleSet option.PlainRuleSetCompat
|
||||
plainRuleSet.Version = C.RuleSetVersion1
|
||||
plainRuleSet.Options.Rules = []option.HeadlessRule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: headlessRule,
|
||||
},
|
||||
}
|
||||
|
||||
outputFile, err := os.Create(outputPath)
|
||||
err = srs.Write(outputFile, plainRuleSet.Upgrade())
|
||||
if err != nil {
|
||||
log.Println("failed to write geosite file:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func NewGeoip() *Geoip {
|
||||
return new(Geoip)
|
||||
}
|
||||
70
libcore/geosite.go
Normal file
70
libcore/geosite.go
Normal file
@ -0,0 +1,70 @@
|
||||
package libcore
|
||||
|
||||
import (
|
||||
"github.com/sagernet/sing-box/common/geosite"
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"os"
|
||||
|
||||
"log"
|
||||
)
|
||||
|
||||
type Geosite struct {
|
||||
geositeReader *geosite.Reader
|
||||
}
|
||||
|
||||
func (g *Geosite) CheckGeositeCode(path string, code string) bool {
|
||||
geositeReader, codes, err := geosite.Open(path)
|
||||
g.geositeReader = geositeReader
|
||||
if err != nil {
|
||||
log.Println("failed to open geosite file:", err)
|
||||
return false
|
||||
} else {
|
||||
log.Println("loaded geosite database: ", len(codes), " codes")
|
||||
}
|
||||
sourceSet, err := geositeReader.Read(code)
|
||||
if err != nil {
|
||||
log.Println("failed to read geosite code:", code, err)
|
||||
return false
|
||||
}
|
||||
return len(sourceSet) >= 1
|
||||
}
|
||||
|
||||
// ConvertGeosite need to run CheckGeositeCode first
|
||||
func (g *Geosite) ConvertGeosite(code string, outputPath string) {
|
||||
|
||||
sourceSet, err := g.geositeReader.Read(code)
|
||||
if err != nil {
|
||||
log.Println("failed to read geosite code:", code, err)
|
||||
return
|
||||
}
|
||||
|
||||
var headlessRule option.DefaultHeadlessRule
|
||||
|
||||
defaultRule := geosite.Compile(sourceSet)
|
||||
|
||||
headlessRule.Domain = defaultRule.Domain
|
||||
headlessRule.DomainSuffix = defaultRule.DomainSuffix
|
||||
headlessRule.DomainKeyword = defaultRule.DomainKeyword
|
||||
headlessRule.DomainRegex = defaultRule.DomainRegex
|
||||
var plainRuleSet option.PlainRuleSetCompat
|
||||
plainRuleSet.Version = C.RuleSetVersion1
|
||||
plainRuleSet.Options.Rules = []option.HeadlessRule{
|
||||
{
|
||||
Type: C.RuleTypeDefault,
|
||||
DefaultOptions: headlessRule,
|
||||
},
|
||||
}
|
||||
|
||||
outputFile, err := os.Create(outputPath)
|
||||
err = srs.Write(outputFile, plainRuleSet.Upgrade())
|
||||
if err != nil {
|
||||
log.Println("failed to write geosite file:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func newGeosite() *Geosite {
|
||||
return new(Geosite)
|
||||
}
|
||||
@ -14,6 +14,8 @@ require (
|
||||
golang.org/x/mobile v0.0.0-20231108233038-35478a0c49da
|
||||
)
|
||||
|
||||
require github.com/oschwald/maxminddb-golang v1.12.0
|
||||
|
||||
require (
|
||||
berty.tech/go-libtor v1.0.385 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
@ -48,7 +50,6 @@ require (
|
||||
github.com/mholt/acmez v1.2.0 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.9.7 // indirect
|
||||
github.com/ooni/go-libtor v1.1.8 // indirect
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
|
||||
@ -6,8 +6,6 @@ github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sx
|
||||
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc=
|
||||
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
|
||||
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
|
||||
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
|
||||
Loading…
Reference in New Issue
Block a user