diff --git a/extras/go.mod b/extras/go.mod index 3c67510..bee0628 100644 --- a/extras/go.mod +++ b/extras/go.mod @@ -10,6 +10,7 @@ require ( github.com/stretchr/testify v1.8.4 github.com/txthinking/socks5 v0.0.0-20230325130024-4230056ae301 golang.org/x/crypto v0.14.0 + golang.org/x/net v0.17.0 google.golang.org/protobuf v1.28.1 ) @@ -28,7 +29,6 @@ require ( go.uber.org/mock v0.3.0 // indirect golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/tools v0.11.1 // indirect diff --git a/extras/outbounds/acl/matchers.go b/extras/outbounds/acl/matchers.go index 65b2458..fe40297 100644 --- a/extras/outbounds/acl/matchers.go +++ b/extras/outbounds/acl/matchers.go @@ -2,6 +2,8 @@ package acl import ( "net" + + "golang.org/x/net/idna" ) type hostMatcher interface { @@ -30,10 +32,14 @@ type domainMatcher struct { } func (m *domainMatcher) Match(host HostInfo) bool { - if m.Wildcard { - return deepMatchRune([]rune(host.Name), []rune(m.Pattern)) + name, err := idna.ToUnicode(host.Name) + if err != nil { + name = host.Name } - return m.Pattern == host.Name + if m.Wildcard { + return deepMatchRune([]rune(name), []rune(m.Pattern)) + } + return name == m.Pattern } func deepMatchRune(str, pattern []rune) bool { diff --git a/extras/outbounds/acl/matchers_test.go b/extras/outbounds/acl/matchers_test.go index e1b437d..0e7f39c 100644 --- a/extras/outbounds/acl/matchers_test.go +++ b/extras/outbounds/acl/matchers_test.go @@ -162,6 +162,17 @@ func Test_domainMatcher_Match(t *testing.T) { }, want: true, }, + { + name: "non-wildcard IDN match", + fields: fields{ + Pattern: "政府.中国", + Wildcard: false, + }, + host: HostInfo{ + Name: "xn--mxtq1m.xn--fiqs8s", + }, + want: true, + }, { name: "non-wildcard no match", fields: fields{ @@ -173,6 +184,17 @@ func Test_domainMatcher_Match(t *testing.T) { }, want: false, }, + { + name: "non-wildcard IDN no match", + fields: fields{ + Pattern: "政府.中国", + Wildcard: false, + }, + host: HostInfo{ + Name: "xn--mxtq1m.xn--yfro4i67o", + }, + want: false, + }, { name: "wildcard match 1", fields: fields{ @@ -195,6 +217,28 @@ func Test_domainMatcher_Match(t *testing.T) { }, want: true, }, + { + name: "wildcard IDN match 1", + fields: fields{ + Pattern: "战狼*.com", + Wildcard: true, + }, + host: HostInfo{ + Name: "xn--2-x14by21c.com", + }, + want: true, + }, + { + name: "wildcard IDN match 2", + fields: fields{ + Pattern: "*大学*", + Wildcard: true, + }, + host: HostInfo{ + Name: "xn--xkry9kk1bz66a.xn--ses554g", + }, + want: true, + }, { name: "wildcard no match", fields: fields{ @@ -206,6 +250,17 @@ func Test_domainMatcher_Match(t *testing.T) { }, want: false, }, + { + name: "wildcard IDN no match", + fields: fields{ + Pattern: "*呵呵*", + Wildcard: true, + }, + host: HostInfo{ + Name: "xn--6qqt7juua.cn", + }, + want: false, + }, { name: "empty", fields: fields{