| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631 |
- package conf
- import (
- "encoding/json"
- "runtime"
- "strconv"
- "strings"
- "github.com/golang/protobuf/proto"
- "github.com/xtls/xray-core/app/router"
- "github.com/xtls/xray-core/common/net"
- "github.com/xtls/xray-core/common/platform/filesystem"
- )
- type RouterRulesConfig struct {
- RuleList []json.RawMessage `json:"rules"`
- DomainStrategy string `json:"domainStrategy"`
- }
- type BalancingRule struct {
- Tag string `json:"tag"`
- Selectors StringList `json:"selector"`
- }
- func (r *BalancingRule) Build() (*router.BalancingRule, error) {
- if r.Tag == "" {
- return nil, newError("empty balancer tag")
- }
- if len(r.Selectors) == 0 {
- return nil, newError("empty selector list")
- }
- return &router.BalancingRule{
- Tag: r.Tag,
- OutboundSelector: []string(r.Selectors),
- }, nil
- }
- type RouterConfig struct {
- Settings *RouterRulesConfig `json:"settings"` // Deprecated
- RuleList []json.RawMessage `json:"rules"`
- DomainStrategy *string `json:"domainStrategy"`
- Balancers []*BalancingRule `json:"balancers"`
- }
- func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
- ds := ""
- if c.DomainStrategy != nil {
- ds = *c.DomainStrategy
- } else if c.Settings != nil {
- ds = c.Settings.DomainStrategy
- }
- switch strings.ToLower(ds) {
- case "alwaysip":
- return router.Config_UseIp
- case "ipifnonmatch":
- return router.Config_IpIfNonMatch
- case "ipondemand":
- return router.Config_IpOnDemand
- default:
- return router.Config_AsIs
- }
- }
- func (c *RouterConfig) Build() (*router.Config, error) {
- config := new(router.Config)
- config.DomainStrategy = c.getDomainStrategy()
- var rawRuleList []json.RawMessage
- if c != nil {
- rawRuleList = c.RuleList
- if c.Settings != nil {
- c.RuleList = append(c.RuleList, c.Settings.RuleList...)
- rawRuleList = c.RuleList
- }
- }
- for _, rawRule := range rawRuleList {
- rule, err := ParseRule(rawRule)
- if err != nil {
- return nil, err
- }
- config.Rule = append(config.Rule, rule)
- }
- for _, rawBalancer := range c.Balancers {
- balancer, err := rawBalancer.Build()
- if err != nil {
- return nil, err
- }
- config.BalancingRule = append(config.BalancingRule, balancer)
- }
- return config, nil
- }
- type RouterRule struct {
- Type string `json:"type"`
- OutboundTag string `json:"outboundTag"`
- BalancerTag string `json:"balancerTag"`
- }
- func ParseIP(s string) (*router.CIDR, error) {
- var addr, mask string
- i := strings.Index(s, "/")
- if i < 0 {
- addr = s
- } else {
- addr = s[:i]
- mask = s[i+1:]
- }
- ip := net.ParseAddress(addr)
- switch ip.Family() {
- case net.AddressFamilyIPv4:
- bits := uint32(32)
- if len(mask) > 0 {
- bits64, err := strconv.ParseUint(mask, 10, 32)
- if err != nil {
- return nil, newError("invalid network mask for router: ", mask).Base(err)
- }
- bits = uint32(bits64)
- }
- if bits > 32 {
- return nil, newError("invalid network mask for router: ", bits)
- }
- return &router.CIDR{
- Ip: []byte(ip.IP()),
- Prefix: bits,
- }, nil
- case net.AddressFamilyIPv6:
- bits := uint32(128)
- if len(mask) > 0 {
- bits64, err := strconv.ParseUint(mask, 10, 32)
- if err != nil {
- return nil, newError("invalid network mask for router: ", mask).Base(err)
- }
- bits = uint32(bits64)
- }
- if bits > 128 {
- return nil, newError("invalid network mask for router: ", bits)
- }
- return &router.CIDR{
- Ip: []byte(ip.IP()),
- Prefix: bits,
- }, nil
- default:
- return nil, newError("unsupported address for router: ", s)
- }
- }
- func loadGeoIP(code string) ([]*router.CIDR, error) {
- return loadIP("geoip.dat", code)
- }
- var (
- FileCache = make(map[string][]byte)
- IPCache = make(map[string]*router.GeoIP)
- SiteCache = make(map[string]*router.GeoSite)
- )
- func loadFile(file string) ([]byte, error) {
- if FileCache[file] == nil {
- bs, err := filesystem.ReadAsset(file)
- if err != nil {
- return nil, newError("failed to open file: ", file).Base(err)
- }
- if len(bs) == 0 {
- return nil, newError("empty file: ", file)
- }
- // Do not cache file, may save RAM when there
- // are many files, but consume CPU each time.
- return bs, nil
- FileCache[file] = bs
- }
- return FileCache[file], nil
- }
- func loadIP(file, code string) ([]*router.CIDR, error) {
- index := file + ":" + code
- if IPCache[index] == nil {
- bs, err := loadFile(file)
- if err != nil {
- return nil, newError("failed to load file: ", file).Base(err)
- }
- bs = find(bs, []byte(code))
- if bs == nil {
- return nil, newError("code not found in ", file, ": ", code)
- }
- var geoip router.GeoIP
- if err := proto.Unmarshal(bs, &geoip); err != nil {
- return nil, newError("error unmarshal IP in ", file, ": ", code).Base(err)
- }
- defer runtime.GC() // or debug.FreeOSMemory()
- return geoip.Cidr, nil // do not cache geoip
- IPCache[index] = &geoip
- }
- return IPCache[index].Cidr, nil
- }
- func loadSite(file, code string) ([]*router.Domain, error) {
- index := file + ":" + code
- if SiteCache[index] == nil {
- bs, err := loadFile(file)
- if err != nil {
- return nil, newError("failed to load file: ", file).Base(err)
- }
- bs = find(bs, []byte(code))
- if bs == nil {
- return nil, newError("list not found in ", file, ": ", code)
- }
- var geosite router.GeoSite
- if err := proto.Unmarshal(bs, &geosite); err != nil {
- return nil, newError("error unmarshal Site in ", file, ": ", code).Base(err)
- }
- defer runtime.GC() // or debug.FreeOSMemory()
- return geosite.Domain, nil // do not cache geosite
- SiteCache[index] = &geosite
- }
- return SiteCache[index].Domain, nil
- }
- func find(data, code []byte) []byte {
- codeL := len(code)
- if codeL == 0 {
- return nil
- }
- for {
- dataL := len(data)
- if dataL < 2 {
- return nil
- }
- x, y := proto.DecodeVarint(data[1:])
- if x == 0 && y == 0 {
- return nil
- }
- headL, bodyL := 1+y, int(x)
- dataL -= headL
- if dataL < bodyL {
- return nil
- }
- data = data[headL:]
- if int(data[1]) == codeL {
- for i := 0; i < codeL && data[2+i] == code[i]; i++ {
- if i+1 == codeL {
- return data[:bodyL]
- }
- }
- }
- if dataL == bodyL {
- return nil
- }
- data = data[bodyL:]
- }
- }
- type AttributeMatcher interface {
- Match(*router.Domain) bool
- }
- type BooleanMatcher string
- func (m BooleanMatcher) Match(domain *router.Domain) bool {
- for _, attr := range domain.Attribute {
- if attr.Key == string(m) {
- return true
- }
- }
- return false
- }
- type AttributeList struct {
- matcher []AttributeMatcher
- }
- func (al *AttributeList) Match(domain *router.Domain) bool {
- for _, matcher := range al.matcher {
- if !matcher.Match(domain) {
- return false
- }
- }
- return true
- }
- func (al *AttributeList) IsEmpty() bool {
- return len(al.matcher) == 0
- }
- func parseAttrs(attrs []string) *AttributeList {
- al := new(AttributeList)
- for _, attr := range attrs {
- lc := strings.ToLower(attr)
- al.matcher = append(al.matcher, BooleanMatcher(lc))
- }
- return al
- }
- func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
- parts := strings.Split(siteWithAttr, "@")
- if len(parts) == 0 {
- return nil, newError("empty site")
- }
- country := strings.ToUpper(parts[0])
- attrs := parseAttrs(parts[1:])
- domains, err := loadSite(file, country)
- if err != nil {
- return nil, err
- }
- if attrs.IsEmpty() {
- return domains, nil
- }
- filteredDomains := make([]*router.Domain, 0, len(domains))
- for _, domain := range domains {
- if attrs.Match(domain) {
- filteredDomains = append(filteredDomains, domain)
- }
- }
- return filteredDomains, nil
- }
- func parseDomainRule(domain string) ([]*router.Domain, error) {
- if strings.HasPrefix(domain, "geosite:") {
- country := strings.ToUpper(domain[8:])
- domains, err := loadGeositeWithAttr("geosite.dat", country)
- if err != nil {
- return nil, newError("failed to load geosite: ", country).Base(err)
- }
- return domains, nil
- }
- var isExtDatFile = 0
- {
- const prefix = "ext:"
- if strings.HasPrefix(domain, prefix) {
- isExtDatFile = len(prefix)
- }
- const prefixQualified = "ext-domain:"
- if strings.HasPrefix(domain, prefixQualified) {
- isExtDatFile = len(prefixQualified)
- }
- }
- if isExtDatFile != 0 {
- kv := strings.Split(domain[isExtDatFile:], ":")
- if len(kv) != 2 {
- return nil, newError("invalid external resource: ", domain)
- }
- filename := kv[0]
- country := kv[1]
- domains, err := loadGeositeWithAttr(filename, country)
- if err != nil {
- return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err)
- }
- return domains, nil
- }
- domainRule := new(router.Domain)
- switch {
- case strings.HasPrefix(domain, "regexp:"):
- domainRule.Type = router.Domain_Regex
- domainRule.Value = domain[7:]
- case strings.HasPrefix(domain, "domain:"):
- domainRule.Type = router.Domain_Domain
- domainRule.Value = domain[7:]
- case strings.HasPrefix(domain, "full:"):
- domainRule.Type = router.Domain_Full
- domainRule.Value = domain[5:]
- case strings.HasPrefix(domain, "keyword:"):
- domainRule.Type = router.Domain_Plain
- domainRule.Value = domain[8:]
- case strings.HasPrefix(domain, "dotless:"):
- domainRule.Type = router.Domain_Regex
- switch substr := domain[8:]; {
- case substr == "":
- domainRule.Value = "^[^.]*$"
- case !strings.Contains(substr, "."):
- domainRule.Value = "^[^.]*" + substr + "[^.]*$"
- default:
- return nil, newError("substr in dotless rule should not contain a dot: ", substr)
- }
- default:
- domainRule.Type = router.Domain_Plain
- domainRule.Value = domain
- }
- return []*router.Domain{domainRule}, nil
- }
- func toCidrList(ips StringList) ([]*router.GeoIP, error) {
- var geoipList []*router.GeoIP
- var customCidrs []*router.CIDR
- for _, ip := range ips {
- if strings.HasPrefix(ip, "geoip:") {
- country := ip[6:]
- geoip, err := loadGeoIP(strings.ToUpper(country))
- if err != nil {
- return nil, newError("failed to load GeoIP: ", country).Base(err)
- }
- geoipList = append(geoipList, &router.GeoIP{
- CountryCode: strings.ToUpper(country),
- Cidr: geoip,
- })
- continue
- }
- var isExtDatFile = 0
- {
- const prefix = "ext:"
- if strings.HasPrefix(ip, prefix) {
- isExtDatFile = len(prefix)
- }
- const prefixQualified = "ext-ip:"
- if strings.HasPrefix(ip, prefixQualified) {
- isExtDatFile = len(prefixQualified)
- }
- }
- if isExtDatFile != 0 {
- kv := strings.Split(ip[isExtDatFile:], ":")
- if len(kv) != 2 {
- return nil, newError("invalid external resource: ", ip)
- }
- filename := kv[0]
- country := kv[1]
- geoip, err := loadIP(filename, strings.ToUpper(country))
- if err != nil {
- return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err)
- }
- geoipList = append(geoipList, &router.GeoIP{
- CountryCode: strings.ToUpper(filename + "_" + country),
- Cidr: geoip,
- })
- continue
- }
- ipRule, err := ParseIP(ip)
- if err != nil {
- return nil, newError("invalid IP: ", ip).Base(err)
- }
- customCidrs = append(customCidrs, ipRule)
- }
- if len(customCidrs) > 0 {
- geoipList = append(geoipList, &router.GeoIP{
- Cidr: customCidrs,
- })
- }
- return geoipList, nil
- }
- func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
- type RawFieldRule struct {
- RouterRule
- Domain *StringList `json:"domain"`
- Domains *StringList `json:"domains"`
- IP *StringList `json:"ip"`
- Port *PortList `json:"port"`
- Network *NetworkList `json:"network"`
- SourceIP *StringList `json:"source"`
- SourcePort *PortList `json:"sourcePort"`
- User *StringList `json:"user"`
- InboundTag *StringList `json:"inboundTag"`
- Protocols *StringList `json:"protocol"`
- Attributes string `json:"attrs"`
- }
- rawFieldRule := new(RawFieldRule)
- err := json.Unmarshal(msg, rawFieldRule)
- if err != nil {
- return nil, err
- }
- rule := new(router.RoutingRule)
- switch {
- case len(rawFieldRule.OutboundTag) > 0:
- rule.TargetTag = &router.RoutingRule_Tag{
- Tag: rawFieldRule.OutboundTag,
- }
- case len(rawFieldRule.BalancerTag) > 0:
- rule.TargetTag = &router.RoutingRule_BalancingTag{
- BalancingTag: rawFieldRule.BalancerTag,
- }
- default:
- return nil, newError("neither outboundTag nor balancerTag is specified in routing rule")
- }
- if rawFieldRule.Domain != nil {
- for _, domain := range *rawFieldRule.Domain {
- rules, err := parseDomainRule(domain)
- if err != nil {
- return nil, newError("failed to parse domain rule: ", domain).Base(err)
- }
- rule.Domain = append(rule.Domain, rules...)
- }
- }
- if rawFieldRule.Domains != nil {
- for _, domain := range *rawFieldRule.Domains {
- rules, err := parseDomainRule(domain)
- if err != nil {
- return nil, newError("failed to parse domain rule: ", domain).Base(err)
- }
- rule.Domain = append(rule.Domain, rules...)
- }
- }
- if rawFieldRule.IP != nil {
- geoipList, err := toCidrList(*rawFieldRule.IP)
- if err != nil {
- return nil, err
- }
- rule.Geoip = geoipList
- }
- if rawFieldRule.Port != nil {
- rule.PortList = rawFieldRule.Port.Build()
- }
- if rawFieldRule.Network != nil {
- rule.Networks = rawFieldRule.Network.Build()
- }
- if rawFieldRule.SourceIP != nil {
- geoipList, err := toCidrList(*rawFieldRule.SourceIP)
- if err != nil {
- return nil, err
- }
- rule.SourceGeoip = geoipList
- }
- if rawFieldRule.SourcePort != nil {
- rule.SourcePortList = rawFieldRule.SourcePort.Build()
- }
- if rawFieldRule.User != nil {
- for _, s := range *rawFieldRule.User {
- rule.UserEmail = append(rule.UserEmail, s)
- }
- }
- if rawFieldRule.InboundTag != nil {
- for _, s := range *rawFieldRule.InboundTag {
- rule.InboundTag = append(rule.InboundTag, s)
- }
- }
- if rawFieldRule.Protocols != nil {
- for _, s := range *rawFieldRule.Protocols {
- rule.Protocol = append(rule.Protocol, s)
- }
- }
- if len(rawFieldRule.Attributes) > 0 {
- rule.Attributes = rawFieldRule.Attributes
- }
- return rule, nil
- }
- func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) {
- rawRule := new(RouterRule)
- err := json.Unmarshal(msg, rawRule)
- if err != nil {
- return nil, newError("invalid router rule").Base(err)
- }
- if rawRule.Type == "field" {
- fieldrule, err := parseFieldRule(msg)
- if err != nil {
- return nil, newError("invalid field rule").Base(err)
- }
- return fieldrule, nil
- }
- if rawRule.Type == "chinaip" {
- chinaiprule, err := parseChinaIPRule(msg)
- if err != nil {
- return nil, newError("invalid chinaip rule").Base(err)
- }
- return chinaiprule, nil
- }
- if rawRule.Type == "chinasites" {
- chinasitesrule, err := parseChinaSitesRule(msg)
- if err != nil {
- return nil, newError("invalid chinasites rule").Base(err)
- }
- return chinasitesrule, nil
- }
- return nil, newError("unknown router rule type: ", rawRule.Type)
- }
- func parseChinaIPRule(data []byte) (*router.RoutingRule, error) {
- rawRule := new(RouterRule)
- err := json.Unmarshal(data, rawRule)
- if err != nil {
- return nil, newError("invalid router rule").Base(err)
- }
- chinaIPs, err := loadGeoIP("CN")
- if err != nil {
- return nil, newError("failed to load geoip:cn").Base(err)
- }
- return &router.RoutingRule{
- TargetTag: &router.RoutingRule_Tag{
- Tag: rawRule.OutboundTag,
- },
- Cidr: chinaIPs,
- }, nil
- }
- func parseChinaSitesRule(data []byte) (*router.RoutingRule, error) {
- rawRule := new(RouterRule)
- err := json.Unmarshal(data, rawRule)
- if err != nil {
- return nil, newError("invalid router rule").Base(err).AtError()
- }
- domains, err := loadGeositeWithAttr("geosite.dat", "CN")
- if err != nil {
- return nil, newError("failed to load geosite:cn.").Base(err)
- }
- return &router.RoutingRule{
- TargetTag: &router.RoutingRule_Tag{
- Tag: rawRule.OutboundTag,
- },
- Domain: domains,
- }, nil
- }
|