| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- package conf
- import (
- "encoding/json"
- "strconv"
- "strings"
- "github.com/v2fly/v2ray-core/v4/app/router"
- "github.com/v2fly/v2ray-core/v4/common/geodata"
- "github.com/v2fly/v2ray-core/v4/common/net"
- )
- type RouterRulesConfig struct {
- RuleList []json.RawMessage `json:"rules"`
- DomainStrategy string `json:"domainStrategy"`
- }
- // StrategyConfig represents a strategy config
- type StrategyConfig struct {
- Type string `json:"type"`
- Settings *json.RawMessage `json:"settings"`
- }
- type BalancingRule struct {
- Tag string `json:"tag"`
- Selectors StringList `json:"selector"`
- Strategy StrategyConfig `json:"strategy"`
- }
- 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")
- }
- var strategy string
- switch strings.ToLower(r.Strategy.Type) {
- case strategyRandom, "":
- strategy = strategyRandom
- case strategyLeastPing:
- strategy = "leastPing"
- default:
- return nil, newError("unknown balancing strategy: " + r.Strategy.Type)
- }
- return &router.BalancingRule{
- Tag: r.Tag,
- OutboundSelector: []string(r.Selectors),
- Strategy: strategy,
- }, nil
- }
- type RouterConfig struct {
- Settings *RouterRulesConfig `json:"settings"` // Deprecated
- RuleList []json.RawMessage `json:"rules"`
- DomainStrategy *string `json:"domainStrategy"`
- Balancers []*BalancingRule `json:"balancers"`
- DomainMatcher string `json:"domainMatcher"`
- }
- 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", "always_ip", "always-ip":
- return router.Config_UseIp
- case "ipifnonmatch", "ip_if_non_match", "ip-if-non-match":
- return router.Config_IpIfNonMatch
- case "ipondemand", "ip_on_demand", "ip-on-demand":
- 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
- }
- if rule.DomainMatcher == "" {
- rule.DomainMatcher = c.DomainMatcher
- }
- 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"`
- DomainMatcher string `json:"domainMatcher"`
- }
- 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)
- }
- }
- type AttributeMatcher interface {
- Match(*router.Domain) bool
- }
- type BooleanMatcher string
- func (m BooleanMatcher) Match(domain *router.Domain) bool {
- for _, attr := range domain.Attribute {
- if strings.EqualFold(attr.GetKey(), 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 {
- trimmedAttr := strings.ToLower(strings.TrimSpace(attr))
- if len(trimmedAttr) == 0 {
- continue
- }
- al.matcher = append(al.matcher, BooleanMatcher(trimmedAttr))
- }
- return al
- }
- func parseDomainRule(domain string) ([]*router.Domain, error) {
- if strings.HasPrefix(domain, "geosite:") {
- list := domain[8:]
- if len(list) == 0 {
- return nil, newError("empty listname in rule: ", domain)
- }
- domains, err := loadGeosite(list)
- if err != nil {
- return nil, newError("failed to load geosite: ", list).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]
- list := kv[1]
- domains, err := loadGeositeWithAttr(filename, list)
- if err != nil {
- return nil, newError("failed to load external geosite: ", list, " from ", filename).Base(err)
- }
- return domains, nil
- }
- domainRule := new(router.Domain)
- switch {
- case strings.HasPrefix(domain, "regexp:"):
- regexpVal := domain[7:]
- if len(regexpVal) == 0 {
- return nil, newError("empty regexp type of rule: ", domain)
- }
- domainRule.Type = router.Domain_Regex
- domainRule.Value = regexpVal
- case strings.HasPrefix(domain, "domain:"):
- domainName := domain[7:]
- if len(domainName) == 0 {
- return nil, newError("empty domain type of rule: ", domain)
- }
- domainRule.Type = router.Domain_Domain
- domainRule.Value = domainName
- case strings.HasPrefix(domain, "full:"):
- fullVal := domain[5:]
- if len(fullVal) == 0 {
- return nil, newError("empty full domain type of rule: ", domain)
- }
- domainRule.Type = router.Domain_Full
- domainRule.Value = fullVal
- case strings.HasPrefix(domain, "keyword:"):
- keywordVal := domain[8:]
- if len(keywordVal) == 0 {
- return nil, newError("empty keyword type of rule: ", domain)
- }
- domainRule.Type = router.Domain_Plain
- domainRule.Value = keywordVal
- 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:]
- isReverseMatch := false
- if strings.HasPrefix(ip, "geoip:!") {
- country = ip[7:]
- isReverseMatch = true
- }
- if len(country) == 0 {
- return nil, newError("empty country name in rule")
- }
- geoip, err := loadGeoIP(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,
- ReverseMatch: isReverseMatch,
- })
- 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]
- if len(filename) == 0 || len(country) == 0 {
- return nil, newError("empty filename or empty country in rule")
- }
- isReverseMatch := false
- if strings.HasPrefix(country, "!") {
- country = country[1:]
- isReverseMatch = true
- }
- geoip, err := geodata.LoadIP(filename, country)
- if err != nil {
- return nil, newError("failed to load geoip:", country, " from ", filename).Base(err)
- }
- geoipList = append(geoipList, &router.GeoIP{
- CountryCode: strings.ToUpper(filename + "_" + country),
- Cidr: geoip,
- ReverseMatch: isReverseMatch,
- })
- 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.DomainMatcher != "" {
- rule.DomainMatcher = rawFieldRule.DomainMatcher
- }
- 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 strings.EqualFold(rawRule.Type, "field") {
- fieldrule, err := parseFieldRule(msg)
- if err != nil {
- return nil, newError("invalid field rule").Base(err)
- }
- return fieldrule, nil
- }
- return nil, newError("unknown router rule type: ", rawRule.Type)
- }
|