github.com/kelleygo/clashcore@v1.0.2/component/geodata/utils.go (about) 1 package geodata 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "golang.org/x/sync/singleflight" 9 10 "github.com/kelleygo/clashcore/component/geodata/router" 11 C "github.com/kelleygo/clashcore/constant" 12 "github.com/kelleygo/clashcore/log" 13 ) 14 15 var ( 16 geoMode bool 17 AutoUpdate bool 18 UpdateInterval int 19 geoLoaderName = "memconservative" 20 geoSiteMatcher = "succinct" 21 ) 22 23 // geoLoaderName = "standard" 24 25 func GeodataMode() bool { 26 return geoMode 27 } 28 29 func GeoAutoUpdate() bool { 30 return AutoUpdate 31 } 32 33 func GeoUpdateInterval() int { 34 return UpdateInterval 35 } 36 37 func LoaderName() string { 38 return geoLoaderName 39 } 40 41 func SiteMatcherName() string { 42 return geoSiteMatcher 43 } 44 45 func SetGeodataMode(newGeodataMode bool) { 46 geoMode = newGeodataMode 47 } 48 func SetGeoAutoUpdate(newAutoUpdate bool) { 49 AutoUpdate = newAutoUpdate 50 } 51 func SetGeoUpdateInterval(newGeoUpdateInterval int) { 52 UpdateInterval = newGeoUpdateInterval 53 } 54 55 func SetLoader(newLoader string) { 56 if newLoader == "memc" { 57 newLoader = "memconservative" 58 } 59 geoLoaderName = newLoader 60 } 61 62 func SetSiteMatcher(newMatcher string) { 63 switch newMatcher { 64 case "mph", "hybrid": 65 geoSiteMatcher = "mph" 66 default: 67 geoSiteMatcher = "succinct" 68 } 69 } 70 71 func Verify(name string) error { 72 switch name { 73 case C.GeositeName: 74 _, _, err := LoadGeoSiteMatcher("CN") 75 return err 76 case C.GeoipName: 77 _, _, err := LoadGeoIPMatcher("CN") 78 return err 79 default: 80 return fmt.Errorf("not support name") 81 } 82 } 83 84 var loadGeoSiteMatcherSF = singleflight.Group{} 85 86 func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, int, error) { 87 if countryCode == "" { 88 return nil, 0, fmt.Errorf("country code could not be empty") 89 } 90 91 not := false 92 if countryCode[0] == '!' { 93 not = true 94 countryCode = countryCode[1:] 95 } 96 countryCode = strings.ToLower(countryCode) 97 98 parts := strings.Split(countryCode, "@") 99 if len(parts) == 0 { 100 return nil, 0, errors.New("empty rule") 101 } 102 listName := strings.TrimSpace(parts[0]) 103 attrVal := parts[1:] 104 105 if listName == "" { 106 return nil, 0, fmt.Errorf("empty listname in rule: %s", countryCode) 107 } 108 109 v, err, shared := loadGeoSiteMatcherSF.Do(listName, func() (interface{}, error) { 110 geoLoader, err := GetGeoDataLoader(geoLoaderName) 111 if err != nil { 112 return nil, err 113 } 114 return geoLoader.LoadGeoSite(listName) 115 }) 116 if err != nil { 117 if !shared { 118 loadGeoSiteMatcherSF.Forget(listName) // don't store the error result 119 } 120 return nil, 0, err 121 } 122 domains := v.([]*router.Domain) 123 124 attrs := parseAttrs(attrVal) 125 if attrs.IsEmpty() { 126 if strings.Contains(countryCode, "@") { 127 log.Warnln("empty attribute list: %s", countryCode) 128 } 129 } else { 130 filteredDomains := make([]*router.Domain, 0, len(domains)) 131 hasAttrMatched := false 132 for _, domain := range domains { 133 if attrs.Match(domain) { 134 hasAttrMatched = true 135 filteredDomains = append(filteredDomains, domain) 136 } 137 } 138 if !hasAttrMatched { 139 log.Warnln("attribute match no rule: geosite: %s", countryCode) 140 } 141 domains = filteredDomains 142 } 143 144 /** 145 linear: linear algorithm 146 matcher, err := router.NewDomainMatcher(domains) 147 mph:minimal perfect hash algorithm 148 */ 149 var matcher router.DomainMatcher 150 if geoSiteMatcher == "mph" { 151 matcher, err = router.NewMphMatcherGroup(domains, not) 152 } else { 153 matcher, err = router.NewSuccinctMatcherGroup(domains, not) 154 } 155 if err != nil { 156 return nil, 0, err 157 } 158 159 return matcher, len(domains), nil 160 } 161 162 var loadGeoIPMatcherSF = singleflight.Group{} 163 164 func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) { 165 if len(country) == 0 { 166 return nil, 0, fmt.Errorf("country code could not be empty") 167 } 168 169 not := false 170 if country[0] == '!' { 171 not = true 172 country = country[1:] 173 } 174 country = strings.ToLower(country) 175 176 v, err, shared := loadGeoIPMatcherSF.Do(country, func() (interface{}, error) { 177 geoLoader, err := GetGeoDataLoader(geoLoaderName) 178 if err != nil { 179 return nil, err 180 } 181 return geoLoader.LoadGeoIP(country) 182 }) 183 if err != nil { 184 if !shared { 185 loadGeoIPMatcherSF.Forget(country) // don't store the error result 186 } 187 return nil, 0, err 188 } 189 records := v.([]*router.CIDR) 190 191 geoIP := &router.GeoIP{ 192 CountryCode: country, 193 Cidr: records, 194 ReverseMatch: not, 195 } 196 197 matcher, err := router.NewGeoIPMatcher(geoIP) 198 if err != nil { 199 return nil, 0, err 200 } 201 return matcher, len(records), nil 202 } 203 204 func ClearCache() { 205 loadGeoSiteMatcherSF = singleflight.Group{} 206 loadGeoIPMatcherSF = singleflight.Group{} 207 }