Browse Source

Feat: routing and freedom outbound ignore Fake DNS (#696)

Turn off fake DNS for request sent from Routing and Freedom outbound.
Fake DNS now only apply to DNS outbound.
This is important for Android, where VPN service take over all system DNS
traffic and pass it to core.  "UseIp" option can be used in Freedom outbound
to avoid getting fake IP and fail connection.

Co-authored-by: loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com>
yuhan6665 4 years ago
parent
commit
afb8385a7e

+ 8 - 25
app/dns/dns.go

@@ -8,6 +8,7 @@ package dns
 import (
 import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
+	"strings"
 	"sync"
 	"sync"
 
 
 	"github.com/v2fly/v2ray-core/v4/app/router"
 	"github.com/v2fly/v2ray-core/v4/app/router"
@@ -105,6 +106,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
 		clients = append(clients, client)
 		clients = append(clients, client)
 	}
 	}
 
 
+	// If there is no DNS client in config, add a `localhost` DNS client
 	if len(clients) == 0 {
 	if len(clients) == 0 {
 		clients = append(clients, NewLocalDNSClient())
 		clients = append(clients, NewLocalDNSClient())
 	}
 	}
@@ -141,36 +143,13 @@ 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) {
-	return s.lookupIPInternal(domain, IPOption{
-		IPv4Enable: true,
-		IPv6Enable: true,
-	})
-}
-
-// LookupIPv4 implements dns.IPv4Lookup.
-func (s *DNS) LookupIPv4(domain string) ([]net.IP, error) {
-	return s.lookupIPInternal(domain, IPOption{
-		IPv4Enable: true,
-		IPv6Enable: false,
-	})
-}
-
-// LookupIPv6 implements dns.IPv6Lookup.
-func (s *DNS) LookupIPv6(domain string) ([]net.IP, error) {
-	return s.lookupIPInternal(domain, IPOption{
-		IPv4Enable: false,
-		IPv6Enable: true,
-	})
-}
-
-func (s *DNS) lookupIPInternal(domain string, option IPOption) ([]net.IP, error) {
+func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]net.IP, error) {
 	if domain == "" {
 	if domain == "" {
 		return nil, newError("empty domain name")
 		return nil, newError("empty domain name")
 	}
 	}
 
 
 	// Normalize the FQDN form query
 	// Normalize the FQDN form query
-	if domain[len(domain)-1] == '.' {
+	if strings.HasSuffix(domain, ".") {
 		domain = domain[:len(domain)-1]
 		domain = domain[:len(domain)-1]
 	}
 	}
 
 
@@ -192,6 +171,10 @@ func (s *DNS) lookupIPInternal(domain string, option IPOption) ([]net.IP, error)
 	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) {
 	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)
 		ips, err := client.QueryIP(ctx, domain, option)
 		if len(ips) > 0 {
 		if len(ips) > 0 {
 			return ips, nil
 			return ips, nil

+ 110 - 26
app/dns/dns_test.go

@@ -154,7 +154,11 @@ func TestUDPServerSubnet(t *testing.T) {
 
 
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 
 
-	ips, err := client.LookupIP("google.com")
+	ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+		IPv4Enable: true,
+		IPv6Enable: true,
+		FakeEnable: false,
+	})
 	if err != nil {
 	if err != nil {
 		t.Fatal("unexpected error: ", err)
 		t.Fatal("unexpected error: ", err)
 	}
 	}
@@ -209,7 +213,11 @@ func TestUDPServer(t *testing.T) {
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 
 
 	{
 	{
-		ips, err := client.LookupIP("google.com")
+		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -220,7 +228,11 @@ func TestUDPServer(t *testing.T) {
 	}
 	}
 
 
 	{
 	{
-		ips, err := client.LookupIP("facebook.com")
+		ips, err := client.LookupIP("facebook.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -231,7 +243,11 @@ func TestUDPServer(t *testing.T) {
 	}
 	}
 
 
 	{
 	{
-		_, err := client.LookupIP("notexist.google.com")
+		_, err := client.LookupIP("notexist.google.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err == nil {
 		if err == nil {
 			t.Fatal("nil error")
 			t.Fatal("nil error")
 		}
 		}
@@ -241,8 +257,11 @@ func TestUDPServer(t *testing.T) {
 	}
 	}
 
 
 	{
 	{
-		clientv6 := client.(feature_dns.IPv6Lookup)
-		ips, err := clientv6.LookupIPv6("ipv4only.google.com")
+		ips, err := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
+			IPv4Enable: false,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != feature_dns.ErrEmptyResponse {
 		if err != feature_dns.ErrEmptyResponse {
 			t.Fatal("error: ", err)
 			t.Fatal("error: ", err)
 		}
 		}
@@ -254,7 +273,11 @@ func TestUDPServer(t *testing.T) {
 	dnsServer.Shutdown()
 	dnsServer.Shutdown()
 
 
 	{
 	{
-		ips, err := client.LookupIP("google.com")
+		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -331,7 +354,11 @@ func TestPrioritizedDomain(t *testing.T) {
 	startTime := time.Now()
 	startTime := time.Now()
 
 
 	{
 	{
-		ips, err := client.LookupIP("google.com")
+		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -390,10 +417,12 @@ func TestUDPServerIPv6(t *testing.T) {
 	common.Must(err)
 	common.Must(err)
 
 
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
-	client6 := client.(feature_dns.IPv6Lookup)
-
 	{
 	{
-		ips, err := client6.LookupIPv6("ipv6.google.com")
+		ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
+			IPv4Enable: false,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -456,7 +485,11 @@ func TestStaticHostDomain(t *testing.T) {
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 	client := v.GetFeature(feature_dns.ClientType()).(feature_dns.Client)
 
 
 	{
 	{
-		ips, err := client.LookupIP("example.com")
+		ips, err := client.LookupIP("example.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -563,7 +596,11 @@ func TestIPMatch(t *testing.T) {
 	startTime := time.Now()
 	startTime := time.Now()
 
 
 	{
 	{
-		ips, err := client.LookupIP("google.com")
+		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -682,7 +719,11 @@ func TestLocalDomain(t *testing.T) {
 	startTime := time.Now()
 	startTime := time.Now()
 
 
 	{ // Will match dotless:
 	{ // Will match dotless:
-		ips, err := client.LookupIP("hostname")
+		ips, err := client.LookupIP("hostname", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -693,7 +734,11 @@ func TestLocalDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match domain:local
 	{ // Will match domain:local
-		ips, err := client.LookupIP("hostname.local")
+		ips, err := client.LookupIP("hostname.local", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -704,7 +749,11 @@ func TestLocalDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match static ip
 	{ // Will match static ip
-		ips, err := client.LookupIP("hostnamestatic")
+		ips, err := client.LookupIP("hostnamestatic", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -715,7 +764,11 @@ func TestLocalDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match domain replacing
 	{ // Will match domain replacing
-		ips, err := client.LookupIP("hostnamealias")
+		ips, err := client.LookupIP("hostnamealias", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -726,7 +779,11 @@ func TestLocalDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
 	{ // Will match dotless:localhost, but not expectIPs: 127.0.0.2, 127.0.0.3, then matches at dotless:
-		ips, err := client.LookupIP("localhost")
+		ips, err := client.LookupIP("localhost", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -737,7 +794,11 @@ func TestLocalDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
 	{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
-		ips, err := client.LookupIP("localhost-a")
+		ips, err := client.LookupIP("localhost-a", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -748,7 +809,11 @@ func TestLocalDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
 	{ // Will match dotless:localhost, and expectIPs: 127.0.0.2, 127.0.0.3
-		ips, err := client.LookupIP("localhost-b")
+		ips, err := client.LookupIP("localhost-b", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -759,7 +824,11 @@ func TestLocalDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match dotless:
 	{ // Will match dotless:
-		ips, err := client.LookupIP("Mijia Cloud")
+		ips, err := client.LookupIP("Mijia Cloud", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -921,7 +990,11 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 	startTime := time.Now()
 	startTime := time.Now()
 
 
 	{ // Will match server 1,2 and server 1 returns expected ip
 	{ // Will match server 1,2 and server 1 returns expected ip
-		ips, err := client.LookupIP("google.com")
+		ips, err := client.LookupIP("google.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -932,8 +1005,11 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one
 	{ // Will match server 1,2 and server 1 returns unexpected ip, then server 2 returns expected one
-		clientv4 := client.(feature_dns.IPv4Lookup)
-		ips, err := clientv4.LookupIPv4("ipv6.google.com")
+		ips, err := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: false,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -944,7 +1020,11 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match server 3,1,2 and server 3 returns expected one
 	{ // Will match server 3,1,2 and server 3 returns expected one
-		ips, err := client.LookupIP("api.google.com")
+		ips, err := client.LookupIP("api.google.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}
@@ -955,7 +1035,11 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 	}
 	}
 
 
 	{ // Will match server 4,3,1,2 and server 4 returns expected one
 	{ // Will match server 4,3,1,2 and server 4 returns expected one
-		ips, err := client.LookupIP("v2.api.google.com")
+		ips, err := client.LookupIP("v2.api.google.com", feature_dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err != nil {
 		if err != nil {
 			t.Fatal("unexpected error: ", err)
 			t.Fatal("unexpected error: ", err)
 		}
 		}

+ 3 - 2
app/dns/dnscommon.go

@@ -4,6 +4,7 @@ package dns
 
 
 import (
 import (
 	"encoding/binary"
 	"encoding/binary"
+	"strings"
 	"time"
 	"time"
 
 
 	"golang.org/x/net/dns/dnsmessage"
 	"golang.org/x/net/dns/dnsmessage"
@@ -16,7 +17,7 @@ import (
 
 
 // Fqdn normalize domain make sure it ends with '.'
 // Fqdn normalize domain make sure it ends with '.'
 func Fqdn(domain string) string {
 func Fqdn(domain string) string {
-	if len(domain) > 0 && domain[len(domain)-1] == '.' {
+	if len(domain) > 0 && strings.HasSuffix(domain, ".") {
 		return domain
 		return domain
 	}
 	}
 	return domain + "."
 	return domain + "."
@@ -115,7 +116,7 @@ func genEDNS0Options(clientIP net.IP) *dnsmessage.Resource {
 	return opt
 	return opt
 }
 }
 
 
-func buildReqMsgs(domain string, option IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {
+func buildReqMsgs(domain string, option dns_feature.IPOption, reqIDGen func() uint16, reqOpts *dnsmessage.Resource) []*dnsRequest {
 	qA := dnsmessage.Question{
 	qA := dnsmessage.Question{
 		Name:  dnsmessage.MustNewName(domain),
 		Name:  dnsmessage.MustNewName(domain),
 		Type:  dnsmessage.TypeA,
 		Type:  dnsmessage.TypeA,

+ 22 - 5
app/dns/dnscommon_test.go

@@ -13,6 +13,7 @@ import (
 
 
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/net"
+	dns_feature "github.com/v2fly/v2ray-core/v4/features/dns"
 )
 )
 
 
 func Test_parseResponse(t *testing.T) {
 func Test_parseResponse(t *testing.T) {
@@ -95,7 +96,7 @@ func Test_buildReqMsgs(t *testing.T) {
 	}
 	}
 	type args struct {
 	type args struct {
 		domain  string
 		domain  string
-		option  IPOption
+		option  dns_feature.IPOption
 		reqOpts *dnsmessage.Resource
 		reqOpts *dnsmessage.Resource
 	}
 	}
 	tests := []struct {
 	tests := []struct {
@@ -103,10 +104,26 @@ func Test_buildReqMsgs(t *testing.T) {
 		args args
 		args args
 		want int
 		want int
 	}{
 	}{
-		{"dual stack", args{"test.com", IPOption{true, true}, nil}, 2},
-		{"ipv4 only", args{"test.com", IPOption{true, false}, nil}, 1},
-		{"ipv6 only", args{"test.com", IPOption{false, true}, nil}, 1},
-		{"none/error", args{"test.com", IPOption{false, false}, nil}, 0},
+		{"dual stack", args{"test.com", dns_feature.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		}, nil}, 2},
+		{"ipv4 only", args{"test.com", dns_feature.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: false,
+			FakeEnable: false,
+		}, nil}, 1},
+		{"ipv6 only", args{"test.com", dns_feature.IPOption{
+			IPv4Enable: false,
+			IPv6Enable: true,
+			FakeEnable: false,
+		}, nil}, 1},
+		{"none/error", args{"test.com", dns_feature.IPOption{
+			IPv4Enable: false,
+			IPv6Enable: false,
+			FakeEnable: false,
+		}, nil}, 0},
 	}
 	}
 	for _, tt := range tests {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {

+ 4 - 3
app/dns/hosts.go

@@ -7,6 +7,7 @@ import (
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/strmatcher"
 	"github.com/v2fly/v2ray-core/v4/common/strmatcher"
 	"github.com/v2fly/v2ray-core/v4/features"
 	"github.com/v2fly/v2ray-core/v4/features"
+	"github.com/v2fly/v2ray-core/v4/features/dns"
 )
 )
 
 
 // StaticHosts represents static domain-ip mapping in DNS server.
 // StaticHosts represents static domain-ip mapping in DNS server.
@@ -75,7 +76,7 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma
 	return sh, nil
 	return sh, nil
 }
 }
 
 
-func filterIP(ips []net.Address, option 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) {
@@ -93,7 +94,7 @@ func (h *StaticHosts) lookupInternal(domain string) []net.Address {
 	return ips
 	return ips
 }
 }
 
 
-func (h *StaticHosts) lookup(domain string, option 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
@@ -111,6 +112,6 @@ func (h *StaticHosts) lookup(domain string, option IPOption, maxDepth int) []net
 }
 }
 
 
 // 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 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)
 }
 }

+ 4 - 3
app/dns/hosts_test.go

@@ -8,6 +8,7 @@ import (
 	. "github.com/v2fly/v2ray-core/v4/app/dns"
 	. "github.com/v2fly/v2ray-core/v4/app/dns"
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/net"
+	"github.com/v2fly/v2ray-core/v4/features/dns"
 )
 )
 
 
 func TestStaticHosts(t *testing.T) {
 func TestStaticHosts(t *testing.T) {
@@ -39,7 +40,7 @@ func TestStaticHosts(t *testing.T) {
 	common.Must(err)
 	common.Must(err)
 
 
 	{
 	{
-		ips := hosts.Lookup("v2fly.org", IPOption{
+		ips := hosts.Lookup("v2fly.org", dns.IPOption{
 			IPv4Enable: true,
 			IPv4Enable: true,
 			IPv6Enable: true,
 			IPv6Enable: true,
 		})
 		})
@@ -52,7 +53,7 @@ func TestStaticHosts(t *testing.T) {
 	}
 	}
 
 
 	{
 	{
-		ips := hosts.Lookup("www.v2ray.cn", IPOption{
+		ips := hosts.Lookup("www.v2ray.cn", dns.IPOption{
 			IPv4Enable: true,
 			IPv4Enable: true,
 			IPv6Enable: true,
 			IPv6Enable: true,
 		})
 		})
@@ -65,7 +66,7 @@ func TestStaticHosts(t *testing.T) {
 	}
 	}
 
 
 	{
 	{
-		ips := hosts.Lookup("baidu.com", IPOption{
+		ips := hosts.Lookup("baidu.com", dns.IPOption{
 			IPv4Enable: false,
 			IPv4Enable: false,
 			IPv6Enable: true,
 			IPv6Enable: true,
 		})
 		})

+ 9 - 13
app/dns/nameserver.go

@@ -5,6 +5,7 @@ package dns
 import (
 import (
 	"context"
 	"context"
 	"net/url"
 	"net/url"
+	"strings"
 	"time"
 	"time"
 
 
 	core "github.com/v2fly/v2ray-core/v4"
 	core "github.com/v2fly/v2ray-core/v4"
@@ -12,21 +13,16 @@ import (
 	"github.com/v2fly/v2ray-core/v4/common/errors"
 	"github.com/v2fly/v2ray-core/v4/common/errors"
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/strmatcher"
 	"github.com/v2fly/v2ray-core/v4/common/strmatcher"
+	"github.com/v2fly/v2ray-core/v4/features/dns"
 	"github.com/v2fly/v2ray-core/v4/features/routing"
 	"github.com/v2fly/v2ray-core/v4/features/routing"
 )
 )
 
 
-// IPOption is an object for IP query options.
-type IPOption struct {
-	IPv4Enable bool
-	IPv6Enable bool
-}
-
 // Server is the interface for Name Server.
 // Server is the interface for Name Server.
 type Server interface {
 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 IPOption) ([]net.IP, error)
+	QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption) ([]net.IP, error)
 }
 }
 
 
 // Client is the interface for DNS client.
 // Client is the interface for DNS client.
@@ -47,15 +43,15 @@ func NewServer(dest net.Destination, dispatcher routing.Dispatcher) (Server, err
 			return nil, err
 			return nil, err
 		}
 		}
 		switch {
 		switch {
-		case u.String() == "localhost":
+		case strings.EqualFold(u.String(), "localhost"):
 			return NewLocalNameServer(), nil
 			return NewLocalNameServer(), nil
-		case u.Scheme == "https": // DOH Remote mode
+		case strings.EqualFold(u.Scheme, "https"): // DOH Remote mode
 			return NewDoHNameServer(u, dispatcher)
 			return NewDoHNameServer(u, dispatcher)
-		case u.Scheme == "https+local": // DOH Local mode
+		case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode
 			return NewDoHLocalNameServer(u), nil
 			return NewDoHLocalNameServer(u), nil
-		case u.Scheme == "quic+local": // DNS-over-QUIC Local mode
+		case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
 			return NewQUICNameServer(u)
 			return NewQUICNameServer(u)
-		case u.String() == "fakedns":
+		case strings.EqualFold(u.String(), "fakedns"):
 			return NewFakeDNSServer(), nil
 			return NewFakeDNSServer(), nil
 		}
 		}
 	}
 	}
@@ -173,7 +169,7 @@ 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 IPOption) ([]net.IP, error) {
+func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption) ([]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)
 	ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option)
 	cancel()
 	cancel()

+ 3 - 3
app/dns/nameserver_doh.go

@@ -207,7 +207,7 @@ func (s *DoHNameServer) newReqID() uint16 {
 	return uint16(atomic.AddUint32(&s.reqID, 1))
 	return uint16(atomic.AddUint32(&s.reqID, 1))
 }
 }
 
 
-func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option IPOption) {
+func (s *DoHNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
 	newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
 	newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
 
 
 	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
 	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
@@ -286,7 +286,7 @@ func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte,
 	return ioutil.ReadAll(resp.Body)
 	return ioutil.ReadAll(resp.Body)
 }
 }
 
 
-func (s *DoHNameServer) findIPsForDomain(domain string, option IPOption) ([]net.IP, error) {
+func (s *DoHNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
 	s.RLock()
 	s.RLock()
 	record, found := s.ips[domain]
 	record, found := s.ips[domain]
 	s.RUnlock()
 	s.RUnlock()
@@ -329,7 +329,7 @@ func (s *DoHNameServer) findIPsForDomain(domain string, option IPOption) ([]net.
 }
 }
 
 
 // QueryIP implements Server.
 // QueryIP implements Server.
-func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option IPOption) ([]net.IP, error) { // nolint: dupl
+func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) ([]net.IP, error) { // nolint: dupl
 	fqdn := Fqdn(domain)
 	fqdn := Fqdn(domain)
 
 
 	ips, err := s.findIPsForDomain(fqdn, option)
 	ips, err := s.findIPsForDomain(fqdn, option)

+ 3 - 1
app/dns/nameserver_fakedns.go

@@ -22,7 +22,7 @@ func (FakeDNSServer) Name() string {
 	return "FakeDNS"
 	return "FakeDNS"
 }
 }
 
 
-func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, _ IPOption) ([]net.IP, error) {
+func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, _ dns.IPOption) ([]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
@@ -37,5 +37,7 @@ func (f *FakeDNSServer) QueryIP(ctx context.Context, domain string, _ net.IP, _
 		return nil, newError("Unable to convert IP to net ip").Base(err).AtError()
 		return nil, newError("Unable to convert IP to net ip").Base(err).AtError()
 	}
 	}
 
 
+	newError(f.Name(), " got answer: ", domain, " -> ", ips).AtInfo().WriteToLog()
+
 	return netIP, nil
 	return netIP, nil
 }
 }

+ 4 - 11
app/dns/nameserver_local.go

@@ -6,6 +6,7 @@ import (
 	"context"
 	"context"
 
 
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/net"
+	"github.com/v2fly/v2ray-core/v4/features/dns"
 	"github.com/v2fly/v2ray-core/v4/features/dns/localdns"
 	"github.com/v2fly/v2ray-core/v4/features/dns/localdns"
 )
 )
 
 
@@ -15,17 +16,9 @@ type LocalNameServer struct {
 }
 }
 
 
 // QueryIP implements Server.
 // QueryIP implements Server.
-func (s *LocalNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option IPOption) ([]net.IP, error) {
-	if option.IPv4Enable && option.IPv6Enable {
-		return s.client.LookupIP(domain)
-	}
-
-	if option.IPv4Enable {
-		return s.client.LookupIPv4(domain)
-	}
-
-	if option.IPv6Enable {
-		return s.client.LookupIPv6(domain)
+func (s *LocalNameServer) QueryIP(_ context.Context, domain string, _ net.IP, option dns.IPOption) ([]net.IP, error) {
+	if option.IPv4Enable || option.IPv6Enable {
+		return s.client.LookupIP(domain, option)
 	}
 	}
 
 
 	return nil, newError("neither IPv4 nor IPv6 is enabled")
 	return nil, newError("neither IPv4 nor IPv6 is enabled")

+ 2 - 1
app/dns/nameserver_local_test.go

@@ -8,12 +8,13 @@ import (
 	. "github.com/v2fly/v2ray-core/v4/app/dns"
 	. "github.com/v2fly/v2ray-core/v4/app/dns"
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/net"
+	"github.com/v2fly/v2ray-core/v4/features/dns"
 )
 )
 
 
 func TestLocalNameServer(t *testing.T) {
 func TestLocalNameServer(t *testing.T) {
 	s := NewLocalNameServer()
 	s := NewLocalNameServer()
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP{}, IPOption{
+	ips, err := s.QueryIP(ctx, "google.com", net.IP{}, dns.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
 		IPv6Enable: true,
 		IPv6Enable: true,
 	})
 	})

+ 3 - 3
app/dns/nameserver_quic.go

@@ -153,7 +153,7 @@ func (s *QUICNameServer) newReqID() uint16 {
 	return uint16(atomic.AddUint32(&s.reqID, 1))
 	return uint16(atomic.AddUint32(&s.reqID, 1))
 }
 }
 
 
-func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option IPOption) {
+func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
 	newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
 	newError(s.name, " querying: ", domain).AtInfo().WriteToLog(session.ExportIDToError(ctx))
 
 
 	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
 	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
@@ -223,7 +223,7 @@ func (s *QUICNameServer) sendQuery(ctx context.Context, domain string, clientIP
 	}
 	}
 }
 }
 
 
-func (s *QUICNameServer) findIPsForDomain(domain string, option IPOption) ([]net.IP, error) {
+func (s *QUICNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
 	s.RLock()
 	s.RLock()
 	record, found := s.ips[domain]
 	record, found := s.ips[domain]
 	s.RUnlock()
 	s.RUnlock()
@@ -266,7 +266,7 @@ func (s *QUICNameServer) findIPsForDomain(domain string, option IPOption) ([]net
 }
 }
 
 
 // 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 IPOption) ([]net.IP, error) {
+func (s *QUICNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) ([]net.IP, error) {
 	fqdn := Fqdn(domain)
 	fqdn := Fqdn(domain)
 
 
 	ips, err := s.findIPsForDomain(fqdn, option)
 	ips, err := s.findIPsForDomain(fqdn, option)

+ 2 - 1
app/dns/nameserver_quic_test.go

@@ -9,6 +9,7 @@ import (
 	. "github.com/v2fly/v2ray-core/v4/app/dns"
 	. "github.com/v2fly/v2ray-core/v4/app/dns"
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/net"
+	dns_feature "github.com/v2fly/v2ray-core/v4/features/dns"
 )
 )
 
 
 func TestQUICNameServer(t *testing.T) {
 func TestQUICNameServer(t *testing.T) {
@@ -17,7 +18,7 @@ func TestQUICNameServer(t *testing.T) {
 	s, err := NewQUICNameServer(url)
 	s, err := NewQUICNameServer(url)
 	common.Must(err)
 	common.Must(err)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
-	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), IPOption{
+	ips, err := s.QueryIP(ctx, "google.com", net.IP(nil), dns_feature.IPOption{
 		IPv4Enable: true,
 		IPv4Enable: true,
 		IPv6Enable: true,
 		IPv6Enable: true,
 	})
 	})

+ 4 - 4
app/dns/nameserver_udp.go

@@ -55,7 +55,7 @@ func NewClassicNameServer(address net.Destination, dispatcher routing.Dispatcher
 		Execute:  s.Cleanup,
 		Execute:  s.Cleanup,
 	}
 	}
 	s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse)
 	s.udpServer = udp.NewDispatcher(dispatcher, s.HandleResponse)
-	newError("DNS: created UDP client inited for ", address.NetAddr()).AtInfo().WriteToLog()
+	newError("DNS: created UDP client initialized for ", address.NetAddr()).AtInfo().WriteToLog()
 	return s
 	return s
 }
 }
 
 
@@ -184,7 +184,7 @@ func (s *ClassicNameServer) addPendingRequest(req *dnsRequest) {
 	s.requests[id] = *req
 	s.requests[id] = *req
 }
 }
 
 
-func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option IPOption) {
+func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) {
 	newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
 	newError(s.name, " querying DNS for: ", domain).AtDebug().WriteToLog(session.ExportIDToError(ctx))
 
 
 	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
 	reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(clientIP))
@@ -203,7 +203,7 @@ func (s *ClassicNameServer) sendQuery(ctx context.Context, domain string, client
 	}
 	}
 }
 }
 
 
-func (s *ClassicNameServer) findIPsForDomain(domain string, option IPOption) ([]net.IP, error) {
+func (s *ClassicNameServer) findIPsForDomain(domain string, option dns_feature.IPOption) ([]net.IP, error) {
 	s.RLock()
 	s.RLock()
 	record, found := s.ips[domain]
 	record, found := s.ips[domain]
 	s.RUnlock()
 	s.RUnlock()
@@ -242,7 +242,7 @@ func (s *ClassicNameServer) findIPsForDomain(domain string, option IPOption) ([]
 }
 }
 
 
 // QueryIP implements Server.
 // QueryIP implements Server.
-func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option IPOption) ([]net.IP, error) {
+func (s *ClassicNameServer) QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns_feature.IPOption) ([]net.IP, error) {
 	fqdn := Fqdn(domain)
 	fqdn := Fqdn(domain)
 
 
 	ips, err := s.findIPsForDomain(fqdn, option)
 	ips, err := s.findIPsForDomain(fqdn, option)

+ 11 - 2
app/router/router_test.go

@@ -10,6 +10,7 @@ import (
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common"
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/net"
 	"github.com/v2fly/v2ray-core/v4/common/session"
 	"github.com/v2fly/v2ray-core/v4/common/session"
+	"github.com/v2fly/v2ray-core/v4/features/dns"
 	"github.com/v2fly/v2ray-core/v4/features/outbound"
 	"github.com/v2fly/v2ray-core/v4/features/outbound"
 	routing_session "github.com/v2fly/v2ray-core/v4/features/routing/session"
 	routing_session "github.com/v2fly/v2ray-core/v4/features/routing/session"
 	"github.com/v2fly/v2ray-core/v4/testing/mocks"
 	"github.com/v2fly/v2ray-core/v4/testing/mocks"
@@ -116,7 +117,11 @@ func TestIPOnDemand(t *testing.T) {
 	defer mockCtl.Finish()
 	defer mockCtl.Finish()
 
 
 	mockDNS := mocks.NewDNSClient(mockCtl)
 	mockDNS := mocks.NewDNSClient(mockCtl)
-	mockDNS.EXPECT().LookupIP(gomock.Eq("v2fly.org")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
+	mockDNS.EXPECT().LookupIP(gomock.Eq("v2fly.org"), dns.IPOption{
+		IPv4Enable: true,
+		IPv6Enable: true,
+		FakeEnable: false,
+	}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
 
 
 	r := new(Router)
 	r := new(Router)
 	common.Must(r.Init(config, mockDNS, nil))
 	common.Must(r.Init(config, mockDNS, nil))
@@ -151,7 +156,11 @@ func TestIPIfNonMatchDomain(t *testing.T) {
 	defer mockCtl.Finish()
 	defer mockCtl.Finish()
 
 
 	mockDNS := mocks.NewDNSClient(mockCtl)
 	mockDNS := mocks.NewDNSClient(mockCtl)
-	mockDNS.EXPECT().LookupIP(gomock.Eq("v2fly.org")).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
+	mockDNS.EXPECT().LookupIP(gomock.Eq("v2fly.org"), dns.IPOption{
+		IPv4Enable: true,
+		IPv6Enable: true,
+		FakeEnable: false,
+	}).Return([]net.IP{{192, 168, 0, 1}}, nil).AnyTimes()
 
 
 	r := new(Router)
 	r := new(Router)
 	common.Must(r.Init(config, mockDNS, nil))
 	common.Must(r.Init(config, mockDNS, nil))

+ 8 - 15
features/dns/client.go

@@ -7,6 +7,13 @@ import (
 	"github.com/v2fly/v2ray-core/v4/features"
 	"github.com/v2fly/v2ray-core/v4/features"
 )
 )
 
 
+// IPOption is an object for IP query options.
+type IPOption struct {
+	IPv4Enable bool
+	IPv6Enable bool
+	FakeEnable bool
+}
+
 // Client is a V2Ray feature for querying DNS information.
 // Client is a V2Ray feature for querying DNS information.
 //
 //
 // v2ray:api:stable
 // v2ray:api:stable
@@ -14,21 +21,7 @@ type Client interface {
 	features.Feature
 	features.Feature
 
 
 	// 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)
-}
-
-// IPv4Lookup is an optional feature for querying IPv4 addresses only.
-//
-// v2ray:api:beta
-type IPv4Lookup interface {
-	LookupIPv4(domain string) ([]net.IP, error)
-}
-
-// IPv6Lookup is an optional feature for querying IPv6 addresses only.
-//
-// v2ray:api:beta
-type IPv6Lookup interface {
-	LookupIPv6(domain string) ([]net.IP, error)
+	LookupIP(domain string, option IPOption) ([]net.IP, error)
 }
 }
 
 
 // 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.

+ 17 - 34
features/dns/localdns/client.go

@@ -20,58 +20,41 @@ func (*Client) Start() error { return nil }
 func (*Client) Close() error { return nil }
 func (*Client) Close() error { return nil }
 
 
 // LookupIP implements Client.
 // LookupIP implements Client.
-func (*Client) LookupIP(host string) ([]net.IP, error) {
+func (*Client) LookupIP(host string, option dns.IPOption) ([]net.IP, error) {
 	ips, err := net.LookupIP(host)
 	ips, err := net.LookupIP(host)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
 	parsedIPs := make([]net.IP, 0, len(ips))
 	parsedIPs := make([]net.IP, 0, len(ips))
+	ipv4 := make([]net.IP, 0, len(ips))
+	ipv6 := make([]net.IP, 0, len(ips))
 	for _, ip := range ips {
 	for _, ip := range ips {
 		parsed := net.IPAddress(ip)
 		parsed := net.IPAddress(ip)
 		if parsed != nil {
 		if parsed != nil {
 			parsedIPs = append(parsedIPs, parsed.IP())
 			parsedIPs = append(parsedIPs, parsed.IP())
 		}
 		}
-	}
-	if len(parsedIPs) == 0 {
-		return nil, dns.ErrEmptyResponse
-	}
-	return parsedIPs, nil
-}
-
-// LookupIPv4 implements IPv4Lookup.
-func (c *Client) LookupIPv4(host string) ([]net.IP, error) {
-	ips, err := c.LookupIP(host)
-	if err != nil {
-		return nil, err
-	}
-	ipv4 := make([]net.IP, 0, len(ips))
-	for _, ip := range ips {
 		if len(ip) == net.IPv4len {
 		if len(ip) == net.IPv4len {
 			ipv4 = append(ipv4, ip)
 			ipv4 = append(ipv4, ip)
 		}
 		}
-	}
-	if len(ipv4) == 0 {
-		return nil, dns.ErrEmptyResponse
-	}
-	return ipv4, nil
-}
-
-// LookupIPv6 implements IPv6Lookup.
-func (c *Client) LookupIPv6(host string) ([]net.IP, error) {
-	ips, err := c.LookupIP(host)
-	if err != nil {
-		return nil, err
-	}
-	ipv6 := make([]net.IP, 0, len(ips))
-	for _, ip := range ips {
 		if len(ip) == net.IPv6len {
 		if len(ip) == net.IPv6len {
 			ipv6 = append(ipv6, ip)
 			ipv6 = append(ipv6, ip)
 		}
 		}
 	}
 	}
-	if len(ipv6) == 0 {
-		return nil, dns.ErrEmptyResponse
+	switch {
+	case option.IPv4Enable && option.IPv6Enable:
+		if len(parsedIPs) > 0 {
+			return parsedIPs, nil
+		}
+	case option.IPv4Enable:
+		if len(ipv4) > 0 {
+			return ipv4, nil
+		}
+	case option.IPv6Enable:
+		if len(ipv6) > 0 {
+			return ipv6, nil
+		}
 	}
 	}
-	return ipv6, nil
+	return nil, dns.ErrEmptyResponse
 }
 }
 
 
 // New create a new dns.Client that queries localhost for DNS.
 // New create a new dns.Client that queries localhost for DNS.

+ 5 - 1
features/routing/dns/context.go

@@ -26,7 +26,11 @@ func (ctx *ResolvableContext) GetTargetIPs() []net.IP {
 	}
 	}
 
 
 	if domain := ctx.GetTargetDomain(); len(domain) != 0 {
 	if domain := ctx.GetTargetDomain(); len(domain) != 0 {
-		ips, err := ctx.dnsClient.LookupIP(domain)
+		ips, err := ctx.dnsClient.LookupIP(domain, dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: true,
+			FakeEnable: false,
+		})
 		if err == nil {
 		if err == nil {
 			ctx.resolvedIPs = ips
 			ctx.resolvedIPs = ips
 			return ips
 			return ips

+ 10 - 16
proxy/dns/dns.go

@@ -39,26 +39,12 @@ 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
 }
 }
 
 
 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
-	ipv4lookup, ok := dnsClient.(dns.IPv4Lookup)
-	if !ok {
-		return newError("dns.Client doesn't implement IPv4Lookup")
-	}
-	h.ipv4Lookup = ipv4lookup
-
-	ipv6lookup, ok := dnsClient.(dns.IPv6Lookup)
-	if !ok {
-		return newError("dns.Client doesn't implement IPv6Lookup")
-	}
-	h.ipv6Lookup = ipv6lookup
-
 	if v, ok := dnsClient.(ownLinkVerifier); ok {
 	if v, ok := dnsClient.(ownLinkVerifier); ok {
 		h.ownLinkVerifier = v
 		h.ownLinkVerifier = v
 	}
 	}
@@ -217,9 +203,17 @@ func (h *Handler) handleIPQuery(id uint16, qType dnsmessage.Type, domain string,
 
 
 	switch qType {
 	switch qType {
 	case dnsmessage.TypeA:
 	case dnsmessage.TypeA:
-		ips, err = h.ipv4Lookup.LookupIPv4(domain)
+		ips, err = h.client.LookupIP(domain, dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: false,
+			FakeEnable: true,
+		})
 	case dnsmessage.TypeAAAA:
 	case dnsmessage.TypeAAAA:
-		ips, err = h.ipv6Lookup.LookupIPv6(domain)
+		ips, err = h.client.LookupIP(domain, dns.IPOption{
+			IPv4Enable: false,
+			IPv6Enable: true,
+			FakeEnable: true,
+		})
 	}
 	}
 
 
 	rcode := dns.RCodeFromError(err)
 	rcode := dns.RCodeFromError(err)

+ 15 - 8
proxy/freedom/freedom.go

@@ -60,19 +60,26 @@ 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 {
-	var lookupFunc func(string) ([]net.IP, error) = h.dns.LookupIP
-
+	var option dns.IPOption = dns.IPOption{
+		IPv4Enable: true,
+		IPv6Enable: true,
+		FakeEnable: false,
+	}
 	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
+		option = dns.IPOption{
+			IPv4Enable: true,
+			IPv6Enable: false,
+			FakeEnable: false,
 		}
 		}
 	} 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
+		option = dns.IPOption{
+			IPv4Enable: false,
+			IPv6Enable: true,
+			FakeEnable: false,
 		}
 		}
 	}
 	}
 
 
-	ips, err := lookupFunc(domain)
+	ips, err := h.dns.LookupIP(domain, option)
 	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))
 	}
 	}
@@ -123,7 +130,7 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte
 					Address: ip,
 					Address: ip,
 					Port:    dialDest.Port,
 					Port:    dialDest.Port,
 				}
 				}
-				newError("dialing to to ", dialDest).WriteToLog(session.ExportIDToError(ctx))
+				newError("dialing to ", dialDest).WriteToLog(session.ExportIDToError(ctx))
 			}
 			}
 		}
 		}
 
 

+ 5 - 4
testing/mocks/dns.go

@@ -9,6 +9,7 @@ import (
 	reflect "reflect"
 	reflect "reflect"
 
 
 	gomock "github.com/golang/mock/gomock"
 	gomock "github.com/golang/mock/gomock"
+	dns "github.com/v2fly/v2ray-core/v4/features/dns"
 )
 )
 
 
 // DNSClient is a mock of Client interface.
 // DNSClient is a mock of Client interface.
@@ -49,18 +50,18 @@ func (mr *DNSClientMockRecorder) Close() *gomock.Call {
 }
 }
 
 
 // LookupIP mocks base method.
 // LookupIP mocks base method.
-func (m *DNSClient) LookupIP(arg0 string) ([]net.IP, error) {
+func (m *DNSClient) LookupIP(arg0 string, arg1 dns.IPOption) ([]net.IP, error) {
 	m.ctrl.T.Helper()
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "LookupIP", arg0)
+	ret := m.ctrl.Call(m, "LookupIP", arg0, arg1)
 	ret0, _ := ret[0].([]net.IP)
 	ret0, _ := ret[0].([]net.IP)
 	ret1, _ := ret[1].(error)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 	return ret0, ret1
 }
 }
 
 
 // LookupIP indicates an expected call of LookupIP.
 // LookupIP indicates an expected call of LookupIP.
-func (mr *DNSClientMockRecorder) LookupIP(arg0 interface{}) *gomock.Call {
+func (mr *DNSClientMockRecorder) LookupIP(arg0, arg1 interface{}) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
 	mr.mock.ctrl.T.Helper()
-	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, arg1)
 }
 }
 
 
 // Start mocks base method.
 // Start mocks base method.