mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-22 01:57:12 -05:00
[app/dispatcher] [proxy/dns] Support domain string validation (#2188)
This commit is contained in:
parent
305ec638f4
commit
ac0d9480bd
@ -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)
|
||||
}()
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user