Browse Source

Fakedns improvements (#731)

Co-authored-by: Shelikhoo <[email protected]>
Co-authored-by: sixg0000d <[email protected]>
Co-authored-by: Loyalsoldier <[email protected]>
yuhan6665 4 years ago
parent
commit
6b6974c804

+ 9 - 4
app/dispatcher/default.go

@@ -193,10 +193,15 @@ func shouldOverride(ctx context.Context, result SniffResult, request session.Sni
 		if strings.HasPrefix(protocolString, p) {
 			return true
 		}
-		if fakeDNSEngine != nil && protocolString != "bittorrent" && p == "fakedns" &&
-			destination.Address.Family().IsIP() && fakeDNSEngine.GetFakeIPRange().Contains(destination.Address.IP()) {
-			newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
-			return true
+		if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok && protocolString != "bittorrent" && p == "fakedns" &&
+			destination.Address.Family().IsIP() && fkr0.IsIPInIPPool(destination.Address) {
+				newError("Using sniffer ", protocolString, " since the fake DNS missed").WriteToLog(session.ExportIDToError(ctx))
+				return true
+		}
+		if resultSubset, ok := result.(SnifferIsProtoSubsetOf); ok {
+			if resultSubset.IsProtoSubsetOf(p) {
+				return true
+			}
 		}
 	}
 

+ 68 - 0
app/dispatcher/fakednssniffer.go

@@ -2,6 +2,7 @@ package dispatcher
 
 import (
 	"context"
+	"strings"
 
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/net"
@@ -32,6 +33,15 @@ func newFakeDNSSniffer(ctx context.Context) (protocolSnifferWithMetadata, error)
 				return &fakeDNSSniffResult{domainName: domainFromFakeDNS}, nil
 			}
 		}
+
+		if ipAddressInRangeValueI := ctx.Value(ipAddressInRange); ipAddressInRangeValueI != nil {
+			ipAddressInRangeValue := ipAddressInRangeValueI.(*ipAddressInRangeOpt)
+			if fkr0, ok := fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
+				inPool := fkr0.IsIPInIPPool(Target.Address)
+				ipAddressInRangeValue.addressInRange = &inPool
+			}
+		}
+
 		return nil, common.ErrNoClue
 	}, metadataSniffer: true}, nil
 }
@@ -47,3 +57,61 @@ func (fakeDNSSniffResult) Protocol() string {
 func (f fakeDNSSniffResult) Domain() string {
 	return f.domainName
 }
+
+type fakeDNSExtraOpts int
+
+const ipAddressInRange fakeDNSExtraOpts = 1
+
+type ipAddressInRangeOpt struct {
+	addressInRange *bool
+}
+
+type DNSThenOthersSniffResult struct {
+	domainName           string
+	protocolOriginalName string
+}
+
+func (f DNSThenOthersSniffResult) IsProtoSubsetOf(protocolName string) bool {
+	return strings.HasPrefix(protocolName, f.protocolOriginalName)
+}
+
+func (DNSThenOthersSniffResult) Protocol() string {
+	return "fakedns+others"
+}
+
+func (f DNSThenOthersSniffResult) Domain() string {
+	return f.domainName
+}
+
+func newFakeDNSThenOthers(ctx context.Context, fakeDNSSniffer protocolSnifferWithMetadata, others []protocolSnifferWithMetadata) (
+	protocolSnifferWithMetadata, error) { // nolint: unparam
+	// ctx may be used in the future
+	_ = ctx
+	return protocolSnifferWithMetadata{
+		protocolSniffer: func(ctx context.Context, bytes []byte) (SniffResult, error) {
+			ipAddressInRangeValue := &ipAddressInRangeOpt{}
+			ctx = context.WithValue(ctx, ipAddressInRange, ipAddressInRangeValue)
+			result, err := fakeDNSSniffer.protocolSniffer(ctx, bytes)
+			if err == nil {
+				return result, nil
+			}
+			if ipAddressInRangeValue.addressInRange != nil {
+				if *ipAddressInRangeValue.addressInRange {
+					for _, v := range others {
+						if v.metadataSniffer || bytes != nil {
+							if result, err := v.protocolSniffer(ctx, bytes); err == nil {
+								return DNSThenOthersSniffResult{domainName: result.Domain(), protocolOriginalName: result.Protocol()}, nil
+							}
+						}
+					}
+					return nil, common.ErrNoClue
+				}
+				newError("ip address not in fake dns range, return as is").AtDebug().WriteToLog()
+				return nil, common.ErrNoClue
+			}
+			newError("fake dns sniffer did not set address in range option, assume false.").AtWarning().WriteToLog()
+			return nil, common.ErrNoClue
+		},
+		metadataSniffer: false,
+	}, nil
+}

+ 9 - 0
app/dispatcher/sniffer.go

@@ -37,7 +37,12 @@ func NewSniffer(ctx context.Context) *Sniffer {
 		},
 	}
 	if sniffer, err := newFakeDNSSniffer(ctx); err == nil {
+		others := ret.sniffer
 		ret.sniffer = append(ret.sniffer, sniffer)
+		fakeDNSThenOthers, err := newFakeDNSThenOthers(ctx, sniffer, others)
+		if err == nil {
+			ret.sniffer = append([]protocolSnifferWithMetadata{fakeDNSThenOthers}, ret.sniffer...)
+		}
 	}
 	return ret
 }
@@ -121,3 +126,7 @@ func (c compositeResult) ProtocolForDomainResult() string {
 type SnifferResultComposite interface {
 	ProtocolForDomainResult() string
 }
+
+type SnifferIsProtoSubsetOf interface {
+	IsProtoSubsetOf(protocolName string) bool
+}

+ 3 - 1
app/dns/dns.go

@@ -29,6 +29,7 @@ type DNS struct {
 	ipOption               *dns.IPOption
 	hosts                  *StaticHosts
 	clients                []*Client
+	ctx                    context.Context
 	domainMatcher          strmatcher.IndexMatcher
 	matcherInfos           []*DomainMatcherInfo
 }
@@ -136,6 +137,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
 		hosts:                  hosts,
 		ipOption:               ipOption,
 		clients:                clients,
+		ctx:                    ctx,
 		domainMatcher:          domainMatcher,
 		matcherInfos:           matcherInfos,
 		disableCache:           config.DisableCache,
@@ -199,7 +201,7 @@ func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
 
 	// Name servers lookup
 	errs := []error{}
-	ctx := session.ContextWithInbound(context.Background(), &session.Inbound{Tag: s.tag})
+	ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag})
 	for _, client := range s.sortClients(domain) {
 		if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
 			newError("skip DNS resolution for domain ", domain, " at server ", client.Name()).AtDebug().WriteToLog()

+ 115 - 5
app/dns/fakedns/fake.go

@@ -20,12 +20,30 @@ type Holder struct {
 	config *FakeDnsPool
 }
 
+func (fkdns *Holder) IsIPInIPPool(ip net.Address) bool {
+	if ip.Family().IsDomain() {
+		return false
+	}
+	return fkdns.ipRange.Contains(ip.IP())
+}
+
+func (fkdns *Holder) GetFakeIPForDomain3(domain string, ipv4, ipv6 bool) []net.Address {
+	isIPv6 := fkdns.ipRange.IP.To4() == nil
+	if (isIPv6 && ipv6) || (!isIPv6 && ipv4) {
+		return fkdns.GetFakeIPForDomain(domain)
+	}
+	return []net.Address{}
+}
+
 func (*Holder) Type() interface{} {
 	return (*dns.FakeDNSEngine)(nil)
 }
 
 func (fkdns *Holder) Start() error {
-	return fkdns.initializeFromConfig()
+	if fkdns.config != nil && fkdns.config.IpPool != "" && fkdns.config.LruSize != 0 {
+		return fkdns.initializeFromConfig()
+	}
+	return newError("invalid fakeDNS setting")
 }
 
 func (fkdns *Holder) Close() error {
@@ -41,7 +59,7 @@ func NewFakeDNSHolder() (*Holder, error) {
 	if fkdns, err = NewFakeDNSHolderConfigOnly(nil); err != nil {
 		return nil, newError("Unable to create Fake Dns Engine").Base(err).AtError()
 	}
-	err = fkdns.initialize(dns.FakeIPPool, 65535)
+	err = fkdns.initialize(dns.FakeIPv4Pool, 65535)
 	if err != nil {
 		return nil, err
 	}
@@ -117,9 +135,92 @@ func (fkdns *Holder) GetDomainFromFakeDNS(ip net.Address) string {
 	return ""
 }
 
-// GetFakeIPRange return fake IP range from configuration
-func (fkdns *Holder) GetFakeIPRange() *gonet.IPNet {
-	return fkdns.ipRange
+type HolderMulti struct {
+	holders []*Holder
+
+	config *FakeDnsPoolMulti
+}
+
+func (h *HolderMulti) IsIPInIPPool(ip net.Address) bool {
+	if ip.Family().IsDomain() {
+		return false
+	}
+	for _, v := range h.holders {
+		if v.IsIPInIPPool(ip) {
+			return true
+		}
+	}
+	return false
+}
+
+func (h *HolderMulti) GetFakeIPForDomain3(domain string, ipv4, ipv6 bool) []net.Address {
+	var ret []net.Address
+	for _, v := range h.holders {
+		ret = append(ret, v.GetFakeIPForDomain3(domain, ipv4, ipv6)...)
+	}
+	return ret
+}
+
+func (h *HolderMulti) GetFakeIPForDomain(domain string) []net.Address {
+	var ret []net.Address
+	for _, v := range h.holders {
+		ret = append(ret, v.GetFakeIPForDomain(domain)...)
+	}
+	return ret
+}
+
+func (h *HolderMulti) GetDomainFromFakeDNS(ip net.Address) string {
+	for _, v := range h.holders {
+		if domain := v.GetDomainFromFakeDNS(ip); domain != "" {
+			return domain
+		}
+	}
+	return ""
+}
+
+func (h *HolderMulti) Type() interface{} {
+	return (*dns.FakeDNSEngine)(nil)
+}
+
+func (h *HolderMulti) Start() error {
+	for _, v := range h.holders {
+		if v.config != nil && v.config.IpPool != "" && v.config.LruSize != 0 {
+			if err := v.Start(); err != nil {
+				return newError("Cannot start all fake dns pools").Base(err)
+			}
+		} else {
+			return newError("invalid fakeDNS setting")
+		}
+	}
+	return nil
+}
+
+func (h *HolderMulti) Close() error {
+	for _, v := range h.holders {
+		if err := v.Close(); err != nil {
+			return newError("Cannot close all fake dns pools").Base(err)
+		}
+	}
+	return nil
+}
+
+func (h *HolderMulti) createHolderGroups() error {
+	for _, v := range h.config.Pools {
+		holder, err := NewFakeDNSHolderConfigOnly(v)
+		if err != nil {
+			return err
+		}
+		h.holders = append(h.holders, holder)
+	}
+	return nil
+}
+
+func NewFakeDNSHolderMulti(conf *FakeDnsPoolMulti) (*HolderMulti, error) {
+	holderMulti := &HolderMulti{nil, conf}
+	if err := holderMulti.createHolderGroups(); err != nil {
+		return nil, err
+	}
+	return holderMulti, nil
 }
 
 func init() {
@@ -131,4 +232,13 @@ func init() {
 		}
 		return f, nil
 	}))
+
+	common.Must(common.RegisterConfig((*FakeDnsPoolMulti)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
+		var f *HolderMulti
+		var err error
+		if f, err = NewFakeDNSHolderMulti(config.(*FakeDnsPoolMulti)); err != nil {
+			return nil, err
+		}
+		return f, nil
+	}))
 }

+ 81 - 15
app/dns/fakedns/fakedns.pb.go

@@ -75,6 +75,53 @@ func (x *FakeDnsPool) GetLruSize() int64 {
 	return 0
 }
 
+type FakeDnsPoolMulti struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Pools []*FakeDnsPool `protobuf:"bytes,1,rep,name=pools,proto3" json:"pools,omitempty"`
+}
+
+func (x *FakeDnsPoolMulti) Reset() {
+	*x = FakeDnsPoolMulti{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FakeDnsPoolMulti) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FakeDnsPoolMulti) ProtoMessage() {}
+
+func (x *FakeDnsPoolMulti) ProtoReflect() protoreflect.Message {
+	mi := &file_app_dns_fakedns_fakedns_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use FakeDnsPoolMulti.ProtoReflect.Descriptor instead.
+func (*FakeDnsPoolMulti) Descriptor() ([]byte, []int) {
+	return file_app_dns_fakedns_fakedns_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *FakeDnsPoolMulti) GetPools() []*FakeDnsPool {
+	if x != nil {
+		return x.Pools
+	}
+	return nil
+}
+
 var File_app_dns_fakedns_fakedns_proto protoreflect.FileDescriptor
 
 var file_app_dns_fakedns_fakedns_proto_rawDesc = []byte{
@@ -85,13 +132,18 @@ var file_app_dns_fakedns_fakedns_proto_rawDesc = []byte{
 	0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x6f, 0x6c, 0x18,
 	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x70, 0x50, 0x6f, 0x6f, 0x6c, 0x12, 0x18, 0x0a,
 	0x07, 0x6c, 0x72, 0x75, 0x53, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07,
-	0x6c, 0x72, 0x75, 0x53, 0x69, 0x7a, 0x65, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
-	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61, 0x6b, 0x65,
-	0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
-	0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65,
-	0x2f, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73,
-	0xaa, 0x02, 0x14, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x2e,
-	0x46, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6c, 0x72, 0x75, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x4b, 0x0a, 0x10, 0x46, 0x61, 0x6b, 0x65, 0x44,
+	0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x12, 0x37, 0x0a, 0x05, 0x70,
+	0x6f, 0x6f, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78, 0x72, 0x61,
+	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e,
+	0x73, 0x2e, 0x46, 0x61, 0x6b, 0x65, 0x44, 0x6e, 0x73, 0x50, 0x6f, 0x6f, 0x6c, 0x52, 0x05, 0x70,
+	0x6f, 0x6f, 0x6c, 0x73, 0x42, 0x5e, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73,
+	0x50, 0x01, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78,
+	0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70,
+	0x70, 0x2f, 0x64, 0x6e, 0x73, 0x2f, 0x66, 0x61, 0x6b, 0x65, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x14,
+	0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x2e, 0x46, 0x61, 0x6b,
+	0x65, 0x64, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -106,16 +158,18 @@ func file_app_dns_fakedns_fakedns_proto_rawDescGZIP() []byte {
 	return file_app_dns_fakedns_fakedns_proto_rawDescData
 }
 
-var file_app_dns_fakedns_fakedns_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_app_dns_fakedns_fakedns_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
 var file_app_dns_fakedns_fakedns_proto_goTypes = []interface{}{
-	(*FakeDnsPool)(nil), // 0: xray.app.dns.fakedns.FakeDnsPool
+	(*FakeDnsPool)(nil),      // 0: xray.app.dns.fakedns.FakeDnsPool
+	(*FakeDnsPoolMulti)(nil), // 1: xray.app.dns.fakedns.FakeDnsPoolMulti
 }
 var file_app_dns_fakedns_fakedns_proto_depIdxs = []int32{
-	0, // [0:0] is the sub-list for method output_type
-	0, // [0:0] is the sub-list for method input_type
-	0, // [0:0] is the sub-list for extension type_name
-	0, // [0:0] is the sub-list for extension extendee
-	0, // [0:0] is the sub-list for field type_name
+	0, // 0: xray.app.dns.fakedns.FakeDnsPoolMulti.pools:type_name -> xray.app.dns.fakedns.FakeDnsPool
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
 }
 
 func init() { file_app_dns_fakedns_fakedns_proto_init() }
@@ -136,6 +190,18 @@ func file_app_dns_fakedns_fakedns_proto_init() {
 				return nil
 			}
 		}
+		file_app_dns_fakedns_fakedns_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FakeDnsPoolMulti); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -143,7 +209,7 @@ func file_app_dns_fakedns_fakedns_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_app_dns_fakedns_fakedns_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   1,
+			NumMessages:   2,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 4 - 0
app/dns/fakedns/fakedns.proto

@@ -9,4 +9,8 @@ option java_multiple_files = true;
 message FakeDnsPool{
   string ip_pool = 1; //CIDR of IP pool used as fake DNS IP
   int64  lruSize = 2; //Size of Pool for remembering relationship between domain name and IP address
+}
+
+message FakeDnsPoolMulti{
+  repeated FakeDnsPool pools = 1;
 }

+ 80 - 2
app/dns/fakedns/fakedns_test.go

@@ -3,6 +3,8 @@ package fakedns
 import (
 	"testing"
 
+	gonet "net"
+
 	"github.com/stretchr/testify/assert"
 
 	"github.com/xtls/xray-core/common"
@@ -11,7 +13,7 @@ import (
 	"github.com/xtls/xray-core/features/dns"
 )
 
-var ipPrefix = "198.18."
+var ipPrefix = "198.1"
 
 func TestNewFakeDnsHolder(_ *testing.T) {
 	_, err := NewFakeDNSHolder()
@@ -67,7 +69,7 @@ func TestFakeDnsHolderCreateMappingManySingleDomain(t *testing.T) {
 
 func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
 	fkdns, err := NewFakeDNSHolderConfigOnly(&FakeDnsPool{
-		IpPool:  dns.FakeIPPool,
+		IpPool:  dns.FakeIPv4Pool,
 		LruSize: 256,
 	})
 	common.Must(err)
@@ -101,3 +103,79 @@ func TestFakeDnsHolderCreateMappingAndRollOver(t *testing.T) {
 		}
 	}
 }
+
+func TestFakeDNSMulti(t *testing.T) {
+	fakeMulti, err := NewFakeDNSHolderMulti(&FakeDnsPoolMulti{
+		Pools: []*FakeDnsPool{{
+			IpPool:  "240.0.0.0/12",
+			LruSize: 256,
+		}, {
+			IpPool:  "fddd:c5b4:ff5f:f4f0::/64",
+			LruSize: 256,
+		}},
+	},
+	)
+	common.Must(err)
+
+	err = fakeMulti.Start()
+
+	common.Must(err)
+
+	assert.Nil(t, err, "Should not throw error")
+	_ = fakeMulti
+
+	t.Run("checkInRange", func(t *testing.T) {
+		t.Run("ipv4", func(t *testing.T) {
+			inPool := fakeMulti.IsIPInIPPool(net.IPAddress([]byte{240, 0, 0, 5}))
+			assert.True(t, inPool)
+		})
+		t.Run("ipv6", func(t *testing.T) {
+			ip, err := gonet.ResolveIPAddr("ip", "fddd:c5b4:ff5f:f4f0::5")
+			assert.Nil(t, err)
+			inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
+			assert.True(t, inPool)
+		})
+		t.Run("ipv4_inverse", func(t *testing.T) {
+			inPool := fakeMulti.IsIPInIPPool(net.IPAddress([]byte{241, 0, 0, 5}))
+			assert.False(t, inPool)
+		})
+		t.Run("ipv6_inverse", func(t *testing.T) {
+			ip, err := gonet.ResolveIPAddr("ip", "fcdd:c5b4:ff5f:f4f0::5")
+			assert.Nil(t, err)
+			inPool := fakeMulti.IsIPInIPPool(net.IPAddress(ip.IP))
+			assert.False(t, inPool)
+		})
+	})
+
+	t.Run("allocateTwoAddressForTwoPool", func(t *testing.T) {
+		address := fakeMulti.GetFakeIPForDomain("fakednstest.v2fly.org")
+		assert.Len(t, address, 2, "should be 2 address one for each pool")
+		t.Run("eachOfThemShouldResolve:0", func(t *testing.T) {
+			domain := fakeMulti.GetDomainFromFakeDNS(address[0])
+			assert.Equal(t, "fakednstest.v2fly.org", domain)
+		})
+		t.Run("eachOfThemShouldResolve:1", func(t *testing.T) {
+			domain := fakeMulti.GetDomainFromFakeDNS(address[1])
+			assert.Equal(t, "fakednstest.v2fly.org", domain)
+		})
+	})
+
+	t.Run("understandIPTypeSelector", func(t *testing.T) {
+		t.Run("ipv4", func(t *testing.T) {
+			address := fakeMulti.GetFakeIPForDomain3("fakednstestipv4.v2fly.org", true, false)
+			assert.Len(t, address, 1, "should be 1 address")
+			assert.True(t, address[0].Family().IsIPv4())
+		})
+		t.Run("ipv6", func(t *testing.T) {
+			address := fakeMulti.GetFakeIPForDomain3("fakednstestipv6.v2fly.org", false, true)
+			assert.Len(t, address, 1, "should be 1 address")
+			assert.True(t, address[0].Family().IsIPv6())
+		})
+		t.Run("ipv46", func(t *testing.T) {
+			address := fakeMulti.GetFakeIPForDomain3("fakednstestipv46.v2fly.org", true, true)
+			assert.Len(t, address, 2, "should be 2 address")
+			assert.True(t, address[0].Family().IsIPv4())
+			assert.True(t, address[1].Family().IsIPv6())
+		})
+	})
+}

+ 11 - 3
app/dns/nameserver_fakedns.go

@@ -20,7 +20,7 @@ func (FakeDNSServer) Name() string {
 	return "FakeDNS"
 }
 
-func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, _ dns.IPOption, _ bool) ([]net.IP, error) {
+func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, opt dns.IPOption, _ bool) ([]net.IP, error) {
 	if f.fakeDNSEngine == nil {
 		if err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) {
 			f.fakeDNSEngine = fd
@@ -28,7 +28,12 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, _
 			return nil, newError("Unable to locate a fake DNS Engine").Base(err).AtError()
 		}
 	}
-	ips := f.fakeDNSEngine.GetFakeIPForDomain(domain)
+	var ips []net.Address
+	if fkr0, ok := f.fakeDNSEngine.(dns.FakeDNSEngineRev0); ok {
+		ips = fkr0.GetFakeIPForDomain3(domain, opt.IPv4Enable, opt.IPv6Enable)
+	} else {
+		ips = f.fakeDNSEngine.GetFakeIPForDomain(domain)
+	}
 
 	netIP, err := toNetIP(ips)
 	if err != nil {
@@ -37,5 +42,8 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, _
 
 	newError(f.Name(), " got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
 
-	return netIP, nil
+	if len(netIP) > 0 {
+		return netIP, nil
+	}
+	return nil, dns.ErrEmptyResponse
 }

+ 8 - 4
features/dns/fakedns.go

@@ -1,8 +1,6 @@
 package dns
 
 import (
-	gonet "net"
-
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/features"
 )
@@ -11,7 +9,13 @@ type FakeDNSEngine interface {
 	features.Feature
 	GetFakeIPForDomain(domain string) []net.Address
 	GetDomainFromFakeDNS(ip net.Address) string
-	GetFakeIPRange() *gonet.IPNet
 }
 
-var FakeIPPool = "198.18.0.0/16"
+var FakeIPv4Pool = "198.18.0.0/15"
+var FakeIPv6Pool = "fc00::/18"
+
+type FakeDNSEngineRev0 interface {
+	FakeDNSEngine
+	IsIPInIPPool(ip net.Address) bool
+	GetFakeIPForDomain3(domain string, IPv4, IPv6 bool) []net.Address
+}

+ 88 - 25
infra/conf/fakedns.go

@@ -1,65 +1,128 @@
 package conf
 
 import (
-	"github.com/golang/protobuf/proto"
+	"encoding/json"
+	"strings"
 
 	"github.com/xtls/xray-core/app/dns/fakedns"
 	"github.com/xtls/xray-core/features/dns"
 )
 
-type FakeDNSConfig struct {
+type FakeDNSPoolElementConfig struct {
 	IPPool  string `json:"ipPool"`
-	LruSize int64  `json:"poolSize"`
+	LRUSize int64  `json:"poolSize"`
+}
+
+type FakeDNSConfig struct {
+	pool  *FakeDNSPoolElementConfig
+	pools []*FakeDNSPoolElementConfig
 }
 
-func (f FakeDNSConfig) Build() (proto.Message, error) {
-	return &fakedns.FakeDnsPool{
-		IpPool:  f.IPPool,
-		LruSize: f.LruSize,
-	}, nil
+// UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
+func (f *FakeDNSConfig) UnmarshalJSON(data []byte) error {
+	var pool FakeDNSPoolElementConfig
+	var pools []*FakeDNSPoolElementConfig
+	switch {
+	case json.Unmarshal(data, &pool) == nil:
+		f.pool = &pool
+	case json.Unmarshal(data, &pools) == nil:
+		f.pools = pools
+	default:
+		return newError("invalid fakedns config")
+	}
+	return nil
+}
+
+func (f *FakeDNSConfig) Build() (*fakedns.FakeDnsPoolMulti, error) {
+	fakeDNSPool := fakedns.FakeDnsPoolMulti{}
+
+	if f.pool != nil {
+		fakeDNSPool.Pools = append(fakeDNSPool.Pools, &fakedns.FakeDnsPool{
+			IpPool:  f.pool.IPPool,
+			LruSize: f.pool.LRUSize,
+		})
+		return &fakeDNSPool, nil
+	}
+
+	if f.pools != nil {
+		for _, v := range f.pools {
+			fakeDNSPool.Pools = append(fakeDNSPool.Pools, &fakedns.FakeDnsPool{IpPool: v.IPPool, LruSize: v.LRUSize})
+		}
+		return &fakeDNSPool, nil
+	}
+
+	return nil, newError("no valid FakeDNS config")
 }
 
 type FakeDNSPostProcessingStage struct{}
 
-func (FakeDNSPostProcessingStage) Process(conf *Config) error {
-	var fakeDNSInUse bool
+func (FakeDNSPostProcessingStage) Process(config *Config) error {
+	fakeDNSInUse := false
+	isIPv4Enable, isIPv6Enable := true, true
 
-	if conf.DNSConfig != nil {
-		for _, v := range conf.DNSConfig.Servers {
-			if v.Address.Family().IsDomain() {
-				if v.Address.Domain() == "fakedns" {
-					fakeDNSInUse = true
-				}
+	if config.DNSConfig != nil {
+		for _, v := range config.DNSConfig.Servers {
+			if v.Address.Family().IsDomain() && strings.EqualFold(v.Address.Domain(), "fakedns") {
+				fakeDNSInUse = true
 			}
 		}
+
+		switch strings.ToLower(config.DNSConfig.QueryStrategy) {
+			case "useip4", "useipv4", "use_ip4", "use_ipv4", "use_ip_v4", "use-ip4", "use-ipv4", "use-ip-v4":
+				isIPv4Enable, isIPv6Enable = true, false
+			case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
+				isIPv4Enable, isIPv6Enable = false, true
+		}
 	}
 
 	if fakeDNSInUse {
-		if conf.FakeDNS == nil {
-			// Add a Fake DNS Config if there is none
-			conf.FakeDNS = &FakeDNSConfig{
-				IPPool:  dns.FakeIPPool,
-				LruSize: 65535,
+		// Add a Fake DNS Config if there is none
+		if config.FakeDNS == nil {
+			config.FakeDNS = &FakeDNSConfig{}
+			switch {
+			case isIPv4Enable && isIPv6Enable:
+				config.FakeDNS.pools = []*FakeDNSPoolElementConfig{
+					{
+						IPPool:  dns.FakeIPv4Pool,
+						LRUSize: 32768,
+					},
+					{
+						IPPool:  dns.FakeIPv6Pool,
+						LRUSize: 32768,
+					},
+				}
+			case !isIPv4Enable && isIPv6Enable:
+				config.FakeDNS.pool = &FakeDNSPoolElementConfig{
+					IPPool:  dns.FakeIPv6Pool,
+					LRUSize: 65535,
+				}
+			case isIPv4Enable && !isIPv6Enable:
+				config.FakeDNS.pool = &FakeDNSPoolElementConfig{
+					IPPool:  dns.FakeIPv4Pool,
+					LRUSize: 65535,
+				}
 			}
 		}
+
 		found := false
 		// Check if there is a Outbound with necessary sniffer on
 		var inbounds []InboundDetourConfig
 
-		if len(conf.InboundConfigs) > 0 {
-			inbounds = append(inbounds, conf.InboundConfigs...)
+		if len(config.InboundConfigs) > 0 {
+			inbounds = append(inbounds, config.InboundConfigs...)
 		}
 		for _, v := range inbounds {
 			if v.SniffingConfig != nil && v.SniffingConfig.Enabled && v.SniffingConfig.DestOverride != nil {
 				for _, dov := range *v.SniffingConfig.DestOverride {
-					if dov == "fakedns" {
+					if strings.EqualFold(dov, "fakedns") || strings.EqualFold(dov, "fakedns+others") {
 						found = true
+						break
 					}
 				}
 			}
 		}
 		if !found {
-			newError("Defined Fake DNS but haven't enabled fake dns sniffing at any inbound.").AtWarning().WriteToLog()
+			newError("Defined FakeDNS but haven't enabled FakeDNS destOverride at any inbound.").AtWarning().WriteToLog()
 		}
 	}
 

+ 2 - 0
infra/conf/xray.go

@@ -78,6 +78,8 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) {
 				p = append(p, "tls")
 			case "fakedns":
 				p = append(p, "fakedns")
+			case "fakedns+others":
+				p = append(p, "fakedns+others")
 			default:
 				return nil, newError("unknown protocol: ", protocol)
 			}