mirror of
https://gitclone.com/github.com/MetaCubeX/Clash.Meta
synced 2024-11-15 13:41:23 +08:00
208 lines
4.3 KiB
Go
208 lines
4.3 KiB
Go
package geodata
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"golang.org/x/sync/singleflight"
|
||
|
||
"github.com/metacubex/mihomo/component/geodata/router"
|
||
C "github.com/metacubex/mihomo/constant"
|
||
"github.com/metacubex/mihomo/log"
|
||
)
|
||
|
||
var (
|
||
geoMode bool
|
||
AutoUpdate bool
|
||
UpdateInterval int
|
||
geoLoaderName = "memconservative"
|
||
geoSiteMatcher = "succinct"
|
||
)
|
||
|
||
// geoLoaderName = "standard"
|
||
|
||
func GeodataMode() bool {
|
||
return geoMode
|
||
}
|
||
|
||
func GeoAutoUpdate() bool {
|
||
return AutoUpdate
|
||
}
|
||
|
||
func GeoUpdateInterval() int {
|
||
return UpdateInterval
|
||
}
|
||
|
||
func LoaderName() string {
|
||
return geoLoaderName
|
||
}
|
||
|
||
func SiteMatcherName() string {
|
||
return geoSiteMatcher
|
||
}
|
||
|
||
func SetGeodataMode(newGeodataMode bool) {
|
||
geoMode = newGeodataMode
|
||
}
|
||
func SetGeoAutoUpdate(newAutoUpdate bool) {
|
||
AutoUpdate = newAutoUpdate
|
||
}
|
||
func SetGeoUpdateInterval(newGeoUpdateInterval int) {
|
||
UpdateInterval = newGeoUpdateInterval
|
||
}
|
||
|
||
func SetLoader(newLoader string) {
|
||
if newLoader == "memc" {
|
||
newLoader = "memconservative"
|
||
}
|
||
geoLoaderName = newLoader
|
||
}
|
||
|
||
func SetSiteMatcher(newMatcher string) {
|
||
switch newMatcher {
|
||
case "mph", "hybrid":
|
||
geoSiteMatcher = "mph"
|
||
default:
|
||
geoSiteMatcher = "succinct"
|
||
}
|
||
}
|
||
|
||
func Verify(name string) error {
|
||
switch name {
|
||
case C.GeositeName:
|
||
_, _, err := LoadGeoSiteMatcher("CN")
|
||
return err
|
||
case C.GeoipName:
|
||
_, _, err := LoadGeoIPMatcher("CN")
|
||
return err
|
||
default:
|
||
return fmt.Errorf("not support name")
|
||
}
|
||
}
|
||
|
||
var loadGeoSiteMatcherSF = singleflight.Group{}
|
||
|
||
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) {
|
||
if countryCode == "" {
|
||
return nil, 0, fmt.Errorf("country code could not be empty")
|
||
}
|
||
|
||
not := false
|
||
if countryCode[0] == '!' {
|
||
not = true
|
||
countryCode = countryCode[1:]
|
||
}
|
||
countryCode = strings.ToLower(countryCode)
|
||
|
||
parts := strings.Split(countryCode, "@")
|
||
if len(parts) == 0 {
|
||
return nil, 0, errors.New("empty rule")
|
||
}
|
||
listName := strings.TrimSpace(parts[0])
|
||
attrVal := parts[1:]
|
||
|
||
if listName == "" {
|
||
return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode)
|
||
}
|
||
|
||
v, err, shared := loadGeoSiteMatcherSF.Do(listName, func() (interface{}, error) {
|
||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return geoLoader.LoadGeoSite(listName)
|
||
})
|
||
if err != nil {
|
||
if !shared {
|
||
loadGeoSiteMatcherSF.Forget(listName) // don't store the error result
|
||
}
|
||
return nil, 0, 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
|
||
}
|
||
|
||
/**
|
||
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
|
||
}
|
||
|
||
var loadGeoIPMatcherSF = singleflight.Group{}
|
||
|
||
func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
|
||
if len(country) == 0 {
|
||
return nil, 0, fmt.Errorf("country code could not be empty")
|
||
}
|
||
|
||
not := false
|
||
if country[0] == '!' {
|
||
not = true
|
||
country = country[1:]
|
||
}
|
||
country = strings.ToLower(country)
|
||
|
||
v, err, shared := loadGeoIPMatcherSF.Do(country, func() (interface{}, error) {
|
||
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return geoLoader.LoadGeoIP(country)
|
||
})
|
||
if err != nil {
|
||
if !shared {
|
||
loadGeoIPMatcherSF.Forget(country) // don't store the error result
|
||
}
|
||
return nil, 0, err
|
||
}
|
||
records := v.([]*router.CIDR)
|
||
|
||
geoIP := &router.GeoIP{
|
||
CountryCode: country,
|
||
Cidr: records,
|
||
ReverseMatch: not,
|
||
}
|
||
|
||
matcher, err := router.NewGeoIPMatcher(geoIP)
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
return matcher, len(records), nil
|
||
}
|
||
|
||
func ClearCache() {
|
||
loadGeoSiteMatcherSF = singleflight.Group{}
|
||
loadGeoIPMatcherSF = singleflight.Group{}
|
||
}
|