github.com/moqsien/xraycore@v1.8.5/app/dns/nameserver.go (about)

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