github.com/EagleQL/Xray-core@v1.4.3/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/xtls/xray-core/app/router" 12 "github.com/xtls/xray-core/common/net" 13 "github.com/xtls/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 }