123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- 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{}
- }
|