clash/component/geodata/utils.go

161 lines
3.5 KiB
Go
Raw Normal View History

2021-11-17 08:03:47 +00:00
package geodata
import (
2023-03-23 10:58:24 +00:00
"errors"
"fmt"
"golang.org/x/sync/singleflight"
"strings"
2021-11-17 08:03:47 +00:00
"github.com/Dreamacro/clash/component/geodata/router"
2022-03-15 14:25:33 +00:00
C "github.com/Dreamacro/clash/constant"
2023-03-23 10:58:24 +00:00
"github.com/Dreamacro/clash/log"
2021-11-17 08:03:47 +00:00
)
2022-02-04 16:51:06 +00:00
var geoLoaderName = "memconservative"
// geoLoaderName = "standard"
func LoaderName() string {
return geoLoaderName
}
func SetLoader(newLoader string) {
2022-04-11 05:23:59 +00:00
if newLoader == "memc" {
newLoader = "memconservative"
}
2022-02-04 16:51:06 +00:00
geoLoaderName = newLoader
}
func Verify(name string) error {
2022-03-15 14:25:33 +00:00
switch name {
case C.GeositeName:
_, _, err := LoadGeoSiteMatcher("CN")
return err
2022-03-15 14:25:33 +00:00
case C.GeoipName:
_, _, err := LoadGeoIPMatcher("CN")
return err
2022-03-15 14:25:33 +00:00
default:
return fmt.Errorf("not support name")
2022-03-15 14:25:33 +00:00
}
}
var loadGeoSiteMatcherSF = singleflight.Group{}
2021-11-17 08:03:47 +00:00
func LoadGeoSiteMatcher(countryCode string) (*router.DomainMatcher, int, error) {
if len(countryCode) == 0 {
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)
2023-03-23 10:58:24 +00:00
parts := strings.Split(countryCode, "@")
if len(parts) == 0 {
return nil, 0, errors.New("empty rule")
}
listName := strings.TrimSpace(parts[0])
attrVal := parts[1:]
if len(listName) == 0 {
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
}
2023-03-23 10:58:24 +00:00
return geoLoader.LoadGeoSite(listName)
})
2021-11-17 08:03:47 +00:00
if err != nil {
2023-03-23 10:58:24 +00:00
if !shared {
loadGeoSiteMatcherSF.Forget(listName) // don't store the error result
}
2021-11-17 08:03:47 +00:00
return nil, 0, err
}
domains := v.([]*router.Domain)
2021-11-17 08:03:47 +00:00
2023-03-23 10:58:24 +00:00
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
}
2021-11-17 08:03:47 +00:00
/**
linear: linear algorithm
matcher, err := router.NewDomainMatcher(domains)
mphminimal perfect hash algorithm
*/
matcher, err := router.NewMphMatcherGroup(domains, not)
2021-11-17 08:03:47 +00:00
if err != nil {
return nil, 0, err
}
return matcher, len(domains), nil
}
var loadGeoIPMatcherSF = singleflight.Group{}
func LoadGeoIPMatcher(country string) (*router.GeoIPMatcher, int, error) {
2022-05-15 15:07:06 +00:00
if len(country) == 0 {
return nil, 0, fmt.Errorf("country code could not be empty")
}
2022-05-15 15:07:06 +00:00
not := false
if country[0] == '!' {
not = true
country = country[1:]
}
country = strings.ToLower(country)
2023-03-23 10:58:24 +00:00
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 {
2023-03-23 10:58:24 +00:00
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,
2022-05-15 15:07:06 +00:00
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{}
}