From 7a020d2d7172d2406e091f0c3c78030ed2683a84 Mon Sep 17 00:00:00 2001 From: Loyalsoldier <10487845+Loyalsoldier@users.noreply.github.com> Date: Fri, 18 Dec 2020 08:36:34 +0800 Subject: [PATCH] DNS & Routing: refine rule parsing process (#528) --- infra/conf/dns.go | 42 ++++++++--- infra/conf/router.go | 161 +++++++++++++++++++++---------------------- 2 files changed, 111 insertions(+), 92 deletions(-) diff --git a/infra/conf/dns.go b/infra/conf/dns.go index a1d42bc2e..d45bc1f94 100644 --- a/infra/conf/dns.go +++ b/infra/conf/dns.go @@ -87,7 +87,7 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) { geoipList, err := toCidrList(c.ExpectIPs) if err != nil { - return nil, newError("invalid ip rule: ", c.ExpectIPs).Base(err) + return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err) } var myClientIP []byte @@ -153,7 +153,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) { for _, server := range c.Servers { ns, err := server.Build() if err != nil { - return nil, newError("failed to build name server").Base(err) + return nil, newError("failed to build nameserver").Base(err) } config.NameServer = append(config.NameServer, ns) } @@ -170,15 +170,23 @@ func (c *DNSConfig) Build() (*dns.Config, error) { var mappings []*dns.Config_HostMapping switch { case strings.HasPrefix(domain, "domain:"): + domainName := domain[7:] + if len(domainName) == 0 { + return nil, newError("empty domain type of rule: ", domain) + } mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Subdomain - mapping.Domain = domain[7:] + mapping.Domain = domainName mappings = append(mappings, mapping) case strings.HasPrefix(domain, "geosite:"): - domains, err := loadGeositeWithAttr("geosite.dat", strings.ToUpper(domain[8:])) + listName := domain[8:] + if len(listName) == 0 { + return nil, newError("empty geosite rule: ", domain) + } + domains, err := loadGeositeWithAttr("geosite.dat", listName) if err != nil { - return nil, newError("invalid geosite settings: ", domain).Base(err) + return nil, newError("failed to load geosite: ", listName).Base(err) } for _, d := range domains { mapping := getHostMapping(addr) @@ -188,21 +196,33 @@ func (c *DNSConfig) Build() (*dns.Config, error) { } case strings.HasPrefix(domain, "regexp:"): + regexpVal := domain[7:] + if len(regexpVal) == 0 { + return nil, newError("empty regexp type of rule: ", domain) + } mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Regex - mapping.Domain = domain[7:] + mapping.Domain = regexpVal mappings = append(mappings, mapping) case strings.HasPrefix(domain, "keyword:"): + keywordVal := domain[8:] + if len(keywordVal) == 0 { + return nil, newError("empty keyword type of rule: ", domain) + } mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Keyword - mapping.Domain = domain[8:] + mapping.Domain = keywordVal mappings = append(mappings, mapping) case strings.HasPrefix(domain, "full:"): + fullVal := domain[5:] + if len(fullVal) == 0 { + return nil, newError("empty full domain type of rule: ", domain) + } mapping := getHostMapping(addr) mapping.Type = dns.DomainMatchingType_Full - mapping.Domain = domain[5:] + mapping.Domain = fullVal mappings = append(mappings, mapping) case strings.HasPrefix(domain, "dotless:"): @@ -224,10 +244,10 @@ func (c *DNSConfig) Build() (*dns.Config, error) { return nil, newError("invalid external resource: ", domain) } filename := kv[0] - country := kv[1] - domains, err := loadGeositeWithAttr(filename, country) + list := kv[1] + domains, err := loadGeositeWithAttr(filename, list) if err != nil { - return nil, newError("failed to load domains: ", country, " from ", filename).Base(err) + return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err) } for _, d := range domains { mapping := getHostMapping(addr) diff --git a/infra/conf/router.go b/infra/conf/router.go index 78f289bfd..0d52d0e4f 100644 --- a/infra/conf/router.go +++ b/infra/conf/router.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/golang/protobuf/proto" - "v2ray.com/core/app/router" "v2ray.com/core/common/net" "v2ray.com/core/common/platform/filesystem" @@ -52,11 +51,11 @@ func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy { } switch strings.ToLower(ds) { - case "alwaysip": + case "alwaysip", "always_ip", "always-ip": return router.Config_UseIp - case "ipifnonmatch": + case "ipifnonmatch", "ip_if_non_match", "ip-if-non-match": return router.Config_IpIfNonMatch - case "ipondemand": + case "ipondemand", "ip_on_demand", "ip-on-demand": return router.Config_IpOnDemand default: return router.Config_AsIs @@ -162,7 +161,7 @@ func loadIP(filename, country string) ([]*router.CIDR, error) { } for _, geoip := range geoipList.Entry { - if geoip.CountryCode == country { + if strings.EqualFold(geoip.CountryCode, country) { return geoip.Cidr, nil } } @@ -170,7 +169,7 @@ func loadIP(filename, country string) ([]*router.CIDR, error) { return nil, newError("country not found in ", filename, ": ", country) } -func loadSite(filename, country string) ([]*router.Domain, error) { +func loadSite(filename, list string) ([]*router.Domain, error) { geositeBytes, err := filesystem.ReadAsset(filename) if err != nil { return nil, newError("failed to open file: ", filename).Base(err) @@ -181,12 +180,12 @@ func loadSite(filename, country string) ([]*router.Domain, error) { } for _, site := range geositeList.Entry { - if site.CountryCode == country { + if strings.EqualFold(site.CountryCode, list) { return site.Domain, nil } } - return nil, newError("list not found in ", filename, ": ", country) + return nil, newError("list not found in ", filename, ": ", list) } type AttributeMatcher interface { @@ -197,7 +196,7 @@ type BooleanMatcher string func (m BooleanMatcher) Match(domain *router.Domain) bool { for _, attr := range domain.Attribute { - if attr.Key == string(m) { + if strings.EqualFold(attr.GetKey(), string(m)) { return true } } @@ -224,8 +223,11 @@ func (al *AttributeList) IsEmpty() bool { func parseAttrs(attrs []string) *AttributeList { al := new(AttributeList) for _, attr := range attrs { - lc := strings.ToLower(attr) - al.matcher = append(al.matcher, BooleanMatcher(lc)) + trimmedAttr := strings.ToLower(strings.TrimSpace(attr)) + if len(trimmedAttr) == 0 { + continue + } + al.matcher = append(al.matcher, BooleanMatcher(trimmedAttr)) } return al } @@ -233,38 +235,57 @@ func parseAttrs(attrs []string) *AttributeList { func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) { parts := strings.Split(siteWithAttr, "@") if len(parts) == 0 { - return nil, newError("empty site") + return nil, newError("empty rule") } - country := strings.ToUpper(parts[0]) - attrs := parseAttrs(parts[1:]) - domains, err := loadSite(file, country) + list := strings.TrimSpace(parts[0]) + attrVal := parts[1:] + + if len(list) == 0 { + return nil, newError("empty listname in rule: ", siteWithAttr) + } + + domains, err := loadSite(file, list) if err != nil { return nil, err } + attrs := parseAttrs(attrVal) if attrs.IsEmpty() { + if strings.Contains(siteWithAttr, "@") { + newError("empty attribute list: ", siteWithAttr) + } return domains, nil } filteredDomains := make([]*router.Domain, 0, len(domains)) + hasAttrMatched := false for _, domain := range domains { if attrs.Match(domain) { + hasAttrMatched = true filteredDomains = append(filteredDomains, domain) } } + if !hasAttrMatched { + newError("attribute match no rule: geosite:", siteWithAttr) + } 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) + list := domain[8:] + if len(list) == 0 { + return nil, newError("empty listname in rule: ", domain) } + domains, err := loadGeositeWithAttr("geosite.dat", list) + if err != nil { + return nil, newError("failed to load geosite: ", list).Base(err) + } + return domains, nil } + var isExtDatFile = 0 { const prefix = "ext:" @@ -276,37 +297,55 @@ func parseDomainRule(domain string) ([]*router.Domain, error) { 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) + list := kv[1] + domains, err := loadGeositeWithAttr(filename, list) if err != nil { - return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err) + 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 = domain[7:] + 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 = domain[7:] + 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 = domain[5:] + 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 = domain[8:] + domainRule.Value = keywordVal case strings.HasPrefix(domain, "dotless:"): domainRule.Type = router.Domain_Regex @@ -333,17 +372,22 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) { for _, ip := range ips { if strings.HasPrefix(ip, "geoip:") { country := ip[6:] - geoip, err := loadGeoIP(strings.ToUpper(country)) + 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) + 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:" @@ -355,6 +399,7 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) { isExtDatFile = len(prefixQualified) } } + if isExtDatFile != 0 { kv := strings.Split(ip[isExtDatFile:], ":") if len(kv) != 2 { @@ -363,9 +408,12 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) { filename := kv[0] country := kv[1] - geoip, err := loadIP(filename, strings.ToUpper(country)) + if len(filename) == 0 || len(country) == 0 { + return nil, newError("empty filename or empty country in rule") + } + geoip, err := loadIP(filename, country) if err != nil { - return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err) + return nil, newError("failed to load geoip: ", country, " from ", filename).Base(err) } geoipList = append(geoipList, &router.GeoIP{ @@ -506,62 +554,13 @@ func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) { if err != nil { return nil, newError("invalid router rule").Base(err) } - if rawRule.Type == "field" { + if strings.EqualFold(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 -}