From 43b6e55b67bb6cfa7bf1ca1305b47e8b113eaef5 Mon Sep 17 00:00:00 2001 From: ALOKY Date: Sun, 16 Aug 2026 15:34:30 +0300 Subject: [PATCH] reduce GeoIP parser memory usage Decode one GeoIP entry at a time and cache successful results. Signed-off-by: ALOKY --- service/common/parseGeoIP/parser.go | 87 +++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 10 deletions(-) --- a/common/parseGeoIP/parser.go +++ b/common/parseGeoIP/parser.go @@ -5,31 +5,91 @@ import ( "net" "os" "strings" + "sync" "github.com/v2rayA/v2rayA/core/v2ray/asset" + "google.golang.org/protobuf/encoding/protowire" "google.golang.org/protobuf/proto" ) -func Parser(filename string, countryCode string) ([]string, []string, error) { - var ipv4List []string - var ipv6List []string - var geoIpProto GeoIPList +type parserResult struct { + ipv4 []string + ipv6 []string +} +type parserCacheKey struct { + path string + size int64 + modTime int64 + countryCode string +} + +var parserCache sync.Map + +func Parser(filename string, countryCode string) ([]string, []string, error) { realpath, err := asset.GetV2rayLocationAsset(filename) if err != nil { - return ipv4List, ipv6List, err + return nil, nil, err } + info, err := os.Stat(realpath) + if err != nil { + return nil, nil, err + } + + cacheKey := parserCacheKey{ + path: realpath, + size: info.Size(), + modTime: info.ModTime().UnixNano(), + countryCode: countryCode, + } + if cached, ok := parserCache.Load(cacheKey); ok { + result := cached.(parserResult) + return result.ipv4, result.ipv6, nil + } + data, err := os.ReadFile(realpath) if err != nil { - return ipv4List, ipv6List, err + return nil, nil, err } - if err := proto.Unmarshal(data, &geoIpProto); err != nil { + ipv4List, ipv6List, err := parseGeoIP(data, countryCode) + if err != nil { return ipv4List, ipv6List, err } - for _, geo := range geoIpProto.Entry { - if geo.CountryCode == countryCode { + parserCache.Store(cacheKey, parserResult{ipv4: ipv4List, ipv6: ipv6List}) + return ipv4List, ipv6List, nil +} + +func parseGeoIP(data []byte, countryCode string) ([]string, []string, error) { + var ipv4List []string + var ipv6List []string + + // GeoIPList contains repeated GeoIP messages in field 1. Decoding the + // complete list expands a roughly 22 MiB asset to hundreds of MiB in + // memory. Decode one entry at a time and stop at the requested country. + for len(data) > 0 { + number, wireType, n := protowire.ConsumeTag(data) + if n < 0 { + return ipv4List, ipv6List, protowire.ParseError(n) + } + data = data[n:] + + if number == 1 && wireType == protowire.BytesType { + entryData, consumed := protowire.ConsumeBytes(data) + if consumed < 0 { + return ipv4List, ipv6List, protowire.ParseError(consumed) + } + data = data[consumed:] + + var geo GeoIP + if err := proto.Unmarshal(entryData, &geo); err != nil { + return ipv4List, ipv6List, err + } + if geo.CountryCode != countryCode { + continue + } + for _, c := range geo.Cidr { ip := net.IP(c.Ip) if strings.Contains(ip.String(), ":") { @@ -42,8 +102,15 @@ func Parser(filename string, countryCode ipv4List = append(ipv4List, fmt.Sprintf("%s/%d", ip.String(), c.Prefix)) } } - break + return ipv4List, ipv6List, nil + } + + consumed := protowire.ConsumeFieldValue(number, wireType, data) + if consumed < 0 { + return ipv4List, ipv6List, protowire.ParseError(consumed) } + data = data[consumed:] } + return ipv4List, ipv6List, nil }