feat: generate ruleset

This commit is contained in:
purofle 2024-02-17 17:02:17 +08:00
parent 0c3c42aa74
commit be1edbd5e4
10 changed files with 253 additions and 123 deletions

View File

@ -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() {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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
View 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
View 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)
}

View File

@ -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

View File

@ -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=