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

     1  //go:build !confonly
     2  // +build !confonly
     3  
     4  package dns
     5  
     6  import (
     7  	"context"
     8  	"net/url"
     9  	"strings"
    10  	"time"
    11  
    12  	core "github.com/v2fly/v2ray-core/v4"
    13  	"github.com/v2fly/v2ray-core/v4/app/router"
    14  	"github.com/v2fly/v2ray-core/v4/common/errors"
    15  	"github.com/v2fly/v2ray-core/v4/common/net"
    16  	"github.com/v2fly/v2ray-core/v4/common/strmatcher"
    17  	"github.com/v2fly/v2ray-core/v4/features/dns"
    18  	"github.com/v2fly/v2ray-core/v4/features/routing"
    19  )
    20  
    21  // Server is the interface for Name Server.
    22  type Server interface {
    23  	// Name of the Client.
    24  	Name() string
    25  	// QueryIP sends IP queries to its configured server.
    26  	QueryIP(ctx context.Context, domain string, clientIP net.IP, option dns.IPOption, disableCache bool) ([]net.IP, error)
    27  }
    28  
    29  // Client is the interface for DNS client.
    30  type Client struct {
    31  	server       Server
    32  	clientIP     net.IP
    33  	skipFallback bool
    34  	domains      []string
    35  	expectIPs    []*router.GeoIPMatcher
    36  }
    37  
    38  var errExpectedIPNonMatch = errors.New("expectIPs not match")
    39  
    40  // NewServer creates a name server object according to the network destination url.
    41  func NewServer(dest net.Destination, dispatcher routing.Dispatcher) (Server, error) {
    42  	if address := dest.Address; address.Family().IsDomain() {
    43  		u, err := url.Parse(address.Domain())
    44  		if err != nil {
    45  			return nil, err
    46  		}
    47  		switch {
    48  		case strings.EqualFold(u.String(), "localhost"):
    49  			return NewLocalNameServer(), nil
    50  		case strings.EqualFold(u.Scheme, "https"): // DOH Remote mode
    51  			return NewDoHNameServer(u, dispatcher)
    52  		case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode
    53  			return NewDoHLocalNameServer(u), nil
    54  		case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
    55  			return NewQUICNameServer(u)
    56  		case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
    57  			return NewTCPNameServer(u, dispatcher)
    58  		case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode
    59  			return NewTCPLocalNameServer(u)
    60  		case strings.EqualFold(u.String(), "fakedns"):
    61  			return NewFakeDNSServer(), nil
    62  		}
    63  	}
    64  	if dest.Network == net.Network_Unknown {
    65  		dest.Network = net.Network_UDP
    66  	}
    67  	if dest.Network == net.Network_UDP { // UDP classic DNS mode
    68  		return NewClassicNameServer(dest, dispatcher), nil
    69  	}
    70  	return nil, newError("No available name server could be created from ", dest).AtWarning()
    71  }
    72  
    73  // NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
    74  func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container router.GeoIPMatcherContainer, matcherInfos *[]*DomainMatcherInfo, updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error) (*Client, error) {
    75  	client := &Client{}
    76  
    77  	err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
    78  		// Create a new server for each client for now
    79  		server, err := NewServer(ns.Address.AsDestination(), dispatcher)
    80  		if err != nil {
    81  			return newError("failed to create nameserver").Base(err).AtWarning()
    82  		}
    83  
    84  		// Priotize local domains with specific TLDs or without any dot to local DNS
    85  		if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {
    86  			ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
    87  			ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)
    88  			// The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config.
    89  			// Because the `localhost` DNS client will apend len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
    90  			// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).
    91  			// To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification.
    92  			// Related issues:
    93  			// https://github.com/v2fly/v2ray-core/issues/529
    94  			// https://github.com/v2fly/v2ray-core/issues/719
    95  			for i := 0; i < len(localTLDsAndDotlessDomains); i++ {
    96  				*matcherInfos = append(*matcherInfos, &DomainMatcherInfo{
    97  					clientIdx:     uint16(0),
    98  					domainRuleIdx: uint16(0),
    99  				})
   100  			}
   101  		}
   102  
   103  		// Establish domain rules
   104  		var rules []string
   105  		ruleCurr := 0
   106  		ruleIter := 0
   107  		for _, domain := range ns.PrioritizedDomain {
   108  			domainRule, err := toStrMatcher(domain.Type, domain.Domain)
   109  			if err != nil {
   110  				return newError("failed to create prioritized domain").Base(err).AtWarning()
   111  			}
   112  			originalRuleIdx := ruleCurr
   113  			if ruleCurr < len(ns.OriginalRules) {
   114  				rule := ns.OriginalRules[ruleCurr]
   115  				if ruleCurr >= len(rules) {
   116  					rules = append(rules, rule.Rule)
   117  				}
   118  				ruleIter++
   119  				if ruleIter >= int(rule.Size) {
   120  					ruleIter = 0
   121  					ruleCurr++
   122  				}
   123  			} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests)
   124  				rules = append(rules, domainRule.String())
   125  				ruleCurr++
   126  			}
   127  			err = updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
   128  			if err != nil {
   129  				return newError("failed to create prioritized domain").Base(err).AtWarning()
   130  			}
   131  		}
   132  
   133  		// Establish expected IPs
   134  		var matchers []*router.GeoIPMatcher
   135  		for _, geoip := range ns.Geoip {
   136  			matcher, err := container.Add(geoip)
   137  			if err != nil {
   138  				return newError("failed to create ip matcher").Base(err).AtWarning()
   139  			}
   140  			matchers = append(matchers, matcher)
   141  		}
   142  
   143  		if len(clientIP) > 0 {
   144  			switch ns.Address.Address.GetAddress().(type) {
   145  			case *net.IPOrDomain_Domain:
   146  				newError("DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
   147  			case *net.IPOrDomain_Ip:
   148  				newError("DNS: client ", ns.Address.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
   149  			}
   150  		}
   151  
   152  		client.server = server
   153  		client.clientIP = clientIP
   154  		client.skipFallback = ns.SkipFallback
   155  		client.domains = rules
   156  		client.expectIPs = matchers
   157  		return nil
   158  	})
   159  	return client, err
   160  }
   161  
   162  // NewSimpleClient creates a DNS client with a simple destination.
   163  func NewSimpleClient(ctx context.Context, endpoint *net.Endpoint, clientIP net.IP) (*Client, error) {
   164  	client := &Client{}
   165  	err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
   166  		server, err := NewServer(endpoint.AsDestination(), dispatcher)
   167  		if err != nil {
   168  			return newError("failed to create nameserver").Base(err).AtWarning()
   169  		}
   170  		client.server = server
   171  		client.clientIP = clientIP
   172  		return nil
   173  	})
   174  
   175  	if len(clientIP) > 0 {
   176  		switch endpoint.Address.GetAddress().(type) {
   177  		case *net.IPOrDomain_Domain:
   178  			newError("DNS: client ", endpoint.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
   179  		case *net.IPOrDomain_Ip:
   180  			newError("DNS: client ", endpoint.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
   181  		}
   182  	}
   183  
   184  	return client, err
   185  }
   186  
   187  // Name returns the server name the client manages.
   188  func (c *Client) Name() string {
   189  	return c.server.Name()
   190  }
   191  
   192  // QueryIP send DNS query to the name server with the client's IP.
   193  func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, error) {
   194  	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
   195  	ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
   196  	cancel()
   197  
   198  	if err != nil {
   199  		return ips, err
   200  	}
   201  	return c.MatchExpectedIPs(domain, ips)
   202  }
   203  
   204  // MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
   205  func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) {
   206  	if len(c.expectIPs) == 0 {
   207  		return ips, nil
   208  	}
   209  	newIps := []net.IP{}
   210  	for _, ip := range ips {
   211  		for _, matcher := range c.expectIPs {
   212  			if matcher.Match(ip) {
   213  				newIps = append(newIps, ip)
   214  				break
   215  			}
   216  		}
   217  	}
   218  	if len(newIps) == 0 {
   219  		return nil, errExpectedIPNonMatch
   220  	}
   221  	newError("domain ", domain, " expectIPs ", newIps, " matched at server ", c.Name()).AtDebug().WriteToLog()
   222  	return newIps, nil
   223  }