github.com/xtls/xray-core@v1.8.12-0.20240518155711-3168d27b0bdb/infra/conf/dns.go (about)

     1  package conf
     2  
     3  import (
     4  	"encoding/json"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/xtls/xray-core/app/dns"
     9  	"github.com/xtls/xray-core/app/router"
    10  	"github.com/xtls/xray-core/common/net"
    11  )
    12  
    13  type NameServerConfig struct {
    14  	Address       *Address
    15  	ClientIP      *Address
    16  	Port          uint16
    17  	SkipFallback  bool
    18  	Domains       []string
    19  	ExpectIPs     StringList
    20  	QueryStrategy string
    21  }
    22  
    23  func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
    24  	var address Address
    25  	if err := json.Unmarshal(data, &address); err == nil {
    26  		c.Address = &address
    27  		return nil
    28  	}
    29  
    30  	var advanced struct {
    31  		Address       *Address   `json:"address"`
    32  		ClientIP      *Address   `json:"clientIp"`
    33  		Port          uint16     `json:"port"`
    34  		SkipFallback  bool       `json:"skipFallback"`
    35  		Domains       []string   `json:"domains"`
    36  		ExpectIPs     StringList `json:"expectIps"`
    37  		QueryStrategy string     `json:"queryStrategy"`
    38  	}
    39  	if err := json.Unmarshal(data, &advanced); err == nil {
    40  		c.Address = advanced.Address
    41  		c.ClientIP = advanced.ClientIP
    42  		c.Port = advanced.Port
    43  		c.SkipFallback = advanced.SkipFallback
    44  		c.Domains = advanced.Domains
    45  		c.ExpectIPs = advanced.ExpectIPs
    46  		c.QueryStrategy = advanced.QueryStrategy
    47  		return nil
    48  	}
    49  
    50  	return newError("failed to parse name server: ", string(data))
    51  }
    52  
    53  func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
    54  	switch t {
    55  	case router.Domain_Domain:
    56  		return dns.DomainMatchingType_Subdomain
    57  	case router.Domain_Full:
    58  		return dns.DomainMatchingType_Full
    59  	case router.Domain_Plain:
    60  		return dns.DomainMatchingType_Keyword
    61  	case router.Domain_Regex:
    62  		return dns.DomainMatchingType_Regex
    63  	default:
    64  		panic("unknown domain type")
    65  	}
    66  }
    67  
    68  func (c *NameServerConfig) Build() (*dns.NameServer, error) {
    69  	if c.Address == nil {
    70  		return nil, newError("NameServer address is not specified.")
    71  	}
    72  
    73  	var domains []*dns.NameServer_PriorityDomain
    74  	var originalRules []*dns.NameServer_OriginalRule
    75  
    76  	for _, rule := range c.Domains {
    77  		parsedDomain, err := parseDomainRule(rule)
    78  		if err != nil {
    79  			return nil, newError("invalid domain rule: ", rule).Base(err)
    80  		}
    81  
    82  		for _, pd := range parsedDomain {
    83  			domains = append(domains, &dns.NameServer_PriorityDomain{
    84  				Type:   toDomainMatchingType(pd.Type),
    85  				Domain: pd.Value,
    86  			})
    87  		}
    88  		originalRules = append(originalRules, &dns.NameServer_OriginalRule{
    89  			Rule: rule,
    90  			Size: uint32(len(parsedDomain)),
    91  		})
    92  	}
    93  
    94  	geoipList, err := ToCidrList(c.ExpectIPs)
    95  	if err != nil {
    96  		return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
    97  	}
    98  
    99  	var myClientIP []byte
   100  	if c.ClientIP != nil {
   101  		if !c.ClientIP.Family().IsIP() {
   102  			return nil, newError("not an IP address:", c.ClientIP.String())
   103  		}
   104  		myClientIP = []byte(c.ClientIP.IP())
   105  	}
   106  
   107  	return &dns.NameServer{
   108  		Address: &net.Endpoint{
   109  			Network: net.Network_UDP,
   110  			Address: c.Address.Build(),
   111  			Port:    uint32(c.Port),
   112  		},
   113  		ClientIp:          myClientIP,
   114  		SkipFallback:      c.SkipFallback,
   115  		PrioritizedDomain: domains,
   116  		Geoip:             geoipList,
   117  		OriginalRules:     originalRules,
   118  		QueryStrategy:     resolveQueryStrategy(c.QueryStrategy),
   119  	}, nil
   120  }
   121  
   122  var typeMap = map[router.Domain_Type]dns.DomainMatchingType{
   123  	router.Domain_Full:   dns.DomainMatchingType_Full,
   124  	router.Domain_Domain: dns.DomainMatchingType_Subdomain,
   125  	router.Domain_Plain:  dns.DomainMatchingType_Keyword,
   126  	router.Domain_Regex:  dns.DomainMatchingType_Regex,
   127  }
   128  
   129  // DNSConfig is a JSON serializable object for dns.Config.
   130  type DNSConfig struct {
   131  	Servers                []*NameServerConfig `json:"servers"`
   132  	Hosts                  *HostsWrapper       `json:"hosts"`
   133  	ClientIP               *Address            `json:"clientIp"`
   134  	Tag                    string              `json:"tag"`
   135  	QueryStrategy          string              `json:"queryStrategy"`
   136  	DisableCache           bool                `json:"disableCache"`
   137  	DisableFallback        bool                `json:"disableFallback"`
   138  	DisableFallbackIfMatch bool                `json:"disableFallbackIfMatch"`
   139  }
   140  
   141  type HostAddress struct {
   142  	addr  *Address
   143  	addrs []*Address
   144  }
   145  
   146  // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
   147  func (h *HostAddress) UnmarshalJSON(data []byte) error {
   148  	addr := new(Address)
   149  	var addrs []*Address
   150  	switch {
   151  	case json.Unmarshal(data, &addr) == nil:
   152  		h.addr = addr
   153  	case json.Unmarshal(data, &addrs) == nil:
   154  		h.addrs = addrs
   155  	default:
   156  		return newError("invalid address")
   157  	}
   158  	return nil
   159  }
   160  
   161  type HostsWrapper struct {
   162  	Hosts map[string]*HostAddress
   163  }
   164  
   165  func getHostMapping(ha *HostAddress) *dns.Config_HostMapping {
   166  	if ha.addr != nil {
   167  		if ha.addr.Family().IsDomain() {
   168  			return &dns.Config_HostMapping{
   169  				ProxiedDomain: ha.addr.Domain(),
   170  			}
   171  		}
   172  		return &dns.Config_HostMapping{
   173  			Ip: [][]byte{ha.addr.IP()},
   174  		}
   175  	}
   176  
   177  	ips := make([][]byte, 0, len(ha.addrs))
   178  	for _, addr := range ha.addrs {
   179  		if addr.Family().IsDomain() {
   180  			return &dns.Config_HostMapping{
   181  				ProxiedDomain: addr.Domain(),
   182  			}
   183  		}
   184  		ips = append(ips, []byte(addr.IP()))
   185  	}
   186  	return &dns.Config_HostMapping{
   187  		Ip: ips,
   188  	}
   189  }
   190  
   191  // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
   192  func (m *HostsWrapper) UnmarshalJSON(data []byte) error {
   193  	hosts := make(map[string]*HostAddress)
   194  	err := json.Unmarshal(data, &hosts)
   195  	if err == nil {
   196  		m.Hosts = hosts
   197  		return nil
   198  	}
   199  	return newError("invalid DNS hosts").Base(err)
   200  }
   201  
   202  // Build implements Buildable
   203  func (m *HostsWrapper) Build() ([]*dns.Config_HostMapping, error) {
   204  	mappings := make([]*dns.Config_HostMapping, 0, 20)
   205  
   206  	domains := make([]string, 0, len(m.Hosts))
   207  	for domain := range m.Hosts {
   208  		domains = append(domains, domain)
   209  	}
   210  	sort.Strings(domains)
   211  
   212  	for _, domain := range domains {
   213  		switch {
   214  		case strings.HasPrefix(domain, "domain:"):
   215  			domainName := domain[7:]
   216  			if len(domainName) == 0 {
   217  				return nil, newError("empty domain type of rule: ", domain)
   218  			}
   219  			mapping := getHostMapping(m.Hosts[domain])
   220  			mapping.Type = dns.DomainMatchingType_Subdomain
   221  			mapping.Domain = domainName
   222  			mappings = append(mappings, mapping)
   223  
   224  		case strings.HasPrefix(domain, "geosite:"):
   225  			listName := domain[8:]
   226  			if len(listName) == 0 {
   227  				return nil, newError("empty geosite rule: ", domain)
   228  			}
   229  			geositeList, err := loadGeositeWithAttr("geosite.dat", listName)
   230  			if err != nil {
   231  				return nil, newError("failed to load geosite: ", listName).Base(err)
   232  			}
   233  			for _, d := range geositeList {
   234  				mapping := getHostMapping(m.Hosts[domain])
   235  				mapping.Type = typeMap[d.Type]
   236  				mapping.Domain = d.Value
   237  				mappings = append(mappings, mapping)
   238  			}
   239  
   240  		case strings.HasPrefix(domain, "regexp:"):
   241  			regexpVal := domain[7:]
   242  			if len(regexpVal) == 0 {
   243  				return nil, newError("empty regexp type of rule: ", domain)
   244  			}
   245  			mapping := getHostMapping(m.Hosts[domain])
   246  			mapping.Type = dns.DomainMatchingType_Regex
   247  			mapping.Domain = regexpVal
   248  			mappings = append(mappings, mapping)
   249  
   250  		case strings.HasPrefix(domain, "keyword:"):
   251  			keywordVal := domain[8:]
   252  			if len(keywordVal) == 0 {
   253  				return nil, newError("empty keyword type of rule: ", domain)
   254  			}
   255  			mapping := getHostMapping(m.Hosts[domain])
   256  			mapping.Type = dns.DomainMatchingType_Keyword
   257  			mapping.Domain = keywordVal
   258  			mappings = append(mappings, mapping)
   259  
   260  		case strings.HasPrefix(domain, "full:"):
   261  			fullVal := domain[5:]
   262  			if len(fullVal) == 0 {
   263  				return nil, newError("empty full domain type of rule: ", domain)
   264  			}
   265  			mapping := getHostMapping(m.Hosts[domain])
   266  			mapping.Type = dns.DomainMatchingType_Full
   267  			mapping.Domain = fullVal
   268  			mappings = append(mappings, mapping)
   269  
   270  		case strings.HasPrefix(domain, "dotless:"):
   271  			mapping := getHostMapping(m.Hosts[domain])
   272  			mapping.Type = dns.DomainMatchingType_Regex
   273  			switch substr := domain[8:]; {
   274  			case substr == "":
   275  				mapping.Domain = "^[^.]*$"
   276  			case !strings.Contains(substr, "."):
   277  				mapping.Domain = "^[^.]*" + substr + "[^.]*$"
   278  			default:
   279  				return nil, newError("substr in dotless rule should not contain a dot: ", substr)
   280  			}
   281  			mappings = append(mappings, mapping)
   282  
   283  		case strings.HasPrefix(domain, "ext:"):
   284  			kv := strings.Split(domain[4:], ":")
   285  			if len(kv) != 2 {
   286  				return nil, newError("invalid external resource: ", domain)
   287  			}
   288  			filename := kv[0]
   289  			list := kv[1]
   290  			geositeList, err := loadGeositeWithAttr(filename, list)
   291  			if err != nil {
   292  				return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
   293  			}
   294  			for _, d := range geositeList {
   295  				mapping := getHostMapping(m.Hosts[domain])
   296  				mapping.Type = typeMap[d.Type]
   297  				mapping.Domain = d.Value
   298  				mappings = append(mappings, mapping)
   299  			}
   300  
   301  		default:
   302  			mapping := getHostMapping(m.Hosts[domain])
   303  			mapping.Type = dns.DomainMatchingType_Full
   304  			mapping.Domain = domain
   305  			mappings = append(mappings, mapping)
   306  		}
   307  	}
   308  	return mappings, nil
   309  }
   310  
   311  // Build implements Buildable
   312  func (c *DNSConfig) Build() (*dns.Config, error) {
   313  	config := &dns.Config{
   314  		Tag:                    c.Tag,
   315  		DisableCache:           c.DisableCache,
   316  		DisableFallback:        c.DisableFallback,
   317  		DisableFallbackIfMatch: c.DisableFallbackIfMatch,
   318  		QueryStrategy:          resolveQueryStrategy(c.QueryStrategy),
   319  	}
   320  
   321  	if c.ClientIP != nil {
   322  		if !c.ClientIP.Family().IsIP() {
   323  			return nil, newError("not an IP address:", c.ClientIP.String())
   324  		}
   325  		config.ClientIp = []byte(c.ClientIP.IP())
   326  	}
   327  
   328  	for _, server := range c.Servers {
   329  		ns, err := server.Build()
   330  		if err != nil {
   331  			return nil, newError("failed to build nameserver").Base(err)
   332  		}
   333  		config.NameServer = append(config.NameServer, ns)
   334  	}
   335  
   336  	if c.Hosts != nil {
   337  		staticHosts, err := c.Hosts.Build()
   338  		if err != nil {
   339  			return nil, newError("failed to build hosts").Base(err)
   340  		}
   341  		config.StaticHosts = append(config.StaticHosts, staticHosts...)
   342  	}
   343  
   344  	return config, nil
   345  }
   346  
   347  func resolveQueryStrategy(queryStrategy string) dns.QueryStrategy {
   348  	switch strings.ToLower(queryStrategy) {
   349  	case "useip", "use_ip", "use-ip":
   350  		return dns.QueryStrategy_USE_IP
   351  	case "useip4", "useipv4", "use_ip4", "use_ipv4", "use_ip_v4", "use-ip4", "use-ipv4", "use-ip-v4":
   352  		return dns.QueryStrategy_USE_IP4
   353  	case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
   354  		return dns.QueryStrategy_USE_IP6
   355  	default:
   356  		return dns.QueryStrategy_USE_IP
   357  	}
   358  }