mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-26 16:57:08 +00:00
chore: update geo unneeded reload whole config
This commit is contained in:
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
type AttributeList struct {
|
||||
matcher []AttributeMatcher
|
||||
matcher []BooleanMatcher
|
||||
}
|
||||
|
||||
func (al *AttributeList) Match(domain *router.Domain) bool {
|
||||
@@ -23,6 +23,14 @@ func (al *AttributeList) IsEmpty() bool {
|
||||
return len(al.matcher) == 0
|
||||
}
|
||||
|
||||
func (al *AttributeList) String() string {
|
||||
matcher := make([]string, len(al.matcher))
|
||||
for i, match := range al.matcher {
|
||||
matcher[i] = string(match)
|
||||
}
|
||||
return strings.Join(matcher, ",")
|
||||
}
|
||||
|
||||
func parseAttrs(attrs []string) *AttributeList {
|
||||
al := new(AttributeList)
|
||||
for _, attr := range attrs {
|
||||
|
||||
@@ -33,12 +33,13 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
|
||||
|
||||
type DomainMatcher interface {
|
||||
ApplyDomain(string) bool
|
||||
Count() int
|
||||
}
|
||||
|
||||
type succinctDomainMatcher struct {
|
||||
set *trie.DomainSet
|
||||
otherMatchers []strmatcher.Matcher
|
||||
not bool
|
||||
count int
|
||||
}
|
||||
|
||||
func (m *succinctDomainMatcher) ApplyDomain(domain string) bool {
|
||||
@@ -51,16 +52,17 @@ func (m *succinctDomainMatcher) ApplyDomain(domain string) bool {
|
||||
}
|
||||
}
|
||||
}
|
||||
if m.not {
|
||||
isMatched = !isMatched
|
||||
}
|
||||
return isMatched
|
||||
}
|
||||
|
||||
func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
||||
func (m *succinctDomainMatcher) Count() int {
|
||||
return m.count
|
||||
}
|
||||
|
||||
func NewSuccinctMatcherGroup(domains []*Domain) (DomainMatcher, error) {
|
||||
t := trie.New[struct{}]()
|
||||
m := &succinctDomainMatcher{
|
||||
not: not,
|
||||
count: len(domains),
|
||||
}
|
||||
for _, d := range domains {
|
||||
switch d.Type {
|
||||
@@ -90,10 +92,10 @@ func NewSuccinctMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error)
|
||||
|
||||
type v2rayDomainMatcher struct {
|
||||
matchers strmatcher.IndexMatcher
|
||||
not bool
|
||||
count int
|
||||
}
|
||||
|
||||
func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
||||
func NewMphMatcherGroup(domains []*Domain) (DomainMatcher, error) {
|
||||
g := strmatcher.NewMphMatcherGroup()
|
||||
for _, d := range domains {
|
||||
matcherType, f := matcherTypeMap[d.Type]
|
||||
@@ -108,119 +110,80 @@ func NewMphMatcherGroup(domains []*Domain, not bool) (DomainMatcher, error) {
|
||||
g.Build()
|
||||
return &v2rayDomainMatcher{
|
||||
matchers: g,
|
||||
not: not,
|
||||
count: len(domains),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *v2rayDomainMatcher) ApplyDomain(domain string) bool {
|
||||
isMatched := len(m.matchers.Match(strings.ToLower(domain))) > 0
|
||||
if m.not {
|
||||
isMatched = !isMatched
|
||||
}
|
||||
return isMatched
|
||||
return len(m.matchers.Match(strings.ToLower(domain))) > 0
|
||||
}
|
||||
|
||||
type GeoIPMatcher struct {
|
||||
countryCode string
|
||||
reverseMatch bool
|
||||
cidrSet *cidr.IpCidrSet
|
||||
func (m *v2rayDomainMatcher) Count() int {
|
||||
return m.count
|
||||
}
|
||||
|
||||
func (m *GeoIPMatcher) Init(cidrs []*CIDR) error {
|
||||
for _, cidr := range cidrs {
|
||||
addr, ok := netip.AddrFromSlice(cidr.Ip)
|
||||
if !ok {
|
||||
return fmt.Errorf("error when loading GeoIP: invalid IP: %s", cidr.Ip)
|
||||
}
|
||||
err := m.cidrSet.AddIpCidr(netip.PrefixFrom(addr, int(cidr.Prefix)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error when loading GeoIP: %w", err)
|
||||
}
|
||||
}
|
||||
return m.cidrSet.Merge()
|
||||
type notDomainMatcher struct {
|
||||
DomainMatcher
|
||||
}
|
||||
|
||||
func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
|
||||
m.reverseMatch = isReverseMatch
|
||||
func (m notDomainMatcher) ApplyDomain(domain string) bool {
|
||||
return !m.DomainMatcher.ApplyDomain(domain)
|
||||
}
|
||||
|
||||
func NewNotDomainMatcherGroup(matcher DomainMatcher) DomainMatcher {
|
||||
return notDomainMatcher{matcher}
|
||||
}
|
||||
|
||||
type IPMatcher interface {
|
||||
Match(ip netip.Addr) bool
|
||||
Count() int
|
||||
}
|
||||
|
||||
type geoIPMatcher struct {
|
||||
cidrSet *cidr.IpCidrSet
|
||||
count int
|
||||
}
|
||||
|
||||
// Match returns true if the given ip is included by the GeoIP.
|
||||
func (m *GeoIPMatcher) Match(ip netip.Addr) bool {
|
||||
match := m.cidrSet.IsContain(ip)
|
||||
if m.reverseMatch {
|
||||
return !match
|
||||
func (m *geoIPMatcher) Match(ip netip.Addr) bool {
|
||||
return m.cidrSet.IsContain(ip)
|
||||
}
|
||||
|
||||
func (m *geoIPMatcher) Count() int {
|
||||
return m.count
|
||||
}
|
||||
|
||||
func NewGeoIPMatcher(cidrList []*CIDR) (IPMatcher, error) {
|
||||
m := &geoIPMatcher{
|
||||
cidrSet: cidr.NewIpCidrSet(),
|
||||
count: len(cidrList),
|
||||
}
|
||||
return match
|
||||
}
|
||||
|
||||
// GeoIPMatcherContainer is a container for GeoIPMatchers. It keeps unique copies of GeoIPMatcher by country code.
|
||||
type GeoIPMatcherContainer struct {
|
||||
matchers []*GeoIPMatcher
|
||||
}
|
||||
|
||||
// Add adds a new GeoIP set into the container.
|
||||
// If the country code of GeoIP is not empty, GeoIPMatcherContainer will try to find an existing one, instead of adding a new one.
|
||||
func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
|
||||
if len(geoip.CountryCode) > 0 {
|
||||
for _, m := range c.matchers {
|
||||
if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch {
|
||||
return m, nil
|
||||
}
|
||||
for _, cidr := range cidrList {
|
||||
addr, ok := netip.AddrFromSlice(cidr.Ip)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error when loading GeoIP: invalid IP: %s", cidr.Ip)
|
||||
}
|
||||
err := m.cidrSet.AddIpCidr(netip.PrefixFrom(addr, int(cidr.Prefix)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error when loading GeoIP: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
m := &GeoIPMatcher{
|
||||
countryCode: geoip.CountryCode,
|
||||
reverseMatch: geoip.ReverseMatch,
|
||||
cidrSet: cidr.NewIpCidrSet(),
|
||||
}
|
||||
if err := m.Init(geoip.Cidr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(geoip.CountryCode) > 0 {
|
||||
c.matchers = append(c.matchers, m)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
var globalGeoIPContainer GeoIPMatcherContainer
|
||||
|
||||
type MultiGeoIPMatcher struct {
|
||||
matchers []*GeoIPMatcher
|
||||
}
|
||||
|
||||
func NewGeoIPMatcher(geoip *GeoIP) (*GeoIPMatcher, error) {
|
||||
matcher, err := globalGeoIPContainer.Add(geoip)
|
||||
err := m.cidrSet.Merge()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return matcher, nil
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *MultiGeoIPMatcher) ApplyIp(ip netip.Addr) bool {
|
||||
for _, matcher := range m.matchers {
|
||||
if matcher.Match(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
type notIPMatcher struct {
|
||||
IPMatcher
|
||||
}
|
||||
|
||||
func NewMultiGeoIPMatcher(geoips []*GeoIP) (*MultiGeoIPMatcher, error) {
|
||||
var matchers []*GeoIPMatcher
|
||||
for _, geoip := range geoips {
|
||||
matcher, err := globalGeoIPContainer.Add(geoip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchers = append(matchers, matcher)
|
||||
}
|
||||
|
||||
matcher := &MultiGeoIPMatcher{
|
||||
matchers: matchers,
|
||||
}
|
||||
|
||||
return matcher, nil
|
||||
func (m notIPMatcher) Match(ip netip.Addr) bool {
|
||||
return !m.IPMatcher.Match(ip)
|
||||
}
|
||||
|
||||
func NewNotIpMatcherGroup(matcher IPMatcher) IPMatcher {
|
||||
return notIPMatcher{matcher}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sync/singleflight"
|
||||
|
||||
"github.com/metacubex/mihomo/common/singleflight"
|
||||
"github.com/metacubex/mihomo/component/geodata/router"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
@@ -71,21 +70,22 @@ func SetSiteMatcher(newMatcher string) {
|
||||
func Verify(name string) error {
|
||||
switch name {
|
||||
case C.GeositeName:
|
||||
_, _, err := LoadGeoSiteMatcher("CN")
|
||||
_, err := LoadGeoSiteMatcher("CN")
|
||||
return err
|
||||
case C.GeoipName:
|
||||
_, _, err := LoadGeoIPMatcher("CN")
|
||||
_, err := LoadGeoIPMatcher("CN")
|
||||
return err
|
||||
default:
|
||||
return fmt.Errorf("not support name")
|
||||
}
|
||||
}
|
||||
|
||||
var loadGeoSiteMatcherSF = singleflight.Group{}
|
||||
var loadGeoSiteMatcherListSF = singleflight.Group[[]*router.Domain]{StoreResult: true}
|
||||
var loadGeoSiteMatcherSF = singleflight.Group[router.DomainMatcher]{StoreResult: true}
|
||||
|
||||
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) {
|
||||
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) {
|
||||
if countryCode == "" {
|
||||
return nil, 0, fmt.Errorf("country code could not be empty")
|
||||
return nil, fmt.Errorf("country code could not be empty")
|
||||
}
|
||||
|
||||
not := false
|
||||
@@ -97,73 +97,84 @@ func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) {
|
||||
|
||||
parts := strings.Split(countryCode, "@")
|
||||
if len(parts) == 0 {
|
||||
return nil, 0, errors.New("empty rule")
|
||||
return nil, errors.New("empty rule")
|
||||
}
|
||||
listName := strings.TrimSpace(parts[0])
|
||||
attrVal := parts[1:]
|
||||
attrs := parseAttrs(attrVal)
|
||||
|
||||
if listName == "" {
|
||||
return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode)
|
||||
return nil, fmt.Errorf("empty listname in rule: %s", countryCode)
|
||||
}
|
||||
|
||||
v, err, shared := loadGeoSiteMatcherSF.Do(listName, func() (interface{}, error) {
|
||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||
matcherName := listName
|
||||
if !attrs.IsEmpty() {
|
||||
matcherName += "@" + attrs.String()
|
||||
}
|
||||
matcher, err, shared := loadGeoSiteMatcherSF.Do(matcherName, func() (router.DomainMatcher, error) {
|
||||
log.Infoln("Load GeoSite rule: %s", matcherName)
|
||||
domains, err, shared := loadGeoSiteMatcherListSF.Do(listName, func() ([]*router.Domain, error) {
|
||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return geoLoader.LoadGeoSite(listName)
|
||||
})
|
||||
if err != nil {
|
||||
if !shared {
|
||||
loadGeoSiteMatcherListSF.Forget(listName) // don't store the error result
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return geoLoader.LoadGeoSite(listName)
|
||||
|
||||
if attrs.IsEmpty() {
|
||||
if strings.Contains(countryCode, "@") {
|
||||
log.Warnln("empty attribute list: %s", countryCode)
|
||||
}
|
||||
} else {
|
||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||
hasAttrMatched := false
|
||||
for _, domain := range domains {
|
||||
if attrs.Match(domain) {
|
||||
hasAttrMatched = true
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
if !hasAttrMatched {
|
||||
log.Warnln("attribute match no rule: geosite: %s", countryCode)
|
||||
}
|
||||
domains = filteredDomains
|
||||
}
|
||||
|
||||
/**
|
||||
linear: linear algorithm
|
||||
matcher, err := router.NewDomainMatcher(domains)
|
||||
mph:minimal perfect hash algorithm
|
||||
*/
|
||||
if geoSiteMatcher == "mph" {
|
||||
return router.NewMphMatcherGroup(domains)
|
||||
} else {
|
||||
return router.NewSuccinctMatcherGroup(domains)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
if !shared {
|
||||
loadGeoSiteMatcherSF.Forget(listName) // don't store the error result
|
||||
loadGeoSiteMatcherSF.Forget(matcherName) // don't store the error result
|
||||
}
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
domains := v.([]*router.Domain)
|
||||
|
||||
attrs := parseAttrs(attrVal)
|
||||
if attrs.IsEmpty() {
|
||||
if strings.Contains(countryCode, "@") {
|
||||
log.Warnln("empty attribute list: %s", countryCode)
|
||||
}
|
||||
} else {
|
||||
filteredDomains := make([]*router.Domain, 0, len(domains))
|
||||
hasAttrMatched := false
|
||||
for _, domain := range domains {
|
||||
if attrs.Match(domain) {
|
||||
hasAttrMatched = true
|
||||
filteredDomains = append(filteredDomains, domain)
|
||||
}
|
||||
}
|
||||
if !hasAttrMatched {
|
||||
log.Warnln("attribute match no rule: geosite: %s", countryCode)
|
||||
}
|
||||
domains = filteredDomains
|
||||
if not {
|
||||
matcher = router.NewNotDomainMatcherGroup(matcher)
|
||||
}
|
||||
|
||||
/**
|
||||
linear: linear algorithm
|
||||
matcher, err := router.NewDomainMatcher(domains)
|
||||
mph:minimal perfect hash algorithm
|
||||
*/
|
||||
var matcher router.DomainMatcher
|
||||
if geoSiteMatcher == "mph" {
|
||||
matcher, err = router.NewMphMatcherGroup(domains, not)
|
||||
} else {
|
||||
matcher, err = router.NewSuccinctMatcherGroup(domains, not)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return matcher, len(domains), nil
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
var loadGeoIPMatcherSF = singleflight.Group{}
|
||||
var loadGeoIPMatcherSF = singleflight.Group[router.IPMatcher]{StoreResult: true}
|
||||
|
||||
func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
|
||||
func LoadGeoIPMatcher(country string) (router.IPMatcher, error) {
|
||||
if len(country) == 0 {
|
||||
return nil, 0, fmt.Errorf("country code could not be empty")
|
||||
return nil, fmt.Errorf("country code could not be empty")
|
||||
}
|
||||
|
||||
not := false
|
||||
@@ -173,35 +184,33 @@ func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
|
||||
}
|
||||
country = strings.ToLower(country)
|
||||
|
||||
v, err, shared := loadGeoIPMatcherSF.Do(country, func() (interface{}, error) {
|
||||
matcher, err, shared := loadGeoIPMatcherSF.Do(country, func() (router.IPMatcher, error) {
|
||||
log.Infoln("Load GeoIP rule: %s", country)
|
||||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return geoLoader.LoadGeoIP(country)
|
||||
cidrList, err := geoLoader.LoadGeoIP(country)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return router.NewGeoIPMatcher(cidrList)
|
||||
})
|
||||
if err != nil {
|
||||
if !shared {
|
||||
loadGeoIPMatcherSF.Forget(country) // don't store the error result
|
||||
log.Warnln("Load GeoIP rule: %s", country)
|
||||
}
|
||||
return nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
records := v.([]*router.CIDR)
|
||||
|
||||
geoIP := &router.GeoIP{
|
||||
CountryCode: country,
|
||||
Cidr: records,
|
||||
ReverseMatch: not,
|
||||
if not {
|
||||
matcher = router.NewNotIpMatcherGroup(matcher)
|
||||
}
|
||||
|
||||
matcher, err := router.NewGeoIPMatcher(geoIP)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return matcher, len(records), nil
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
func ClearCache() {
|
||||
loadGeoSiteMatcherSF = singleflight.Group{}
|
||||
loadGeoIPMatcherSF = singleflight.Group{}
|
||||
loadGeoSiteMatcherListSF.Reset()
|
||||
loadGeoSiteMatcherSF.Reset()
|
||||
loadGeoIPMatcherSF.Reset()
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ func getUpdateTime() (err error, time time.Time) {
|
||||
return nil, fileInfo.ModTime()
|
||||
}
|
||||
|
||||
func RegisterGeoUpdater(onSuccess func()) {
|
||||
func RegisterGeoUpdater() {
|
||||
if C.GeoUpdateInterval <= 0 {
|
||||
log.Errorln("[GEO] Invalid update interval: %d", C.GeoUpdateInterval)
|
||||
return
|
||||
@@ -159,8 +159,6 @@ func RegisterGeoUpdater(onSuccess func()) {
|
||||
if err := UpdateGeoDatabases(); err != nil {
|
||||
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
||||
return
|
||||
} else {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,8 +166,6 @@ func RegisterGeoUpdater(onSuccess func()) {
|
||||
log.Infoln("[GEO] updating database every %d hours", C.GeoUpdateInterval)
|
||||
if err := UpdateGeoDatabases(); err != nil {
|
||||
log.Errorln("[GEO] Failed to update GEO database: %s", err.Error())
|
||||
} else {
|
||||
onSuccess()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
Reference in New Issue
Block a user