github.com/v2fly/v2ray-core/v4@v4.45.2/app/dns/dns.go (about)

     1  //go:build !confonly
     2  // +build !confonly
     3  
     4  // Package dns is an implementation of core.DNS feature.
     5  package dns
     6  
     7  //go:generate go run github.com/v2fly/v2ray-core/v4/common/errors/errorgen
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"strings"
    13  	"sync"
    14  
    15  	"github.com/v2fly/v2ray-core/v4/app/router"
    16  	"github.com/v2fly/v2ray-core/v4/common"
    17  	"github.com/v2fly/v2ray-core/v4/common/errors"
    18  	"github.com/v2fly/v2ray-core/v4/common/net"
    19  	"github.com/v2fly/v2ray-core/v4/common/session"
    20  	"github.com/v2fly/v2ray-core/v4/common/strmatcher"
    21  	"github.com/v2fly/v2ray-core/v4/features"
    22  	"github.com/v2fly/v2ray-core/v4/features/dns"
    23  )
    24  
    25  // DNS is a DNS rely server.
    26  type DNS struct {
    27  	sync.Mutex
    28  	tag                    string
    29  	disableCache           bool
    30  	disableFallback        bool
    31  	disableFallbackIfMatch bool
    32  	ipOption               *dns.IPOption
    33  	hosts                  *StaticHosts
    34  	clients                []*Client
    35  	ctx                    context.Context
    36  	domainMatcher          strmatcher.IndexMatcher
    37  	matcherInfos           []*DomainMatcherInfo
    38  }
    39  
    40  // DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
    41  type DomainMatcherInfo struct {
    42  	clientIdx     uint16
    43  	domainRuleIdx uint16
    44  }
    45  
    46  // New creates a new DNS server with given configuration.
    47  func New(ctx context.Context, config *Config) (*DNS, error) {
    48  	var tag string
    49  	if len(config.Tag) > 0 {
    50  		tag = config.Tag
    51  	} else {
    52  		tag = generateRandomTag()
    53  	}
    54  
    55  	var clientIP net.IP
    56  	switch len(config.ClientIp) {
    57  	case 0, net.IPv4len, net.IPv6len:
    58  		clientIP = net.IP(config.ClientIp)
    59  	default:
    60  		return nil, newError("unexpected client IP length ", len(config.ClientIp))
    61  	}
    62  
    63  	var ipOption *dns.IPOption
    64  	switch config.QueryStrategy {
    65  	case QueryStrategy_USE_IP:
    66  		ipOption = &dns.IPOption{
    67  			IPv4Enable: true,
    68  			IPv6Enable: true,
    69  			FakeEnable: false,
    70  		}
    71  	case QueryStrategy_USE_IP4:
    72  		ipOption = &dns.IPOption{
    73  			IPv4Enable: true,
    74  			IPv6Enable: false,
    75  			FakeEnable: false,
    76  		}
    77  	case QueryStrategy_USE_IP6:
    78  		ipOption = &dns.IPOption{
    79  			IPv4Enable: false,
    80  			IPv6Enable: true,
    81  			FakeEnable: false,
    82  		}
    83  	}
    84  
    85  	hosts, err := NewStaticHosts(config.StaticHosts, config.Hosts)
    86  	if err != nil {
    87  		return nil, newError("failed to create hosts").Base(err)
    88  	}
    89  
    90  	clients := []*Client{}
    91  	domainRuleCount := 0
    92  	for _, ns := range config.NameServer {
    93  		domainRuleCount += len(ns.PrioritizedDomain)
    94  	}
    95  
    96  	// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
    97  	matcherInfos := make([]*DomainMatcherInfo, domainRuleCount+1)
    98  	domainMatcher := &strmatcher.MatcherGroup{}
    99  	geoipContainer := router.GeoIPMatcherContainer{}
   100  
   101  	for _, endpoint := range config.NameServers {
   102  		features.PrintDeprecatedFeatureWarning("simple DNS server")
   103  		client, err := NewSimpleClient(ctx, endpoint, clientIP)
   104  		if err != nil {
   105  			return nil, newError("failed to create client").Base(err)
   106  		}
   107  		clients = append(clients, client)
   108  	}
   109  
   110  	for _, ns := range config.NameServer {
   111  		clientIdx := len(clients)
   112  		updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) error {
   113  			midx := domainMatcher.Add(domainRule)
   114  			matcherInfos[midx] = &DomainMatcherInfo{
   115  				clientIdx:     uint16(clientIdx),
   116  				domainRuleIdx: uint16(originalRuleIdx),
   117  			}
   118  			return nil
   119  		}
   120  
   121  		myClientIP := clientIP
   122  		switch len(ns.ClientIp) {
   123  		case net.IPv4len, net.IPv6len:
   124  			myClientIP = net.IP(ns.ClientIp)
   125  		}
   126  		client, err := NewClient(ctx, ns, myClientIP, geoipContainer, &matcherInfos, updateDomain)
   127  		if err != nil {
   128  			return nil, newError("failed to create client").Base(err)
   129  		}
   130  		clients = append(clients, client)
   131  	}
   132  
   133  	// If there is no DNS client in config, add a `localhost` DNS client
   134  	if len(clients) == 0 {
   135  		clients = append(clients, NewLocalDNSClient())
   136  	}
   137  
   138  	return &DNS{
   139  		tag:                    tag,
   140  		hosts:                  hosts,
   141  		ipOption:               ipOption,
   142  		clients:                clients,
   143  		ctx:                    ctx,
   144  		domainMatcher:          domainMatcher,
   145  		matcherInfos:           matcherInfos,
   146  		disableCache:           config.DisableCache,
   147  		disableFallback:        config.DisableFallback,
   148  		disableFallbackIfMatch: config.DisableFallbackIfMatch,
   149  	}, nil
   150  }
   151  
   152  // Type implements common.HasType.
   153  func (*DNS) Type() interface{} {
   154  	return dns.ClientType()
   155  }
   156  
   157  // Start implements common.Runnable.
   158  func (s *DNS) Start() error {
   159  	return nil
   160  }
   161  
   162  // Close implements common.Closable.
   163  func (s *DNS) Close() error {
   164  	return nil
   165  }
   166  
   167  // IsOwnLink implements proxy.dns.ownLinkVerifier
   168  func (s *DNS) IsOwnLink(ctx context.Context) bool {
   169  	inbound := session.InboundFromContext(ctx)
   170  	return inbound != nil && inbound.Tag == s.tag
   171  }
   172  
   173  // LookupIP implements dns.Client.
   174  func (s *DNS) LookupIP(domain string) ([]net.IP, error) {
   175  	return s.lookupIPInternal(domain, *s.ipOption)
   176  }
   177  
   178  // LookupIPv4 implements dns.IPv4Lookup.
   179  func (s *DNS) LookupIPv4(domain string) ([]net.IP, error) {
   180  	if !s.ipOption.IPv4Enable {
   181  		return nil, dns.ErrEmptyResponse
   182  	}
   183  	o := *s.ipOption
   184  	o.IPv6Enable = false
   185  	return s.lookupIPInternal(domain, o)
   186  }
   187  
   188  // LookupIPv6 implements dns.IPv6Lookup.
   189  func (s *DNS) LookupIPv6(domain string) ([]net.IP, error) {
   190  	if !s.ipOption.IPv6Enable {
   191  		return nil, dns.ErrEmptyResponse
   192  	}
   193  	o := *s.ipOption
   194  	o.IPv4Enable = false
   195  	return s.lookupIPInternal(domain, o)
   196  }
   197  
   198  func (s *DNS) lookupIPInternal(domain string, option dns.IPOption) ([]net.IP, error) {
   199  	if domain == "" {
   200  		return nil, newError("empty domain name")
   201  	}
   202  
   203  	// Normalize the FQDN form query
   204  	domain = strings.TrimSuffix(domain, ".")
   205  
   206  	// Static host lookup
   207  	switch addrs := s.hosts.Lookup(domain, option); {
   208  	case addrs == nil: // Domain not recorded in static host
   209  		break
   210  	case len(addrs) == 0: // Domain recorded, but no valid IP returned (e.g. IPv4 address with only IPv6 enabled)
   211  		return nil, dns.ErrEmptyResponse
   212  	case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Domain replacement
   213  		newError("domain replaced: ", domain, " -> ", addrs[0].Domain()).WriteToLog()
   214  		domain = addrs[0].Domain()
   215  	default: // Successfully found ip records in static host
   216  		newError("returning ", len(addrs), " IP(s) for domain ", domain, " -> ", addrs).WriteToLog()
   217  		return toNetIP(addrs)
   218  	}
   219  
   220  	// Name servers lookup
   221  	errs := []error{}
   222  	ctx := session.ContextWithInbound(s.ctx, &session.Inbound{Tag: s.tag})
   223  	for _, client := range s.sortClients(domain) {
   224  		if !option.FakeEnable && strings.EqualFold(client.Name(), "FakeDNS") {
   225  			newError("skip DNS resolution for domain ", domain, " at server ", client.Name()).AtDebug().WriteToLog()
   226  			continue
   227  		}
   228  		ips, err := client.QueryIP(ctx, domain, option, s.disableCache)
   229  		if len(ips) > 0 {
   230  			return ips, nil
   231  		}
   232  		if err != nil {
   233  			newError("failed to lookup ip for domain ", domain, " at server ", client.Name()).Base(err).WriteToLog()
   234  			errs = append(errs, err)
   235  		}
   236  		if err != context.Canceled && err != context.DeadlineExceeded && err != errExpectedIPNonMatch {
   237  			return nil, err
   238  		}
   239  	}
   240  
   241  	return nil, newError("returning nil for domain ", domain).Base(errors.Combine(errs...))
   242  }
   243  
   244  // GetIPOption implements ClientWithIPOption.
   245  func (s *DNS) GetIPOption() *dns.IPOption {
   246  	return s.ipOption
   247  }
   248  
   249  // SetQueryOption implements ClientWithIPOption.
   250  func (s *DNS) SetQueryOption(isIPv4Enable, isIPv6Enable bool) {
   251  	s.ipOption.IPv4Enable = isIPv4Enable
   252  	s.ipOption.IPv6Enable = isIPv6Enable
   253  }
   254  
   255  // SetFakeDNSOption implements ClientWithIPOption.
   256  func (s *DNS) SetFakeDNSOption(isFakeEnable bool) {
   257  	s.ipOption.FakeEnable = isFakeEnable
   258  }
   259  
   260  func (s *DNS) sortClients(domain string) []*Client {
   261  	clients := make([]*Client, 0, len(s.clients))
   262  	clientUsed := make([]bool, len(s.clients))
   263  	clientNames := make([]string, 0, len(s.clients))
   264  	domainRules := []string{}
   265  
   266  	// Priority domain matching
   267  	hasMatch := false
   268  	for _, match := range s.domainMatcher.Match(domain) {
   269  		info := s.matcherInfos[match]
   270  		client := s.clients[info.clientIdx]
   271  		domainRule := client.domains[info.domainRuleIdx]
   272  		domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
   273  		if clientUsed[info.clientIdx] {
   274  			continue
   275  		}
   276  		clientUsed[info.clientIdx] = true
   277  		clients = append(clients, client)
   278  		clientNames = append(clientNames, client.Name())
   279  		hasMatch = true
   280  	}
   281  
   282  	if !(s.disableFallback || s.disableFallbackIfMatch && hasMatch) {
   283  		// Default round-robin query
   284  		for idx, client := range s.clients {
   285  			if clientUsed[idx] || client.skipFallback {
   286  				continue
   287  			}
   288  			clientUsed[idx] = true
   289  			clients = append(clients, client)
   290  			clientNames = append(clientNames, client.Name())
   291  		}
   292  	}
   293  
   294  	if len(domainRules) > 0 {
   295  		newError("domain ", domain, " matches following rules: ", domainRules).AtDebug().WriteToLog()
   296  	}
   297  	if len(clientNames) > 0 {
   298  		newError("domain ", domain, " will use DNS in order: ", clientNames).AtDebug().WriteToLog()
   299  	}
   300  
   301  	if len(clients) == 0 {
   302  		clients = append(clients, s.clients[0])
   303  		clientNames = append(clientNames, s.clients[0].Name())
   304  		newError("domain ", domain, " will use the first DNS: ", clientNames).AtDebug().WriteToLog()
   305  	}
   306  
   307  	return clients
   308  }
   309  
   310  func init() {
   311  	common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) {
   312  		return New(ctx, config.(*Config))
   313  	}))
   314  }