2021-11-17 16:03:47 +08:00
|
|
|
|
package geodata
|
|
|
|
|
|
|
|
|
|
import (
|
2023-03-23 18:58:24 +08:00
|
|
|
|
"errors"
|
2022-05-15 13:16:45 +08:00
|
|
|
|
"fmt"
|
2023-03-23 18:35:37 +08:00
|
|
|
|
"strings"
|
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
"github.com/metacubex/mihomo/common/singleflight"
|
2023-11-03 21:01:45 +08:00
|
|
|
|
"github.com/metacubex/mihomo/component/geodata/router"
|
|
|
|
|
C "github.com/metacubex/mihomo/constant"
|
|
|
|
|
"github.com/metacubex/mihomo/log"
|
2021-11-17 16:03:47 +08:00
|
|
|
|
)
|
|
|
|
|
|
2024-02-21 17:14:08 +08:00
|
|
|
|
var (
|
|
|
|
|
geoMode bool
|
|
|
|
|
geoLoaderName = "memconservative"
|
|
|
|
|
geoSiteMatcher = "succinct"
|
|
|
|
|
)
|
2022-02-05 00:51:06 +08:00
|
|
|
|
|
|
|
|
|
// geoLoaderName = "standard"
|
|
|
|
|
|
2024-02-21 17:14:08 +08:00
|
|
|
|
func GeodataMode() bool {
|
|
|
|
|
return geoMode
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-05 00:51:06 +08:00
|
|
|
|
func LoaderName() string {
|
|
|
|
|
return geoLoaderName
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-17 00:00:35 +08:00
|
|
|
|
func SiteMatcherName() string {
|
|
|
|
|
return geoSiteMatcher
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-21 17:14:08 +08:00
|
|
|
|
func SetGeodataMode(newGeodataMode bool) {
|
|
|
|
|
geoMode = newGeodataMode
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-05 00:51:06 +08:00
|
|
|
|
func SetLoader(newLoader string) {
|
2022-04-11 13:23:59 +08:00
|
|
|
|
if newLoader == "memc" {
|
|
|
|
|
newLoader = "memconservative"
|
|
|
|
|
}
|
2022-02-05 00:51:06 +08:00
|
|
|
|
geoLoaderName = newLoader
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-17 00:00:35 +08:00
|
|
|
|
func SetSiteMatcher(newMatcher string) {
|
|
|
|
|
switch newMatcher {
|
2023-12-23 00:05:07 +08:00
|
|
|
|
case "mph", "hybrid":
|
|
|
|
|
geoSiteMatcher = "mph"
|
2023-12-17 00:00:35 +08:00
|
|
|
|
default:
|
2023-12-23 00:05:07 +08:00
|
|
|
|
geoSiteMatcher = "succinct"
|
2023-12-17 00:00:35 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-24 15:04:13 +08:00
|
|
|
|
func Verify(name string) error {
|
2022-03-15 22:25:33 +08:00
|
|
|
|
switch name {
|
|
|
|
|
case C.GeositeName:
|
2024-08-16 09:19:18 +08:00
|
|
|
|
_, err := LoadGeoSiteMatcher("CN")
|
2022-05-24 15:04:13 +08:00
|
|
|
|
return err
|
2022-03-15 22:25:33 +08:00
|
|
|
|
case C.GeoipName:
|
2024-08-16 09:19:18 +08:00
|
|
|
|
_, err := LoadGeoIPMatcher("CN")
|
2022-05-24 15:04:13 +08:00
|
|
|
|
return err
|
2022-03-15 22:25:33 +08:00
|
|
|
|
default:
|
2022-05-24 15:04:13 +08:00
|
|
|
|
return fmt.Errorf("not support name")
|
2022-03-15 22:25:33 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
var loadGeoSiteMatcherListSF = singleflight.Group[[]*router.Domain]{StoreResult: true}
|
|
|
|
|
var loadGeoSiteMatcherSF = singleflight.Group[router.DomainMatcher]{StoreResult: true}
|
2023-03-23 18:35:37 +08:00
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) {
|
2023-12-17 00:00:35 +08:00
|
|
|
|
if countryCode == "" {
|
2024-08-16 09:19:18 +08:00
|
|
|
|
return nil, fmt.Errorf("country code could not be empty")
|
2022-05-15 13:16:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
not := false
|
|
|
|
|
if countryCode[0] == '!' {
|
|
|
|
|
not = true
|
|
|
|
|
countryCode = countryCode[1:]
|
|
|
|
|
}
|
2023-03-23 18:35:37 +08:00
|
|
|
|
countryCode = strings.ToLower(countryCode)
|
|
|
|
|
|
2023-03-23 18:58:24 +08:00
|
|
|
|
parts := strings.Split(countryCode, "@")
|
|
|
|
|
if len(parts) == 0 {
|
2024-08-16 09:19:18 +08:00
|
|
|
|
return nil, errors.New("empty rule")
|
2023-03-23 18:58:24 +08:00
|
|
|
|
}
|
|
|
|
|
listName := strings.TrimSpace(parts[0])
|
|
|
|
|
attrVal := parts[1:]
|
2024-08-16 09:19:18 +08:00
|
|
|
|
attrs := parseAttrs(attrVal)
|
2023-03-23 18:58:24 +08:00
|
|
|
|
|
2023-12-17 00:00:35 +08:00
|
|
|
|
if listName == "" {
|
2024-08-16 09:19:18 +08:00
|
|
|
|
return nil, fmt.Errorf("empty listname in rule: %s", countryCode)
|
2023-03-23 18:58:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
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)
|
|
|
|
|
})
|
2023-03-23 18:35:37 +08:00
|
|
|
|
if err != nil {
|
2024-08-16 09:19:18 +08:00
|
|
|
|
if !shared {
|
|
|
|
|
loadGeoSiteMatcherListSF.Forget(listName) // don't store the error result
|
|
|
|
|
}
|
2023-03-23 18:35:37 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2021-11-17 16:03:47 +08:00
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
if attrs.IsEmpty() {
|
|
|
|
|
if strings.Contains(countryCode, "@") {
|
|
|
|
|
log.Warnln("empty attribute list: %s", countryCode)
|
2023-03-23 18:58:24 +08:00
|
|
|
|
}
|
2024-08-16 09:19:18 +08:00
|
|
|
|
} 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
|
2023-03-23 18:58:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
/**
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
})
|
2021-11-17 16:03:47 +08:00
|
|
|
|
if err != nil {
|
2024-08-16 09:19:18 +08:00
|
|
|
|
if !shared {
|
|
|
|
|
loadGeoSiteMatcherSF.Forget(matcherName) // don't store the error result
|
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if not {
|
|
|
|
|
matcher = router.NewNotDomainMatcherGroup(matcher)
|
2021-11-17 16:03:47 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
return matcher, nil
|
2021-11-17 16:03:47 +08:00
|
|
|
|
}
|
2022-02-04 23:33:36 +08:00
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
var loadGeoIPMatcherSF = singleflight.Group[router.IPMatcher]{StoreResult: true}
|
2023-03-23 18:35:37 +08:00
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
func LoadGeoIPMatcher(country string) (router.IPMatcher, error) {
|
2022-05-15 23:07:06 +08:00
|
|
|
|
if len(country) == 0 {
|
2024-08-16 09:19:18 +08:00
|
|
|
|
return nil, fmt.Errorf("country code could not be empty")
|
2022-05-15 23:07:06 +08:00
|
|
|
|
}
|
2022-02-04 23:33:36 +08:00
|
|
|
|
|
2022-05-15 23:07:06 +08:00
|
|
|
|
not := false
|
|
|
|
|
if country[0] == '!' {
|
|
|
|
|
not = true
|
|
|
|
|
country = country[1:]
|
|
|
|
|
}
|
2023-03-23 18:35:37 +08:00
|
|
|
|
country = strings.ToLower(country)
|
|
|
|
|
|
2024-08-16 09:19:18 +08:00
|
|
|
|
matcher, err, shared := loadGeoIPMatcherSF.Do(country, func() (router.IPMatcher, error) {
|
|
|
|
|
log.Infoln("Load GeoIP rule: %s", country)
|
2023-03-23 18:35:37 +08:00
|
|
|
|
geoLoader, err := GetGeoDataLoader(geoLoaderName)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-08-16 09:19:18 +08:00
|
|
|
|
cidrList, err := geoLoader.LoadGeoIP(country)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return router.NewGeoIPMatcher(cidrList)
|
2023-03-23 18:35:37 +08:00
|
|
|
|
})
|
2022-02-04 23:33:36 +08:00
|
|
|
|
if err != nil {
|
2023-03-23 18:58:24 +08:00
|
|
|
|
if !shared {
|
|
|
|
|
loadGeoIPMatcherSF.Forget(country) // don't store the error result
|
2024-08-16 09:19:18 +08:00
|
|
|
|
log.Warnln("Load GeoIP rule: %s", country)
|
2023-03-23 18:58:24 +08:00
|
|
|
|
}
|
2024-08-16 09:19:18 +08:00
|
|
|
|
return nil, err
|
2022-02-04 23:33:36 +08:00
|
|
|
|
}
|
2024-08-16 09:19:18 +08:00
|
|
|
|
if not {
|
|
|
|
|
matcher = router.NewNotIpMatcherGroup(matcher)
|
2022-02-04 23:33:36 +08:00
|
|
|
|
}
|
2024-08-16 09:19:18 +08:00
|
|
|
|
return matcher, nil
|
2022-02-04 23:33:36 +08:00
|
|
|
|
}
|
2023-03-23 18:35:37 +08:00
|
|
|
|
|
2024-09-09 10:05:38 +08:00
|
|
|
|
func ClearGeoSiteCache() {
|
2024-08-16 09:19:18 +08:00
|
|
|
|
loadGeoSiteMatcherListSF.Reset()
|
|
|
|
|
loadGeoSiteMatcherSF.Reset()
|
2024-09-09 10:05:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ClearGeoIPCache() {
|
2024-08-16 09:19:18 +08:00
|
|
|
|
loadGeoIPMatcherSF.Reset()
|
2023-03-23 18:35:37 +08:00
|
|
|
|
}
|