Просмотр исходного кода

Merge branch 'dns' into dns-geo

秋のかえで 5 лет назад
Родитель
Сommit
b0a66e650c

+ 182 - 104
app/dns/config.pb.go

@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
 // versions:
 // 	protoc-gen-go v1.25.0
 // 	protoc-gen-go v1.25.0
-// 	protoc        v3.15.6
+// 	protoc        v3.15.8
 // source: app/dns/config.proto
 // source: app/dns/config.proto
 
 
 package dns
 package dns
@@ -77,6 +77,55 @@ func (QueryStrategy) EnumDescriptor() ([]byte, []int) {
 	return file_app_dns_config_proto_rawDescGZIP(), []int{0}
 	return file_app_dns_config_proto_rawDescGZIP(), []int{0}
 }
 }
 
 
+type CacheStrategy int32
+
+const (
+	CacheStrategy_Cache_ALL     CacheStrategy = 0
+	CacheStrategy_Cache_NOERROR CacheStrategy = 1
+	CacheStrategy_Cache_DISABLE CacheStrategy = 2
+)
+
+// Enum value maps for CacheStrategy.
+var (
+	CacheStrategy_name = map[int32]string{
+		0: "Cache_ALL",
+		1: "Cache_NOERROR",
+		2: "Cache_DISABLE",
+	}
+	CacheStrategy_value = map[string]int32{
+		"Cache_ALL":     0,
+		"Cache_NOERROR": 1,
+		"Cache_DISABLE": 2,
+	}
+)
+
+func (x CacheStrategy) Enum() *CacheStrategy {
+	p := new(CacheStrategy)
+	*p = x
+	return p
+}
+
+func (x CacheStrategy) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (CacheStrategy) Descriptor() protoreflect.EnumDescriptor {
+	return file_app_dns_config_proto_enumTypes[1].Descriptor()
+}
+
+func (CacheStrategy) Type() protoreflect.EnumType {
+	return &file_app_dns_config_proto_enumTypes[1]
+}
+
+func (x CacheStrategy) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use CacheStrategy.Descriptor instead.
+func (CacheStrategy) EnumDescriptor() ([]byte, []int) {
+	return file_app_dns_config_proto_rawDescGZIP(), []int{1}
+}
+
 type NameServer struct {
 type NameServer struct {
 	state         protoimpl.MessageState
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
@@ -84,6 +133,7 @@ type NameServer struct {
 
 
 	Address           *net.Endpoint              `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
 	Address           *net.Endpoint              `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
 	ClientIp          []byte                     `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
 	ClientIp          []byte                     `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
+	SkipFallback      bool                       `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
 	PrioritizedDomain []*domain.Domain           `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
 	PrioritizedDomain []*domain.Domain           `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
 	Geoip             []*geoip.GeoIP             `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"`
 	Geoip             []*geoip.GeoIP             `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"`
 	OriginalRules     []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
 	OriginalRules     []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
@@ -135,6 +185,13 @@ func (x *NameServer) GetClientIp() []byte {
 	return nil
 	return nil
 }
 }
 
 
+func (x *NameServer) GetSkipFallback() bool {
+	if x != nil {
+		return x.SkipFallback
+	}
+	return false
+}
+
 func (x *NameServer) GetPrioritizedDomain() []*domain.Domain {
 func (x *NameServer) GetPrioritizedDomain() []*domain.Domain {
 	if x != nil {
 	if x != nil {
 		return x.PrioritizedDomain
 		return x.PrioritizedDomain
@@ -180,9 +237,10 @@ type Config struct {
 	StaticHosts []*Config_HostMapping `protobuf:"bytes,4,rep,name=static_hosts,json=staticHosts,proto3" json:"static_hosts,omitempty"`
 	StaticHosts []*Config_HostMapping `protobuf:"bytes,4,rep,name=static_hosts,json=staticHosts,proto3" json:"static_hosts,omitempty"`
 	// Tag is the inbound tag of DNS client.
 	// Tag is the inbound tag of DNS client.
 	Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"`
 	Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"`
-	// DisableCache Disable DNS cache
-	DisableCache  bool          `protobuf:"varint,8,opt,name=disableCache,proto3" json:"disableCache,omitempty"`
-	QueryStrategy QueryStrategy `protobuf:"varint,9,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
+	// DisableCache disables DNS cache
+	CacheStrategy   CacheStrategy `protobuf:"varint,8,opt,name=cache_strategy,json=cacheStrategy,proto3,enum=xray.app.dns.CacheStrategy" json:"cache_strategy,omitempty"`
+	QueryStrategy   QueryStrategy `protobuf:"varint,9,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
+	DisableFallback bool          `protobuf:"varint,10,opt,name=disableFallback,proto3" json:"disableFallback,omitempty"`
 }
 }
 
 
 func (x *Config) Reset() {
 func (x *Config) Reset() {
@@ -261,11 +319,11 @@ func (x *Config) GetTag() string {
 	return ""
 	return ""
 }
 }
 
 
-func (x *Config) GetDisableCache() bool {
+func (x *Config) GetCacheStrategy() CacheStrategy {
 	if x != nil {
 	if x != nil {
-		return x.DisableCache
+		return x.CacheStrategy
 	}
 	}
-	return false
+	return CacheStrategy_Cache_ALL
 }
 }
 
 
 func (x *Config) GetQueryStrategy() QueryStrategy {
 func (x *Config) GetQueryStrategy() QueryStrategy {
@@ -275,6 +333,13 @@ func (x *Config) GetQueryStrategy() QueryStrategy {
 	return QueryStrategy_USE_IP
 	return QueryStrategy_USE_IP
 }
 }
 
 
+func (x *Config) GetDisableFallback() bool {
+	if x != nil {
+		return x.DisableFallback
+	}
+	return false
+}
+
 type NameServer_OriginalRule struct {
 type NameServer_OriginalRule struct {
 	state         protoimpl.MessageState
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
@@ -417,80 +482,91 @@ var file_app_dns_config_proto_rawDesc = []byte{
 	0x61, 0x69, 0x6e, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 	0x61, 0x69, 0x6e, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 	0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
 	0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
 	0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f,
 	0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x22, 0xef, 0x02, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
+	0x74, 0x6f, 0x22, 0x93, 0x03, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
 	0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01,
 	0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01,
 	0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
 	0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
 	0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x07, 0x61,
 	0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x07, 0x61,
 	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
 	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
 	0x5f, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e,
 	0x5f, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e,
-	0x74, 0x49, 0x70, 0x12, 0x51, 0x0a, 0x12, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a,
-	0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61,
-	0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x44, 0x6f, 0x6d,
-	0x61, 0x69, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64,
-	0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x36, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18,
-	0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
-	0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69,
-	0x70, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x4c,
-	0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73,
-	0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
-	0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
-	0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x6f,
-	0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x36, 0x0a, 0x0c,
-	0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04,
-	0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65,
-	0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04,
-	0x73, 0x69, 0x7a, 0x65, 0x22, 0x95, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-	0x3f, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
-	0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42,
-	0x02, 0x18, 0x01, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73,
-	0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18,
-	0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
-	0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52,
-	0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x05, 0x48,
-	0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61,
-	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x02, 0x18, 0x01, 0x52,
-	0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
-	0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e,
-	0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68, 0x6f,
-	0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79,
-	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
-	0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74, 0x61,
-	0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18,
-	0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69,
-	0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08,
-	0x52, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x42,
-	0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
-	0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
-	0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74,
-	0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
-	0x67, 0x79, 0x1a, 0x55, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
-	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
-	0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
-	0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05,
-	0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x9a, 0x01, 0x0a, 0x0b, 0x48, 0x6f,
-	0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3c, 0x0a, 0x04, 0x74, 0x79, 0x70,
-	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f,
-	0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70,
-	0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
-	0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
-	0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12,
-	0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69,
-	0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64,
-	0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x2a, 0x35, 0x0a, 0x0d,
-	0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0a, 0x0a,
-	0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
-	0x5f, 0x49, 0x50, 0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
-	0x36, 0x10, 0x02, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
-	0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 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, 0xaa, 0x02, 0x0c, 0x58,
-	0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x33,
+	0x74, 0x49, 0x70, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46, 0x61, 0x6c, 0x6c, 0x62,
+	0x61, 0x63, 0x6b, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x46,
+	0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x51, 0x0a, 0x12, 0x70, 0x72, 0x69, 0x6f, 0x72,
+	0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
+	0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
+	0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74,
+	0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x36, 0x0a, 0x05, 0x67, 0x65,
+	0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e,
+	0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f,
+	0x69, 0x70, 0x12, 0x4c, 0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72,
+	0x75, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61,
+	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
+	0x72, 0x76, 0x65, 0x72, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c,
+	0x65, 0x52, 0x0d, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73,
+	0x1a, 0x36, 0x0a, 0x0c, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65,
+	0x12, 0x12, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x72, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x0d, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x22, 0xdf, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x12, 0x3f, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
+	0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
+	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f,
+	0x69, 0x6e, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
+	0x76, 0x65, 0x72, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72,
+	0x76, 0x65, 0x72, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72,
+	0x76, 0x65, 0x72, 0x52, 0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12,
+	0x39, 0x0a, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f,
+	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42,
+	0x02, 0x18, 0x01, 0x52, 0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c,
+	0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63,
+	0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69,
+	0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e,
+	0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52,
+	0x0b, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03,
+	0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x42,
+	0x0a, 0x0e, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
+	0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
+	0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74,
+	0x65, 0x67, 0x79, 0x52, 0x0d, 0x63, 0x61, 0x63, 0x68, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
+	0x67, 0x79, 0x12, 0x42, 0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61,
+	0x74, 0x65, 0x67, 0x79, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61,
+	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53,
+	0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74,
+	0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x28, 0x0a, 0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c,
+	0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x0f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x61, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b,
+	0x1a, 0x55, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
+	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
+	0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65,
+	0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x9a, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74,
+	0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
+	0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61,
+	0x69, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52,
+	0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a,
+	0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x25, 0x0a,
+	0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x44, 0x6f,
+	0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x2a, 0x35, 0x0a, 0x0d, 0x51, 0x75,
+	0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x55,
+	0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49,
+	0x50, 0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10,
+	0x02, 0x2a, 0x44, 0x0a, 0x0d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
+	0x67, 0x79, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x41, 0x4c, 0x4c, 0x10,
+	0x00, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x4e, 0x4f, 0x45, 0x52, 0x52,
+	0x4f, 0x52, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x43, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x44, 0x49,
+	0x53, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x02, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
+	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 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,
+	0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62,
+	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 }
 
 
 var (
 var (
@@ -505,38 +581,40 @@ func file_app_dns_config_proto_rawDescGZIP() []byte {
 	return file_app_dns_config_proto_rawDescData
 	return file_app_dns_config_proto_rawDescData
 }
 }
 
 
-var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
 var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
 var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
 var file_app_dns_config_proto_goTypes = []interface{}{
 var file_app_dns_config_proto_goTypes = []interface{}{
 	(QueryStrategy)(0),              // 0: xray.app.dns.QueryStrategy
 	(QueryStrategy)(0),              // 0: xray.app.dns.QueryStrategy
-	(*NameServer)(nil),              // 1: xray.app.dns.NameServer
-	(*Config)(nil),                  // 2: xray.app.dns.Config
-	(*NameServer_OriginalRule)(nil), // 3: xray.app.dns.NameServer.OriginalRule
-	nil,                             // 4: xray.app.dns.Config.HostsEntry
-	(*Config_HostMapping)(nil),      // 5: xray.app.dns.Config.HostMapping
-	(*net.Endpoint)(nil),            // 6: xray.common.net.Endpoint
-	(*domain.Domain)(nil),           // 7: xray.common.matcher.domain.Domain
-	(*geoip.GeoIP)(nil),             // 8: xray.common.matcher.geoip.GeoIP
-	(*net.IPOrDomain)(nil),          // 9: xray.common.net.IPOrDomain
-	(domain.MatchingType)(0),        // 10: xray.common.matcher.domain.MatchingType
+	(CacheStrategy)(0),              // 1: xray.app.dns.CacheStrategy
+	(*NameServer)(nil),              // 2: xray.app.dns.NameServer
+	(*Config)(nil),                  // 3: xray.app.dns.Config
+	(*NameServer_OriginalRule)(nil), // 4: xray.app.dns.NameServer.OriginalRule
+	nil,                             // 5: xray.app.dns.Config.HostsEntry
+	(*Config_HostMapping)(nil),      // 6: xray.app.dns.Config.HostMapping
+	(*net.Endpoint)(nil),            // 7: xray.common.net.Endpoint
+	(*domain.Domain)(nil),           // 8: xray.common.matcher.domain.Domain
+	(*geoip.GeoIP)(nil),             // 9: xray.common.matcher.geoip.GeoIP
+	(*net.IPOrDomain)(nil),          // 10: xray.common.net.IPOrDomain
+	(domain.MatchingType)(0),        // 11: xray.common.matcher.domain.MatchingType
 }
 }
 var file_app_dns_config_proto_depIdxs = []int32{
 var file_app_dns_config_proto_depIdxs = []int32{
-	6,  // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
-	7,  // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.common.matcher.domain.Domain
-	8,  // 2: xray.app.dns.NameServer.geoip:type_name -> xray.common.matcher.geoip.GeoIP
-	3,  // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
-	6,  // 4: xray.app.dns.Config.NameServers:type_name -> xray.common.net.Endpoint
-	1,  // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
-	4,  // 6: xray.app.dns.Config.Hosts:type_name -> xray.app.dns.Config.HostsEntry
-	5,  // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
-	0,  // 8: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
-	9,  // 9: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain
-	10, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.common.matcher.domain.MatchingType
-	11, // [11:11] is the sub-list for method output_type
-	11, // [11:11] is the sub-list for method input_type
-	11, // [11:11] is the sub-list for extension type_name
-	11, // [11:11] is the sub-list for extension extendee
-	0,  // [0:11] is the sub-list for field type_name
+	7,  // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
+	8,  // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.common.matcher.domain.Domain
+	9,  // 2: xray.app.dns.NameServer.geoip:type_name -> xray.common.matcher.geoip.GeoIP
+	4,  // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
+	7,  // 4: xray.app.dns.Config.NameServers:type_name -> xray.common.net.Endpoint
+	2,  // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
+	5,  // 6: xray.app.dns.Config.Hosts:type_name -> xray.app.dns.Config.HostsEntry
+	6,  // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
+	1,  // 8: xray.app.dns.Config.cache_strategy:type_name -> xray.app.dns.CacheStrategy
+	0,  // 9: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
+	10, // 10: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain
+	11, // 11: xray.app.dns.Config.HostMapping.type:type_name -> xray.common.matcher.domain.MatchingType
+	12, // [12:12] is the sub-list for method output_type
+	12, // [12:12] is the sub-list for method input_type
+	12, // [12:12] is the sub-list for extension type_name
+	12, // [12:12] is the sub-list for extension extendee
+	0,  // [0:12] is the sub-list for field type_name
 }
 }
 
 
 func init() { file_app_dns_config_proto_init() }
 func init() { file_app_dns_config_proto_init() }
@@ -599,7 +677,7 @@ func file_app_dns_config_proto_init() {
 		File: protoimpl.DescBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_app_dns_config_proto_rawDesc,
 			RawDescriptor: file_app_dns_config_proto_rawDesc,
-			NumEnums:      1,
+			NumEnums:      2,
 			NumMessages:   5,
 			NumMessages:   5,
 			NumExtensions: 0,
 			NumExtensions: 0,
 			NumServices:   0,
 			NumServices:   0,

+ 11 - 2
app/dns/config.proto

@@ -14,6 +14,7 @@ import "common/matcher/geoip/geoip.proto";
 message NameServer {
 message NameServer {
   xray.common.net.Endpoint address = 1;
   xray.common.net.Endpoint address = 1;
   bytes client_ip = 5;
   bytes client_ip = 5;
+  bool skipFallback = 6;
 
 
   message OriginalRule {
   message OriginalRule {
     string rule = 1;
     string rule = 1;
@@ -31,6 +32,12 @@ enum QueryStrategy {
   USE_IP6 = 2;
   USE_IP6 = 2;
 }
 }
 
 
+enum CacheStrategy {
+  Cache_ALL = 0;
+  Cache_NOERROR = 1;
+  Cache_DISABLE = 2;
+}
+
 message Config {
 message Config {
   // Nameservers used by this DNS. Only traditional UDP servers are support at
   // Nameservers used by this DNS. Only traditional UDP servers are support at
   // the moment. A special value 'localhost' as a domain address can be set to
   // the moment. A special value 'localhost' as a domain address can be set to
@@ -67,8 +74,10 @@ message Config {
 
 
   reserved 7;
   reserved 7;
 
 
-  // DisableCache Disable DNS cache
-  bool disableCache = 8;
+  // DisableCache disables DNS cache
+  CacheStrategy cache_strategy = 8;
 
 
   QueryStrategy query_strategy = 9;
   QueryStrategy query_strategy = 9;
+
+  bool disableFallback = 10;
 }
 }

+ 80 - 67
app/dns/dns.go

@@ -22,14 +22,15 @@ import (
 // DNS is a DNS rely server.
 // DNS is a DNS rely server.
 type DNS struct {
 type DNS struct {
 	sync.Mutex
 	sync.Mutex
-	tag           string
-	disableCache  bool
-	ipOption      *dns.IPOption
-	hosts         *StaticHosts
-	clients       []*Client
-	ctx           context.Context
-	domainMatcher str.IndexMatcher
-	matcherInfos  []DomainMatcherInfo
+	tag             string
+	cs              CacheStrategy
+	disableFallback bool
+	ipOption        *dns.IPOption
+	hosts           *StaticHosts
+	clients         []*Client
+	ctx             context.Context
+	domainMatcher   str.IndexMatcher
+	matcherInfos    []DomainMatcherInfo
 }
 }
 
 
 // DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
 // DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
@@ -131,14 +132,15 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
 	}
 	}
 
 
 	return &DNS{
 	return &DNS{
-		tag:           tag,
-		hosts:         hosts,
-		ipOption:      ipOption,
-		clients:       clients,
-		ctx:           ctx,
-		domainMatcher: domainMatcher,
-		matcherInfos:  matcherInfos,
-		disableCache:  config.DisableCache,
+		tag:             tag,
+		hosts:           hosts,
+		ipOption:        ipOption,
+		clients:         clients,
+		ctx:             ctx,
+		domainMatcher:   domainMatcher,
+		matcherInfos:    matcherInfos,
+		cs:              config.CacheStrategy,
+		disableFallback: config.DisableFallback,
 	}, nil
 	}, nil
 }
 }
 
 
@@ -165,35 +167,42 @@ func (s *DNS) IsOwnLink(ctx context.Context) bool {
 
 
 // LookupIP implements dns.Client.
 // LookupIP implements dns.Client.
 func (s *DNS) LookupIP(domain string) ([]net.IP, error) {
 func (s *DNS) LookupIP(domain string) ([]net.IP, error) {
-	return s.lookupIPInternal(domain, dns.IPOption{
-		IPv4Enable: true,
-		IPv6Enable: true,
-		FakeEnable: false,
-	})
+	return s.lookupIPInternal(domain, s.ipOption.Copy())
+}
+
+// LookupOptions implements dns.Client.
+func (s *DNS) LookupOptions(domain string, opts ...dns.Option) ([]net.IP, error) {
+	opt := s.ipOption.Copy()
+	for _, o := range opts {
+		if o != nil {
+			o(opt)
+		}
+	}
+
+	return s.lookupIPInternal(domain, opt)
 }
 }
 
 
 // LookupIPv4 implements dns.IPv4Lookup.
 // LookupIPv4 implements dns.IPv4Lookup.
 func (s *DNS) LookupIPv4(domain string) ([]net.IP, error) {
 func (s *DNS) LookupIPv4(domain string) ([]net.IP, error) {
-	return s.lookupIPInternal(domain, dns.IPOption{
+	return s.lookupIPInternal(domain, &dns.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
-		IPv6Enable: false,
-		FakeEnable: false,
 	})
 	})
 }
 }
 
 
 // LookupIPv6 implements dns.IPv6Lookup.
 // LookupIPv6 implements dns.IPv6Lookup.
 func (s *DNS) LookupIPv6(domain string) ([]net.IP, error) {
 func (s *DNS) LookupIPv6(domain string) ([]net.IP, error) {
-	return s.lookupIPInternal(domain, dns.IPOption{
-		IPv4Enable: false,
+	return s.lookupIPInternal(domain, &dns.IPOption{
 		IPv6Enable: true,
 		IPv6Enable: true,
-		FakeEnable: false,
 	})
 	})
 }
 }
 
 
-func (s *DNS) lookupIPInternal(domain string, option dns.IPOption) ([]net.IP, error) {
+func (s *DNS) lookupIPInternal(domain string, option *dns.IPOption) ([]net.IP, error) {
 	if domain == "" {
 	if domain == "" {
 		return nil, newError("empty domain name")
 		return nil, newError("empty domain name")
 	}
 	}
+	if isQuery(option) {
+		return nil, newError("empty option: Impossible.").AtWarning()
+	}
 
 
 	// Normalize the FQDN form query
 	// Normalize the FQDN form query
 	if strings.HasSuffix(domain, ".") {
 	if strings.HasSuffix(domain, ".") {
@@ -209,20 +218,20 @@ func (s *DNS) lookupIPInternal(domain string, option dns.IPOption) ([]net.IP, er
 	case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
 	case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
 		newError("domain replaced: ", domain, " -> ", addrs[0].Domain()).WriteToLog()
 		newError("domain replaced: ", domain, " -> ", addrs[0].Domain()).WriteToLog()
 		domain = addrs[0].Domain()
 		domain = addrs[0].Domain()
-	default: // Successfully found ip records in static host
-		newError("returning ", len(addrs), " IPs for domain ", domain).WriteToLog()
-		return toNetIP(addrs)
+	default:
+		// Successfully found ip records in static host.
+		// Skip hosts mapping result in FakeDNS query.
+		if isIPQuery(option) {
+			newError("returning ", len(addrs), " IPs for domain ", domain).WriteToLog()
+			return toNetIP(addrs)
+		}
 	}
 	}
 
 
 	// Name servers lookup
 	// Name servers lookup
 	errs := []error{}
 	errs := []error{}
 	ctx := session.ContextWithInbound(s.ctx, &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()
-			continue
-		}
-		ips, err := client.QueryIP(ctx, domain, option, s.disableCache)
+	for _, client := range s.sortClients(domain, option) {
+		ips, err := client.QueryIP(ctx, domain, *option, s.cs)
 		if len(ips) > 0 {
 		if len(ips) > 0 {
 			return ips, nil
 			return ips, nil
 		}
 		}
@@ -238,33 +247,35 @@ func (s *DNS) lookupIPInternal(domain string, option dns.IPOption) ([]net.IP, er
 	return nil, newError("returning nil for domain ", domain).Base(errors.Combine(errs...))
 	return nil, newError("returning nil for domain ", domain).Base(errors.Combine(errs...))
 }
 }
 
 
-// GetIPOption implements ClientWithIPOption.
-func (s *DNS) GetIPOption() *dns.IPOption {
-	return s.ipOption
-}
-
-// SetQueryOption implements ClientWithIPOption.
-func (s *DNS) SetQueryOption(isIPv4Enable, isIPv6Enable bool) {
-	s.ipOption.IPv4Enable = isIPv4Enable
-	s.ipOption.IPv6Enable = isIPv6Enable
-}
-
-// SetFakeDNSOption implements ClientWithIPOption.
-func (s *DNS) SetFakeDNSOption(isFakeEnable bool) {
-	s.ipOption.FakeEnable = isFakeEnable
-}
-
-func (s *DNS) sortClients(domain string) []*Client {
+func (s *DNS) sortClients(domain string, option *dns.IPOption) []*Client {
 	clients := make([]*Client, 0, len(s.clients))
 	clients := make([]*Client, 0, len(s.clients))
 	clientUsed := make([]bool, len(s.clients))
 	clientUsed := make([]bool, len(s.clients))
 	clientNames := make([]string, 0, len(s.clients))
 	clientNames := make([]string, 0, len(s.clients))
 	domainRules := []string{}
 	domainRules := []string{}
 
 
+	defer func() {
+		if len(domainRules) > 0 {
+			newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog()
+		}
+		if len(clientNames) > 0 {
+			newError("domain ", domain, " will use DNS in order: ", clientNames).AtDebug().WriteToLog()
+		}
+		if len(clients) == 0 {
+			clients = append(clients, s.clients[0])
+			clientNames = append(clientNames, s.clients[0].Name())
+			newError("domain ", domain, " will use the first DNS: ", clientNames).AtDebug().WriteToLog()
+		}
+	}()
+
 	// Priority domain matching
 	// Priority domain matching
 	for _, match := range s.domainMatcher.Match(domain) {
 	for _, match := range s.domainMatcher.Match(domain) {
 		info := s.matcherInfos[match]
 		info := s.matcherInfos[match]
 		client := s.clients[info.clientIdx]
 		client := s.clients[info.clientIdx]
 		domainRule := client.domains[info.domainRuleIdx]
 		domainRule := client.domains[info.domainRuleIdx]
+		if !canQueryOnClient(option, client) {
+			newError("skipping the client " + client.Name()).AtDebug().WriteToLog()
+			continue
+		}
 		domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
 		domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
 		if clientUsed[info.clientIdx] {
 		if clientUsed[info.clientIdx] {
 			continue
 			continue
@@ -274,22 +285,24 @@ func (s *DNS) sortClients(domain string) []*Client {
 		clientNames = append(clientNames, client.Name())
 		clientNames = append(clientNames, client.Name())
 	}
 	}
 
 
-	// Default round-robin query
-	for idx, client := range s.clients {
-		if clientUsed[idx] {
-			continue
+	if !s.disableFallback {
+		// Default round-robin query
+		for idx, client := range s.clients {
+			if clientUsed[idx] || client.skipFallback {
+				continue
+			}
+
+			if !canQueryOnClient(option, client) {
+				newError("skipping the client " + client.Name()).AtDebug().WriteToLog()
+				continue
+			}
+
+			clientUsed[idx] = true
+			clients = append(clients, client)
+			clientNames = append(clientNames, client.Name())
 		}
 		}
-		clientUsed[idx] = true
-		clients = append(clients, client)
-		clientNames = append(clientNames, client.Name())
 	}
 	}
 
 
-	if len(domainRules) > 0 {
-		newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog()
-	}
-	if len(clientNames) > 0 {
-		newError("domain ", domain, " will use DNS in order: ", clientNames).AtDebug().WriteToLog()
-	}
 	return clients
 	return clients
 }
 }
 
 

+ 3 - 3
app/dns/hosts.go

@@ -74,7 +74,7 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma
 	return sh, nil
 	return sh, nil
 }
 }
 
 
-func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
+func filterIP(ips []net.Address, option *dns.IPOption) []net.Address {
 	filtered := make([]net.Address, 0, len(ips))
 	filtered := make([]net.Address, 0, len(ips))
 	for _, ip := range ips {
 	for _, ip := range ips {
 		if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) {
 		if (ip.Family().IsIPv4() && option.IPv4Enable) || (ip.Family().IsIPv6() && option.IPv6Enable) {
@@ -95,7 +95,7 @@ func (h *StaticHosts) lookupInternal(domain string) []net.Address {
 	return ips
 	return ips
 }
 }
 
 
-func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) []net.Address {
+func (h *StaticHosts) lookup(domain string, option *dns.IPOption, maxDepth int) []net.Address {
 	switch addrs := h.lookupInternal(domain); {
 	switch addrs := h.lookupInternal(domain); {
 	case len(addrs) == 0: // Not recorded in static hosts, return nil
 	case len(addrs) == 0: // Not recorded in static hosts, return nil
 		return nil
 		return nil
@@ -113,6 +113,6 @@ func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) [
 }
 }
 
 
 // Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
 // Lookup returns IP addresses or proxied domain for the given domain, if exists in this StaticHosts.
-func (h *StaticHosts) Lookup(domain string, option dns.IPOption) []net.Address {
+func (h *StaticHosts) Lookup(domain string, option *dns.IPOption) []net.Address {
 	return h.lookup(domain, option, 5)
 	return h.lookup(domain, option, 5)
 }
 }

+ 3 - 3
app/dns/hosts_test.go

@@ -41,7 +41,7 @@ func TestStaticHosts(t *testing.T) {
 	common.Must(err)
 	common.Must(err)
 
 
 	{
 	{
-		ips := hosts.Lookup("example.com", dns.IPOption{
+		ips := hosts.Lookup("example.com", &dns.IPOption{
 			IPv4Enable: true,
 			IPv4Enable: true,
 			IPv6Enable: true,
 			IPv6Enable: true,
 		})
 		})
@@ -54,7 +54,7 @@ func TestStaticHosts(t *testing.T) {
 	}
 	}
 
 
 	{
 	{
-		ips := hosts.Lookup("www.example.cn", dns.IPOption{
+		ips := hosts.Lookup("www.example.cn", &dns.IPOption{
 			IPv4Enable: true,
 			IPv4Enable: true,
 			IPv6Enable: true,
 			IPv6Enable: true,
 		})
 		})
@@ -67,7 +67,7 @@ func TestStaticHosts(t *testing.T) {
 	}
 	}
 
 
 	{
 	{
-		ips := hosts.Lookup("baidu.com", dns.IPOption{
+		ips := hosts.Lookup("baidu.com", &dns.IPOption{
 			IPv4Enable: false,
 			IPv4Enable: false,
 			IPv6Enable: true,
 			IPv6Enable: true,
 		})
 		})

+ 9 - 7
app/dns/nameserver.go

@@ -20,15 +20,16 @@ type Server interface {
 	// Name of the Client.
 	// Name of the Client.
 	Name() string
 	Name() string
 	// QueryIP sends IP queries to its configured server.
 	// QueryIP sends IP queries to its configured server.
-	QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error)
+	QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, cs CacheStrategy) ([]net.IP, error)
 }
 }
 
 
 // Client is the interface for DNS client.
 // Client is the interface for DNS client.
 type Client struct {
 type Client struct {
-	server    Server
-	clientIP  net.IP
-	domains   []string
-	expectIPs []*geoip.GeoIPMatcher
+	server       Server
+	clientIP     net.IP
+	skipFallback bool
+	domains      []string
+	expectIPs    []*geoip.GeoIPMatcher
 }
 }
 
 
 var errExpectedIPNonMatch = errors.New("expectIPs not match")
 var errExpectedIPNonMatch = errors.New("expectIPs not match")
@@ -65,6 +66,7 @@ func NewServer(dest net.Destination, dispatcher routing.Dispatcher) (Server, err
 // NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
 // NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
 func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container geoip.GeoIPMatcherContainer, matcherInfos *[]DomainMatcherInfo, updateDomainRule func(str.Matcher, int, []DomainMatcherInfo) error) (*Client, error) {
 func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container geoip.GeoIPMatcherContainer, matcherInfos *[]DomainMatcherInfo, updateDomainRule func(str.Matcher, int, []DomainMatcherInfo) error) (*Client, error) {
 	client := &Client{}
 	client := &Client{}
+
 	err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
 	err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
 		// Create a new server for each client for now
 		// Create a new server for each client for now
 		server, err := NewServer(ns.Address.AsDestination(), dispatcher)
 		server, err := NewServer(ns.Address.AsDestination(), dispatcher)
@@ -177,9 +179,9 @@ func (c *Client) Name() string {
 }
 }
 
 
 // QueryIP send DNS query to the name server with the client's IP.
 // QueryIP send DNS query to the name server with the client's IP.
-func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, error) {
+func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, cs CacheStrategy) ([]net.IP, error) {
 	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
 	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
-	ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
+	ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, cs)
 	cancel()
 	cancel()
 
 
 	if err != nil {
 	if err != nil {

+ 7 - 5
app/dns/nameserver_doh.go

@@ -369,17 +369,19 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOpt
 }
 }
 
 
 // QueryIP implements Server.
 // QueryIP implements Server.
-func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) { // nolint: dupl
+func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, cs CacheStrategy) ([]net.IP, error) { // nolint: dupl
 	fqdn := Fqdn(domain)
 	fqdn := Fqdn(domain)
 
 
-	if disableCache {
+	if cs == CacheStrategy_Cache_DISABLE {
 		newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
 		newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
 	} else {
 	} else {
 		ips, err := s.findIPsForDomain(fqdn, option)
 		ips, err := s.findIPsForDomain(fqdn, option)
 		if err != errRecordNotFound {
 		if err != errRecordNotFound {
-			newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
-			log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
-			return ips, err
+			if cs == CacheStrategy_Cache_NOERROR && err == nil {
+				newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
+				log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
+				return ips, err
+			}
 		}
 		}
 	}
 	}
 
 

+ 3 - 3
app/dns/nameserver_doh_test.go

@@ -23,7 +23,7 @@ func TestDOHNameServer(t *testing.T) {
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
 		IPv6Enable: true,
 		IPv6Enable: true,
-	}, false)
+	}, CacheStrategy_Cache_ALL)
 	cancel()
 	cancel()
 	common.Must(err)
 	common.Must(err)
 	if len(ips) == 0 {
 	if len(ips) == 0 {
@@ -40,7 +40,7 @@ func TestDOHNameServerWithCache(t *testing.T) {
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
 		IPv6Enable: true,
 		IPv6Enable: true,
-	}, false)
+	}, CacheStrategy_Cache_ALL)
 	cancel()
 	cancel()
 	common.Must(err)
 	common.Must(err)
 	if len(ips) == 0 {
 	if len(ips) == 0 {
@@ -51,7 +51,7 @@ func TestDOHNameServerWithCache(t *testing.T) {
 	ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
 		IPv6Enable: true,
 		IPv6Enable: true,
-	}, true)
+	}, CacheStrategy_Cache_ALL)
 	cancel()
 	cancel()
 	common.Must(err)
 	common.Must(err)
 	if r := cmp.Diff(ips2, ips); r != "" {
 	if r := cmp.Diff(ips2, ips); r != "" {

+ 4 - 2
app/dns/nameserver_fakedns.go

@@ -16,11 +16,13 @@ func NewFakeDNSServer() *FakeDNSServer {
 	return &FakeDNSServer{}
 	return &FakeDNSServer{}
 }
 }
 
 
+const FakeDNSName = "FakeDNS"
+
 func (FakeDNSServer) Name() string {
 func (FakeDNSServer) Name() string {
-	return "FakeDNS"
+	return FakeDNSName
 }
 }
 
 
-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, _ dns.IPOption, _ CacheStrategy) ([]net.IP, error) {
 	if f.fakeDNSEngine == nil {
 	if f.fakeDNSEngine == nil {
 		if err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) {
 		if err := core.RequireFeatures(ctx, func(fd dns.FakeDNSEngine) {
 			f.fakeDNSEngine = fd
 			f.fakeDNSEngine = fd

+ 1 - 1
app/dns/nameserver_local.go

@@ -14,7 +14,7 @@ type LocalNameServer struct {
 }
 }
 
 
 // QueryIP implements Server.
 // QueryIP implements Server.
-func (s *LocalNameServer) QueryIP(_ context.Context, domain string, _ net.IP, option dns.IPOption, _ bool) ([]net.IP, error) {
+func (s *LocalNameServer) QueryIP(_ context.Context, domain string, _ net.IP, option dns.IPOption, _ CacheStrategy) ([]net.IP, error) {
 	var ips []net.IP
 	var ips []net.IP
 	var err error
 	var err error
 
 

+ 1 - 1
app/dns/nameserver_local_test.go

@@ -17,7 +17,7 @@ func TestLocalNameServer(t *testing.T) {
 	ips, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{
 	ips, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
 		IPv6Enable: true,
 		IPv6Enable: true,
-	}, false)
+	}, CacheStrategy_Cache_ALL)
 	cancel()
 	cancel()
 	common.Must(err)
 	common.Must(err)
 	if len(ips) == 0 {
 	if len(ips) == 0 {

+ 7 - 5
app/dns/nameserver_quic.go

@@ -268,17 +268,19 @@ func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOp
 }
 }
 
 
 // QueryIP is called from dns.Server->queryIPTimeout
 // QueryIP is called from dns.Server->queryIPTimeout
-func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
+func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, cs CacheStrategy) ([]net.IP, error) {
 	fqdn := Fqdn(domain)
 	fqdn := Fqdn(domain)
 
 
-	if disableCache {
+	if cs == CacheStrategy_Cache_DISABLE {
 		newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
 		newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
 	} else {
 	} else {
 		ips, err := s.findIPsForDomain(fqdn, option)
 		ips, err := s.findIPsForDomain(fqdn, option)
 		if err != errRecordNotFound {
 		if err != errRecordNotFound {
-			newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
-			log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
-			return ips, err
+			if cs == CacheStrategy_Cache_NOERROR && err == nil {
+				newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
+				log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
+				return ips, err
+			}
 		}
 		}
 	}
 	}
 
 

+ 3 - 3
app/dns/nameserver_quic_test.go

@@ -23,7 +23,7 @@ func TestQUICNameServer(t *testing.T) {
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
 		IPv6Enable: true,
 		IPv6Enable: true,
-	}, false)
+	}, CacheStrategy_Cache_ALL)
 	cancel()
 	cancel()
 	common.Must(err)
 	common.Must(err)
 	if len(ips) == 0 {
 	if len(ips) == 0 {
@@ -40,7 +40,7 @@ func TestQUICNameServerWithCache(t *testing.T) {
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
 		IPv6Enable: true,
 		IPv6Enable: true,
-	}, false)
+	}, CacheStrategy_Cache_ALL)
 	cancel()
 	cancel()
 	common.Must(err)
 	common.Must(err)
 	if len(ips) == 0 {
 	if len(ips) == 0 {
@@ -51,7 +51,7 @@ func TestQUICNameServerWithCache(t *testing.T) {
 	ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
 	ips2, err := s.QueryIP(ctx2, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
 		IPv6Enable: true,
 		IPv6Enable: true,
-	}, true)
+	}, CacheStrategy_Cache_ALL)
 	cancel()
 	cancel()
 	common.Must(err)
 	common.Must(err)
 	if r := cmp.Diff(ips2, ips); r != "" {
 	if r := cmp.Diff(ips2, ips); r != "" {

+ 7 - 5
app/dns/nameserver_udp.go

@@ -245,17 +245,19 @@ func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.I
 }
 }
 
 
 // QueryIP implements Server.
 // QueryIP implements Server.
-func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, disableCache bool) ([]net.IP, error) {
+func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption, cs CacheStrategy) ([]net.IP, error) {
 	fqdn := Fqdn(domain)
 	fqdn := Fqdn(domain)
 
 
-	if disableCache {
+	if cs == CacheStrategy_Cache_DISABLE {
 		newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
 		newError("DNS cache is disabled. Querying IP for ", domain, " at ", s.name).AtDebug().WriteToLog()
 	} else {
 	} else {
 		ips, err := s.findIPsForDomain(fqdn, option)
 		ips, err := s.findIPsForDomain(fqdn, option)
 		if err != errRecordNotFound {
 		if err != errRecordNotFound {
-			newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
-			log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
-			return ips, err
+			if cs == CacheStrategy_Cache_NOERROR && err == nil {
+				newError(s.name, " cache HIT ", domain, " -> ", ips).Base(err).AtDebug().WriteToLog()
+				log.Record(&log.DNSLog{s.name, domain, ips, log.DNSCacheHit, 0, err})
+				return ips, err
+			}
 		}
 		}
 	}
 	}
 
 

+ 16 - 0
app/dns/options.go

@@ -0,0 +1,16 @@
+package dns
+
+import "github.com/xtls/xray-core/features/dns"
+
+func isIPQuery(o *dns.IPOption) bool {
+	return o.IPv4Enable || o.IPv6Enable
+}
+
+func canQueryOnClient(o *dns.IPOption, c *Client) bool {
+	isIPClient := !(c.Name() == FakeDNSName)
+	return isIPClient && isIPQuery(o)
+}
+
+func isQuery(o *dns.IPOption) bool {
+	return !(o.IPv4Enable || o.IPv6Enable || o.FakeEnable)
+}

+ 41 - 14
features/dns/client.go

@@ -14,6 +14,12 @@ type IPOption struct {
 	FakeEnable bool
 	FakeEnable bool
 }
 }
 
 
+func (p *IPOption) Copy() *IPOption {
+	return &IPOption{p.IPv4Enable, p.IPv6Enable, p.FakeEnable}
+}
+
+type Option func(dopt *IPOption) *IPOption
+
 // Client is a Xray feature for querying DNS information.
 // Client is a Xray feature for querying DNS information.
 //
 //
 // xray:api:stable
 // xray:api:stable
@@ -22,6 +28,9 @@ type Client interface {
 
 
 	// LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses.
 	// LookupIP returns IP address for the given domain. IPs may contain IPv4 and/or IPv6 addresses.
 	LookupIP(domain string) ([]net.IP, error)
 	LookupIP(domain string) ([]net.IP, error)
+
+	// LookupOptions query IP address for domain with *IPOption.
+	LookupOptions(domain string, opt ...Option) ([]net.IP, error)
 }
 }
 
 
 // IPv4Lookup is an optional feature for querying IPv4 addresses only.
 // IPv4Lookup is an optional feature for querying IPv4 addresses only.
@@ -38,20 +47,6 @@ type IPv6Lookup interface {
 	LookupIPv6(domain string) ([]net.IP, error)
 	LookupIPv6(domain string) ([]net.IP, error)
 }
 }
 
 
-// ClientWithIPOption is an optional feature for querying DNS information.
-//
-// xray:api:beta
-type ClientWithIPOption interface {
-	// GetIPOption returns IPOption for the DNS client.
-	GetIPOption() *IPOption
-
-	// SetQueryOption sets IPv4Enable and IPv6Enable for the DNS client.
-	SetQueryOption(isIPv4Enable, isIPv6Enable bool)
-
-	// SetFakeDNSOption sets FakeEnable option for DNS client.
-	SetFakeDNSOption(isFakeEnable bool)
-}
-
 // ClientType returns the type of Client interface. Can be used for implementing common.HasType.
 // ClientType returns the type of Client interface. Can be used for implementing common.HasType.
 //
 //
 // xray:api:beta
 // xray:api:beta
@@ -78,3 +73,35 @@ func RCodeFromError(err error) uint16 {
 	}
 	}
 	return 0
 	return 0
 }
 }
+
+var (
+	LookupIPv4Only = func(d *IPOption) *IPOption {
+		d.IPv4Enable = true
+		d.IPv6Enable = false
+		return d
+	}
+	LookupIPv6Only = func(d *IPOption) *IPOption {
+		d.IPv4Enable = false
+		d.IPv6Enable = true
+		return d
+	}
+	LookupIP = func(d *IPOption) *IPOption {
+		d.IPv4Enable = true
+		d.IPv6Enable = true
+		return d
+	}
+	LookupFake = func(d *IPOption) *IPOption {
+		d.FakeEnable = true
+		return d
+	}
+	LookupNoFake = func(d *IPOption) *IPOption {
+		d.FakeEnable = false
+		return d
+	}
+
+	LookupAll = func(d *IPOption) *IPOption {
+		LookupIP(d)
+		LookupFake(d)
+		return d
+	}
+)

+ 5 - 0
features/dns/localdns/client.go

@@ -38,6 +38,11 @@ func (*Client) LookupIP(host string) ([]net.IP, error) {
 	return parsedIPs, nil
 	return parsedIPs, nil
 }
 }
 
 
+// LookupOptions implements Client.
+func (c *Client) LookupOptions(host string, _ ...dns.Option) ([]net.IP, error) {
+	return c.LookupIP(host)
+}
+
 // LookupIPv4 implements IPv4Lookup.
 // LookupIPv4 implements IPv4Lookup.
 func (c *Client) LookupIPv4(host string) ([]net.IP, error) {
 func (c *Client) LookupIPv4(host string) ([]net.IP, error) {
 	ips, err := c.LookupIP(host)
 	ips, err := c.LookupIP(host)

+ 2 - 30
features/routing/dns/context.go

@@ -26,40 +26,12 @@ func (ctx *ResolvableContext) GetTargetIPs() []net.IP {
 	}
 	}
 
 
 	if domain := ctx.GetTargetDomain(); len(domain) != 0 {
 	if domain := ctx.GetTargetDomain(); len(domain) != 0 {
-		var lookupFunc func(string) ([]net.IP, error) = ctx.dnsClient.LookupIP
-		ipOption := &dns.IPOption{
-			IPv4Enable: true,
-			IPv6Enable: true,
-		}
-
-		if c, ok := ctx.dnsClient.(dns.ClientWithIPOption); ok {
-			ipOption = c.GetIPOption()
-			c.SetFakeDNSOption(false) // Skip FakeDNS.
-		} else {
-			newError("ctx.dnsClient doesn't implement ClientWithIPOption").AtDebug().WriteToLog()
-		}
-
-		switch {
-		case ipOption.IPv4Enable && !ipOption.IPv6Enable:
-			if lookupIPv4, ok := ctx.dnsClient.(dns.IPv4Lookup); ok {
-				lookupFunc = lookupIPv4.LookupIPv4
-			} else {
-				newError("ctx.dnsClient doesn't implement IPv4Lookup. Use LookupIP instead.").AtDebug().WriteToLog()
-			}
-		case !ipOption.IPv4Enable && ipOption.IPv6Enable:
-			if lookupIPv6, ok := ctx.dnsClient.(dns.IPv6Lookup); ok {
-				lookupFunc = lookupIPv6.LookupIPv6
-			} else {
-				newError("ctx.dnsClient doesn't implement IPv6Lookup. Use LookupIP instead.").AtDebug().WriteToLog()
-			}
-		}
-
-		ips, err := lookupFunc(domain)
+		ips, err := ctx.dnsClient.LookupIP(domain)
 		if err == nil {
 		if err == nil {
 			ctx.resolvedIPs = ips
 			ctx.resolvedIPs = ips
 			return ips
 			return ips
 		}
 		}
-		newError("resolve ip for ", domain).Base(err).WriteToLog()
+		newError("failed to resolve ip for ", domain).Base(err).WriteToLog()
 	}
 	}
 
 
 	return nil
 	return nil

+ 38 - 18
infra/conf/dns.go

@@ -15,11 +15,12 @@ import (
 )
 )
 
 
 type NameServerConfig struct {
 type NameServerConfig struct {
-	Address   *Address
-	ClientIP  *Address
-	Port      uint16
-	Domains   []string
-	ExpectIPs StringList
+	Address      *Address
+	ClientIP     *Address
+	Port         uint16
+	SkipFallback bool
+	Domains      []string
+	ExpectIPs    StringList
 }
 }
 
 
 func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
 func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
@@ -30,16 +31,18 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
 	}
 	}
 
 
 	var advanced struct {
 	var advanced struct {
-		Address   *Address   `json:"address"`
-		ClientIP  *Address   `json:"clientIp"`
-		Port      uint16     `json:"port"`
-		Domains   []string   `json:"domains"`
-		ExpectIPs StringList `json:"expectIps"`
+		Address      *Address   `json:"address"`
+		ClientIP     *Address   `json:"clientIp"`
+		Port         uint16     `json:"port"`
+		SkipFallback bool       `json:"skipFallback"`
+		Domains      []string   `json:"domains"`
+		ExpectIPs    StringList `json:"expectIps"`
 	}
 	}
 	if err := json.Unmarshal(data, &advanced); err == nil {
 	if err := json.Unmarshal(data, &advanced); err == nil {
 		c.Address = advanced.Address
 		c.Address = advanced.Address
 		c.ClientIP = advanced.ClientIP
 		c.ClientIP = advanced.ClientIP
 		c.Port = advanced.Port
 		c.Port = advanced.Port
+		c.SkipFallback = advanced.SkipFallback
 		c.Domains = advanced.Domains
 		c.Domains = advanced.Domains
 		c.ExpectIPs = advanced.ExpectIPs
 		c.ExpectIPs = advanced.ExpectIPs
 		return nil
 		return nil
@@ -93,6 +96,7 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
 			Port:    uint32(c.Port),
 			Port:    uint32(c.Port),
 		},
 		},
 		ClientIp:          myClientIP,
 		ClientIp:          myClientIP,
+		SkipFallback:      c.SkipFallback,
 		PrioritizedDomain: domains,
 		PrioritizedDomain: domains,
 		Geoip:             geoipList,
 		Geoip:             geoipList,
 		OriginalRules:     originalRules,
 		OriginalRules:     originalRules,
@@ -101,12 +105,14 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
 
 
 // DNSConfig is a JSON serializable object for dns.Config.
 // DNSConfig is a JSON serializable object for dns.Config.
 type DNSConfig struct {
 type DNSConfig struct {
-	Servers       []*NameServerConfig `json:"servers"`
-	Hosts         map[string]*Address `json:"hosts"`
-	ClientIP      *Address            `json:"clientIp"`
-	Tag           string              `json:"tag"`
-	QueryStrategy string              `json:"queryStrategy"`
-	DisableCache  bool                `json:"disableCache"`
+	Servers         []*NameServerConfig `json:"servers"`
+	Hosts           map[string]*Address `json:"hosts"`
+	ClientIP        *Address            `json:"clientIp"`
+	Tag             string              `json:"tag"`
+	QueryStrategy   string              `json:"queryStrategy"`
+	CacheStrategy   string              `json:"cacheStrategy"`
+	DisableCache    bool                `json:"disableCache"`
+	DisableFallback bool                `json:"disableFallback"`
 }
 }
 
 
 func getHostMapping(addr *Address) *dns.Config_HostMapping {
 func getHostMapping(addr *Address) *dns.Config_HostMapping {
@@ -124,8 +130,13 @@ func getHostMapping(addr *Address) *dns.Config_HostMapping {
 // Build implements Buildable
 // Build implements Buildable
 func (c *DNSConfig) Build() (*dns.Config, error) {
 func (c *DNSConfig) Build() (*dns.Config, error) {
 	config := &dns.Config{
 	config := &dns.Config{
-		Tag:          c.Tag,
-		DisableCache: c.DisableCache,
+		Tag:             c.Tag,
+		CacheStrategy:   dns.CacheStrategy_Cache_ALL,
+		DisableFallback: c.DisableFallback,
+	}
+
+	if c.DisableCache {
+		config.CacheStrategy = dns.CacheStrategy_Cache_DISABLE
 	}
 	}
 
 
 	if c.ClientIP != nil {
 	if c.ClientIP != nil {
@@ -145,6 +156,15 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
 		config.QueryStrategy = dns.QueryStrategy_USE_IP6
 		config.QueryStrategy = dns.QueryStrategy_USE_IP6
 	}
 	}
 
 
+	switch strings.ToLower(c.CacheStrategy) {
+	case "noerror":
+		config.CacheStrategy = dns.CacheStrategy_Cache_NOERROR
+	case "all":
+		config.CacheStrategy = dns.CacheStrategy_Cache_ALL
+	case "disable", "none":
+		config.CacheStrategy = dns.CacheStrategy_Cache_DISABLE
+	}
+
 	for _, server := range c.Servers {
 	for _, server := range c.Servers {
 		ns, err := server.Build()
 		ns, err := server.Build()
 		if err != nil {
 		if err != nil {

+ 9 - 5
infra/conf/dns_test.go

@@ -70,6 +70,7 @@ func TestDNSConfigParsing(t *testing.T) {
 					"address": "8.8.8.8",
 					"address": "8.8.8.8",
 					"clientIp": "10.0.0.1",
 					"clientIp": "10.0.0.1",
 					"port": 5353,
 					"port": 5353,
+					"skipFallback": true,
 					"domains": ["domain:example.com"]
 					"domains": ["domain:example.com"]
 				}],
 				}],
 				"hosts": {
 				"hosts": {
@@ -81,7 +82,8 @@ func TestDNSConfigParsing(t *testing.T) {
 				},
 				},
 				"clientIp": "10.0.0.1",
 				"clientIp": "10.0.0.1",
 				"queryStrategy": "UseIPv4",
 				"queryStrategy": "UseIPv4",
-				"disableCache": true
+				"disableCache": true,
+				"disableFallback": true
 			}`,
 			}`,
 			Parser: parserCreator(),
 			Parser: parserCreator(),
 			Output: &dns.Config{
 			Output: &dns.Config{
@@ -96,7 +98,8 @@ func TestDNSConfigParsing(t *testing.T) {
 							Network: net.Network_UDP,
 							Network: net.Network_UDP,
 							Port:    5353,
 							Port:    5353,
 						},
 						},
-						ClientIp: []byte{10, 0, 0, 1},
+						ClientIp:     []byte{10, 0, 0, 1},
+						SkipFallback: true,
 						PrioritizedDomain: []*domain.Domain{
 						PrioritizedDomain: []*domain.Domain{
 							{
 							{
 								Type:  domain.MatchingType_Subdomain,
 								Type:  domain.MatchingType_Subdomain,
@@ -138,9 +141,10 @@ func TestDNSConfigParsing(t *testing.T) {
 						Ip:     [][]byte{{8, 8, 4, 4}},
 						Ip:     [][]byte{{8, 8, 4, 4}},
 					},
 					},
 				},
 				},
-				ClientIp:      []byte{10, 0, 0, 1},
-				QueryStrategy: dns.QueryStrategy_USE_IP4,
-				DisableCache:  true,
+				ClientIp:        []byte{10, 0, 0, 1},
+				QueryStrategy:   dns.QueryStrategy_USE_IP4,
+				CacheStrategy:   dns.CacheStrategy_Cache_DISABLE,
+				DisableFallback: true,
 			},
 			},
 		},
 		},
 	})
 	})

+ 4 - 23
proxy/dns/dns.go

@@ -37,8 +37,6 @@ type ownLinkVerifier interface {
 
 
 type Handler struct {
 type Handler struct {
 	client          dns.Client
 	client          dns.Client
-	ipv4Lookup      dns.IPv4Lookup
-	ipv6Lookup      dns.IPv6Lookup
 	ownLinkVerifier ownLinkVerifier
 	ownLinkVerifier ownLinkVerifier
 	server          net.Destination
 	server          net.Destination
 }
 }
@@ -46,18 +44,6 @@ type Handler struct {
 func (h *Handler) Init(config *Config, dnsClient dns.Client) error {
 func (h *Handler) Init(config *Config, dnsClient dns.Client) error {
 	h.client = dnsClient
 	h.client = dnsClient
 
 
-	if ipv4lookup, ok := dnsClient.(dns.IPv4Lookup); ok {
-		h.ipv4Lookup = ipv4lookup
-	} else {
-		return newError("dns.Client doesn't implement IPv4Lookup")
-	}
-
-	if ipv6lookup, ok := dnsClient.(dns.IPv6Lookup); ok {
-		h.ipv6Lookup = ipv6lookup
-	} else {
-		return newError("dns.Client doesn't implement IPv6Lookup")
-	}
-
 	if v, ok := dnsClient.(ownLinkVerifier); ok {
 	if v, ok := dnsClient.(ownLinkVerifier); ok {
 		h.ownLinkVerifier = v
 		h.ownLinkVerifier = v
 	}
 	}
@@ -213,21 +199,16 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
 	var err error
 	var err error
 
 
 	var ttl uint32 = 600
 	var ttl uint32 = 600
-
-	// Do NOT skip FakeDNS
-	if c, ok := h.client.(dns.ClientWithIPOption); ok {
-		c.SetFakeDNSOption(true)
-	} else {
-		newError("dns.Client doesn't implement ClientWithIPOption")
-	}
+	var opt dns.Option
 
 
 	switch qType {
 	switch qType {
 	case dnsmessage.TypeA:
 	case dnsmessage.TypeA:
-		ips, err = h.ipv4Lookup.LookupIPv4(domain)
+		opt = dns.LookupIPv4Only
 	case dnsmessage.TypeAAAA:
 	case dnsmessage.TypeAAAA:
-		ips, err = h.ipv6Lookup.LookupIPv6(domain)
+		opt = dns.LookupIPv6Only
 	}
 	}
 
 
+	ips, err = h.client.LookupOptions(domain, opt, dns.LookupFake)
 	rcode := dns.RCodeFromError(err)
 	rcode := dns.RCodeFromError(err)
 	if rcode == 0 && len(ips) == 0 && err != dns.ErrEmptyResponse {
 	if rcode == 0 && len(ips) == 0 && err != dns.ErrEmptyResponse {
 		newError("ip query").Base(err).WriteToLog()
 		newError("ip query").Base(err).WriteToLog()

+ 4 - 14
proxy/freedom/freedom.go

@@ -59,24 +59,14 @@ func (h *Handler) policy() policy.Session {
 }
 }
 
 
 func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
 func (h *Handler) resolveIP(ctx context.Context, domain string, localAddr net.Address) net.Address {
-	if c, ok := h.dns.(dns.ClientWithIPOption); ok {
-		c.SetFakeDNSOption(false) // Skip FakeDNS
-	} else {
-		newError("DNS client doesn't implement ClientWithIPOption")
-	}
-
-	var lookupFunc func(string) ([]net.IP, error) = h.dns.LookupIP
+	var opt dns.Option
 	if h.config.DomainStrategy == Config_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()) {
 	if h.config.DomainStrategy == Config_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()) {
-		if lookupIPv4, ok := h.dns.(dns.IPv4Lookup); ok {
-			lookupFunc = lookupIPv4.LookupIPv4
-		}
+		opt = dns.LookupIPv4Only
 	} else if h.config.DomainStrategy == Config_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()) {
 	} else if h.config.DomainStrategy == Config_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()) {
-		if lookupIPv6, ok := h.dns.(dns.IPv6Lookup); ok {
-			lookupFunc = lookupIPv6.LookupIPv6
-		}
+		opt = dns.LookupIPv6Only
 	}
 	}
 
 
-	ips, err := lookupFunc(domain)
+	ips, err := h.dns.LookupOptions(domain, opt, dns.LookupNoFake)
 	if err != nil {
 	if err != nil {
 		newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx))
 		newError("failed to get IP address for domain ", domain).Base(err).WriteToLog(session.ExportIDToError(ctx))
 	}
 	}

+ 22 - 1
testing/mocks/dns.go

@@ -1,5 +1,5 @@
 // Code generated by MockGen. DO NOT EDIT.
 // Code generated by MockGen. DO NOT EDIT.
-// Source: github.com/xray/xray-core/v4/features/dns (interfaces: Client)
+// Source: github.com/xtls/xray-core/features/dns (interfaces: Client)
 
 
 // Package mocks is a generated GoMock package.
 // Package mocks is a generated GoMock package.
 package mocks
 package mocks
@@ -9,6 +9,7 @@ import (
 	reflect "reflect"
 	reflect "reflect"
 
 
 	gomock "github.com/golang/mock/gomock"
 	gomock "github.com/golang/mock/gomock"
+	dns "github.com/xtls/xray-core/features/dns"
 )
 )
 
 
 // DNSClient is a mock of Client interface.
 // DNSClient is a mock of Client interface.
@@ -63,6 +64,26 @@ func (mr *DNSClientMockRecorder) LookupIP(arg0 interface{}) *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupIP", reflect.TypeOf((*DNSClient)(nil).LookupIP), arg0)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupIP", reflect.TypeOf((*DNSClient)(nil).LookupIP), arg0)
 }
 }
 
 
+// LookupOptions mocks base method.
+func (m *DNSClient) LookupOptions(arg0 string, arg1 ...dns.Option) ([]net.IP, error) {
+	m.ctrl.T.Helper()
+	varargs := []interface{}{arg0}
+	for _, a := range arg1 {
+		varargs = append(varargs, a)
+	}
+	ret := m.ctrl.Call(m, "LookupOptions", varargs...)
+	ret0, _ := ret[0].([]net.IP)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// LookupOptions indicates an expected call of LookupOptions.
+func (mr *DNSClientMockRecorder) LookupOptions(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	varargs := append([]interface{}{arg0}, arg1...)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LookupOptions", reflect.TypeOf((*DNSClient)(nil).LookupOptions), varargs...)
+}
+
 // Start mocks base method.
 // Start mocks base method.
 func (m *DNSClient) Start() error {
 func (m *DNSClient) Start() error {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()

+ 14 - 13
testing/mocks/io.go

@@ -5,34 +5,35 @@
 package mocks
 package mocks
 
 
 import (
 import (
-	gomock "github.com/golang/mock/gomock"
 	reflect "reflect"
 	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
 )
 )
 
 
-// Reader is a mock of Reader interface
+// Reader is a mock of Reader interface.
 type Reader struct {
 type Reader struct {
 	ctrl     *gomock.Controller
 	ctrl     *gomock.Controller
 	recorder *ReaderMockRecorder
 	recorder *ReaderMockRecorder
 }
 }
 
 
-// ReaderMockRecorder is the mock recorder for Reader
+// ReaderMockRecorder is the mock recorder for Reader.
 type ReaderMockRecorder struct {
 type ReaderMockRecorder struct {
 	mock *Reader
 	mock *Reader
 }
 }
 
 
-// NewReader creates a new mock instance
+// NewReader creates a new mock instance.
 func NewReader(ctrl *gomock.Controller) *Reader {
 func NewReader(ctrl *gomock.Controller) *Reader {
 	mock := &Reader{ctrl: ctrl}
 	mock := &Reader{ctrl: ctrl}
 	mock.recorder = &ReaderMockRecorder{mock}
 	mock.recorder = &ReaderMockRecorder{mock}
 	return mock
 	return mock
 }
 }
 
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *Reader) EXPECT() *ReaderMockRecorder {
 func (m *Reader) EXPECT() *ReaderMockRecorder {
 	return m.recorder
 	return m.recorder
 }
 }
 
 
-// Read mocks base method
+// Read mocks base method.
 func (m *Reader) Read(arg0 []byte) (int, error) {
 func (m *Reader) Read(arg0 []byte) (int, error) {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Read", arg0)
 	ret := m.ctrl.Call(m, "Read", arg0)
@@ -41,36 +42,36 @@ func (m *Reader) Read(arg0 []byte) (int, error) {
 	return ret0, ret1
 	return ret0, ret1
 }
 }
 
 
-// Read indicates an expected call of Read
+// Read indicates an expected call of Read.
 func (mr *ReaderMockRecorder) Read(arg0 interface{}) *gomock.Call {
 func (mr *ReaderMockRecorder) Read(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*Reader)(nil).Read), arg0)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*Reader)(nil).Read), arg0)
 }
 }
 
 
-// Writer is a mock of Writer interface
+// Writer is a mock of Writer interface.
 type Writer struct {
 type Writer struct {
 	ctrl     *gomock.Controller
 	ctrl     *gomock.Controller
 	recorder *WriterMockRecorder
 	recorder *WriterMockRecorder
 }
 }
 
 
-// WriterMockRecorder is the mock recorder for Writer
+// WriterMockRecorder is the mock recorder for Writer.
 type WriterMockRecorder struct {
 type WriterMockRecorder struct {
 	mock *Writer
 	mock *Writer
 }
 }
 
 
-// NewWriter creates a new mock instance
+// NewWriter creates a new mock instance.
 func NewWriter(ctrl *gomock.Controller) *Writer {
 func NewWriter(ctrl *gomock.Controller) *Writer {
 	mock := &Writer{ctrl: ctrl}
 	mock := &Writer{ctrl: ctrl}
 	mock.recorder = &WriterMockRecorder{mock}
 	mock.recorder = &WriterMockRecorder{mock}
 	return mock
 	return mock
 }
 }
 
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *Writer) EXPECT() *WriterMockRecorder {
 func (m *Writer) EXPECT() *WriterMockRecorder {
 	return m.recorder
 	return m.recorder
 }
 }
 
 
-// Write mocks base method
+// Write mocks base method.
 func (m *Writer) Write(arg0 []byte) (int, error) {
 func (m *Writer) Write(arg0 []byte) (int, error) {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Write", arg0)
 	ret := m.ctrl.Call(m, "Write", arg0)
@@ -79,7 +80,7 @@ func (m *Writer) Write(arg0 []byte) (int, error) {
 	return ret0, ret1
 	return ret0, ret1
 }
 }
 
 
-// Write indicates an expected call of Write
+// Write indicates an expected call of Write.
 func (mr *WriterMockRecorder) Write(arg0 interface{}) *gomock.Call {
 func (mr *WriterMockRecorder) Write(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*Writer)(nil).Write), arg0)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*Writer)(nil).Write), arg0)

+ 8 - 7
testing/mocks/log.go

@@ -5,41 +5,42 @@
 package mocks
 package mocks
 
 
 import (
 import (
+	reflect "reflect"
+
 	gomock "github.com/golang/mock/gomock"
 	gomock "github.com/golang/mock/gomock"
 	log "github.com/xtls/xray-core/common/log"
 	log "github.com/xtls/xray-core/common/log"
-	reflect "reflect"
 )
 )
 
 
-// LogHandler is a mock of Handler interface
+// LogHandler is a mock of Handler interface.
 type LogHandler struct {
 type LogHandler struct {
 	ctrl     *gomock.Controller
 	ctrl     *gomock.Controller
 	recorder *LogHandlerMockRecorder
 	recorder *LogHandlerMockRecorder
 }
 }
 
 
-// LogHandlerMockRecorder is the mock recorder for LogHandler
+// LogHandlerMockRecorder is the mock recorder for LogHandler.
 type LogHandlerMockRecorder struct {
 type LogHandlerMockRecorder struct {
 	mock *LogHandler
 	mock *LogHandler
 }
 }
 
 
-// NewLogHandler creates a new mock instance
+// NewLogHandler creates a new mock instance.
 func NewLogHandler(ctrl *gomock.Controller) *LogHandler {
 func NewLogHandler(ctrl *gomock.Controller) *LogHandler {
 	mock := &LogHandler{ctrl: ctrl}
 	mock := &LogHandler{ctrl: ctrl}
 	mock.recorder = &LogHandlerMockRecorder{mock}
 	mock.recorder = &LogHandlerMockRecorder{mock}
 	return mock
 	return mock
 }
 }
 
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *LogHandler) EXPECT() *LogHandlerMockRecorder {
 func (m *LogHandler) EXPECT() *LogHandlerMockRecorder {
 	return m.recorder
 	return m.recorder
 }
 }
 
 
-// Handle mocks base method
+// Handle mocks base method.
 func (m *LogHandler) Handle(arg0 log.Message) {
 func (m *LogHandler) Handle(arg0 log.Message) {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	m.ctrl.Call(m, "Handle", arg0)
 	m.ctrl.Call(m, "Handle", arg0)
 }
 }
 
 
-// Handle indicates an expected call of Handle
+// Handle indicates an expected call of Handle.
 func (mr *LogHandlerMockRecorder) Handle(arg0 interface{}) *gomock.Call {
 func (mr *LogHandlerMockRecorder) Handle(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handle", reflect.TypeOf((*LogHandler)(nil).Handle), arg0)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handle", reflect.TypeOf((*LogHandler)(nil).Handle), arg0)

+ 8 - 7
testing/mocks/mux.go

@@ -5,35 +5,36 @@
 package mocks
 package mocks
 
 
 import (
 import (
+	reflect "reflect"
+
 	gomock "github.com/golang/mock/gomock"
 	gomock "github.com/golang/mock/gomock"
 	mux "github.com/xtls/xray-core/common/mux"
 	mux "github.com/xtls/xray-core/common/mux"
-	reflect "reflect"
 )
 )
 
 
-// MuxClientWorkerFactory is a mock of ClientWorkerFactory interface
+// MuxClientWorkerFactory is a mock of ClientWorkerFactory interface.
 type MuxClientWorkerFactory struct {
 type MuxClientWorkerFactory struct {
 	ctrl     *gomock.Controller
 	ctrl     *gomock.Controller
 	recorder *MuxClientWorkerFactoryMockRecorder
 	recorder *MuxClientWorkerFactoryMockRecorder
 }
 }
 
 
-// MuxClientWorkerFactoryMockRecorder is the mock recorder for MuxClientWorkerFactory
+// MuxClientWorkerFactoryMockRecorder is the mock recorder for MuxClientWorkerFactory.
 type MuxClientWorkerFactoryMockRecorder struct {
 type MuxClientWorkerFactoryMockRecorder struct {
 	mock *MuxClientWorkerFactory
 	mock *MuxClientWorkerFactory
 }
 }
 
 
-// NewMuxClientWorkerFactory creates a new mock instance
+// NewMuxClientWorkerFactory creates a new mock instance.
 func NewMuxClientWorkerFactory(ctrl *gomock.Controller) *MuxClientWorkerFactory {
 func NewMuxClientWorkerFactory(ctrl *gomock.Controller) *MuxClientWorkerFactory {
 	mock := &MuxClientWorkerFactory{ctrl: ctrl}
 	mock := &MuxClientWorkerFactory{ctrl: ctrl}
 	mock.recorder = &MuxClientWorkerFactoryMockRecorder{mock}
 	mock.recorder = &MuxClientWorkerFactoryMockRecorder{mock}
 	return mock
 	return mock
 }
 }
 
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *MuxClientWorkerFactory) EXPECT() *MuxClientWorkerFactoryMockRecorder {
 func (m *MuxClientWorkerFactory) EXPECT() *MuxClientWorkerFactoryMockRecorder {
 	return m.recorder
 	return m.recorder
 }
 }
 
 
-// Create mocks base method
+// Create mocks base method.
 func (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) {
 func (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Create")
 	ret := m.ctrl.Call(m, "Create")
@@ -42,7 +43,7 @@ func (m *MuxClientWorkerFactory) Create() (*mux.ClientWorker, error) {
 	return ret0, ret1
 	return ret0, ret1
 }
 }
 
 
-// Create indicates an expected call of Create
+// Create indicates an expected call of Create.
 func (mr *MuxClientWorkerFactoryMockRecorder) Create() *gomock.Call {
 func (mr *MuxClientWorkerFactoryMockRecorder) Create() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MuxClientWorkerFactory)(nil).Create))
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MuxClientWorkerFactory)(nil).Create))

+ 26 - 25
testing/mocks/outbound.go

@@ -6,35 +6,36 @@ package mocks
 
 
 import (
 import (
 	context "context"
 	context "context"
+	reflect "reflect"
+
 	gomock "github.com/golang/mock/gomock"
 	gomock "github.com/golang/mock/gomock"
 	outbound "github.com/xtls/xray-core/features/outbound"
 	outbound "github.com/xtls/xray-core/features/outbound"
-	reflect "reflect"
 )
 )
 
 
-// OutboundManager is a mock of Manager interface
+// OutboundManager is a mock of Manager interface.
 type OutboundManager struct {
 type OutboundManager struct {
 	ctrl     *gomock.Controller
 	ctrl     *gomock.Controller
 	recorder *OutboundManagerMockRecorder
 	recorder *OutboundManagerMockRecorder
 }
 }
 
 
-// OutboundManagerMockRecorder is the mock recorder for OutboundManager
+// OutboundManagerMockRecorder is the mock recorder for OutboundManager.
 type OutboundManagerMockRecorder struct {
 type OutboundManagerMockRecorder struct {
 	mock *OutboundManager
 	mock *OutboundManager
 }
 }
 
 
-// NewOutboundManager creates a new mock instance
+// NewOutboundManager creates a new mock instance.
 func NewOutboundManager(ctrl *gomock.Controller) *OutboundManager {
 func NewOutboundManager(ctrl *gomock.Controller) *OutboundManager {
 	mock := &OutboundManager{ctrl: ctrl}
 	mock := &OutboundManager{ctrl: ctrl}
 	mock.recorder = &OutboundManagerMockRecorder{mock}
 	mock.recorder = &OutboundManagerMockRecorder{mock}
 	return mock
 	return mock
 }
 }
 
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *OutboundManager) EXPECT() *OutboundManagerMockRecorder {
 func (m *OutboundManager) EXPECT() *OutboundManagerMockRecorder {
 	return m.recorder
 	return m.recorder
 }
 }
 
 
-// AddHandler mocks base method
+// AddHandler mocks base method.
 func (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler) error {
 func (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler) error {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "AddHandler", arg0, arg1)
 	ret := m.ctrl.Call(m, "AddHandler", arg0, arg1)
@@ -42,13 +43,13 @@ func (m *OutboundManager) AddHandler(arg0 context.Context, arg1 outbound.Handler
 	return ret0
 	return ret0
 }
 }
 
 
-// AddHandler indicates an expected call of AddHandler
+// AddHandler indicates an expected call of AddHandler.
 func (mr *OutboundManagerMockRecorder) AddHandler(arg0, arg1 interface{}) *gomock.Call {
 func (mr *OutboundManagerMockRecorder) AddHandler(arg0, arg1 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddHandler", reflect.TypeOf((*OutboundManager)(nil).AddHandler), arg0, arg1)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddHandler", reflect.TypeOf((*OutboundManager)(nil).AddHandler), arg0, arg1)
 }
 }
 
 
-// Close mocks base method
+// Close mocks base method.
 func (m *OutboundManager) Close() error {
 func (m *OutboundManager) Close() error {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Close")
 	ret := m.ctrl.Call(m, "Close")
@@ -56,13 +57,13 @@ func (m *OutboundManager) Close() error {
 	return ret0
 	return ret0
 }
 }
 
 
-// Close indicates an expected call of Close
+// Close indicates an expected call of Close.
 func (mr *OutboundManagerMockRecorder) Close() *gomock.Call {
 func (mr *OutboundManagerMockRecorder) Close() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*OutboundManager)(nil).Close))
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*OutboundManager)(nil).Close))
 }
 }
 
 
-// GetDefaultHandler mocks base method
+// GetDefaultHandler mocks base method.
 func (m *OutboundManager) GetDefaultHandler() outbound.Handler {
 func (m *OutboundManager) GetDefaultHandler() outbound.Handler {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetDefaultHandler")
 	ret := m.ctrl.Call(m, "GetDefaultHandler")
@@ -70,13 +71,13 @@ func (m *OutboundManager) GetDefaultHandler() outbound.Handler {
 	return ret0
 	return ret0
 }
 }
 
 
-// GetDefaultHandler indicates an expected call of GetDefaultHandler
+// GetDefaultHandler indicates an expected call of GetDefaultHandler.
 func (mr *OutboundManagerMockRecorder) GetDefaultHandler() *gomock.Call {
 func (mr *OutboundManagerMockRecorder) GetDefaultHandler() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultHandler", reflect.TypeOf((*OutboundManager)(nil).GetDefaultHandler))
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDefaultHandler", reflect.TypeOf((*OutboundManager)(nil).GetDefaultHandler))
 }
 }
 
 
-// GetHandler mocks base method
+// GetHandler mocks base method.
 func (m *OutboundManager) GetHandler(arg0 string) outbound.Handler {
 func (m *OutboundManager) GetHandler(arg0 string) outbound.Handler {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "GetHandler", arg0)
 	ret := m.ctrl.Call(m, "GetHandler", arg0)
@@ -84,13 +85,13 @@ func (m *OutboundManager) GetHandler(arg0 string) outbound.Handler {
 	return ret0
 	return ret0
 }
 }
 
 
-// GetHandler indicates an expected call of GetHandler
+// GetHandler indicates an expected call of GetHandler.
 func (mr *OutboundManagerMockRecorder) GetHandler(arg0 interface{}) *gomock.Call {
 func (mr *OutboundManagerMockRecorder) GetHandler(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHandler", reflect.TypeOf((*OutboundManager)(nil).GetHandler), arg0)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHandler", reflect.TypeOf((*OutboundManager)(nil).GetHandler), arg0)
 }
 }
 
 
-// RemoveHandler mocks base method
+// RemoveHandler mocks base method.
 func (m *OutboundManager) RemoveHandler(arg0 context.Context, arg1 string) error {
 func (m *OutboundManager) RemoveHandler(arg0 context.Context, arg1 string) error {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "RemoveHandler", arg0, arg1)
 	ret := m.ctrl.Call(m, "RemoveHandler", arg0, arg1)
@@ -98,13 +99,13 @@ func (m *OutboundManager) RemoveHandler(arg0 context.Context, arg1 string) error
 	return ret0
 	return ret0
 }
 }
 
 
-// RemoveHandler indicates an expected call of RemoveHandler
+// RemoveHandler indicates an expected call of RemoveHandler.
 func (mr *OutboundManagerMockRecorder) RemoveHandler(arg0, arg1 interface{}) *gomock.Call {
 func (mr *OutboundManagerMockRecorder) RemoveHandler(arg0, arg1 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveHandler", reflect.TypeOf((*OutboundManager)(nil).RemoveHandler), arg0, arg1)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveHandler", reflect.TypeOf((*OutboundManager)(nil).RemoveHandler), arg0, arg1)
 }
 }
 
 
-// Start mocks base method
+// Start mocks base method.
 func (m *OutboundManager) Start() error {
 func (m *OutboundManager) Start() error {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Start")
 	ret := m.ctrl.Call(m, "Start")
@@ -112,13 +113,13 @@ func (m *OutboundManager) Start() error {
 	return ret0
 	return ret0
 }
 }
 
 
-// Start indicates an expected call of Start
+// Start indicates an expected call of Start.
 func (mr *OutboundManagerMockRecorder) Start() *gomock.Call {
 func (mr *OutboundManagerMockRecorder) Start() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*OutboundManager)(nil).Start))
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*OutboundManager)(nil).Start))
 }
 }
 
 
-// Type mocks base method
+// Type mocks base method.
 func (m *OutboundManager) Type() interface{} {
 func (m *OutboundManager) Type() interface{} {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Type")
 	ret := m.ctrl.Call(m, "Type")
@@ -126,36 +127,36 @@ func (m *OutboundManager) Type() interface{} {
 	return ret0
 	return ret0
 }
 }
 
 
-// Type indicates an expected call of Type
+// Type indicates an expected call of Type.
 func (mr *OutboundManagerMockRecorder) Type() *gomock.Call {
 func (mr *OutboundManagerMockRecorder) Type() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*OutboundManager)(nil).Type))
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Type", reflect.TypeOf((*OutboundManager)(nil).Type))
 }
 }
 
 
-// OutboundHandlerSelector is a mock of HandlerSelector interface
+// OutboundHandlerSelector is a mock of HandlerSelector interface.
 type OutboundHandlerSelector struct {
 type OutboundHandlerSelector struct {
 	ctrl     *gomock.Controller
 	ctrl     *gomock.Controller
 	recorder *OutboundHandlerSelectorMockRecorder
 	recorder *OutboundHandlerSelectorMockRecorder
 }
 }
 
 
-// OutboundHandlerSelectorMockRecorder is the mock recorder for OutboundHandlerSelector
+// OutboundHandlerSelectorMockRecorder is the mock recorder for OutboundHandlerSelector.
 type OutboundHandlerSelectorMockRecorder struct {
 type OutboundHandlerSelectorMockRecorder struct {
 	mock *OutboundHandlerSelector
 	mock *OutboundHandlerSelector
 }
 }
 
 
-// NewOutboundHandlerSelector creates a new mock instance
+// NewOutboundHandlerSelector creates a new mock instance.
 func NewOutboundHandlerSelector(ctrl *gomock.Controller) *OutboundHandlerSelector {
 func NewOutboundHandlerSelector(ctrl *gomock.Controller) *OutboundHandlerSelector {
 	mock := &OutboundHandlerSelector{ctrl: ctrl}
 	mock := &OutboundHandlerSelector{ctrl: ctrl}
 	mock.recorder = &OutboundHandlerSelectorMockRecorder{mock}
 	mock.recorder = &OutboundHandlerSelectorMockRecorder{mock}
 	return mock
 	return mock
 }
 }
 
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *OutboundHandlerSelector) EXPECT() *OutboundHandlerSelectorMockRecorder {
 func (m *OutboundHandlerSelector) EXPECT() *OutboundHandlerSelectorMockRecorder {
 	return m.recorder
 	return m.recorder
 }
 }
 
 
-// Select mocks base method
+// Select mocks base method.
 func (m *OutboundHandlerSelector) Select(arg0 []string) []string {
 func (m *OutboundHandlerSelector) Select(arg0 []string) []string {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Select", arg0)
 	ret := m.ctrl.Call(m, "Select", arg0)
@@ -163,7 +164,7 @@ func (m *OutboundHandlerSelector) Select(arg0 []string) []string {
 	return ret0
 	return ret0
 }
 }
 
 
-// Select indicates an expected call of Select
+// Select indicates an expected call of Select.
 func (mr *OutboundHandlerSelectorMockRecorder) Select(arg0 interface{}) *gomock.Call {
 func (mr *OutboundHandlerSelectorMockRecorder) Select(arg0 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*OutboundHandlerSelector)(nil).Select), arg0)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*OutboundHandlerSelector)(nil).Select), arg0)

+ 16 - 15
testing/mocks/proxy.go

@@ -6,38 +6,39 @@ package mocks
 
 
 import (
 import (
 	context "context"
 	context "context"
+	reflect "reflect"
+
 	gomock "github.com/golang/mock/gomock"
 	gomock "github.com/golang/mock/gomock"
 	net "github.com/xtls/xray-core/common/net"
 	net "github.com/xtls/xray-core/common/net"
 	routing "github.com/xtls/xray-core/features/routing"
 	routing "github.com/xtls/xray-core/features/routing"
 	transport "github.com/xtls/xray-core/transport"
 	transport "github.com/xtls/xray-core/transport"
 	internet "github.com/xtls/xray-core/transport/internet"
 	internet "github.com/xtls/xray-core/transport/internet"
-	reflect "reflect"
 )
 )
 
 
-// ProxyInbound is a mock of Inbound interface
+// ProxyInbound is a mock of Inbound interface.
 type ProxyInbound struct {
 type ProxyInbound struct {
 	ctrl     *gomock.Controller
 	ctrl     *gomock.Controller
 	recorder *ProxyInboundMockRecorder
 	recorder *ProxyInboundMockRecorder
 }
 }
 
 
-// ProxyInboundMockRecorder is the mock recorder for ProxyInbound
+// ProxyInboundMockRecorder is the mock recorder for ProxyInbound.
 type ProxyInboundMockRecorder struct {
 type ProxyInboundMockRecorder struct {
 	mock *ProxyInbound
 	mock *ProxyInbound
 }
 }
 
 
-// NewProxyInbound creates a new mock instance
+// NewProxyInbound creates a new mock instance.
 func NewProxyInbound(ctrl *gomock.Controller) *ProxyInbound {
 func NewProxyInbound(ctrl *gomock.Controller) *ProxyInbound {
 	mock := &ProxyInbound{ctrl: ctrl}
 	mock := &ProxyInbound{ctrl: ctrl}
 	mock.recorder = &ProxyInboundMockRecorder{mock}
 	mock.recorder = &ProxyInboundMockRecorder{mock}
 	return mock
 	return mock
 }
 }
 
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *ProxyInbound) EXPECT() *ProxyInboundMockRecorder {
 func (m *ProxyInbound) EXPECT() *ProxyInboundMockRecorder {
 	return m.recorder
 	return m.recorder
 }
 }
 
 
-// Network mocks base method
+// Network mocks base method.
 func (m *ProxyInbound) Network() []net.Network {
 func (m *ProxyInbound) Network() []net.Network {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Network")
 	ret := m.ctrl.Call(m, "Network")
@@ -45,13 +46,13 @@ func (m *ProxyInbound) Network() []net.Network {
 	return ret0
 	return ret0
 }
 }
 
 
-// Network indicates an expected call of Network
+// Network indicates an expected call of Network.
 func (mr *ProxyInboundMockRecorder) Network() *gomock.Call {
 func (mr *ProxyInboundMockRecorder) Network() *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*ProxyInbound)(nil).Network))
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Network", reflect.TypeOf((*ProxyInbound)(nil).Network))
 }
 }
 
 
-// Process mocks base method
+// Process mocks base method.
 func (m *ProxyInbound) Process(arg0 context.Context, arg1 net.Network, arg2 internet.Connection, arg3 routing.Dispatcher) error {
 func (m *ProxyInbound) Process(arg0 context.Context, arg1 net.Network, arg2 internet.Connection, arg3 routing.Dispatcher) error {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2, arg3)
 	ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2, arg3)
@@ -59,36 +60,36 @@ func (m *ProxyInbound) Process(arg0 context.Context, arg1 net.Network, arg2 inte
 	return ret0
 	return ret0
 }
 }
 
 
-// Process indicates an expected call of Process
+// Process indicates an expected call of Process.
 func (mr *ProxyInboundMockRecorder) Process(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
 func (mr *ProxyInboundMockRecorder) Process(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyInbound)(nil).Process), arg0, arg1, arg2, arg3)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyInbound)(nil).Process), arg0, arg1, arg2, arg3)
 }
 }
 
 
-// ProxyOutbound is a mock of Outbound interface
+// ProxyOutbound is a mock of Outbound interface.
 type ProxyOutbound struct {
 type ProxyOutbound struct {
 	ctrl     *gomock.Controller
 	ctrl     *gomock.Controller
 	recorder *ProxyOutboundMockRecorder
 	recorder *ProxyOutboundMockRecorder
 }
 }
 
 
-// ProxyOutboundMockRecorder is the mock recorder for ProxyOutbound
+// ProxyOutboundMockRecorder is the mock recorder for ProxyOutbound.
 type ProxyOutboundMockRecorder struct {
 type ProxyOutboundMockRecorder struct {
 	mock *ProxyOutbound
 	mock *ProxyOutbound
 }
 }
 
 
-// NewProxyOutbound creates a new mock instance
+// NewProxyOutbound creates a new mock instance.
 func NewProxyOutbound(ctrl *gomock.Controller) *ProxyOutbound {
 func NewProxyOutbound(ctrl *gomock.Controller) *ProxyOutbound {
 	mock := &ProxyOutbound{ctrl: ctrl}
 	mock := &ProxyOutbound{ctrl: ctrl}
 	mock.recorder = &ProxyOutboundMockRecorder{mock}
 	mock.recorder = &ProxyOutboundMockRecorder{mock}
 	return mock
 	return mock
 }
 }
 
 
-// EXPECT returns an object that allows the caller to indicate expected use
+// EXPECT returns an object that allows the caller to indicate expected use.
 func (m *ProxyOutbound) EXPECT() *ProxyOutboundMockRecorder {
 func (m *ProxyOutbound) EXPECT() *ProxyOutboundMockRecorder {
 	return m.recorder
 	return m.recorder
 }
 }
 
 
-// Process mocks base method
+// Process mocks base method.
 func (m *ProxyOutbound) Process(arg0 context.Context, arg1 *transport.Link, arg2 internet.Dialer) error {
 func (m *ProxyOutbound) Process(arg0 context.Context, arg1 *transport.Link, arg2 internet.Dialer) error {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
 	ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2)
 	ret := m.ctrl.Call(m, "Process", arg0, arg1, arg2)
@@ -96,7 +97,7 @@ func (m *ProxyOutbound) Process(arg0 context.Context, arg1 *transport.Link, arg2
 	return ret0
 	return ret0
 }
 }
 
 
-// Process indicates an expected call of Process
+// Process indicates an expected call of Process.
 func (mr *ProxyOutboundMockRecorder) Process(arg0, arg1, arg2 interface{}) *gomock.Call {
 func (mr *ProxyOutboundMockRecorder) Process(arg0, arg1, arg2 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyOutbound)(nil).Process), arg0, arg1, arg2)
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*ProxyOutbound)(nil).Process), arg0, arg1, arg2)

+ 6 - 16
transport/internet/system_dialer.go

@@ -63,30 +63,20 @@ func (d *DefaultSystemDialer) lookupIP(domain string, strategy DomainStrategy, l
 		return nil, nil
 		return nil, nil
 	}
 	}
 
 
-	if c, ok := d.dns.(dns.ClientWithIPOption); ok {
-		c.SetFakeDNSOption(false) // Skip FakeDNS
-	} else {
-		newError("DNS client doesn't implement ClientWithIPOption")
-	}
-
-	var lookupFunc func(string) ([]net.IP, error) = d.dns.LookupIP
+	var opt dns.Option
 	switch {
 	switch {
 	case strategy == DomainStrategy_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()):
 	case strategy == DomainStrategy_USE_IP4 || (localAddr != nil && localAddr.Family().IsIPv4()):
-		if lookupIPv4, ok := d.dns.(dns.IPv4Lookup); ok {
-			lookupFunc = lookupIPv4.LookupIPv4
-		}
+		opt = dns.LookupIPv4Only
 	case strategy == DomainStrategy_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()):
 	case strategy == DomainStrategy_USE_IP6 || (localAddr != nil && localAddr.Family().IsIPv6()):
-		if lookupIPv6, ok := d.dns.(dns.IPv6Lookup); ok {
-			lookupFunc = lookupIPv6.LookupIPv6
-		}
+		opt = dns.LookupIPv6Only
 	case strategy == DomainStrategy_AS_IS:
 	case strategy == DomainStrategy_AS_IS:
 		return nil, nil
 		return nil, nil
 	}
 	}
 
 
-	return lookupFunc(domain)
+	return d.dns.LookupOptions(domain, opt, dns.LookupNoFake)
 }
 }
 
 
-func (d *DefaultSystemDialer) doLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
+func (d *DefaultSystemDialer) canLookupIP(ctx context.Context, dst net.Destination, sockopt *SocketConfig) bool {
 	if sockopt == nil || dst.Address.Family().IsIP() || d.dns == nil {
 	if sockopt == nil || dst.Address.Family().IsIP() || d.dns == nil {
 		return false
 		return false
 	}
 	}
@@ -121,7 +111,7 @@ func (d *DefaultSystemDialer) Dial(ctx context.Context, src net.Address, dest ne
 		}
 		}
 	}
 	}
 
 
-	if d.doLookupIP(ctx, dest, sockopt) {
+	if d.canLookupIP(ctx, dest, sockopt) {
 		ips, err := d.lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
 		ips, err := d.lookupIP(dest.Address.String(), sockopt.DomainStrategy, src)
 		if err == nil && len(ips) > 0 {
 		if err == nil && len(ips) > 0 {
 			dest.Address = net.IPAddress(ips[dice.Roll(len(ips))])
 			dest.Address = net.IPAddress(ips[dice.Roll(len(ips))])