github.com/eagleql/xray-core@v1.4.4/infra/conf/dns.go (about)

     1  package conf
     2  
     3  import (
     4  	"encoding/json"
     5  	"sort"
     6  	"strings"
     7  
     8  	"github.com/eagleql/xray-core/app/dns"
     9  	"github.com/eagleql/xray-core/app/router"
    10  	"github.com/eagleql/xray-core/common/net"
    11  )
    12  
    13  type NameServerConfig struct {
    14  	Address   *Address
    15  	Port      uint16
    16  	Domains   []string
    17  	ExpectIPs StringList
    18  }
    19  
    20  func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
    21  	var address Address
    22  	if err := json.Unmarshal(data, &address); err == nil {
    23  		c.Address = &address
    24  		return nil
    25  	}
    26  
    27  	var advanced struct {
    28  		Address   *Address   `json:"address"`
    29  		Port      uint16     `json:"port"`
    30  		Domains   []string   `json:"domains"`
    31  		ExpectIPs StringList `json:"expectIps"`
    32  	}
    33  	if err := json.Unmarshal(data, &advanced); err == nil {
    34  		c.Address = advanced.Address
    35  		c.Port = advanced.Port
    36  		c.Domains = advanced.Domains
    37  		c.ExpectIPs = advanced.ExpectIPs
    38  		return nil
    39  	}
    40  
    41  	return newError("failed to parse name server: ", string(data))
    42  }
    43  
    44  func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
    45  	switch t {
    46  	case router.Domain_Domain:
    47  		return dns.DomainMatchingType_Subdomain
    48  	case router.Domain_Full:
    49  		return dns.DomainMatchingType_Full
    50  	case router.Domain_Plain:
    51  		return dns.DomainMatchingType_Keyword
    52  	case router.Domain_Regex:
    53  		return dns.DomainMatchingType_Regex
    54  	default:
    55  		panic("unknown domain type")
    56  	}
    57  }
    58  
    59  func (c *NameServerConfig) Build() (*dns.NameServer, error) {
    60  	if c.Address == nil {
    61  		return nil, newError("NameServer address is not specified.")
    62  	}
    63  
    64  	var domains []*dns.NameServer_PriorityDomain
    65  	var originalRules []*dns.NameServer_OriginalRule
    66  
    67  	for _, rule := range c.Domains {
    68  		parsedDomain, err := parseDomainRule(rule)
    69  		if err != nil {
    70  			return nil, newError("invalid domain rule: ", rule).Base(err)
    71  		}
    72  
    73  		for _, pd := range parsedDomain {
    74  			domains = append(domains, &dns.NameServer_PriorityDomain{
    75  				Type:   toDomainMatchingType(pd.Type),
    76  				Domain: pd.Value,
    77  			})
    78  		}
    79  		originalRules = append(originalRules, &dns.NameServer_OriginalRule{
    80  			Rule: rule,
    81  			Size: uint32(len(parsedDomain)),
    82  		})
    83  	}
    84  
    85  	geoipList, err := toCidrList(c.ExpectIPs)
    86  	if err != nil {
    87  		return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
    88  	}
    89  
    90  	return &dns.NameServer{
    91  		Address: &net.Endpoint{
    92  			Network: net.Network_UDP,
    93  			Address: c.Address.Build(),
    94  			Port:    uint32(c.Port),
    95  		},
    96  		PrioritizedDomain: domains,
    97  		Geoip:             geoipList,
    98  		OriginalRules:     originalRules,
    99  	}, nil
   100  }
   101  
   102  var typeMap = map[router.Domain_Type]dns.DomainMatchingType{
   103  	router.Domain_Full:   dns.DomainMatchingType_Full,
   104  	router.Domain_Domain: dns.DomainMatchingType_Subdomain,
   105  	router.Domain_Plain:  dns.DomainMatchingType_Keyword,
   106  	router.Domain_Regex:  dns.DomainMatchingType_Regex,
   107  }
   108  
   109  // DNSConfig is a JSON serializable object for dns.Config.
   110  type DNSConfig struct {
   111  	Servers  []*NameServerConfig `json:"servers"`
   112  	Hosts    map[string]*Address `json:"hosts"`
   113  	ClientIP *Address            `json:"clientIp"`
   114  	Tag      string              `json:"tag"`
   115  }
   116  
   117  func getHostMapping(addr *Address) *dns.Config_HostMapping {
   118  	if addr.Family().IsIP() {
   119  		return &dns.Config_HostMapping{
   120  			Ip: [][]byte{[]byte(addr.IP())},
   121  		}
   122  	} else {
   123  		return &dns.Config_HostMapping{
   124  			ProxiedDomain: addr.Domain(),
   125  		}
   126  	}
   127  }
   128  
   129  // Build implements Buildable
   130  func (c *DNSConfig) Build() (*dns.Config, error) {
   131  	config := &dns.Config{
   132  		Tag: c.Tag,
   133  	}
   134  
   135  	if c.ClientIP != nil {
   136  		if !c.ClientIP.Family().IsIP() {
   137  			return nil, newError("not an IP address:", c.ClientIP.String())
   138  		}
   139  		config.ClientIp = []byte(c.ClientIP.IP())
   140  	}
   141  
   142  	for _, server := range c.Servers {
   143  		ns, err := server.Build()
   144  		if err != nil {
   145  			return nil, newError("failed to build nameserver").Base(err)
   146  		}
   147  		config.NameServer = append(config.NameServer, ns)
   148  	}
   149  
   150  	if c.Hosts != nil && len(c.Hosts) > 0 {
   151  		domains := make([]string, 0, len(c.Hosts))
   152  		for domain := range c.Hosts {
   153  			domains = append(domains, domain)
   154  		}
   155  		sort.Strings(domains)
   156  
   157  		for _, domain := range domains {
   158  			addr := c.Hosts[domain]
   159  			var mappings []*dns.Config_HostMapping
   160  			switch {
   161  			case strings.HasPrefix(domain, "domain:"):
   162  				domainName := domain[7:]
   163  				if len(domainName) == 0 {
   164  					return nil, newError("empty domain type of rule: ", domain)
   165  				}
   166  				mapping := getHostMapping(addr)
   167  				mapping.Type = dns.DomainMatchingType_Subdomain
   168  				mapping.Domain = domainName
   169  				mappings = append(mappings, mapping)
   170  
   171  			case strings.HasPrefix(domain, "geosite:"):
   172  				listName := domain[8:]
   173  				if len(listName) == 0 {
   174  					return nil, newError("empty geosite rule: ", domain)
   175  				}
   176  				domains, err := loadGeositeWithAttr("geosite.dat", listName)
   177  				if err != nil {
   178  					return nil, newError("failed to load geosite: ", listName).Base(err)
   179  				}
   180  				for _, d := range domains {
   181  					mapping := getHostMapping(addr)
   182  					mapping.Type = typeMap[d.Type]
   183  					mapping.Domain = d.Value
   184  					mappings = append(mappings, mapping)
   185  				}
   186  
   187  			case strings.HasPrefix(domain, "regexp:"):
   188  				regexpVal := domain[7:]
   189  				if len(regexpVal) == 0 {
   190  					return nil, newError("empty regexp type of rule: ", domain)
   191  				}
   192  				mapping := getHostMapping(addr)
   193  				mapping.Type = dns.DomainMatchingType_Regex
   194  				mapping.Domain = regexpVal
   195  				mappings = append(mappings, mapping)
   196  
   197  			case strings.HasPrefix(domain, "keyword:"):
   198  				keywordVal := domain[8:]
   199  				if len(keywordVal) == 0 {
   200  					return nil, newError("empty keyword type of rule: ", domain)
   201  				}
   202  				mapping := getHostMapping(addr)
   203  				mapping.Type = dns.DomainMatchingType_Keyword
   204  				mapping.Domain = keywordVal
   205  				mappings = append(mappings, mapping)
   206  
   207  			case strings.HasPrefix(domain, "full:"):
   208  				fullVal := domain[5:]
   209  				if len(fullVal) == 0 {
   210  					return nil, newError("empty full domain type of rule: ", domain)
   211  				}
   212  				mapping := getHostMapping(addr)
   213  				mapping.Type = dns.DomainMatchingType_Full
   214  				mapping.Domain = fullVal
   215  				mappings = append(mappings, mapping)
   216  
   217  			case strings.HasPrefix(domain, "dotless:"):
   218  				mapping := getHostMapping(addr)
   219  				mapping.Type = dns.DomainMatchingType_Regex
   220  				switch substr := domain[8:]; {
   221  				case substr == "":
   222  					mapping.Domain = "^[^.]*$"
   223  				case !strings.Contains(substr, "."):
   224  					mapping.Domain = "^[^.]*" + substr + "[^.]*$"
   225  				default:
   226  					return nil, newError("substr in dotless rule should not contain a dot: ", substr)
   227  				}
   228  				mappings = append(mappings, mapping)
   229  
   230  			case strings.HasPrefix(domain, "ext:"):
   231  				kv := strings.Split(domain[4:], ":")
   232  				if len(kv) != 2 {
   233  					return nil, newError("invalid external resource: ", domain)
   234  				}
   235  				filename := kv[0]
   236  				list := kv[1]
   237  				domains, err := loadGeositeWithAttr(filename, list)
   238  				if err != nil {
   239  					return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
   240  				}
   241  				for _, d := range domains {
   242  					mapping := getHostMapping(addr)
   243  					mapping.Type = typeMap[d.Type]
   244  					mapping.Domain = d.Value
   245  					mappings = append(mappings, mapping)
   246  				}
   247  
   248  			default:
   249  				mapping := getHostMapping(addr)
   250  				mapping.Type = dns.DomainMatchingType_Full
   251  				mapping.Domain = domain
   252  				mappings = append(mappings, mapping)
   253  			}
   254  
   255  			config.StaticHosts = append(config.StaticHosts, mappings...)
   256  		}
   257  	}
   258  
   259  	return config, nil
   260  }