github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/app/dns/nameserver.go (about)

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"net/url"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/xmplusdev/xmcore/app/router"
    10  	"github.com/xmplusdev/xmcore/common/errors"
    11  	"github.com/xmplusdev/xmcore/common/net"
    12  	"github.com/xmplusdev/xmcore/common/strmatcher"
    13  	"github.com/xmplusdev/xmcore/core"
    14  	"github.com/xmplusdev/xmcore/features/dns"
    15  	"github.com/xmplusdev/xmcore/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, queryStrategy QueryStrategy) (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, queryStrategy)
    49  		case strings.EqualFold(u.Scheme, "https+local"): // DOH Local mode
    50  			return NewDoHLocalNameServer(u, queryStrategy), nil
    51  		case strings.EqualFold(u.Scheme, "quic+local"): // DNS-over-QUIC Local mode
    52  			return NewQUICNameServer(u, queryStrategy)
    53  		case strings.EqualFold(u.Scheme, "tcp"): // DNS-over-TCP Remote mode
    54  			return NewTCPNameServer(u, dispatcher, queryStrategy)
    55  		case strings.EqualFold(u.Scheme, "tcp+local"): // DNS-over-TCP Local mode
    56  			return NewTCPLocalNameServer(u, queryStrategy)
    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(
    72  	ctx context.Context,
    73  	ns *NameServer,
    74  	clientIP net.IP,
    75  	container router.GeoIPMatcherContainer,
    76  	matcherInfos *[]*DomainMatcherInfo,
    77  	updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo) error,
    78  ) (*Client, error) {
    79  	client := &Client{}
    80  
    81  	err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
    82  		// Create a new server for each client for now
    83  		server, err := NewServer(ns.Address.AsDestination(), dispatcher, ns.GetQueryStrategy())
    84  		if err != nil {
    85  			return newError("failed to create nameserver").Base(err).AtWarning()
    86  		}
    87  
    88  		// Priotize local domains with specific TLDs or without any dot to local DNS
    89  		if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {
    90  			ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
    91  			ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)
    92  			// The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config.
    93  			// Because the `localhost` DNS client will apend len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
    94  			// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).
    95  			// 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.
    96  			// Related issues:
    97  			// https://github.com/v2fly/v2ray-core/issues/529
    98  			// https://github.com/v2fly/v2ray-core/issues/719
    99  			for i := 0; i < len(localTLDsAndDotlessDomains); i++ {
   100  				*matcherInfos = append(*matcherInfos, &DomainMatcherInfo{
   101  					clientIdx:     uint16(0),
   102  					domainRuleIdx: uint16(0),
   103  				})
   104  			}
   105  		}
   106  
   107  		// Establish domain rules
   108  		var rules []string
   109  		ruleCurr := 0
   110  		ruleIter := 0
   111  		for _, domain := range ns.PrioritizedDomain {
   112  			domainRule, err := toStrMatcher(domain.Type, domain.Domain)
   113  			if err != nil {
   114  				return newError("failed to create prioritized domain").Base(err).AtWarning()
   115  			}
   116  			originalRuleIdx := ruleCurr
   117  			if ruleCurr < len(ns.OriginalRules) {
   118  				rule := ns.OriginalRules[ruleCurr]
   119  				if ruleCurr >= len(rules) {
   120  					rules = append(rules, rule.Rule)
   121  				}
   122  				ruleIter++
   123  				if ruleIter >= int(rule.Size) {
   124  					ruleIter = 0
   125  					ruleCurr++
   126  				}
   127  			} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests)
   128  				rules = append(rules, domainRule.String())
   129  				ruleCurr++
   130  			}
   131  			err = updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
   132  			if err != nil {
   133  				return newError("failed to create prioritized domain").Base(err).AtWarning()
   134  			}
   135  		}
   136  
   137  		// Establish expected IPs
   138  		var matchers []*router.GeoIPMatcher
   139  		for _, geoip := range ns.Geoip {
   140  			matcher, err := container.Add(geoip)
   141  			if err != nil {
   142  				return newError("failed to create ip matcher").Base(err).AtWarning()
   143  			}
   144  			matchers = append(matchers, matcher)
   145  		}
   146  
   147  		if len(clientIP) > 0 {
   148  			switch ns.Address.Address.GetAddress().(type) {
   149  			case *net.IPOrDomain_Domain:
   150  				newError("DNS: client ", ns.Address.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
   151  			case *net.IPOrDomain_Ip:
   152  				newError("DNS: client ", ns.Address.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
   153  			}
   154  		}
   155  
   156  		client.server = server
   157  		client.clientIP = clientIP
   158  		client.skipFallback = ns.SkipFallback
   159  		client.domains = rules
   160  		client.expectIPs = matchers
   161  		return nil
   162  	})
   163  	return client, err
   164  }
   165  
   166  // NewSimpleClient creates a DNS client with a simple destination.
   167  func NewSimpleClient(ctx context.Context, endpoint *net.Endpoint, clientIP net.IP) (*Client, error) {
   168  	client := &Client{}
   169  	err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
   170  		server, err := NewServer(endpoint.AsDestination(), dispatcher, QueryStrategy_USE_IP)
   171  		if err != nil {
   172  			return newError("failed to create nameserver").Base(err).AtWarning()
   173  		}
   174  		client.server = server
   175  		client.clientIP = clientIP
   176  		return nil
   177  	})
   178  
   179  	if len(clientIP) > 0 {
   180  		switch endpoint.Address.GetAddress().(type) {
   181  		case *net.IPOrDomain_Domain:
   182  			newError("DNS: client ", endpoint.Address.GetDomain(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
   183  		case *net.IPOrDomain_Ip:
   184  			newError("DNS: client ", endpoint.Address.GetIp(), " uses clientIP ", clientIP.String()).AtInfo().WriteToLog()
   185  		}
   186  	}
   187  
   188  	return client, err
   189  }
   190  
   191  // Name returns the server name the client manages.
   192  func (c *Client) Name() string {
   193  	return c.server.Name()
   194  }
   195  
   196  // QueryIP sends DNS query to the name server with the client's IP.
   197  func (c *Client) QueryIP(ctx context.Context, domain string, option dns.IPOption, disableCache bool) ([]net.IP, error) {
   198  	ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
   199  	ips, err := c.server.QueryIP(ctx, domain, c.clientIP, option, disableCache)
   200  	cancel()
   201  
   202  	if err != nil {
   203  		return ips, err
   204  	}
   205  	return c.MatchExpectedIPs(domain, ips)
   206  }
   207  
   208  // MatchExpectedIPs matches queried domain IPs with expected IPs and returns matched ones.
   209  func (c *Client) MatchExpectedIPs(domain string, ips []net.IP) ([]net.IP, error) {
   210  	if len(c.expectIPs) == 0 {
   211  		return ips, nil
   212  	}
   213  	newIps := []net.IP{}
   214  	for _, ip := range ips {
   215  		for _, matcher := range c.expectIPs {
   216  			if matcher.Match(ip) {
   217  				newIps = append(newIps, ip)
   218  				break
   219  			}
   220  		}
   221  	}
   222  	if len(newIps) == 0 {
   223  		return nil, errExpectedIPNonMatch
   224  	}
   225  	newError("domain ", domain, " expectIPs ", newIps, " matched at server ", c.Name()).AtDebug().WriteToLog()
   226  	return newIps, nil
   227  }
   228  
   229  func ResolveIpOptionOverride(queryStrategy QueryStrategy, ipOption dns.IPOption) dns.IPOption {
   230  	switch queryStrategy {
   231  	case QueryStrategy_USE_IP:
   232  		return ipOption
   233  	case QueryStrategy_USE_IP4:
   234  		return dns.IPOption{
   235  			IPv4Enable: ipOption.IPv4Enable,
   236  			IPv6Enable: false,
   237  			FakeEnable: false,
   238  		}
   239  	case QueryStrategy_USE_IP6:
   240  		return dns.IPOption{
   241  			IPv4Enable: false,
   242  			IPv6Enable: ipOption.IPv6Enable,
   243  			FakeEnable: false,
   244  		}
   245  	default:
   246  		return ipOption
   247  	}
   248  }