mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-12-19 16:30:07 +08:00
feat: Add succinct matcher support for GeoSite
and use it by default
This commit is contained in:
parent
5b23b979df
commit
2bba8aa14a
@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/geodata/strmatcher"
|
"github.com/metacubex/mihomo/component/geodata/strmatcher"
|
||||||
|
"github.com/metacubex/mihomo/component/trie"
|
||||||
)
|
)
|
||||||
|
|
||||||
var matcherTypeMap = map[Domain_Type]strmatcher.Type{
|
var matcherTypeMap = map[Domain_Type]strmatcher.Type{
|
||||||
@ -31,12 +32,69 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
|
|||||||
return matcher, nil
|
return matcher, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DomainMatcher struct {
|
type DomainMatcher interface {
|
||||||
|
ApplyDomain(string) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type succinctDomainMatcher struct {
|
||||||
|
set *trie.DomainSet
|
||||||
|
otherMatchers []strmatcher.Matcher
|
||||||
|
not bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m succinctDomainMatcher) ApplyDomain(domain string) bool {
|
||||||
|
isMatched := m.set.Has(domain)
|
||||||
|
if !isMatched {
|
||||||
|
for _, matcher := range m.otherMatchers {
|
||||||
|
isMatched = matcher.Match(domain)
|
||||||
|
if isMatched {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.not {
|
||||||
|
isMatched = !isMatched
|
||||||
|
}
|
||||||
|
return isMatched
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
||||||
|
t := trie.New[struct{}]()
|
||||||
|
m := &succinctDomainMatcher{
|
||||||
|
not: not,
|
||||||
|
}
|
||||||
|
for _, d := range domains {
|
||||||
|
switch d.Type {
|
||||||
|
case Domain_Plain, Domain_Regex:
|
||||||
|
matcher, err := matcherTypeMap[d.Type].New(d.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.otherMatchers = append(m.otherMatchers, matcher)
|
||||||
|
|
||||||
|
case Domain_Domain:
|
||||||
|
err := t.Insert("+."+d.Value, struct{}{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
case Domain_Full:
|
||||||
|
err := t.Insert(d.Value, struct{}{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.set = t.NewDomainSet()
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type v2rayDomainMatcher struct {
|
||||||
matchers strmatcher.IndexMatcher
|
matchers strmatcher.IndexMatcher
|
||||||
not bool
|
not bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMphMatcherGroup(domains []*Domain, not bool) (*DomainMatcher, error) {
|
func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
||||||
g := strmatcher.NewMphMatcherGroup()
|
g := strmatcher.NewMphMatcherGroup()
|
||||||
for _, d := range domains {
|
for _, d := range domains {
|
||||||
matcherType, f := matcherTypeMap[d.Type]
|
matcherType, f := matcherTypeMap[d.Type]
|
||||||
@ -49,14 +107,13 @@ func NewMphMatcherGroup(domains []*Domain, not bool) (*DomainMatcher, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.Build()
|
g.Build()
|
||||||
return &DomainMatcher{
|
return &v2rayDomainMatcher{
|
||||||
matchers: g,
|
matchers: g,
|
||||||
not: not,
|
not: not,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDomainMatcher new domain matcher.
|
func NewDomainMatcher(domains []*Domain, not bool) (DomainMatcher, error) {
|
||||||
func NewDomainMatcher(domains []*Domain, not bool) (*DomainMatcher, error) {
|
|
||||||
g := new(strmatcher.MatcherGroup)
|
g := new(strmatcher.MatcherGroup)
|
||||||
for _, d := range domains {
|
for _, d := range domains {
|
||||||
m, err := domainToMatcher(d)
|
m, err := domainToMatcher(d)
|
||||||
@ -66,13 +123,13 @@ func NewDomainMatcher(domains []*Domain, not bool) (*DomainMatcher, error) {
|
|||||||
g.Add(m)
|
g.Add(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &DomainMatcher{
|
return &v2rayDomainMatcher{
|
||||||
matchers: g,
|
matchers: g,
|
||||||
not: not,
|
not: not,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *DomainMatcher) ApplyDomain(domain string) bool {
|
func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool {
|
||||||
isMatched := len(m.matchers.Match(strings.ToLower(domain))) > 0
|
isMatched := len(m.matchers.Match(strings.ToLower(domain))) > 0
|
||||||
if m.not {
|
if m.not {
|
||||||
isMatched = !isMatched
|
isMatched = !isMatched
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var geoLoaderName = "memconservative"
|
var geoLoaderName = "memconservative"
|
||||||
|
var geoSiteMatcher = "succinct"
|
||||||
|
|
||||||
// geoLoaderName = "standard"
|
// geoLoaderName = "standard"
|
||||||
|
|
||||||
@ -19,6 +20,10 @@ func LoaderName() string {
|
|||||||
return geoLoaderName
|
return geoLoaderName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SiteMatcherName() string {
|
||||||
|
return geoSiteMatcher
|
||||||
|
}
|
||||||
|
|
||||||
func SetLoader(newLoader string) {
|
func SetLoader(newLoader string) {
|
||||||
if newLoader == "memc" {
|
if newLoader == "memc" {
|
||||||
newLoader = "memconservative"
|
newLoader = "memconservative"
|
||||||
@ -26,6 +31,16 @@ func SetLoader(newLoader string) {
|
|||||||
geoLoaderName = newLoader
|
geoLoaderName = newLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetSiteMatcher(newMatcher string) {
|
||||||
|
switch newMatcher {
|
||||||
|
case "hybrid":
|
||||||
|
newMatcher = "mph"
|
||||||
|
default:
|
||||||
|
newMatcher = "succinct"
|
||||||
|
}
|
||||||
|
geoSiteMatcher = newMatcher
|
||||||
|
}
|
||||||
|
|
||||||
func Verify(name string) error {
|
func Verify(name string) error {
|
||||||
switch name {
|
switch name {
|
||||||
case C.GeositeName:
|
case C.GeositeName:
|
||||||
@ -41,8 +56,8 @@ func Verify(name string) error {
|
|||||||
|
|
||||||
var loadGeoSiteMatcherSF = singleflight.Group{}
|
var loadGeoSiteMatcherSF = singleflight.Group{}
|
||||||
|
|
||||||
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
|
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) {
|
||||||
if len(countryCode) == 0 {
|
if countryCode == "" {
|
||||||
return nil, 0, fmt.Errorf("country code could not be empty")
|
return nil, 0, fmt.Errorf("country code could not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +75,7 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
|
|||||||
listName := strings.TrimSpace(parts[0])
|
listName := strings.TrimSpace(parts[0])
|
||||||
attrVal := parts[1:]
|
attrVal := parts[1:]
|
||||||
|
|
||||||
if len(listName) == 0 {
|
if listName == "" {
|
||||||
return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode)
|
return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +119,12 @@ func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error)
|
|||||||
matcher, err := router.NewDomainMatcher(domains)
|
matcher, err := router.NewDomainMatcher(domains)
|
||||||
mph:minimal perfect hash algorithm
|
mph:minimal perfect hash algorithm
|
||||||
*/
|
*/
|
||||||
matcher, err := router.NewMphMatcherGroup(domains, not)
|
var matcher router.DomainMatcher
|
||||||
|
if geoSiteMatcher == "mph" {
|
||||||
|
matcher, err = router.NewMphMatcherGroup(domains, not)
|
||||||
|
} else {
|
||||||
|
matcher, err = router.NewSuccinctMatcherGroup(domains, not)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,6 +59,7 @@ type General struct {
|
|||||||
GeoUpdateInterval int `json:"geo-update-interval"`
|
GeoUpdateInterval int `json:"geo-update-interval"`
|
||||||
GeodataMode bool `json:"geodata-mode"`
|
GeodataMode bool `json:"geodata-mode"`
|
||||||
GeodataLoader string `json:"geodata-loader"`
|
GeodataLoader string `json:"geodata-loader"`
|
||||||
|
GeositeMatcher string `json:"geosite-matcher"`
|
||||||
TCPConcurrent bool `json:"tcp-concurrent"`
|
TCPConcurrent bool `json:"tcp-concurrent"`
|
||||||
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
FindProcessMode P.FindProcessMode `json:"find-process-mode"`
|
||||||
Sniffing bool `json:"sniffing"`
|
Sniffing bool `json:"sniffing"`
|
||||||
@ -127,11 +128,11 @@ type DNS struct {
|
|||||||
|
|
||||||
// FallbackFilter config
|
// FallbackFilter config
|
||||||
type FallbackFilter struct {
|
type FallbackFilter struct {
|
||||||
GeoIP bool `yaml:"geoip"`
|
GeoIP bool `yaml:"geoip"`
|
||||||
GeoIPCode string `yaml:"geoip-code"`
|
GeoIPCode string `yaml:"geoip-code"`
|
||||||
IPCIDR []netip.Prefix `yaml:"ipcidr"`
|
IPCIDR []netip.Prefix `yaml:"ipcidr"`
|
||||||
Domain []string `yaml:"domain"`
|
Domain []string `yaml:"domain"`
|
||||||
GeoSite []*router.DomainMatcher `yaml:"geosite"`
|
GeoSite []router.DomainMatcher `yaml:"geosite"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile config
|
// Profile config
|
||||||
@ -312,6 +313,7 @@ type RawConfig struct {
|
|||||||
GeoUpdateInterval int `yaml:"geo-update-interval" json:"geo-update-interval"`
|
GeoUpdateInterval int `yaml:"geo-update-interval" json:"geo-update-interval"`
|
||||||
GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"`
|
GeodataMode bool `yaml:"geodata-mode" json:"geodata-mode"`
|
||||||
GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"`
|
GeodataLoader string `yaml:"geodata-loader" json:"geodata-loader"`
|
||||||
|
GeositeMatcher string `yaml:"geosite-matcher" json:"geosite-matcher"`
|
||||||
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"`
|
||||||
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"`
|
||||||
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
GlobalClientFingerprint string `yaml:"global-client-fingerprint"`
|
||||||
@ -537,6 +539,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
config.Listeners = listener
|
config.Listeners = listener
|
||||||
|
|
||||||
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
|
log.Infoln("Geodata Loader mode: %s", geodata.LoaderName())
|
||||||
|
log.Infoln("Geosite Matcher implementation: %s", geodata.SiteMatcherName())
|
||||||
ruleProviders, err := parseRuleProviders(rawCfg)
|
ruleProviders, err := parseRuleProviders(rawCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -605,6 +608,7 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
|
|
||||||
func parseGeneral(cfg *RawConfig) (*General, error) {
|
func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||||
geodata.SetLoader(cfg.GeodataLoader)
|
geodata.SetLoader(cfg.GeodataLoader)
|
||||||
|
geodata.SetSiteMatcher(cfg.GeositeMatcher)
|
||||||
C.GeoAutoUpdate = cfg.GeoAutoUpdate
|
C.GeoAutoUpdate = cfg.GeoAutoUpdate
|
||||||
C.GeoUpdateInterval = cfg.GeoUpdateInterval
|
C.GeoUpdateInterval = cfg.GeoUpdateInterval
|
||||||
C.GeoIpUrl = cfg.GeoXUrl.GeoIp
|
C.GeoIpUrl = cfg.GeoXUrl.GeoIp
|
||||||
@ -1204,8 +1208,8 @@ func parseFallbackIPCIDR(ips []string) ([]netip.Prefix, error) {
|
|||||||
return ipNets, nil
|
return ipNets, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) {
|
func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]router.DomainMatcher, error) {
|
||||||
var sites []*router.DomainMatcher
|
var sites []router.DomainMatcher
|
||||||
if len(countries) > 0 {
|
if len(countries) > 0 {
|
||||||
if err := geodata.InitGeoSite(); err != nil {
|
if err := geodata.InitGeoSite(); err != nil {
|
||||||
return nil, fmt.Errorf("can't initial GeoSite: %s", err)
|
return nil, fmt.Errorf("can't initial GeoSite: %s", err)
|
||||||
@ -1267,7 +1271,7 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
|||||||
EnhancedMode: cfg.EnhancedMode,
|
EnhancedMode: cfg.EnhancedMode,
|
||||||
FallbackFilter: FallbackFilter{
|
FallbackFilter: FallbackFilter{
|
||||||
IPCIDR: []netip.Prefix{},
|
IPCIDR: []netip.Prefix{},
|
||||||
GeoSite: []*router.DomainMatcher{},
|
GeoSite: []router.DomainMatcher{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type RuleGeoSite interface {
|
type RuleGeoSite interface {
|
||||||
GetDomainMatcher() *router.DomainMatcher
|
GetDomainMatcher() router.DomainMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
type RuleGeoIP interface {
|
type RuleGeoIP interface {
|
||||||
|
|||||||
@ -74,7 +74,7 @@ func (df *domainFilter) Match(domain string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type geoSiteFilter struct {
|
type geoSiteFilter struct {
|
||||||
matchers []*router.DomainMatcher
|
matchers []router.DomainMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGeoSite(group string) (fallbackDomainFilter, error) {
|
func NewGeoSite(group string) (fallbackDomainFilter, error) {
|
||||||
@ -87,7 +87,7 @@ func NewGeoSite(group string) (fallbackDomainFilter, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
filter := &geoSiteFilter{
|
filter := &geoSiteFilter{
|
||||||
matchers: []*router.DomainMatcher{matcher},
|
matchers: []router.DomainMatcher{matcher},
|
||||||
}
|
}
|
||||||
return filter, nil
|
return filter, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -400,7 +400,7 @@ type FallbackFilter struct {
|
|||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
IPCIDR []netip.Prefix
|
IPCIDR []netip.Prefix
|
||||||
Domain []string
|
Domain []string
|
||||||
GeoSite []*router.DomainMatcher
|
GeoSite []router.DomainMatcher
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|||||||
@ -36,6 +36,11 @@ geox-url:
|
|||||||
geo-auto-update: false # 是否自动更新 geodata
|
geo-auto-update: false # 是否自动更新 geodata
|
||||||
geo-update-interval: 24 # 更新间隔,单位:小时
|
geo-update-interval: 24 # 更新间隔,单位:小时
|
||||||
|
|
||||||
|
# Matcher implementation used by GeoSite, available implementations:
|
||||||
|
# - succinct (default, same as rule-set)
|
||||||
|
# - mph (from V2Ray, also `hybrid` in Xray)
|
||||||
|
# geosite-matcher: succinct
|
||||||
|
|
||||||
log-level: debug # 日志等级 silent/error/warning/info/debug
|
log-level: debug # 日志等级 silent/error/warning/info/debug
|
||||||
|
|
||||||
ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录
|
ipv6: true # 开启 IPv6 总开关,关闭阻断所有 IPv6 链接和屏蔽 DNS 请求 AAAA 记录
|
||||||
|
|||||||
@ -146,14 +146,15 @@ func GetGeneral() *config.General {
|
|||||||
AllowLan: listener.AllowLan(),
|
AllowLan: listener.AllowLan(),
|
||||||
BindAddress: listener.BindAddress(),
|
BindAddress: listener.BindAddress(),
|
||||||
},
|
},
|
||||||
Controller: config.Controller{},
|
Controller: config.Controller{},
|
||||||
Mode: tunnel.Mode(),
|
Mode: tunnel.Mode(),
|
||||||
LogLevel: log.Level(),
|
LogLevel: log.Level(),
|
||||||
IPv6: !resolver.DisableIPv6,
|
IPv6: !resolver.DisableIPv6,
|
||||||
GeodataLoader: G.LoaderName(),
|
GeodataLoader: G.LoaderName(),
|
||||||
Interface: dialer.DefaultInterface.Load(),
|
GeositeMatcher: G.SiteMatcherName(),
|
||||||
Sniffing: tunnel.IsSniffing(),
|
Interface: dialer.DefaultInterface.Load(),
|
||||||
TCPConcurrent: dialer.GetTcpConcurrent(),
|
Sniffing: tunnel.IsSniffing(),
|
||||||
|
TCPConcurrent: dialer.GetTcpConcurrent(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return general
|
return general
|
||||||
@ -401,8 +402,8 @@ func updateGeneral(general *config.General) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iface.FlushCache()
|
iface.FlushCache()
|
||||||
geodataLoader := general.GeodataLoader
|
G.SetLoader(general.GeodataLoader)
|
||||||
G.SetLoader(geodataLoader)
|
G.SetSiteMatcher(general.GeositeMatcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUsers(users []auth.AuthUser) {
|
func updateUsers(users []auth.AuthUser) {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ type GEOSITE struct {
|
|||||||
*Base
|
*Base
|
||||||
country string
|
country string
|
||||||
adapter string
|
adapter string
|
||||||
matcher *router.DomainMatcher
|
matcher router.DomainMatcher
|
||||||
recodeSize int
|
recodeSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ func (gs *GEOSITE) Payload() string {
|
|||||||
return gs.country
|
return gs.country
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gs *GEOSITE) GetDomainMatcher() *router.DomainMatcher {
|
func (gs *GEOSITE) GetDomainMatcher() router.DomainMatcher {
|
||||||
return gs.matcher
|
return gs.matcher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user