1
0
mirror of https://github.com/v2fly/v2ray-core.git synced 2024-12-22 10:08:15 -05: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/protocol"
"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/policy"
"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()
}
if err == nil && shouldOverride(result, sniffingRequest.OverrideDestinationForProtocol) {
domain := result.Domain()
newError("sniffed domain: ", domain).WriteToLog(session.ExportIDToError(ctx))
destination.Address = net.ParseAddress(domain)
ob.Target = destination
if domain, err := strmatcher.ToDomain(result.Domain()); err == nil {
newError("sniffed domain: ", domain, " for ", destination).WriteToLog(session.ExportIDToError(ctx))
destination.Address = net.ParseAddress(domain)
ob.Target = destination
}
}
d.routedDispatch(ctx, outbound, destination)
}()

View File

@ -5,6 +5,8 @@ import (
"regexp"
"strings"
"unicode/utf8"
"golang.org/x/net/idna"
)
// 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)
// * Digits 0 to 9
// * Hyphens(-) and Periods(.)
// 2. Non-ASCII characters not supported for now.
// * May support Internationalized domain name to Punycode if needed in the future.
// 2. If any non-ASCII characters, domain are converted from Internationalized domain name to Punycode.
func ToDomain(pattern string) (string, error) {
builder := strings.Builder{}
builder.Grow(len(pattern))
for i := 0; i < len(pattern); i++ {
c := pattern[i]
if c >= utf8.RuneSelf {
return "", errors.New("non-ASCII characters not supported for now")
for {
isASCII, hasUpper := true, false
for i := 0; i < len(pattern); i++ {
c := pattern[i]
if c >= utf8.RuneSelf {
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 {
case 'A' <= c && c <= 'Z':
c += 'a' - 'A'
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")
if !isASCII {
var err error
pattern, err = idna.New().ToASCII(pattern)
if err != nil {
return "", err
}
continue
}
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.

View File

@ -1,7 +1,9 @@
package strmatcher_test
import (
"reflect"
"testing"
"unsafe"
"github.com/v2fly/v2ray-core/v5/common"
. "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"
"github.com/v2fly/v2ray-core/v5/common/session"
"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/features/dns"
"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) {
isIPQuery, domain, id, qType := parseIPQuery(b.Bytes())
if isIPQuery {
go h.handleIPQuery(id, qType, domain, writer)
continue
if domain, err := strmatcher.ToDomain(domain); err == nil {
go h.handleIPQuery(id, qType, domain, writer)
continue
}
}
}