mirror of
https://github.com/v2fly/v2ray-core.git
synced 2024-12-22 18:17:52 -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/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)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user