From 5ef85697f30608cb03310861e7e7208adc623b34 Mon Sep 17 00:00:00 2001 From: Haruue Date: Wed, 10 Sep 2025 15:00:04 +0900 Subject: [PATCH] fix(acl): error rule applied due to bad cache key close: #1432 --- extras/outbounds/acl/compile.go | 29 +++++++++++++++++++++--- extras/outbounds/acl/compile_test.go | 34 +++++++++++++++++++++++++--- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/extras/outbounds/acl/compile.go b/extras/outbounds/acl/compile.go index caee138..29433fe 100644 --- a/extras/outbounds/acl/compile.go +++ b/extras/outbounds/acl/compile.go @@ -19,6 +19,19 @@ const ( ProtocolUDP ) +func (p Protocol) String() string { + switch p { + case ProtocolBoth: + return "tcp+udp" + case ProtocolTCP: + return "tcp" + case ProtocolUDP: + return "udp" + default: + return fmt.Sprintf("Protocol(%d)", int(p)) + } +} + type Outbound interface { any } @@ -63,12 +76,22 @@ type matchResult[O Outbound] struct { type compiledRuleSetImpl[O Outbound] struct { Rules []compiledRule[O] - Cache *lru.Cache[string, matchResult[O]] // key: HostInfo.String() + Cache *lru.Cache[matchResultCacheKey, matchResult[O]] // key: HostInfo.String() +} + +type matchResultCacheKey struct { + Host string + Proto Protocol + Port uint16 } func (s *compiledRuleSetImpl[O]) Match(host HostInfo, proto Protocol, port uint16) (O, net.IP) { host.Name = strings.ToLower(host.Name) // Normalize host name to lower case - key := host.String() + key := matchResultCacheKey{ + Host: host.String(), + Proto: proto, + Port: port, + } if result, ok := s.Cache.Get(key); ok { return result.Outbound, result.HijackAddress } @@ -130,7 +153,7 @@ func Compile[O Outbound](rules []TextRule, outbounds map[string]O, } compiledRules[i] = compiledRule[O]{outbound, hm, proto, startPort, endPort, hijackAddress} } - cache, err := lru.New[string, matchResult[O]](cacheSize) + cache, err := lru.New[matchResultCacheKey, matchResult[O]](cacheSize) if err != nil { return nil, err } diff --git a/extras/outbounds/acl/compile_test.go b/extras/outbounds/acl/compile_test.go index bf51f68..9e0ace2 100644 --- a/extras/outbounds/acl/compile_test.go +++ b/extras/outbounds/acl/compile_test.go @@ -1,6 +1,7 @@ package acl import ( + "fmt" "net" "testing" @@ -177,6 +178,30 @@ func TestCompile(t *testing.T) { wantOutbound: ob1, wantIP: net.ParseIP("2.2.2.2"), }, + { + host: HostInfo{ + Name: "crap.v2ex.com", + }, + proto: ProtocolTCP, + port: 81, + wantOutbound: 0, + }, + { + host: HostInfo{ + Name: "crap.v2ex.com", + }, + proto: ProtocolUDP, + port: 80, + wantOutbound: ob3, + }, + { + host: HostInfo{ + Name: "crap.v2ex.com", + }, + proto: ProtocolUDP, + port: 81, + wantOutbound: ob3, + }, { host: HostInfo{ IPv4: net.ParseIP("210.140.92.187"), @@ -261,9 +286,12 @@ func TestCompile(t *testing.T) { } for _, test := range tests { - gotOutbound, gotIP := comp.Match(test.host, test.proto, test.port) - assert.Equal(t, test.wantOutbound, gotOutbound) - assert.Equal(t, test.wantIP, gotIP) + testName := fmt.Sprintf("%s#%s#%d", test.host, test.proto, test.port) + t.Run(testName, func(t *testing.T) { + gotOutbound, gotIP := comp.Match(test.host, test.proto, test.port) + assert.Equal(t, test.wantOutbound, gotOutbound) + assert.Equal(t, test.wantIP, gotIP) + }) } // Test Invalid Port Range Rule