DNS & Routing: refine rule parsing process (#528)

This commit is contained in:
Loyalsoldier 2020-12-18 08:36:34 +08:00 committed by GitHub
parent 3f7e6a07cf
commit 7a020d2d71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 111 additions and 92 deletions

View File

@ -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)

View File

@ -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
}