Skip to content

Commit ba501f2

Browse files
authored
perf(GeoIPMatcher): faster heuristic matching with reduced memory usage (XTLS#5289)
1 parent fb50813 commit ba501f2

6 files changed

Lines changed: 985 additions & 192 deletions

File tree

app/dns/nameserver.go

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ type Client struct {
2929
server Server
3030
skipFallback bool
3131
domains []string
32-
expectedIPs []*router.GeoIPMatcher
33-
unexpectedIPs []*router.GeoIPMatcher
32+
expectedIPs router.GeoIPMatcher
33+
unexpectedIPs router.GeoIPMatcher
3434
actPrior bool
3535
actUnprior bool
3636
tag string
@@ -154,23 +154,21 @@ func NewClient(
154154
}
155155

156156
// Establish expected IPs
157-
var expectedMatchers []*router.GeoIPMatcher
158-
for _, geoip := range ns.ExpectedGeoip {
159-
matcher, err := router.GlobalGeoIPContainer.Add(geoip)
157+
var expectedMatcher router.GeoIPMatcher
158+
if len(ns.ExpectedGeoip) > 0 {
159+
expectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.ExpectedGeoip...)
160160
if err != nil {
161161
return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
162162
}
163-
expectedMatchers = append(expectedMatchers, matcher)
164163
}
165164

166165
// Establish unexpected IPs
167-
var unexpectedMatchers []*router.GeoIPMatcher
168-
for _, geoip := range ns.UnexpectedGeoip {
169-
matcher, err := router.GlobalGeoIPContainer.Add(geoip)
166+
var unexpectedMatcher router.GeoIPMatcher
167+
if len(ns.UnexpectedGeoip) > 0 {
168+
unexpectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.UnexpectedGeoip...)
170169
if err != nil {
171170
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
172171
}
173-
unexpectedMatchers = append(unexpectedMatchers, matcher)
174172
}
175173

176174
if len(clientIP) > 0 {
@@ -192,8 +190,8 @@ func NewClient(
192190
client.server = server
193191
client.skipFallback = ns.SkipFallback
194192
client.domains = rules
195-
client.expectedIPs = expectedMatchers
196-
client.unexpectedIPs = unexpectedMatchers
193+
client.expectedIPs = expectedMatcher
194+
client.unexpectedIPs = unexpectedMatcher
197195
client.actPrior = ns.ActPrior
198196
client.actUnprior = ns.ActUnprior
199197
client.tag = tag
@@ -243,32 +241,32 @@ func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption
243241
return nil, 0, dns.ErrEmptyResponse
244242
}
245243

246-
if len(c.expectedIPs) > 0 && !c.actPrior {
247-
ips = router.MatchIPs(c.expectedIPs, ips, false)
244+
if c.expectedIPs != nil && !c.actPrior {
245+
ips, _ = c.expectedIPs.FilterIPs(ips)
248246
errors.LogDebug(context.Background(), "domain ", domain, " expectedIPs ", ips, " matched at server ", c.Name())
249247
if len(ips) == 0 {
250248
return nil, 0, dns.ErrEmptyResponse
251249
}
252250
}
253251

254-
if len(c.unexpectedIPs) > 0 && !c.actUnprior {
255-
ips = router.MatchIPs(c.unexpectedIPs, ips, true)
252+
if c.unexpectedIPs != nil && !c.actUnprior {
253+
_, ips = c.unexpectedIPs.FilterIPs(ips)
256254
errors.LogDebug(context.Background(), "domain ", domain, " unexpectedIPs ", ips, " matched at server ", c.Name())
257255
if len(ips) == 0 {
258256
return nil, 0, dns.ErrEmptyResponse
259257
}
260258
}
261259

262-
if len(c.expectedIPs) > 0 && c.actPrior {
263-
ipsNew := router.MatchIPs(c.expectedIPs, ips, false)
260+
if c.expectedIPs != nil && c.actPrior {
261+
ipsNew, _ := c.expectedIPs.FilterIPs(ips)
264262
if len(ipsNew) > 0 {
265263
ips = ipsNew
266264
errors.LogDebug(context.Background(), "domain ", domain, " priorIPs ", ips, " matched at server ", c.Name())
267265
}
268266
}
269267

270-
if len(c.unexpectedIPs) > 0 && c.actUnprior {
271-
ipsNew := router.MatchIPs(c.unexpectedIPs, ips, true)
268+
if c.unexpectedIPs != nil && c.actUnprior {
269+
_, ipsNew := c.unexpectedIPs.FilterIPs(ips)
272270
if len(ipsNew) > 0 {
273271
ips = ipsNew
274272
errors.LogDebug(context.Background(), "domain ", domain, " unpriorIPs ", ips, " matched at server ", c.Name())

app/router/condition.go

Lines changed: 30 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -96,61 +96,53 @@ func (m *DomainMatcher) Apply(ctx routing.Context) bool {
9696
return m.ApplyDomain(domain)
9797
}
9898

99-
type MultiGeoIPMatcher struct {
100-
matchers []*GeoIPMatcher
101-
asType string // local, source, target
102-
}
99+
type MatcherAsType byte
103100

104-
func NewMultiGeoIPMatcher(geoips []*GeoIP, asType string) (*MultiGeoIPMatcher, error) {
105-
var matchers []*GeoIPMatcher
106-
for _, geoip := range geoips {
107-
matcher, err := GlobalGeoIPContainer.Add(geoip)
108-
if err != nil {
109-
return nil, err
110-
}
111-
matchers = append(matchers, matcher)
112-
}
101+
const (
102+
MatcherAsType_Local MatcherAsType = iota
103+
MatcherAsType_Source
104+
MatcherAsType_Target
105+
MatcherAsType_VlessRoute // for port
106+
)
113107

114-
matcher := &MultiGeoIPMatcher{
115-
matchers: matchers,
116-
asType: asType,
117-
}
108+
type IPMatcher struct {
109+
matcher GeoIPMatcher
110+
asType MatcherAsType
111+
}
118112

119-
return matcher, nil
113+
func NewIPMatcher(geoips []*GeoIP, asType MatcherAsType) (*IPMatcher, error) {
114+
matcher, err := BuildOptimizedGeoIPMatcher(geoips...)
115+
if err != nil {
116+
return nil, err
117+
}
118+
return &IPMatcher{matcher: matcher, asType: asType}, nil
120119
}
121120

122121
// Apply implements Condition.
123-
func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
122+
func (m *IPMatcher) Apply(ctx routing.Context) bool {
124123
var ips []net.IP
125124

126125
switch m.asType {
127-
case "local":
126+
case MatcherAsType_Local:
128127
ips = ctx.GetLocalIPs()
129-
case "source":
128+
case MatcherAsType_Source:
130129
ips = ctx.GetSourceIPs()
131-
case "target":
130+
case MatcherAsType_Target:
132131
ips = ctx.GetTargetIPs()
133132
default:
134-
panic("unreachable, asType should be local or source or target")
133+
panic("unk asType")
135134
}
136135

137-
for _, ip := range ips {
138-
for _, matcher := range m.matchers {
139-
if matcher.Match(ip) {
140-
return true
141-
}
142-
}
143-
}
144-
return false
136+
return m.matcher.AnyMatch(ips)
145137
}
146138

147139
type PortMatcher struct {
148140
port net.MemoryPortList
149-
asType string // local, source, target
141+
asType MatcherAsType
150142
}
151143

152144
// NewPortMatcher create a new port matcher that can match source or local or destination port
153-
func NewPortMatcher(list *net.PortList, asType string) *PortMatcher {
145+
func NewPortMatcher(list *net.PortList, asType MatcherAsType) *PortMatcher {
154146
return &PortMatcher{
155147
port: net.PortListFromProto(list),
156148
asType: asType,
@@ -160,18 +152,17 @@ func NewPortMatcher(list *net.PortList, asType string) *PortMatcher {
160152
// Apply implements Condition.
161153
func (v *PortMatcher) Apply(ctx routing.Context) bool {
162154
switch v.asType {
163-
case "local":
155+
case MatcherAsType_Local:
164156
return v.port.Contains(ctx.GetLocalPort())
165-
case "source":
157+
case MatcherAsType_Source:
166158
return v.port.Contains(ctx.GetSourcePort())
167-
case "target":
159+
case MatcherAsType_Target:
168160
return v.port.Contains(ctx.GetTargetPort())
169-
case "vlessRoute":
161+
case MatcherAsType_VlessRoute:
170162
return v.port.Contains(ctx.GetVlessRoute())
171163
default:
172-
panic("unreachable, asType should be local or source or target")
164+
panic("unk asType")
173165
}
174-
175166
}
176167

177168
type NetworkMatcher struct {

0 commit comments

Comments
 (0)