github.com/xraypb/xray-core@v1.6.6/infra/conf/router.go (about)

     1  package conf
     2  
     3  import (
     4  	"encoding/json"
     5  	"runtime"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/golang/protobuf/proto"
    10  	"github.com/xraypb/xray-core/app/router"
    11  	"github.com/xraypb/xray-core/common/net"
    12  	"github.com/xraypb/xray-core/common/platform/filesystem"
    13  )
    14  
    15  type RouterRulesConfig struct {
    16  	RuleList       []json.RawMessage `json:"rules"`
    17  	DomainStrategy string            `json:"domainStrategy"`
    18  }
    19  
    20  // StrategyConfig represents a strategy config
    21  type StrategyConfig struct {
    22  	Type     string           `json:"type"`
    23  	Settings *json.RawMessage `json:"settings"`
    24  }
    25  
    26  type BalancingRule struct {
    27  	Tag       string         `json:"tag"`
    28  	Selectors StringList     `json:"selector"`
    29  	Strategy  StrategyConfig `json:"strategy"`
    30  }
    31  
    32  func (r *BalancingRule) Build() (*router.BalancingRule, error) {
    33  	if r.Tag == "" {
    34  		return nil, newError("empty balancer tag")
    35  	}
    36  	if len(r.Selectors) == 0 {
    37  		return nil, newError("empty selector list")
    38  	}
    39  
    40  	var strategy string
    41  	switch strings.ToLower(r.Strategy.Type) {
    42  	case strategyRandom, "":
    43  		strategy = strategyRandom
    44  	case strategyLeastPing:
    45  		strategy = "leastPing"
    46  	default:
    47  		return nil, newError("unknown balancing strategy: " + r.Strategy.Type)
    48  	}
    49  
    50  	return &router.BalancingRule{
    51  		Tag:              r.Tag,
    52  		OutboundSelector: []string(r.Selectors),
    53  		Strategy:         strategy,
    54  	}, nil
    55  }
    56  
    57  type RouterConfig struct {
    58  	Settings       *RouterRulesConfig `json:"settings"` // Deprecated
    59  	RuleList       []json.RawMessage  `json:"rules"`
    60  	DomainStrategy *string            `json:"domainStrategy"`
    61  	Balancers      []*BalancingRule   `json:"balancers"`
    62  
    63  	DomainMatcher string `json:"domainMatcher"`
    64  }
    65  
    66  func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
    67  	ds := ""
    68  	if c.DomainStrategy != nil {
    69  		ds = *c.DomainStrategy
    70  	} else if c.Settings != nil {
    71  		ds = c.Settings.DomainStrategy
    72  	}
    73  
    74  	switch strings.ToLower(ds) {
    75  	case "alwaysip":
    76  		return router.Config_UseIp
    77  	case "ipifnonmatch":
    78  		return router.Config_IpIfNonMatch
    79  	case "ipondemand":
    80  		return router.Config_IpOnDemand
    81  	default:
    82  		return router.Config_AsIs
    83  	}
    84  }
    85  
    86  func (c *RouterConfig) Build() (*router.Config, error) {
    87  	config := new(router.Config)
    88  	config.DomainStrategy = c.getDomainStrategy()
    89  
    90  	var rawRuleList []json.RawMessage
    91  	if c != nil {
    92  		rawRuleList = c.RuleList
    93  		if c.Settings != nil {
    94  			c.RuleList = append(c.RuleList, c.Settings.RuleList...)
    95  			rawRuleList = c.RuleList
    96  		}
    97  	}
    98  
    99  	for _, rawRule := range rawRuleList {
   100  		rule, err := ParseRule(rawRule)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  
   105  		if rule.DomainMatcher == "" {
   106  			rule.DomainMatcher = c.DomainMatcher
   107  		}
   108  
   109  		config.Rule = append(config.Rule, rule)
   110  	}
   111  	for _, rawBalancer := range c.Balancers {
   112  		balancer, err := rawBalancer.Build()
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  		config.BalancingRule = append(config.BalancingRule, balancer)
   117  	}
   118  	return config, nil
   119  }
   120  
   121  type RouterRule struct {
   122  	Type        string `json:"type"`
   123  	OutboundTag string `json:"outboundTag"`
   124  	BalancerTag string `json:"balancerTag"`
   125  
   126  	DomainMatcher string `json:"domainMatcher"`
   127  }
   128  
   129  func ParseIP(s string) (*router.CIDR, error) {
   130  	var addr, mask string
   131  	i := strings.Index(s, "/")
   132  	if i < 0 {
   133  		addr = s
   134  	} else {
   135  		addr = s[:i]
   136  		mask = s[i+1:]
   137  	}
   138  	ip := net.ParseAddress(addr)
   139  	switch ip.Family() {
   140  	case net.AddressFamilyIPv4:
   141  		bits := uint32(32)
   142  		if len(mask) > 0 {
   143  			bits64, err := strconv.ParseUint(mask, 10, 32)
   144  			if err != nil {
   145  				return nil, newError("invalid network mask for router: ", mask).Base(err)
   146  			}
   147  			bits = uint32(bits64)
   148  		}
   149  		if bits > 32 {
   150  			return nil, newError("invalid network mask for router: ", bits)
   151  		}
   152  		return &router.CIDR{
   153  			Ip:     []byte(ip.IP()),
   154  			Prefix: bits,
   155  		}, nil
   156  	case net.AddressFamilyIPv6:
   157  		bits := uint32(128)
   158  		if len(mask) > 0 {
   159  			bits64, err := strconv.ParseUint(mask, 10, 32)
   160  			if err != nil {
   161  				return nil, newError("invalid network mask for router: ", mask).Base(err)
   162  			}
   163  			bits = uint32(bits64)
   164  		}
   165  		if bits > 128 {
   166  			return nil, newError("invalid network mask for router: ", bits)
   167  		}
   168  		return &router.CIDR{
   169  			Ip:     []byte(ip.IP()),
   170  			Prefix: bits,
   171  		}, nil
   172  	default:
   173  		return nil, newError("unsupported address for router: ", s)
   174  	}
   175  }
   176  
   177  func loadGeoIP(code string) ([]*router.CIDR, error) {
   178  	return loadIP("geoip.dat", code)
   179  }
   180  
   181  var (
   182  	FileCache = make(map[string][]byte)
   183  	IPCache   = make(map[string]*router.GeoIP)
   184  	SiteCache = make(map[string]*router.GeoSite)
   185  )
   186  
   187  func loadFile(file string) ([]byte, error) {
   188  	if FileCache[file] == nil {
   189  		bs, err := filesystem.ReadAsset(file)
   190  		if err != nil {
   191  			return nil, newError("failed to open file: ", file).Base(err)
   192  		}
   193  		if len(bs) == 0 {
   194  			return nil, newError("empty file: ", file)
   195  		}
   196  		// Do not cache file, may save RAM when there
   197  		// are many files, but consume CPU each time.
   198  		return bs, nil
   199  		FileCache[file] = bs
   200  	}
   201  	return FileCache[file], nil
   202  }
   203  
   204  func loadIP(file, code string) ([]*router.CIDR, error) {
   205  	index := file + ":" + code
   206  	if IPCache[index] == nil {
   207  		bs, err := loadFile(file)
   208  		if err != nil {
   209  			return nil, newError("failed to load file: ", file).Base(err)
   210  		}
   211  		bs = find(bs, []byte(code))
   212  		if bs == nil {
   213  			return nil, newError("code not found in ", file, ": ", code)
   214  		}
   215  		var geoip router.GeoIP
   216  		if err := proto.Unmarshal(bs, &geoip); err != nil {
   217  			return nil, newError("error unmarshal IP in ", file, ": ", code).Base(err)
   218  		}
   219  		defer runtime.GC()     // or debug.FreeOSMemory()
   220  		return geoip.Cidr, nil // do not cache geoip
   221  		IPCache[index] = &geoip
   222  	}
   223  	return IPCache[index].Cidr, nil
   224  }
   225  
   226  func loadSite(file, code string) ([]*router.Domain, error) {
   227  	index := file + ":" + code
   228  	if SiteCache[index] == nil {
   229  		bs, err := loadFile(file)
   230  		if err != nil {
   231  			return nil, newError("failed to load file: ", file).Base(err)
   232  		}
   233  		bs = find(bs, []byte(code))
   234  		if bs == nil {
   235  			return nil, newError("list not found in ", file, ": ", code)
   236  		}
   237  		var geosite router.GeoSite
   238  		if err := proto.Unmarshal(bs, &geosite); err != nil {
   239  			return nil, newError("error unmarshal Site in ", file, ": ", code).Base(err)
   240  		}
   241  		defer runtime.GC()         // or debug.FreeOSMemory()
   242  		return geosite.Domain, nil // do not cache geosite
   243  		SiteCache[index] = &geosite
   244  	}
   245  	return SiteCache[index].Domain, nil
   246  }
   247  
   248  func find(data, code []byte) []byte {
   249  	codeL := len(code)
   250  	if codeL == 0 {
   251  		return nil
   252  	}
   253  	for {
   254  		dataL := len(data)
   255  		if dataL < 2 {
   256  			return nil
   257  		}
   258  		x, y := proto.DecodeVarint(data[1:])
   259  		if x == 0 && y == 0 {
   260  			return nil
   261  		}
   262  		headL, bodyL := 1+y, int(x)
   263  		dataL -= headL
   264  		if dataL < bodyL {
   265  			return nil
   266  		}
   267  		data = data[headL:]
   268  		if int(data[1]) == codeL {
   269  			for i := 0; i < codeL && data[2+i] == code[i]; i++ {
   270  				if i+1 == codeL {
   271  					return data[:bodyL]
   272  				}
   273  			}
   274  		}
   275  		if dataL == bodyL {
   276  			return nil
   277  		}
   278  		data = data[bodyL:]
   279  	}
   280  }
   281  
   282  type AttributeMatcher interface {
   283  	Match(*router.Domain) bool
   284  }
   285  
   286  type BooleanMatcher string
   287  
   288  func (m BooleanMatcher) Match(domain *router.Domain) bool {
   289  	for _, attr := range domain.Attribute {
   290  		if attr.Key == string(m) {
   291  			return true
   292  		}
   293  	}
   294  	return false
   295  }
   296  
   297  type AttributeList struct {
   298  	matcher []AttributeMatcher
   299  }
   300  
   301  func (al *AttributeList) Match(domain *router.Domain) bool {
   302  	for _, matcher := range al.matcher {
   303  		if !matcher.Match(domain) {
   304  			return false
   305  		}
   306  	}
   307  	return true
   308  }
   309  
   310  func (al *AttributeList) IsEmpty() bool {
   311  	return len(al.matcher) == 0
   312  }
   313  
   314  func parseAttrs(attrs []string) *AttributeList {
   315  	al := new(AttributeList)
   316  	for _, attr := range attrs {
   317  		lc := strings.ToLower(attr)
   318  		al.matcher = append(al.matcher, BooleanMatcher(lc))
   319  	}
   320  	return al
   321  }
   322  
   323  func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
   324  	parts := strings.Split(siteWithAttr, "@")
   325  	if len(parts) == 0 {
   326  		return nil, newError("empty site")
   327  	}
   328  	country := strings.ToUpper(parts[0])
   329  	attrs := parseAttrs(parts[1:])
   330  	domains, err := loadSite(file, country)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	if attrs.IsEmpty() {
   336  		return domains, nil
   337  	}
   338  
   339  	filteredDomains := make([]*router.Domain, 0, len(domains))
   340  	for _, domain := range domains {
   341  		if attrs.Match(domain) {
   342  			filteredDomains = append(filteredDomains, domain)
   343  		}
   344  	}
   345  
   346  	return filteredDomains, nil
   347  }
   348  
   349  func parseDomainRule(domain string) ([]*router.Domain, error) {
   350  	if strings.HasPrefix(domain, "geosite:") {
   351  		country := strings.ToUpper(domain[8:])
   352  		domains, err := loadGeositeWithAttr("geosite.dat", country)
   353  		if err != nil {
   354  			return nil, newError("failed to load geosite: ", country).Base(err)
   355  		}
   356  		return domains, nil
   357  	}
   358  	isExtDatFile := 0
   359  	{
   360  		const prefix = "ext:"
   361  		if strings.HasPrefix(domain, prefix) {
   362  			isExtDatFile = len(prefix)
   363  		}
   364  		const prefixQualified = "ext-domain:"
   365  		if strings.HasPrefix(domain, prefixQualified) {
   366  			isExtDatFile = len(prefixQualified)
   367  		}
   368  	}
   369  	if isExtDatFile != 0 {
   370  		kv := strings.Split(domain[isExtDatFile:], ":")
   371  		if len(kv) != 2 {
   372  			return nil, newError("invalid external resource: ", domain)
   373  		}
   374  		filename := kv[0]
   375  		country := kv[1]
   376  		domains, err := loadGeositeWithAttr(filename, country)
   377  		if err != nil {
   378  			return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err)
   379  		}
   380  		return domains, nil
   381  	}
   382  
   383  	domainRule := new(router.Domain)
   384  	switch {
   385  	case strings.HasPrefix(domain, "regexp:"):
   386  		domainRule.Type = router.Domain_Regex
   387  		domainRule.Value = domain[7:]
   388  
   389  	case strings.HasPrefix(domain, "domain:"):
   390  		domainRule.Type = router.Domain_Domain
   391  		domainRule.Value = domain[7:]
   392  
   393  	case strings.HasPrefix(domain, "full:"):
   394  		domainRule.Type = router.Domain_Full
   395  		domainRule.Value = domain[5:]
   396  
   397  	case strings.HasPrefix(domain, "keyword:"):
   398  		domainRule.Type = router.Domain_Plain
   399  		domainRule.Value = domain[8:]
   400  
   401  	case strings.HasPrefix(domain, "dotless:"):
   402  		domainRule.Type = router.Domain_Regex
   403  		switch substr := domain[8:]; {
   404  		case substr == "":
   405  			domainRule.Value = "^[^.]*$"
   406  		case !strings.Contains(substr, "."):
   407  			domainRule.Value = "^[^.]*" + substr + "[^.]*$"
   408  		default:
   409  			return nil, newError("substr in dotless rule should not contain a dot: ", substr)
   410  		}
   411  
   412  	default:
   413  		domainRule.Type = router.Domain_Plain
   414  		domainRule.Value = domain
   415  	}
   416  	return []*router.Domain{domainRule}, nil
   417  }
   418  
   419  func ToCidrList(ips StringList) ([]*router.GeoIP, error) {
   420  	var geoipList []*router.GeoIP
   421  	var customCidrs []*router.CIDR
   422  
   423  	for _, ip := range ips {
   424  		if strings.HasPrefix(ip, "geoip:") {
   425  			country := ip[6:]
   426  			isReverseMatch := false
   427  			if strings.HasPrefix(ip, "geoip:!") {
   428  				country = ip[7:]
   429  				isReverseMatch = true
   430  			}
   431  			if len(country) == 0 {
   432  				return nil, newError("empty country name in rule")
   433  			}
   434  			geoip, err := loadGeoIP(strings.ToUpper(country))
   435  			if err != nil {
   436  				return nil, newError("failed to load GeoIP: ", country).Base(err)
   437  			}
   438  
   439  			geoipList = append(geoipList, &router.GeoIP{
   440  				CountryCode:  strings.ToUpper(country),
   441  				Cidr:         geoip,
   442  				ReverseMatch: isReverseMatch,
   443  			})
   444  			continue
   445  		}
   446  		isExtDatFile := 0
   447  		{
   448  			const prefix = "ext:"
   449  			if strings.HasPrefix(ip, prefix) {
   450  				isExtDatFile = len(prefix)
   451  			}
   452  			const prefixQualified = "ext-ip:"
   453  			if strings.HasPrefix(ip, prefixQualified) {
   454  				isExtDatFile = len(prefixQualified)
   455  			}
   456  		}
   457  		if isExtDatFile != 0 {
   458  			kv := strings.Split(ip[isExtDatFile:], ":")
   459  			if len(kv) != 2 {
   460  				return nil, newError("invalid external resource: ", ip)
   461  			}
   462  
   463  			filename := kv[0]
   464  			country := kv[1]
   465  			if len(filename) == 0 || len(country) == 0 {
   466  				return nil, newError("empty filename or empty country in rule")
   467  			}
   468  
   469  			isReverseMatch := false
   470  			if strings.HasPrefix(country, "!") {
   471  				country = country[1:]
   472  				isReverseMatch = true
   473  			}
   474  			geoip, err := loadIP(filename, strings.ToUpper(country))
   475  			if err != nil {
   476  				return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err)
   477  			}
   478  
   479  			geoipList = append(geoipList, &router.GeoIP{
   480  				CountryCode:  strings.ToUpper(filename + "_" + country),
   481  				Cidr:         geoip,
   482  				ReverseMatch: isReverseMatch,
   483  			})
   484  
   485  			continue
   486  		}
   487  
   488  		ipRule, err := ParseIP(ip)
   489  		if err != nil {
   490  			return nil, newError("invalid IP: ", ip).Base(err)
   491  		}
   492  		customCidrs = append(customCidrs, ipRule)
   493  	}
   494  
   495  	if len(customCidrs) > 0 {
   496  		geoipList = append(geoipList, &router.GeoIP{
   497  			Cidr: customCidrs,
   498  		})
   499  	}
   500  
   501  	return geoipList, nil
   502  }
   503  
   504  func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
   505  	type RawFieldRule struct {
   506  		RouterRule
   507  		Domain     *StringList  `json:"domain"`
   508  		Domains    *StringList  `json:"domains"`
   509  		IP         *StringList  `json:"ip"`
   510  		Port       *PortList    `json:"port"`
   511  		Network    *NetworkList `json:"network"`
   512  		SourceIP   *StringList  `json:"source"`
   513  		SourcePort *PortList    `json:"sourcePort"`
   514  		User       *StringList  `json:"user"`
   515  		InboundTag *StringList  `json:"inboundTag"`
   516  		Protocols  *StringList  `json:"protocol"`
   517  		Attributes string       `json:"attrs"`
   518  	}
   519  	rawFieldRule := new(RawFieldRule)
   520  	err := json.Unmarshal(msg, rawFieldRule)
   521  	if err != nil {
   522  		return nil, err
   523  	}
   524  
   525  	rule := new(router.RoutingRule)
   526  	switch {
   527  	case len(rawFieldRule.OutboundTag) > 0:
   528  		rule.TargetTag = &router.RoutingRule_Tag{
   529  			Tag: rawFieldRule.OutboundTag,
   530  		}
   531  	case len(rawFieldRule.BalancerTag) > 0:
   532  		rule.TargetTag = &router.RoutingRule_BalancingTag{
   533  			BalancingTag: rawFieldRule.BalancerTag,
   534  		}
   535  	default:
   536  		return nil, newError("neither outboundTag nor balancerTag is specified in routing rule")
   537  	}
   538  
   539  	if rawFieldRule.DomainMatcher != "" {
   540  		rule.DomainMatcher = rawFieldRule.DomainMatcher
   541  	}
   542  
   543  	if rawFieldRule.Domain != nil {
   544  		for _, domain := range *rawFieldRule.Domain {
   545  			rules, err := parseDomainRule(domain)
   546  			if err != nil {
   547  				return nil, newError("failed to parse domain rule: ", domain).Base(err)
   548  			}
   549  			rule.Domain = append(rule.Domain, rules...)
   550  		}
   551  	}
   552  
   553  	if rawFieldRule.Domains != nil {
   554  		for _, domain := range *rawFieldRule.Domains {
   555  			rules, err := parseDomainRule(domain)
   556  			if err != nil {
   557  				return nil, newError("failed to parse domain rule: ", domain).Base(err)
   558  			}
   559  			rule.Domain = append(rule.Domain, rules...)
   560  		}
   561  	}
   562  
   563  	if rawFieldRule.IP != nil {
   564  		geoipList, err := ToCidrList(*rawFieldRule.IP)
   565  		if err != nil {
   566  			return nil, err
   567  		}
   568  		rule.Geoip = geoipList
   569  	}
   570  
   571  	if rawFieldRule.Port != nil {
   572  		rule.PortList = rawFieldRule.Port.Build()
   573  	}
   574  
   575  	if rawFieldRule.Network != nil {
   576  		rule.Networks = rawFieldRule.Network.Build()
   577  	}
   578  
   579  	if rawFieldRule.SourceIP != nil {
   580  		geoipList, err := ToCidrList(*rawFieldRule.SourceIP)
   581  		if err != nil {
   582  			return nil, err
   583  		}
   584  		rule.SourceGeoip = geoipList
   585  	}
   586  
   587  	if rawFieldRule.SourcePort != nil {
   588  		rule.SourcePortList = rawFieldRule.SourcePort.Build()
   589  	}
   590  
   591  	if rawFieldRule.User != nil {
   592  		for _, s := range *rawFieldRule.User {
   593  			rule.UserEmail = append(rule.UserEmail, s)
   594  		}
   595  	}
   596  
   597  	if rawFieldRule.InboundTag != nil {
   598  		for _, s := range *rawFieldRule.InboundTag {
   599  			rule.InboundTag = append(rule.InboundTag, s)
   600  		}
   601  	}
   602  
   603  	if rawFieldRule.Protocols != nil {
   604  		for _, s := range *rawFieldRule.Protocols {
   605  			rule.Protocol = append(rule.Protocol, s)
   606  		}
   607  	}
   608  
   609  	if len(rawFieldRule.Attributes) > 0 {
   610  		rule.Attributes = rawFieldRule.Attributes
   611  	}
   612  
   613  	return rule, nil
   614  }
   615  
   616  func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) {
   617  	rawRule := new(RouterRule)
   618  	err := json.Unmarshal(msg, rawRule)
   619  	if err != nil {
   620  		return nil, newError("invalid router rule").Base(err)
   621  	}
   622  	if strings.EqualFold(rawRule.Type, "field") {
   623  		fieldrule, err := parseFieldRule(msg)
   624  		if err != nil {
   625  			return nil, newError("invalid field rule").Base(err)
   626  		}
   627  		return fieldrule, nil
   628  	}
   629  	if strings.EqualFold(rawRule.Type, "chinaip") {
   630  		chinaiprule, err := parseChinaIPRule(msg)
   631  		if err != nil {
   632  			return nil, newError("invalid chinaip rule").Base(err)
   633  		}
   634  		return chinaiprule, nil
   635  	}
   636  	if strings.EqualFold(rawRule.Type, "chinasites") {
   637  		chinasitesrule, err := parseChinaSitesRule(msg)
   638  		if err != nil {
   639  			return nil, newError("invalid chinasites rule").Base(err)
   640  		}
   641  		return chinasitesrule, nil
   642  	}
   643  	return nil, newError("unknown router rule type: ", rawRule.Type)
   644  }
   645  
   646  func parseChinaIPRule(data []byte) (*router.RoutingRule, error) {
   647  	rawRule := new(RouterRule)
   648  	err := json.Unmarshal(data, rawRule)
   649  	if err != nil {
   650  		return nil, newError("invalid router rule").Base(err)
   651  	}
   652  	chinaIPs, err := loadGeoIP("CN")
   653  	if err != nil {
   654  		return nil, newError("failed to load geoip:cn").Base(err)
   655  	}
   656  	return &router.RoutingRule{
   657  		TargetTag: &router.RoutingRule_Tag{
   658  			Tag: rawRule.OutboundTag,
   659  		},
   660  		Cidr: chinaIPs,
   661  	}, nil
   662  }
   663  
   664  func parseChinaSitesRule(data []byte) (*router.RoutingRule, error) {
   665  	rawRule := new(RouterRule)
   666  	err := json.Unmarshal(data, rawRule)
   667  	if err != nil {
   668  		return nil, newError("invalid router rule").Base(err).AtError()
   669  	}
   670  	domains, err := loadGeositeWithAttr("geosite.dat", "CN")
   671  	if err != nil {
   672  		return nil, newError("failed to load geosite:cn.").Base(err)
   673  	}
   674  	return &router.RoutingRule{
   675  		TargetTag: &router.RoutingRule_Tag{
   676  			Tag: rawRule.OutboundTag,
   677  		},
   678  		Domain: domains,
   679  	}, nil
   680  }