diff --git a/extras/outbounds/acl/compile.go b/extras/outbounds/acl/compile.go index 29433fe..4afc77b 100644 --- a/extras/outbounds/acl/compile.go +++ b/extras/outbounds/acl/compile.go @@ -86,7 +86,7 @@ type matchResultCacheKey struct { } 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 + host.Name = strings.TrimRight(strings.ToLower(host.Name), ".") // Normalize host name (lower case, no trailing dots) key := matchResultCacheKey{ Host: host.String(), Proto: proto, @@ -233,7 +233,7 @@ func parseProtoPort(protoPort string) (Protocol, uint16, uint16, bool) { } func compileHostMatcher(addr string, geoLoader GeoLoader) (hostMatcher, string) { - addr = strings.ToLower(addr) // Normalize to lower case + addr = strings.TrimRight(strings.ToLower(addr), ".") // Normalize host pattern (lower case, no trailing dots) if addr == "*" || addr == "all" { // Match all hosts return &allMatcher{}, "" diff --git a/extras/outbounds/acl/compile_test.go b/extras/outbounds/acl/compile_test.go index 9e0ace2..2ba93a1 100644 --- a/extras/outbounds/acl/compile_test.go +++ b/extras/outbounds/acl/compile_test.go @@ -97,6 +97,12 @@ func TestCompile(t *testing.T) { ProtoPort: "tcp/6881-6889", HijackAddress: "", }, + { + Outbound: "ob1", + Address: "dotpattern.test.", // trailing dot in pattern should be normalized away + ProtoPort: "tcp/443", + HijackAddress: "", + }, } comp, err := Compile[int](rules, map[string]int{ "ob1": ob1, @@ -283,6 +289,51 @@ func TestCompile(t *testing.T) { wantOutbound: ob6, // match range port rule 6881-6889 wantIP: nil, }, + { + host: HostInfo{ + Name: "crap.v2ex.com.", // trailing dot must not bypass exact domain rule + }, + proto: ProtocolTCP, + port: 80, + wantOutbound: ob1, + wantIP: net.ParseIP("2.2.2.2"), + }, + { + host: HostInfo{ + Name: "hoho.v2ex.com.", // trailing dot must not bypass wildcard domain rule + }, + proto: ProtocolUDP, + port: 9999, + wantOutbound: ob3, + wantIP: nil, + }, + { + host: HostInfo{ + Name: "real.microsoft.com.", // trailing dot must not bypass suffix domain rule + }, + proto: ProtocolUDP, + port: 5353, + wantOutbound: ob5, + wantIP: nil, + }, + { + host: HostInfo{ + Name: "microsoft.com...", // multiple trailing dots must also be normalized + }, + proto: ProtocolTCP, + port: 6000, + wantOutbound: ob5, + wantIP: nil, + }, + { + host: HostInfo{ + Name: "dotpattern.test", // host without dot must match rule whose pattern had a trailing dot + }, + proto: ProtocolTCP, + port: 443, + wantOutbound: ob1, + wantIP: nil, + }, } for _, test := range tests {