1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-09-28 14:56:33 -04:00

[app/dispatcher] [proxy/dns] Support domain string validation (#2188)

This commit is contained in:
Vigilans 2022-12-10 17:07:59 +08:00 committed by GitHub
parent 305ec638f4
commit ac0d9480bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 120 additions and 25 deletions

View File

@ -15,6 +15,7 @@ import (
"github.com/v2fly/v2ray-core/v5/common/net" "github.com/v2fly/v2ray-core/v5/common/net"
"github.com/v2fly/v2ray-core/v5/common/protocol" "github.com/v2fly/v2ray-core/v5/common/protocol"
"github.com/v2fly/v2ray-core/v5/common/session" "github.com/v2fly/v2ray-core/v5/common/session"
"github.com/v2fly/v2ray-core/v5/common/strmatcher"
"github.com/v2fly/v2ray-core/v5/features/outbound" "github.com/v2fly/v2ray-core/v5/features/outbound"
"github.com/v2fly/v2ray-core/v5/features/policy" "github.com/v2fly/v2ray-core/v5/features/policy"
"github.com/v2fly/v2ray-core/v5/features/routing" "github.com/v2fly/v2ray-core/v5/features/routing"
@ -224,11 +225,12 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
content.Protocol = result.Protocol() content.Protocol = result.Protocol()
} }
if err == nil && shouldOverride(result, sniffingRequest.OverrideDestinationForProtocol) { if err == nil && shouldOverride(result, sniffingRequest.OverrideDestinationForProtocol) {
domain := result.Domain() if domain, err := strmatcher.ToDomain(result.Domain()); err == nil {
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx)) newError("sniffed domain: ", domain, " for ", destination).WriteToLog(session.ExportIDToError(ctx))
destination.Address = net.ParseAddress(domain) destination.Address = net.ParseAddress(domain)
ob.Target = destination ob.Target = destination
} }
}
d.routedDispatch(ctx, outbound, destination) d.routedDispatch(ctx, outbound, destination)
}() }()
} }

View File

@ -5,6 +5,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"golang.org/x/net/idna"
) )
// FullMatcher is an implementation of Matcher. // FullMatcher is an implementation of Matcher.
@ -151,19 +153,19 @@ func (t Type) NewDomainPattern(pattern string) (Matcher, error) {
// * Letters A to Z (no distinction between uppercase and lowercase, we convert to lowers) // * Letters A to Z (no distinction between uppercase and lowercase, we convert to lowers)
// * Digits 0 to 9 // * Digits 0 to 9
// * Hyphens(-) and Periods(.) // * Hyphens(-) and Periods(.)
// 2. Non-ASCII characters not supported for now. // 2. If any non-ASCII characters, domain are converted from Internationalized domain name to Punycode.
// * May support Internationalized domain name to Punycode if needed in the future.
func ToDomain(pattern string) (string, error) { func ToDomain(pattern string) (string, error) {
builder := strings.Builder{} for {
builder.Grow(len(pattern)) isASCII, hasUpper := true, false
for i := 0; i < len(pattern); i++ { for i := 0; i < len(pattern); i++ {
c := pattern[i] c := pattern[i]
if c >= utf8.RuneSelf { if c >= utf8.RuneSelf {
return "", errors.New("non-ASCII characters not supported for now") isASCII = false
break
} }
switch { switch {
case 'A' <= c && c <= 'Z': case 'A' <= c && c <= 'Z':
c += 'a' - 'A' hasUpper = true
case 'a' <= c && c <= 'z': case 'a' <= c && c <= 'z':
case '0' <= c && c <= '9': case '0' <= c && c <= '9':
case c == '-': case c == '-':
@ -171,9 +173,21 @@ func ToDomain(pattern string) (string, error) {
default: default:
return "", errors.New("pattern string does not conform to Letter-Digit-Hyphen (LDH) subset") return "", errors.New("pattern string does not conform to Letter-Digit-Hyphen (LDH) subset")
} }
builder.WriteByte(c)
} }
return builder.String(), nil if !isASCII {
var err error
pattern, err = idna.New().ToASCII(pattern)
if err != nil {
return "", err
}
continue
}
if hasUpper {
pattern = strings.ToLower(pattern)
}
break
}
return pattern, nil
} }
// MatcherGroupForAll is an interface indicating a MatcherGroup could accept all types of matchers. // MatcherGroupForAll is an interface indicating a MatcherGroup could accept all types of matchers.

View File

@ -1,7 +1,9 @@
package strmatcher_test package strmatcher_test
import ( import (
"reflect"
"testing" "testing"
"unsafe"
"github.com/v2fly/v2ray-core/v5/common" "github.com/v2fly/v2ray-core/v5/common"
. "github.com/v2fly/v2ray-core/v5/common/strmatcher" . "github.com/v2fly/v2ray-core/v5/common/strmatcher"
@ -71,3 +73,77 @@ func TestMatcher(t *testing.T) {
} }
} }
} }
func TestToDomain(t *testing.T) {
{ // Test normal ASCII domain, which should not trigger new string data allocation
input := "v2fly.org"
domain, err := ToDomain(input)
if err != nil {
t.Error("unexpected error: ", err)
}
if domain != input {
t.Error("unexpected output: ", domain, " for test case ", input)
}
if (*reflect.StringHeader)(unsafe.Pointer(&input)).Data != (*reflect.StringHeader)(unsafe.Pointer(&domain)).Data {
t.Error("different string data of output: ", domain, " and test case ", input)
}
}
{ // Test ASCII domain containing upper case letter, which should be converted to lower case
input := "v2FLY.oRg"
domain, err := ToDomain(input)
if err != nil {
t.Error("unexpected error: ", err)
}
if domain != "v2fly.org" {
t.Error("unexpected output: ", domain, " for test case ", input)
}
}
{ // Test internationalized domain, which should be translated to ASCII punycode
input := "v2fly.公益"
domain, err := ToDomain(input)
if err != nil {
t.Error("unexpected error: ", err)
}
if domain != "v2fly.xn--55qw42g" {
t.Error("unexpected output: ", domain, " for test case ", input)
}
}
{ // Test internationalized domain containing upper case letter
input := "v2FLY.公益"
domain, err := ToDomain(input)
if err != nil {
t.Error("unexpected error: ", err)
}
if domain != "v2fly.xn--55qw42g" {
t.Error("unexpected output: ", domain, " for test case ", input)
}
}
{ // Test domain name of invalid character, which should return with error
input := "{"
_, err := ToDomain(input)
if err == nil {
t.Error("unexpected non error for test case ", input)
}
}
{ // Test domain name containing a space, which should return with error
input := "Mijia Cloud"
_, err := ToDomain(input)
if err == nil {
t.Error("unexpected non error for test case ", input)
}
}
{ // Test domain name containing an underscore, which should return with error
input := "Mijia_Cloud.com"
_, err := ToDomain(input)
if err == nil {
t.Error("unexpected non error for test case ", input)
}
}
{ // Test internationalized domain containing invalid character
input := "Mijia Cloud.公司"
_, err := ToDomain(input)
if err == nil {
t.Error("unexpected non error for test case ", input)
}
}
}

View File

@ -15,6 +15,7 @@ import (
dns_proto "github.com/v2fly/v2ray-core/v5/common/protocol/dns" dns_proto "github.com/v2fly/v2ray-core/v5/common/protocol/dns"
"github.com/v2fly/v2ray-core/v5/common/session" "github.com/v2fly/v2ray-core/v5/common/session"
"github.com/v2fly/v2ray-core/v5/common/signal" "github.com/v2fly/v2ray-core/v5/common/signal"
"github.com/v2fly/v2ray-core/v5/common/strmatcher"
"github.com/v2fly/v2ray-core/v5/common/task" "github.com/v2fly/v2ray-core/v5/common/task"
"github.com/v2fly/v2ray-core/v5/features/dns" "github.com/v2fly/v2ray-core/v5/features/dns"
"github.com/v2fly/v2ray-core/v5/features/policy" "github.com/v2fly/v2ray-core/v5/features/policy"
@ -190,10 +191,12 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, d internet.
if !h.isOwnLink(ctx) { if !h.isOwnLink(ctx) {
isIPQuery, domain, id, qType := parseIPQuery(b.Bytes()) isIPQuery, domain, id, qType := parseIPQuery(b.Bytes())
if isIPQuery { if isIPQuery {
if domain, err := strmatcher.ToDomain(domain); err == nil {
go h.handleIPQuery(id, qType, domain, writer) go h.handleIPQuery(id, qType, domain, writer)
continue continue
} }
} }
}
if err := connWriter.WriteMessage(b); err != nil { if err := connWriter.WriteMessage(b); err != nil {
return err return err