mirror of
https://github.com/v2fly/v2ray-core.git
synced 2025-01-29 04:37:06 -05:00
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>
This commit is contained in:
parent
232ba8c26f
commit
afb8385a7e
@ -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) {
|
func (s *DNS) LookupIP(domain string, option dns.IPOption) ([]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) {
|
|
||||||
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
|
||||||
|
@ -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 := client.LookupIP("ipv4only.google.com", feature_dns.IPOption{
|
||||||
ips, err := clientv6.LookupIPv6("ipv4only.google.com")
|
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 := client.LookupIP("ipv6.google.com", feature_dns.IPOption{
|
||||||
ips, err := clientv4.LookupIPv4("ipv6.google.com")
|
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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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},
|
{"dual stack", args{"test.com", dns_feature.IPOption{
|
||||||
{"ipv4 only", args{"test.com", IPOption{true, false}, nil}, 1},
|
IPv4Enable: true,
|
||||||
{"ipv6 only", args{"test.com", IPOption{false, true}, nil}, 1},
|
IPv6Enable: true,
|
||||||
{"none/error", args{"test.com", IPOption{false, false}, nil}, 0},
|
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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
func (s *LocalNameServer) QueryIP(_ context.Context, domain string, _ net.IP, option dns.IPOption) ([]net.IP, error) {
|
||||||
if option.IPv4Enable && option.IPv6Enable {
|
if option.IPv4Enable || option.IPv6Enable {
|
||||||
return s.client.LookupIP(domain)
|
return s.client.LookupIP(domain, option)
|
||||||
}
|
|
||||||
|
|
||||||
if option.IPv4Enable {
|
|
||||||
return s.client.LookupIPv4(domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.IPv6Enable {
|
|
||||||
return s.client.LookupIPv6(domain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, newError("neither IPv4 nor IPv6 is enabled")
|
return nil, newError("neither IPv4 nor IPv6 is enabled")
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
LookupIP(domain string, option IPOption) ([]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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -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 {
|
switch {
|
||||||
return nil, dns.ErrEmptyResponse
|
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.
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
option = dns.IPOption{
|
||||||
lookupFunc = lookupIPv4.LookupIPv4
|
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 {
|
option = dns.IPOption{
|
||||||
lookupFunc = lookupIPv6.LookupIPv6
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user