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 }