From 7e876709fe5fbfacebbcfd4387be7b7d88df8539 Mon Sep 17 00:00:00 2001 From: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> Date: Sat, 1 May 2021 07:59:29 +0800 Subject: [PATCH] Refactor: memory-efficient geo file decoder (#934) --- app/router/condition_geoip_test.go | 31 ++----- app/router/condition_test.go | 36 ++------ common/geodata/cache.go | 140 +++++++++++++++++++++++++++++ common/geodata/decode.go | 116 ++++++++++++++++++++++++ common/geodata/decode_test.go | 76 ++++++++++++++++ common/geodata/errors.generated.go | 9 ++ common/geodata/load.go | 28 ++++++ infra/conf/geodata.go | 58 ++++++++++++ infra/conf/observatory.go | 1 + infra/conf/router.go | 96 +------------------- 10 files changed, 445 insertions(+), 146 deletions(-) create mode 100644 common/geodata/cache.go create mode 100644 common/geodata/decode.go create mode 100644 common/geodata/decode_test.go create mode 100644 common/geodata/errors.generated.go create mode 100644 common/geodata/load.go create mode 100644 infra/conf/geodata.go diff --git a/app/router/condition_geoip_test.go b/app/router/condition_geoip_test.go index 2996b1794..2a1259897 100644 --- a/app/router/condition_geoip_test.go +++ b/app/router/condition_geoip_test.go @@ -5,13 +5,11 @@ import ( "io/fs" "os" "path/filepath" - "strings" "testing" - "google.golang.org/protobuf/proto" - "github.com/v2fly/v2ray-core/v4/app/router" "github.com/v2fly/v2ray-core/v4/common" + "github.com/v2fly/v2ray-core/v4/common/geodata" "github.com/v2fly/v2ray-core/v4/common/net" "github.com/v2fly/v2ray-core/v4/common/platform" "github.com/v2fly/v2ray-core/v4/common/platform/filesystem" @@ -171,7 +169,7 @@ func TestGeoIPReverseMatcher(t *testing.T) { } func TestGeoIPMatcher4CN(t *testing.T) { - ips, err := loadGeoIP("CN") + ips, err := geodata.LoadIP("geoip.dat", "CN") common.Must(err) matcher := &router.GeoIPMatcher{} @@ -183,7 +181,7 @@ func TestGeoIPMatcher4CN(t *testing.T) { } func TestGeoIPMatcher6US(t *testing.T) { - ips, err := loadGeoIP("US") + ips, err := geodata.LoadIP("geoip.dat", "US") common.Must(err) matcher := &router.GeoIPMatcher{} @@ -194,27 +192,8 @@ func TestGeoIPMatcher6US(t *testing.T) { } } -func loadGeoIP(country string) ([]*router.CIDR, error) { - geoipBytes, err := filesystem.ReadAsset("geoip.dat") - if err != nil { - return nil, err - } - var geoipList router.GeoIPList - if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil { - return nil, err - } - - for _, geoip := range geoipList.Entry { - if strings.EqualFold(geoip.CountryCode, country) { - return geoip.Cidr, nil - } - } - - panic("country not found: " + country) -} - func BenchmarkGeoIPMatcher4CN(b *testing.B) { - ips, err := loadGeoIP("CN") + ips, err := geodata.LoadIP("geoip.dat", "CN") common.Must(err) matcher := &router.GeoIPMatcher{} @@ -228,7 +207,7 @@ func BenchmarkGeoIPMatcher4CN(b *testing.B) { } func BenchmarkGeoIPMatcher6US(b *testing.B) { - ips, err := loadGeoIP("US") + ips, err := geodata.LoadIP("geoip.dat", "US") common.Must(err) matcher := &router.GeoIPMatcher{} diff --git a/app/router/condition_test.go b/app/router/condition_test.go index 363fafb52..7cee49d7a 100644 --- a/app/router/condition_test.go +++ b/app/router/condition_test.go @@ -6,13 +6,11 @@ import ( "os" "path/filepath" "strconv" - "strings" "testing" - "google.golang.org/protobuf/proto" - "github.com/v2fly/v2ray-core/v4/app/router" "github.com/v2fly/v2ray-core/v4/common" + "github.com/v2fly/v2ray-core/v4/common/geodata" "github.com/v2fly/v2ray-core/v4/common/net" "github.com/v2fly/v2ray-core/v4/common/platform" "github.com/v2fly/v2ray-core/v4/common/platform/filesystem" @@ -352,26 +350,8 @@ func TestRoutingRule(t *testing.T) { } } -func loadGeoSite(country string) ([]*router.Domain, error) { - geositeBytes, err := filesystem.ReadAsset("geosite.dat") - if err != nil { - return nil, err - } - var geositeList router.GeoSiteList - if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { - return nil, err - } - - for _, site := range geositeList.Entry { - if strings.EqualFold(site.CountryCode, country) { - return site.Domain, nil - } - } - - return nil, errors.New("country not found: " + country) -} func TestChinaSites(t *testing.T) { - domains, err := loadGeoSite("CN") + domains, err := geodata.LoadSite("geosite.dat", "CN") common.Must(err) matcher, err := router.NewDomainMatcher(domains) @@ -418,7 +398,7 @@ func TestChinaSites(t *testing.T) { } func BenchmarkMphDomainMatcher(b *testing.B) { - domains, err := loadGeoSite("CN") + domains, err := geodata.LoadSite("geosite.dat", "CN") common.Must(err) matcher, err := router.NewMphMatcherGroup(domains) @@ -460,7 +440,7 @@ func BenchmarkMphDomainMatcher(b *testing.B) { } func BenchmarkDomainMatcher(b *testing.B) { - domains, err := loadGeoSite("CN") + domains, err := geodata.LoadSite("geosite.dat", "CN") common.Must(err) matcher, err := router.NewDomainMatcher(domains) @@ -505,7 +485,7 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) { var geoips []*router.GeoIP { - ips, err := loadGeoIP("CN") + ips, err := geodata.LoadIP("geoip.dat", "CN") common.Must(err) geoips = append(geoips, &router.GeoIP{ CountryCode: "CN", @@ -514,7 +494,7 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) { } { - ips, err := loadGeoIP("JP") + ips, err := geodata.LoadIP("geoip.dat", "JP") common.Must(err) geoips = append(geoips, &router.GeoIP{ CountryCode: "JP", @@ -523,7 +503,7 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) { } { - ips, err := loadGeoIP("CA") + ips, err := geodata.LoadIP("geoip.dat", "CA") common.Must(err) geoips = append(geoips, &router.GeoIP{ CountryCode: "CA", @@ -532,7 +512,7 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) { } { - ips, err := loadGeoIP("US") + ips, err := geodata.LoadIP("geoip.dat", "US") common.Must(err) geoips = append(geoips, &router.GeoIP{ CountryCode: "US", diff --git a/common/geodata/cache.go b/common/geodata/cache.go new file mode 100644 index 000000000..4e0cbfa53 --- /dev/null +++ b/common/geodata/cache.go @@ -0,0 +1,140 @@ +package geodata + +import ( + "io/ioutil" + "runtime" + "strings" + + "google.golang.org/protobuf/proto" + + "github.com/v2fly/v2ray-core/v4/app/router" + "github.com/v2fly/v2ray-core/v4/common/platform" +) + +type GeoIPCache map[string]*router.GeoIP + +func (g GeoIPCache) Has(key string) bool { + return !(g.Get(key) == nil) +} + +func (g GeoIPCache) Get(key string) *router.GeoIP { + if g == nil { + return nil + } + return g[key] +} + +func (g GeoIPCache) Set(key string, value *router.GeoIP) { + if g == nil { + g = make(map[string]*router.GeoIP) + } + g[key] = value +} + +func (g GeoIPCache) Unmarshal(filename, code string) (*router.GeoIP, error) { + filename = platform.GetAssetLocation(filename) + idx := strings.ToUpper(filename + "|" + code) + if g.Has(idx) { + return g.Get(idx), nil + } + + geoipBytes, err := Decode(filename, code) + switch err { + case nil: + var geoip router.GeoIP + if err := proto.Unmarshal(geoipBytes, &geoip); err != nil { + return nil, err + } + g.Set(idx, &geoip) + return &geoip, nil + + case errFailedToReadBytes, errFailedToReadExpectedLenBytes, + errInvalidGeodataFile, errInvalidGeodataVarintLength: + newError("failed to decode geodata file: ", filename, ". Fallback to the original ReadFile method.").AtWarning().WriteToLog() + geoipBytes, err = ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var geoipList router.GeoIPList + if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil { + return nil, err + } + runtime.GC() + for _, geoip := range geoipList.GetEntry() { + if strings.EqualFold(code, geoip.GetCountryCode()) { + g.Set(idx, geoip) + return geoip, nil + } + runtime.GC() + } + + default: + return nil, err + } + + return nil, nil +} + +type GeoSiteCache map[string]*router.GeoSite + +func (g GeoSiteCache) Has(key string) bool { + return !(g.Get(key) == nil) +} + +func (g GeoSiteCache) Get(key string) *router.GeoSite { + if g == nil { + return nil + } + return g[key] +} + +func (g GeoSiteCache) Set(key string, value *router.GeoSite) { + if g == nil { + g = make(map[string]*router.GeoSite) + } + g[key] = value +} + +func (g GeoSiteCache) Unmarshal(filename, code string) (*router.GeoSite, error) { + filename = platform.GetAssetLocation(filename) + idx := strings.ToUpper(filename + "|" + code) + if g.Has(idx) { + return g.Get(idx), nil + } + + geositeBytes, err := Decode(filename, code) + switch err { + case nil: + var geosite router.GeoSite + if err := proto.Unmarshal(geositeBytes, &geosite); err != nil { + return nil, err + } + g.Set(idx, &geosite) + return &geosite, nil + + case errFailedToReadBytes, errFailedToReadExpectedLenBytes, + errInvalidGeodataFile, errInvalidGeodataVarintLength: + newError("failed to decode geodata file: ", filename, ". Fallback to the original ReadFile method.").AtWarning().WriteToLog() + geositeBytes, err = ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var geositeList router.GeoSiteList + if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { + return nil, err + } + runtime.GC() + for _, geosite := range geositeList.GetEntry() { + if strings.EqualFold(code, geosite.GetCountryCode()) { + g.Set(idx, geosite) + return geosite, nil + } + runtime.GC() + } + + default: + return nil, err + } + + return nil, nil +} diff --git a/common/geodata/decode.go b/common/geodata/decode.go new file mode 100644 index 000000000..7ee645a57 --- /dev/null +++ b/common/geodata/decode.go @@ -0,0 +1,116 @@ +// Package geodata includes utilities to decode and parse the geoip & geosite dat files for V2Ray. +// +// It relies on the proto structure of GeoIP, GeoIPList, GeoSite and GeoSiteList in +// github.com/v2fly/v2ray-core/v4/app/router/config.proto to comply with following rules: +// +// 1. GeoIPList and GeoSiteList cannot be changed +// 2. The country_code in GeoIP and GeoSite must be +// a length-delimited `string`(wired type) and has field_number set to 1 +// +package geodata + +import ( + "os" + "runtime" + "strings" + + "google.golang.org/protobuf/encoding/protowire" + + "github.com/v2fly/v2ray-core/v4/common/errors" +) + +//go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen + +var ( + errFailedToReadBytes = errors.New("failed to read bytes") + errFailedToReadExpectedLenBytes = errors.New("failed to read expected length of bytes") + errInvalidGeodataFile = errors.New("invalid geodata file") + errInvalidGeodataVarintLength = errors.New("invalid geodata varint length") +) + +func emitBytes(f *os.File, code string) ([]byte, error) { + count := 1 + isInner := false + tempContainer := make([]byte, 0, 5) + + var result []byte + var advancedN uint64 = 1 + var geoDataVarintLength, codeVarintLength, varintLenByteLen uint64 = 0, 0, 0 + +Loop: + for { + container := make([]byte, advancedN) + bytesRead, err := f.Read(container) + if err != nil { + return nil, errFailedToReadBytes + } + if bytesRead != len(container) { + return nil, errFailedToReadExpectedLenBytes + } + + switch count { + case 1, 3: // data type ((field_number << 3) | wire_type) + if container[0] != 10 { // byte `0A` equals to `10` in decimal + return nil, errInvalidGeodataFile + } + advancedN = 1 + count++ + case 2, 4: // data length + tempContainer = append(tempContainer, container...) + if container[0] > 127 { // max one-byte-length byte `7F`(0FFF FFFF) equals to `127` in decimal + advancedN = 1 + goto Loop + } + lenVarint, n := protowire.ConsumeVarint(tempContainer) + if n < 0 { + return nil, errInvalidGeodataVarintLength + } + tempContainer = nil + if !isInner { + isInner = true + geoDataVarintLength = lenVarint + advancedN = 1 + } else { + isInner = false + codeVarintLength = lenVarint + varintLenByteLen = uint64(n) + advancedN = codeVarintLength + } + count++ + case 5: // data value + if strings.EqualFold(string(container), code) { + count++ + offset := -(1 + int64(varintLenByteLen) + int64(codeVarintLength)) + f.Seek(offset, 1) // back to the start of GeoIP or GeoSite varint + advancedN = geoDataVarintLength // the number of bytes to be read in next round + } else { + count = 1 + offset := int64(geoDataVarintLength) - int64(codeVarintLength) - int64(varintLenByteLen) - 1 + f.Seek(offset, 1) // skip the unmatched GeoIP or GeoSite varint + advancedN = 1 // the next round will be the start of another GeoIPList or GeoSiteList + } + case 6: // matched GeoIP or GeoSite varint + result = container + break Loop + } + + runtime.GC() // run GC every round to save memory + } + + runtime.GC() // run GC at the end to save memory + return result, nil +} + +func Decode(filename, code string) ([]byte, error) { + f, err := os.Open(filename) + if err != nil { + return nil, newError("failed to open file: ", filename).Base(err) + } + defer f.Close() + + geoBytes, err := emitBytes(f, code) + if err != nil { + return nil, err + } + return geoBytes, nil +} diff --git a/common/geodata/decode_test.go b/common/geodata/decode_test.go new file mode 100644 index 000000000..8c77d488e --- /dev/null +++ b/common/geodata/decode_test.go @@ -0,0 +1,76 @@ +package geodata_test + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/v2fly/v2ray-core/v4/common" + "github.com/v2fly/v2ray-core/v4/common/geodata" + "github.com/v2fly/v2ray-core/v4/common/platform" + "github.com/v2fly/v2ray-core/v4/common/platform/filesystem" +) + +const ( + geoipURL = "https://raw.githubusercontent.com/v2fly/geoip/release/geoip.dat" + geositeURL = "https://raw.githubusercontent.com/v2fly/domain-list-community/release/dlc.dat" +) + +func init() { + wd, err := os.Getwd() + common.Must(err) + + tempPath := filepath.Join(wd, "..", "..", "testing", "temp") + geoipPath := filepath.Join(tempPath, "geoip.dat") + geositePath := filepath.Join(tempPath, "geosite.dat") + + os.Setenv("v2ray.location.asset", tempPath) + + common.Must(os.MkdirAll(tempPath, 0755)) + + if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && errors.Is(err, fs.ErrNotExist) { + if _, err := os.Stat(geoipPath); err != nil && errors.Is(err, fs.ErrNotExist) { + geoipBytes, err := common.FetchHTTPContent(geoipURL) + common.Must(err) + common.Must(filesystem.WriteFile(geoipPath, geoipBytes)) + } + } + + if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && errors.Is(err, fs.ErrNotExist) { + if _, err := os.Stat(geositePath); err != nil && errors.Is(err, fs.ErrNotExist) { + geositeBytes, err := common.FetchHTTPContent(geositeURL) + common.Must(err) + common.Must(filesystem.WriteFile(geositePath, geositeBytes)) + } + } +} + +func TestDecodeGeoIP(t *testing.T) { + filename := platform.GetAssetLocation("geoip.dat") + result, err := geodata.Decode(filename, "test") + if err != nil { + t.Error(err) + } + + expected := []byte{10, 4, 84, 69, 83, 84, 18, 8, 10, 4, 127, 0, 0, 0, 16, 8} + if cmp.Diff(result, expected) != "" { + t.Errorf("failed to load geoip:test, expected: %v, got: %v", expected, result) + } +} + +func TestDecodeGeoSite(t *testing.T) { + filename := platform.GetAssetLocation("geosite.dat") + result, err := geodata.Decode(filename, "test") + if err != nil { + t.Error(err) + } + + expected := []byte{10, 4, 84, 69, 83, 84, 18, 20, 8, 3, 18, 16, 116, 101, 115, 116, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109} + if cmp.Diff(result, expected) != "" { + t.Errorf("failed to load geosite:test, expected: %v, got: %v", expected, result) + } +} diff --git a/common/geodata/errors.generated.go b/common/geodata/errors.generated.go new file mode 100644 index 000000000..2770f1a1a --- /dev/null +++ b/common/geodata/errors.generated.go @@ -0,0 +1,9 @@ +package geodata + +import "github.com/v2fly/v2ray-core/v4/common/errors" + +type errPathObjHolder struct{} + +func newError(values ...interface{}) *errors.Error { + return errors.New(values...).WithPathObj(errPathObjHolder{}) +} diff --git a/common/geodata/load.go b/common/geodata/load.go new file mode 100644 index 000000000..4693638a6 --- /dev/null +++ b/common/geodata/load.go @@ -0,0 +1,28 @@ +package geodata + +import ( + "runtime" + + "github.com/v2fly/v2ray-core/v4/app/router" +) + +var geoipcache GeoIPCache = make(map[string]*router.GeoIP) +var geositecache GeoSiteCache = make(map[string]*router.GeoSite) + +func LoadIP(filename, country string) ([]*router.CIDR, error) { + geoip, err := geoipcache.Unmarshal(filename, country) + if err != nil { + return nil, newError("failed to decode geodata file: ", filename).Base(err) + } + runtime.GC() + return geoip.Cidr, nil +} + +func LoadSite(filename, list string) ([]*router.Domain, error) { + geosite, err := geositecache.Unmarshal(filename, list) + if err != nil { + return nil, newError("failed to decode geodata file: ", filename).Base(err) + } + runtime.GC() + return geosite.Domain, nil +} diff --git a/infra/conf/geodata.go b/infra/conf/geodata.go new file mode 100644 index 000000000..3eef052f6 --- /dev/null +++ b/infra/conf/geodata.go @@ -0,0 +1,58 @@ +package conf + +import ( + "runtime" + "strings" + + "github.com/v2fly/v2ray-core/v4/app/router" + "github.com/v2fly/v2ray-core/v4/common/geodata" +) + +func loadGeoIP(country string) ([]*router.CIDR, error) { + return geodata.LoadIP("geoip.dat", country) +} + +func loadGeosite(list string) ([]*router.Domain, error) { + return loadGeositeWithAttr("geosite.dat", list) +} + +func loadGeositeWithAttr(filename string, siteWithAttr string) ([]*router.Domain, error) { + parts := strings.Split(siteWithAttr, "@") + if len(parts) == 0 { + return nil, newError("empty rule") + } + list := strings.TrimSpace(parts[0]) + attrVal := parts[1:] + + if len(list) == 0 { + return nil, newError("empty listname in rule: ", siteWithAttr) + } + + domains, err := geodata.LoadSite(filename, list) + if err != nil { + return nil, err + } + + attrs := parseAttrs(attrVal) + if attrs.IsEmpty() { + if strings.Contains(siteWithAttr, "@") { + newError("empty attribute list: ", siteWithAttr) + } + return domains, nil + } + + 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 { + newError("attribute match no rule: geosite:", siteWithAttr) + } + + runtime.GC() + return filteredDomains, nil +} diff --git a/infra/conf/observatory.go b/infra/conf/observatory.go index fd8038e7e..dbf9b0d84 100644 --- a/infra/conf/observatory.go +++ b/infra/conf/observatory.go @@ -2,6 +2,7 @@ package conf import ( "github.com/golang/protobuf/proto" + "github.com/v2fly/v2ray-core/v4/app/observatory" ) diff --git a/infra/conf/router.go b/infra/conf/router.go index 9cc9d5281..3cc442be6 100644 --- a/infra/conf/router.go +++ b/infra/conf/router.go @@ -5,11 +5,9 @@ import ( "strconv" "strings" - "github.com/golang/protobuf/proto" - "github.com/v2fly/v2ray-core/v4/app/router" + "github.com/v2fly/v2ray-core/v4/common/geodata" "github.com/v2fly/v2ray-core/v4/common/net" - "github.com/v2fly/v2ray-core/v4/common/platform/filesystem" ) type RouterRulesConfig struct { @@ -174,48 +172,6 @@ func ParseIP(s string) (*router.CIDR, error) { } } -func loadGeoIP(country string) ([]*router.CIDR, error) { - return loadIP("geoip.dat", country) -} - -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 - } - - for _, geoip := range geoipList.Entry { - if strings.EqualFold(geoip.CountryCode, country) { - return geoip.Cidr, nil - } - } - - return nil, newError("country not found in ", filename, ": ", country) -} - -func loadSite(filename, list 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 - } - - for _, site := range geositeList.Entry { - if strings.EqualFold(site.CountryCode, list) { - return site.Domain, nil - } - } - - return nil, newError("list not found in ", filename, ": ", list) -} - type AttributeMatcher interface { Match(*router.Domain) bool } @@ -260,50 +216,6 @@ func parseAttrs(attrs []string) *AttributeList { return al } -func loadGeosite(list string) ([]*router.Domain, error) { - return loadGeositeWithAttr("geosite.dat", list) -} - -func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) { - parts := strings.Split(siteWithAttr, "@") - if len(parts) == 0 { - return nil, newError("empty rule") - } - list := strings.TrimSpace(parts[0]) - attrVal := parts[1:] - - if len(list) == 0 { - return nil, newError("empty listname in rule: ", siteWithAttr) - } - - domains, err := loadSite(file, list) - if err != nil { - return nil, err - } - - attrs := parseAttrs(attrVal) - if attrs.IsEmpty() { - if strings.Contains(siteWithAttr, "@") { - newError("empty attribute list: ", siteWithAttr) - } - return domains, nil - } - - 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 { - newError("attribute match no rule: geosite:", siteWithAttr) - } - - return filteredDomains, nil -} - func parseDomainRule(domain string) ([]*router.Domain, error) { if strings.HasPrefix(domain, "geosite:") { list := domain[8:] @@ -414,7 +326,7 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) { } geoip, err := loadGeoIP(country) if err != nil { - return nil, newError("failed to load geoip: ", country).Base(err) + return nil, newError("failed to load geoip:", country).Base(err) } geoipList = append(geoipList, &router.GeoIP{ @@ -455,9 +367,9 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) { country = country[1:] isReverseMatch = true } - geoip, err := loadIP(filename, country) + geoip, err := geodata.LoadIP(filename, country) if err != nil { - return nil, newError("failed to load geoip: ", country, " from ", filename).Base(err) + return nil, newError("failed to load geoip:", country, " from ", filename).Base(err) } geoipList = append(geoipList, &router.GeoIP{