github.com/imannamdari/v2ray-core/v5@v5.0.5/infra/conf/synthetic/dns/dns.go (about)

     1  package dns
     2  
     3  //go:generate go run github.com/imannamdari/v2ray-core/v5/common/errors/errorgen
     4  
     5  import (
     6  	"context"
     7  	"encoding/json"
     8  	"sort"
     9  	"strings"
    10  
    11  	"github.com/imannamdari/v2ray-core/v5/app/dns"
    12  	"github.com/imannamdari/v2ray-core/v5/app/dns/fakedns"
    13  	"github.com/imannamdari/v2ray-core/v5/app/router/routercommon"
    14  	"github.com/imannamdari/v2ray-core/v5/common/net"
    15  	"github.com/imannamdari/v2ray-core/v5/common/platform"
    16  	"github.com/imannamdari/v2ray-core/v5/infra/conf/cfgcommon"
    17  	"github.com/imannamdari/v2ray-core/v5/infra/conf/geodata"
    18  	rule2 "github.com/imannamdari/v2ray-core/v5/infra/conf/rule"
    19  )
    20  
    21  type NameServerConfig struct {
    22  	Address          *cfgcommon.Address
    23  	ClientIP         *cfgcommon.Address
    24  	Port             uint16
    25  	Tag              string
    26  	QueryStrategy    string
    27  	CacheStrategy    string
    28  	FallbackStrategy string
    29  	SkipFallback     bool
    30  	Domains          []string
    31  	ExpectIPs        cfgcommon.StringList
    32  	FakeDNS          FakeDNSConfigExtend
    33  
    34  	cfgctx context.Context
    35  }
    36  
    37  func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
    38  	var address cfgcommon.Address
    39  	if err := json.Unmarshal(data, &address); err == nil {
    40  		c.Address = &address
    41  		return nil
    42  	}
    43  
    44  	var advanced struct {
    45  		Address          *cfgcommon.Address   `json:"address"`
    46  		ClientIP         *cfgcommon.Address   `json:"clientIp"`
    47  		Port             uint16               `json:"port"`
    48  		Tag              string               `json:"tag"`
    49  		QueryStrategy    string               `json:"queryStrategy"`
    50  		CacheStrategy    string               `json:"cacheStrategy"`
    51  		FallbackStrategy string               `json:"fallbackStrategy"`
    52  		SkipFallback     bool                 `json:"skipFallback"`
    53  		Domains          []string             `json:"domains"`
    54  		ExpectIPs        cfgcommon.StringList `json:"expectIps"`
    55  		FakeDNS          FakeDNSConfigExtend  `json:"fakedns"`
    56  	}
    57  	if err := json.Unmarshal(data, &advanced); err == nil {
    58  		c.Address = advanced.Address
    59  		c.ClientIP = advanced.ClientIP
    60  		c.Port = advanced.Port
    61  		c.Tag = advanced.Tag
    62  		c.QueryStrategy = advanced.QueryStrategy
    63  		c.CacheStrategy = advanced.CacheStrategy
    64  		c.FallbackStrategy = advanced.FallbackStrategy
    65  		c.SkipFallback = advanced.SkipFallback
    66  		c.Domains = advanced.Domains
    67  		c.ExpectIPs = advanced.ExpectIPs
    68  		c.FakeDNS = advanced.FakeDNS
    69  		return nil
    70  	}
    71  
    72  	return newError("failed to parse name server: ", string(data))
    73  }
    74  
    75  func toDomainMatchingType(t routercommon.Domain_Type) dns.DomainMatchingType {
    76  	switch t {
    77  	case routercommon.Domain_RootDomain:
    78  		return dns.DomainMatchingType_Subdomain
    79  	case routercommon.Domain_Full:
    80  		return dns.DomainMatchingType_Full
    81  	case routercommon.Domain_Plain:
    82  		return dns.DomainMatchingType_Keyword
    83  	case routercommon.Domain_Regex:
    84  		return dns.DomainMatchingType_Regex
    85  	default:
    86  		panic("unknown domain type")
    87  	}
    88  }
    89  
    90  func (c *NameServerConfig) BuildV5(ctx context.Context) (*dns.NameServer, error) {
    91  	c.cfgctx = ctx
    92  	return c.Build()
    93  }
    94  
    95  func (c *NameServerConfig) Build() (*dns.NameServer, error) {
    96  	cfgctx := c.cfgctx
    97  
    98  	if c.Address == nil {
    99  		return nil, newError("NameServer address is not specified.")
   100  	}
   101  
   102  	var domains []*dns.NameServer_PriorityDomain
   103  	var originalRules []*dns.NameServer_OriginalRule
   104  
   105  	for _, rule := range c.Domains {
   106  		parsedDomain, err := rule2.ParseDomainRule(cfgctx, rule)
   107  		if err != nil {
   108  			return nil, newError("invalid domain rule: ", rule).Base(err)
   109  		}
   110  
   111  		for _, pd := range parsedDomain {
   112  			domains = append(domains, &dns.NameServer_PriorityDomain{
   113  				Type:   toDomainMatchingType(pd.Type),
   114  				Domain: pd.Value,
   115  			})
   116  		}
   117  		originalRules = append(originalRules, &dns.NameServer_OriginalRule{
   118  			Rule: rule,
   119  			Size: uint32(len(parsedDomain)),
   120  		})
   121  	}
   122  
   123  	geoipList, err := rule2.ToCidrList(cfgctx, c.ExpectIPs)
   124  	if err != nil {
   125  		return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
   126  	}
   127  
   128  	var fakeDNS *fakedns.FakeDnsPoolMulti
   129  	if c.FakeDNS.FakeDNSConfig != nil {
   130  		fake, err := c.FakeDNS.FakeDNSConfig.Build()
   131  		if err != nil {
   132  			return nil, newError("failed to build fakedns").Base(err)
   133  		}
   134  		fakeDNS = fake
   135  	}
   136  
   137  	var myClientIP []byte
   138  	if c.ClientIP != nil {
   139  		if !c.ClientIP.Family().IsIP() {
   140  			return nil, newError("not an IP address:", c.ClientIP.String())
   141  		}
   142  		myClientIP = []byte(c.ClientIP.IP())
   143  	}
   144  
   145  	queryStrategy := new(dns.QueryStrategy)
   146  	switch strings.ToLower(c.QueryStrategy) {
   147  	case "useip", "use_ip", "use-ip":
   148  		*queryStrategy = dns.QueryStrategy_USE_IP
   149  	case "useip4", "useipv4", "use_ip4", "use_ipv4", "use_ip_v4", "use-ip4", "use-ipv4", "use-ip-v4":
   150  		*queryStrategy = dns.QueryStrategy_USE_IP4
   151  	case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
   152  		*queryStrategy = dns.QueryStrategy_USE_IP6
   153  	default:
   154  		queryStrategy = nil
   155  	}
   156  
   157  	cacheStrategy := new(dns.CacheStrategy)
   158  	switch strings.ToLower(c.CacheStrategy) {
   159  	case "enabled":
   160  		*cacheStrategy = dns.CacheStrategy_CacheEnabled
   161  	case "disabled":
   162  		*cacheStrategy = dns.CacheStrategy_CacheDisabled
   163  	default:
   164  		cacheStrategy = nil
   165  	}
   166  
   167  	fallbackStrategy := new(dns.FallbackStrategy)
   168  	switch strings.ToLower(c.FallbackStrategy) {
   169  	case "enabled":
   170  		*fallbackStrategy = dns.FallbackStrategy_Enabled
   171  	case "disabled":
   172  		*fallbackStrategy = dns.FallbackStrategy_Disabled
   173  	case "disabledifanymatch", "disabled_if_any_match", "disabled-if-any-match":
   174  		*fallbackStrategy = dns.FallbackStrategy_DisabledIfAnyMatch
   175  	default:
   176  		fallbackStrategy = nil
   177  	}
   178  
   179  	return &dns.NameServer{
   180  		Address: &net.Endpoint{
   181  			Network: net.Network_UDP,
   182  			Address: c.Address.Build(),
   183  			Port:    uint32(c.Port),
   184  		},
   185  		ClientIp:          myClientIP,
   186  		Tag:               c.Tag,
   187  		SkipFallback:      c.SkipFallback,
   188  		QueryStrategy:     queryStrategy,
   189  		CacheStrategy:     cacheStrategy,
   190  		FallbackStrategy:  fallbackStrategy,
   191  		PrioritizedDomain: domains,
   192  		Geoip:             geoipList,
   193  		OriginalRules:     originalRules,
   194  		FakeDns:           fakeDNS,
   195  	}, nil
   196  }
   197  
   198  var typeMap = map[routercommon.Domain_Type]dns.DomainMatchingType{
   199  	routercommon.Domain_Full:       dns.DomainMatchingType_Full,
   200  	routercommon.Domain_RootDomain: dns.DomainMatchingType_Subdomain,
   201  	routercommon.Domain_Plain:      dns.DomainMatchingType_Keyword,
   202  	routercommon.Domain_Regex:      dns.DomainMatchingType_Regex,
   203  }
   204  
   205  // DNSConfig is a JSON serializable object for dns.Config.
   206  type DNSConfig struct { // nolint: revive
   207  	Servers                []*NameServerConfig     `json:"servers"`
   208  	Hosts                  map[string]*HostAddress `json:"hosts"`
   209  	FakeDNS                *FakeDNSConfig          `json:"fakedns"`
   210  	DomainMatcher          string                  `json:"domainMatcher"`
   211  	ClientIP               *cfgcommon.Address      `json:"clientIp"`
   212  	Tag                    string                  `json:"tag"`
   213  	QueryStrategy          string                  `json:"queryStrategy"`
   214  	CacheStrategy          string                  `json:"cacheStrategy"`
   215  	FallbackStrategy       string                  `json:"fallbackStrategy"`
   216  	DisableCache           bool                    `json:"disableCache"`
   217  	DisableFallback        bool                    `json:"disableFallback"`
   218  	DisableFallbackIfMatch bool                    `json:"disableFallbackIfMatch"`
   219  	cfgctx                 context.Context
   220  }
   221  
   222  type HostAddress struct {
   223  	addr  *cfgcommon.Address
   224  	addrs []*cfgcommon.Address
   225  }
   226  
   227  // UnmarshalJSON implements encoding/json.Unmarshaler.UnmarshalJSON
   228  func (h *HostAddress) UnmarshalJSON(data []byte) error {
   229  	addr := new(cfgcommon.Address)
   230  	var addrs []*cfgcommon.Address
   231  	switch {
   232  	case json.Unmarshal(data, &addr) == nil:
   233  		h.addr = addr
   234  	case json.Unmarshal(data, &addrs) == nil:
   235  		h.addrs = addrs
   236  	default:
   237  		return newError("invalid address")
   238  	}
   239  	return nil
   240  }
   241  
   242  func getHostMapping(ha *HostAddress) *dns.HostMapping {
   243  	if ha.addr != nil {
   244  		if ha.addr.Family().IsDomain() {
   245  			return &dns.HostMapping{
   246  				ProxiedDomain: ha.addr.Domain(),
   247  			}
   248  		}
   249  		return &dns.HostMapping{
   250  			Ip: [][]byte{ha.addr.IP()},
   251  		}
   252  	}
   253  
   254  	ips := make([][]byte, 0, len(ha.addrs))
   255  	for _, addr := range ha.addrs {
   256  		if addr.Family().IsDomain() {
   257  			return &dns.HostMapping{
   258  				ProxiedDomain: addr.Domain(),
   259  			}
   260  		}
   261  		ips = append(ips, []byte(addr.IP()))
   262  	}
   263  	return &dns.HostMapping{
   264  		Ip: ips,
   265  	}
   266  }
   267  
   268  func (c *DNSConfig) BuildV5(ctx context.Context) (*dns.Config, error) {
   269  	c.cfgctx = ctx
   270  	return c.Build()
   271  }
   272  
   273  // Build implements Buildable
   274  func (c *DNSConfig) Build() (*dns.Config, error) {
   275  	if c.cfgctx == nil {
   276  		c.cfgctx = cfgcommon.NewConfigureLoadingContext(context.Background())
   277  
   278  		geoloadername := platform.NewEnvFlag("v2ray.conf.geoloader").GetValue(func() string {
   279  			return "standard"
   280  		})
   281  
   282  		if loader, err := geodata.GetGeoDataLoader(geoloadername); err == nil {
   283  			cfgcommon.SetGeoDataLoader(c.cfgctx, loader)
   284  		} else {
   285  			return nil, newError("unable to create geo data loader ").Base(err)
   286  		}
   287  	}
   288  
   289  	cfgEnv := cfgcommon.GetConfigureLoadingEnvironment(c.cfgctx)
   290  	geoLoader := cfgEnv.GetGeoLoader()
   291  
   292  	config := &dns.Config{
   293  		Tag:                    c.Tag,
   294  		DisableCache:           c.DisableCache,
   295  		DisableFallback:        c.DisableFallback,
   296  		DisableFallbackIfMatch: c.DisableFallbackIfMatch,
   297  		DomainMatcher:          c.DomainMatcher,
   298  	}
   299  
   300  	if c.ClientIP != nil {
   301  		if !c.ClientIP.Family().IsIP() {
   302  			return nil, newError("not an IP address:", c.ClientIP.String())
   303  		}
   304  		config.ClientIp = []byte(c.ClientIP.IP())
   305  	}
   306  
   307  	config.QueryStrategy = dns.QueryStrategy_USE_IP
   308  	switch strings.ToLower(c.QueryStrategy) {
   309  	case "useip", "use_ip", "use-ip":
   310  		config.QueryStrategy = dns.QueryStrategy_USE_IP
   311  	case "useip4", "useipv4", "use_ip4", "use_ipv4", "use_ip_v4", "use-ip4", "use-ipv4", "use-ip-v4":
   312  		config.QueryStrategy = dns.QueryStrategy_USE_IP4
   313  	case "useip6", "useipv6", "use_ip6", "use_ipv6", "use_ip_v6", "use-ip6", "use-ipv6", "use-ip-v6":
   314  		config.QueryStrategy = dns.QueryStrategy_USE_IP6
   315  	}
   316  
   317  	config.CacheStrategy = dns.CacheStrategy_CacheEnabled
   318  	switch strings.ToLower(c.CacheStrategy) {
   319  	case "enabled":
   320  		config.CacheStrategy = dns.CacheStrategy_CacheEnabled
   321  	case "disabled":
   322  		config.CacheStrategy = dns.CacheStrategy_CacheDisabled
   323  	}
   324  
   325  	config.FallbackStrategy = dns.FallbackStrategy_Enabled
   326  	switch strings.ToLower(c.FallbackStrategy) {
   327  	case "enabled":
   328  		config.FallbackStrategy = dns.FallbackStrategy_Enabled
   329  	case "disabled":
   330  		config.FallbackStrategy = dns.FallbackStrategy_Disabled
   331  	case "disabledifanymatch", "disabled_if_any_match", "disabled-if-any-match":
   332  		config.FallbackStrategy = dns.FallbackStrategy_DisabledIfAnyMatch
   333  	}
   334  
   335  	for _, server := range c.Servers {
   336  		server.cfgctx = c.cfgctx
   337  		ns, err := server.Build()
   338  		if err != nil {
   339  			return nil, newError("failed to build nameserver").Base(err)
   340  		}
   341  		config.NameServer = append(config.NameServer, ns)
   342  	}
   343  
   344  	if c.Hosts != nil {
   345  		mappings := make([]*dns.HostMapping, 0, 20)
   346  
   347  		domains := make([]string, 0, len(c.Hosts))
   348  		for domain := range c.Hosts {
   349  			domains = append(domains, domain)
   350  		}
   351  		sort.Strings(domains)
   352  
   353  		for _, domain := range domains {
   354  			switch {
   355  			case strings.HasPrefix(domain, "domain:"):
   356  				domainName := domain[7:]
   357  				if len(domainName) == 0 {
   358  					return nil, newError("empty domain type of rule: ", domain)
   359  				}
   360  				mapping := getHostMapping(c.Hosts[domain])
   361  				mapping.Type = dns.DomainMatchingType_Subdomain
   362  				mapping.Domain = domainName
   363  				mappings = append(mappings, mapping)
   364  
   365  			case strings.HasPrefix(domain, "geosite:"):
   366  				listName := domain[8:]
   367  				if len(listName) == 0 {
   368  					return nil, newError("empty geosite rule: ", domain)
   369  				}
   370  				geositeList, err := geoLoader.LoadGeoSite(listName)
   371  				if err != nil {
   372  					return nil, newError("failed to load geosite: ", listName).Base(err)
   373  				}
   374  				for _, d := range geositeList {
   375  					mapping := getHostMapping(c.Hosts[domain])
   376  					mapping.Type = typeMap[d.Type]
   377  					mapping.Domain = d.Value
   378  					mappings = append(mappings, mapping)
   379  				}
   380  
   381  			case strings.HasPrefix(domain, "regexp:"):
   382  				regexpVal := domain[7:]
   383  				if len(regexpVal) == 0 {
   384  					return nil, newError("empty regexp type of rule: ", domain)
   385  				}
   386  				mapping := getHostMapping(c.Hosts[domain])
   387  				mapping.Type = dns.DomainMatchingType_Regex
   388  				mapping.Domain = regexpVal
   389  				mappings = append(mappings, mapping)
   390  
   391  			case strings.HasPrefix(domain, "keyword:"):
   392  				keywordVal := domain[8:]
   393  				if len(keywordVal) == 0 {
   394  					return nil, newError("empty keyword type of rule: ", domain)
   395  				}
   396  				mapping := getHostMapping(c.Hosts[domain])
   397  				mapping.Type = dns.DomainMatchingType_Keyword
   398  				mapping.Domain = keywordVal
   399  				mappings = append(mappings, mapping)
   400  
   401  			case strings.HasPrefix(domain, "full:"):
   402  				fullVal := domain[5:]
   403  				if len(fullVal) == 0 {
   404  					return nil, newError("empty full domain type of rule: ", domain)
   405  				}
   406  				mapping := getHostMapping(c.Hosts[domain])
   407  				mapping.Type = dns.DomainMatchingType_Full
   408  				mapping.Domain = fullVal
   409  				mappings = append(mappings, mapping)
   410  
   411  			case strings.HasPrefix(domain, "dotless:"):
   412  				mapping := getHostMapping(c.Hosts[domain])
   413  				mapping.Type = dns.DomainMatchingType_Regex
   414  				switch substr := domain[8:]; {
   415  				case substr == "":
   416  					mapping.Domain = "^[^.]*$"
   417  				case !strings.Contains(substr, "."):
   418  					mapping.Domain = "^[^.]*" + substr + "[^.]*$"
   419  				default:
   420  					return nil, newError("substr in dotless rule should not contain a dot: ", substr)
   421  				}
   422  				mappings = append(mappings, mapping)
   423  
   424  			case strings.HasPrefix(domain, "ext:"):
   425  				kv := strings.Split(domain[4:], ":")
   426  				if len(kv) != 2 {
   427  					return nil, newError("invalid external resource: ", domain)
   428  				}
   429  				filename := kv[0]
   430  				list := kv[1]
   431  				geositeList, err := geoLoader.LoadGeoSiteWithAttr(filename, list)
   432  				if err != nil {
   433  					return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
   434  				}
   435  				for _, d := range geositeList {
   436  					mapping := getHostMapping(c.Hosts[domain])
   437  					mapping.Type = typeMap[d.Type]
   438  					mapping.Domain = d.Value
   439  					mappings = append(mappings, mapping)
   440  				}
   441  
   442  			default:
   443  				mapping := getHostMapping(c.Hosts[domain])
   444  				mapping.Type = dns.DomainMatchingType_Full
   445  				mapping.Domain = domain
   446  				mappings = append(mappings, mapping)
   447  			}
   448  		}
   449  
   450  		config.StaticHosts = append(config.StaticHosts, mappings...)
   451  	}
   452  
   453  	if c.FakeDNS != nil {
   454  		fakeDNS, err := c.FakeDNS.Build()
   455  		if err != nil {
   456  			return nil, newError("failed to build fakedns").Base(err)
   457  		}
   458  		config.FakeDns = fakeDNS
   459  	}
   460  
   461  	return config, nil
   462  }