1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
From 43b6e55b67bb6cfa7bf1ca1305b47e8b113eaef5 Mon Sep 17 00:00:00 2001
From: ALOKY <ajm1055@uomustansiriyah.edu.iq>
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 <ajm1055@uomustansiriyah.edu.iq>
---
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
}
|