|
@@ -6,7 +6,6 @@ import (
|
|
|
"strings"
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/golang/protobuf/proto"
|
|
|
-
|
|
|
|
|
"v2ray.com/core/app/router"
|
|
"v2ray.com/core/app/router"
|
|
|
"v2ray.com/core/common/net"
|
|
"v2ray.com/core/common/net"
|
|
|
"v2ray.com/core/common/platform/filesystem"
|
|
"v2ray.com/core/common/platform/filesystem"
|
|
@@ -52,11 +51,11 @@ func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
switch strings.ToLower(ds) {
|
|
switch strings.ToLower(ds) {
|
|
|
- case "alwaysip":
|
|
|
|
|
|
|
+ case "alwaysip", "always_ip", "always-ip":
|
|
|
return router.Config_UseIp
|
|
return router.Config_UseIp
|
|
|
- case "ipifnonmatch":
|
|
|
|
|
|
|
+ case "ipifnonmatch", "ip_if_non_match", "ip-if-non-match":
|
|
|
return router.Config_IpIfNonMatch
|
|
return router.Config_IpIfNonMatch
|
|
|
- case "ipondemand":
|
|
|
|
|
|
|
+ case "ipondemand", "ip_on_demand", "ip-on-demand":
|
|
|
return router.Config_IpOnDemand
|
|
return router.Config_IpOnDemand
|
|
|
default:
|
|
default:
|
|
|
return router.Config_AsIs
|
|
return router.Config_AsIs
|
|
@@ -162,7 +161,7 @@ func loadIP(filename, country string) ([]*router.CIDR, error) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
for _, geoip := range geoipList.Entry {
|
|
for _, geoip := range geoipList.Entry {
|
|
|
- if geoip.CountryCode == country {
|
|
|
|
|
|
|
+ if strings.EqualFold(geoip.CountryCode, country) {
|
|
|
return geoip.Cidr, nil
|
|
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)
|
|
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)
|
|
geositeBytes, err := filesystem.ReadAsset(filename)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, newError("failed to open file: ", filename).Base(err)
|
|
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 {
|
|
for _, site := range geositeList.Entry {
|
|
|
- if site.CountryCode == country {
|
|
|
|
|
|
|
+ if strings.EqualFold(site.CountryCode, list) {
|
|
|
return site.Domain, nil
|
|
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 {
|
|
type AttributeMatcher interface {
|
|
@@ -197,7 +196,7 @@ type BooleanMatcher string
|
|
|
|
|
|
|
|
func (m BooleanMatcher) Match(domain *router.Domain) bool {
|
|
func (m BooleanMatcher) Match(domain *router.Domain) bool {
|
|
|
for _, attr := range domain.Attribute {
|
|
for _, attr := range domain.Attribute {
|
|
|
- if attr.Key == string(m) {
|
|
|
|
|
|
|
+ if strings.EqualFold(attr.GetKey(), string(m)) {
|
|
|
return true
|
|
return true
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -224,8 +223,11 @@ func (al *AttributeList) IsEmpty() bool {
|
|
|
func parseAttrs(attrs []string) *AttributeList {
|
|
func parseAttrs(attrs []string) *AttributeList {
|
|
|
al := new(AttributeList)
|
|
al := new(AttributeList)
|
|
|
for _, attr := range attrs {
|
|
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
|
|
return al
|
|
|
}
|
|
}
|
|
@@ -233,38 +235,57 @@ func parseAttrs(attrs []string) *AttributeList {
|
|
|
func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
|
|
func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
|
|
|
parts := strings.Split(siteWithAttr, "@")
|
|
parts := strings.Split(siteWithAttr, "@")
|
|
|
if len(parts) == 0 {
|
|
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 {
|
|
if err != nil {
|
|
|
return nil, err
|
|
return nil, err
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ attrs := parseAttrs(attrVal)
|
|
|
if attrs.IsEmpty() {
|
|
if attrs.IsEmpty() {
|
|
|
|
|
+ if strings.Contains(siteWithAttr, "@") {
|
|
|
|
|
+ newError("empty attribute list: ", siteWithAttr)
|
|
|
|
|
+ }
|
|
|
return domains, nil
|
|
return domains, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
filteredDomains := make([]*router.Domain, 0, len(domains))
|
|
filteredDomains := make([]*router.Domain, 0, len(domains))
|
|
|
|
|
+ hasAttrMatched := false
|
|
|
for _, domain := range domains {
|
|
for _, domain := range domains {
|
|
|
if attrs.Match(domain) {
|
|
if attrs.Match(domain) {
|
|
|
|
|
+ hasAttrMatched = true
|
|
|
filteredDomains = append(filteredDomains, domain)
|
|
filteredDomains = append(filteredDomains, domain)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ if !hasAttrMatched {
|
|
|
|
|
+ newError("attribute match no rule: geosite:", siteWithAttr)
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
return filteredDomains, nil
|
|
return filteredDomains, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
func parseDomainRule(domain string) ([]*router.Domain, error) {
|
|
func parseDomainRule(domain string) ([]*router.Domain, error) {
|
|
|
if strings.HasPrefix(domain, "geosite:") {
|
|
if strings.HasPrefix(domain, "geosite:") {
|
|
|
- country := strings.ToUpper(domain[8:])
|
|
|
|
|
- domains, err := loadGeositeWithAttr("geosite.dat", country)
|
|
|
|
|
|
|
+ list := domain[8:]
|
|
|
|
|
+ if len(list) == 0 {
|
|
|
|
|
+ return nil, newError("empty listname in rule: ", domain)
|
|
|
|
|
+ }
|
|
|
|
|
+ domains, err := loadGeositeWithAttr("geosite.dat", list)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
- return nil, newError("failed to load geosite: ", country).Base(err)
|
|
|
|
|
|
|
+ return nil, newError("failed to load geosite: ", list).Base(err)
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
return domains, nil
|
|
return domains, nil
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
var isExtDatFile = 0
|
|
var isExtDatFile = 0
|
|
|
{
|
|
{
|
|
|
const prefix = "ext:"
|
|
const prefix = "ext:"
|
|
@@ -276,37 +297,55 @@ func parseDomainRule(domain string) ([]*router.Domain, error) {
|
|
|
isExtDatFile = len(prefixQualified)
|
|
isExtDatFile = len(prefixQualified)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
if isExtDatFile != 0 {
|
|
if isExtDatFile != 0 {
|
|
|
kv := strings.Split(domain[isExtDatFile:], ":")
|
|
kv := strings.Split(domain[isExtDatFile:], ":")
|
|
|
if len(kv) != 2 {
|
|
if len(kv) != 2 {
|
|
|
return nil, newError("invalid external resource: ", domain)
|
|
return nil, newError("invalid external resource: ", domain)
|
|
|
}
|
|
}
|
|
|
filename := kv[0]
|
|
filename := kv[0]
|
|
|
- country := kv[1]
|
|
|
|
|
- domains, err := loadGeositeWithAttr(filename, country)
|
|
|
|
|
|
|
+ list := kv[1]
|
|
|
|
|
+ domains, err := loadGeositeWithAttr(filename, list)
|
|
|
if err != nil {
|
|
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
|
|
return domains, nil
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
domainRule := new(router.Domain)
|
|
domainRule := new(router.Domain)
|
|
|
switch {
|
|
switch {
|
|
|
case strings.HasPrefix(domain, "regexp:"):
|
|
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.Type = router.Domain_Regex
|
|
|
- domainRule.Value = domain[7:]
|
|
|
|
|
|
|
+ domainRule.Value = regexpVal
|
|
|
|
|
|
|
|
case strings.HasPrefix(domain, "domain:"):
|
|
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.Type = router.Domain_Domain
|
|
|
- domainRule.Value = domain[7:]
|
|
|
|
|
|
|
+ domainRule.Value = domainName
|
|
|
|
|
|
|
|
case strings.HasPrefix(domain, "full:"):
|
|
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.Type = router.Domain_Full
|
|
|
- domainRule.Value = domain[5:]
|
|
|
|
|
|
|
+ domainRule.Value = fullVal
|
|
|
|
|
|
|
|
case strings.HasPrefix(domain, "keyword:"):
|
|
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.Type = router.Domain_Plain
|
|
|
- domainRule.Value = domain[8:]
|
|
|
|
|
|
|
+ domainRule.Value = keywordVal
|
|
|
|
|
|
|
|
case strings.HasPrefix(domain, "dotless:"):
|
|
case strings.HasPrefix(domain, "dotless:"):
|
|
|
domainRule.Type = router.Domain_Regex
|
|
domainRule.Type = router.Domain_Regex
|
|
@@ -333,17 +372,22 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) {
|
|
|
for _, ip := range ips {
|
|
for _, ip := range ips {
|
|
|
if strings.HasPrefix(ip, "geoip:") {
|
|
if strings.HasPrefix(ip, "geoip:") {
|
|
|
country := ip[6:]
|
|
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 {
|
|
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{
|
|
geoipList = append(geoipList, &router.GeoIP{
|
|
|
CountryCode: strings.ToUpper(country),
|
|
CountryCode: strings.ToUpper(country),
|
|
|
Cidr: geoip,
|
|
Cidr: geoip,
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
continue
|
|
continue
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
var isExtDatFile = 0
|
|
var isExtDatFile = 0
|
|
|
{
|
|
{
|
|
|
const prefix = "ext:"
|
|
const prefix = "ext:"
|
|
@@ -355,6 +399,7 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) {
|
|
|
isExtDatFile = len(prefixQualified)
|
|
isExtDatFile = len(prefixQualified)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
if isExtDatFile != 0 {
|
|
if isExtDatFile != 0 {
|
|
|
kv := strings.Split(ip[isExtDatFile:], ":")
|
|
kv := strings.Split(ip[isExtDatFile:], ":")
|
|
|
if len(kv) != 2 {
|
|
if len(kv) != 2 {
|
|
@@ -363,9 +408,12 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) {
|
|
|
|
|
|
|
|
filename := kv[0]
|
|
filename := kv[0]
|
|
|
country := kv[1]
|
|
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 {
|
|
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{
|
|
geoipList = append(geoipList, &router.GeoIP{
|
|
@@ -506,62 +554,13 @@ func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) {
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, newError("invalid router rule").Base(err)
|
|
return nil, newError("invalid router rule").Base(err)
|
|
|
}
|
|
}
|
|
|
- if rawRule.Type == "field" {
|
|
|
|
|
|
|
+ if strings.EqualFold(rawRule.Type, "field") {
|
|
|
fieldrule, err := parseFieldRule(msg)
|
|
fieldrule, err := parseFieldRule(msg)
|
|
|
if err != nil {
|
|
if err != nil {
|
|
|
return nil, newError("invalid field rule").Base(err)
|
|
return nil, newError("invalid field rule").Base(err)
|
|
|
}
|
|
}
|
|
|
return fieldrule, nil
|
|
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
|
|
|
|
|
|
|
+ return nil, newError("unknown router rule type: ", rawRule.Type)
|
|
|
}
|
|
}
|