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