|
|
@@ -2,6 +2,7 @@ package conf
|
|
|
|
|
|
import (
|
|
|
"encoding/json"
|
|
|
+ "runtime"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
|
|
|
@@ -147,46 +148,109 @@ func ParseIP(s string) (*router.CIDR, error) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func loadGeoIP(country string) ([]*router.CIDR, error) {
|
|
|
- return loadIP("geoip.dat", country)
|
|
|
+func loadGeoIP(code string) ([]*router.CIDR, error) {
|
|
|
+ return loadIP("geoip.dat", code)
|
|
|
}
|
|
|
|
|
|
-func loadIP(filename, country string) ([]*router.CIDR, error) {
|
|
|
- geoipBytes, err := filesystem.ReadAsset(filename)
|
|
|
- if err != nil {
|
|
|
- return nil, newError("failed to open file: ", filename).Base(err)
|
|
|
- }
|
|
|
- var geoipList router.GeoIPList
|
|
|
- if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
|
|
|
- return nil, err
|
|
|
- }
|
|
|
+var (
|
|
|
+ FileCache = make(map[string][]byte)
|
|
|
+ IPCache = make(map[string]*router.GeoIP)
|
|
|
+ SiteCache = make(map[string]*router.GeoSite)
|
|
|
+)
|
|
|
|
|
|
- for _, geoip := range geoipList.Entry {
|
|
|
- if geoip.CountryCode == country {
|
|
|
- return geoip.Cidr, nil
|
|
|
+func loadFile(file string) ([]byte, error) {
|
|
|
+ if FileCache[file] == nil {
|
|
|
+ bs, err := filesystem.ReadAsset(file)
|
|
|
+ if err != nil {
|
|
|
+ return nil, newError("failed to open file: ", file).Base(err)
|
|
|
}
|
|
|
+ if len(bs) == 0 {
|
|
|
+ return nil, newError("empty file: ", file)
|
|
|
+ }
|
|
|
+ // Do not cache file, may save RAM when there
|
|
|
+ // are many files, but consume CPU each time.
|
|
|
+ return bs, nil
|
|
|
+ FileCache[file] = bs
|
|
|
}
|
|
|
-
|
|
|
- return nil, newError("country not found in ", filename, ": ", country)
|
|
|
+ return FileCache[file], nil
|
|
|
}
|
|
|
|
|
|
-func loadSite(filename, country string) ([]*router.Domain, error) {
|
|
|
- geositeBytes, err := filesystem.ReadAsset(filename)
|
|
|
- if err != nil {
|
|
|
- return nil, newError("failed to open file: ", filename).Base(err)
|
|
|
- }
|
|
|
- var geositeList router.GeoSiteList
|
|
|
- if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
|
|
|
- return nil, err
|
|
|
+func loadIP(file, code string) ([]*router.CIDR, error) {
|
|
|
+ index := file + ":" + code
|
|
|
+ if IPCache[index] == nil {
|
|
|
+ bs, err := loadFile(file)
|
|
|
+ if err != nil {
|
|
|
+ return nil, newError("failed to load file: ", file).Base(err)
|
|
|
+ }
|
|
|
+ bs = find(bs, []byte(code))
|
|
|
+ if bs == nil {
|
|
|
+ return nil, newError("code not found in ", file, ": ", code)
|
|
|
+ }
|
|
|
+ var geoip router.GeoIP
|
|
|
+ if err := proto.Unmarshal(bs, &geoip); err != nil {
|
|
|
+ return nil, newError("error unmarshal IP in ", file, ": ", code).Base(err)
|
|
|
+ }
|
|
|
+ defer runtime.GC() // or debug.FreeOSMemory()
|
|
|
+ return geoip.Cidr, nil // do not cache geoip
|
|
|
+ IPCache[index] = &geoip
|
|
|
}
|
|
|
+ return IPCache[index].Cidr, nil
|
|
|
+}
|
|
|
|
|
|
- for _, site := range geositeList.Entry {
|
|
|
- if site.CountryCode == country {
|
|
|
- return site.Domain, nil
|
|
|
+func loadSite(file, code string) ([]*router.Domain, error) {
|
|
|
+ index := file + ":" + code
|
|
|
+ if SiteCache[index] == nil {
|
|
|
+ bs, err := loadFile(file)
|
|
|
+ if err != nil {
|
|
|
+ return nil, newError("failed to load file: ", file).Base(err)
|
|
|
+ }
|
|
|
+ bs = find(bs, []byte(code))
|
|
|
+ if bs == nil {
|
|
|
+ return nil, newError("list not found in ", file, ": ", code)
|
|
|
}
|
|
|
+ var geosite router.GeoSite
|
|
|
+ if err := proto.Unmarshal(bs, &geosite); err != nil {
|
|
|
+ return nil, newError("error unmarshal Site in ", file, ": ", code).Base(err)
|
|
|
+ }
|
|
|
+ defer runtime.GC() // or debug.FreeOSMemory()
|
|
|
+ return geosite.Domain, nil // do not cache geosite
|
|
|
+ SiteCache[index] = &geosite
|
|
|
}
|
|
|
+ return SiteCache[index].Domain, nil
|
|
|
+}
|
|
|
|
|
|
- return nil, newError("list not found in ", filename, ": ", country)
|
|
|
+func find(data, code []byte) []byte {
|
|
|
+ codeL := len(code)
|
|
|
+ if codeL == 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ for {
|
|
|
+ dataL := len(data)
|
|
|
+ if dataL < 2 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ x, y := proto.DecodeVarint(data[1:])
|
|
|
+ if x == 0 && y == 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ headL, bodyL := 1+y, int(x)
|
|
|
+ dataL -= headL
|
|
|
+ if dataL < bodyL {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ data = data[headL:]
|
|
|
+ if int(data[1]) == codeL {
|
|
|
+ for i := 0; i < codeL && data[2+i] == code[i]; i++ {
|
|
|
+ if i+1 == codeL {
|
|
|
+ return data[:bodyL]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if dataL == bodyL {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ data = data[bodyL:]
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
type AttributeMatcher interface {
|