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,10 +225,11 @@ 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,29 +153,41 @@ 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 {
case 'A' <= c && c <= 'Z':
hasUpper = true
case 'a' <= c && c <= 'z':
case '0' <= c && c <= '9':
case c == '-':
case c == '.':
default:
return "", errors.New("pattern string does not conform to Letter-Digit-Hyphen (LDH) subset")
}
} }
switch { if !isASCII {
case 'A' <= c && c <= 'Z': var err error
c += 'a' - 'A' pattern, err = idna.New().ToASCII(pattern)
case 'a' <= c && c <= 'z': if err != nil {
case '0' <= c && c <= '9': return "", err
case c == '-': }
case c == '.': continue
default:
return "", errors.New("pattern string does not conform to Letter-Digit-Hyphen (LDH) subset")
} }
builder.WriteByte(c) if hasUpper {
pattern = strings.ToLower(pattern)
}
break
} }
return builder.String(), nil 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,8 +191,10 @@ 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 {
go h.handleIPQuery(id, qType, domain, writer) if domain, err := strmatcher.ToDomain(domain); err == nil {
continue go h.handleIPQuery(id, qType, domain, writer)
continue
}
} }
} }