diff --git a/common/matcher/domain/conf/domain.go b/common/matcher/domain/conf/domain.go index 10a8cf57..524b39bd 100644 --- a/common/matcher/domain/conf/domain.go +++ b/common/matcher/domain/conf/domain.go @@ -44,20 +44,36 @@ func ParseDomainRule(domain string) ([]*dm.Domain, error) { domainRule := new(dm.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 = dm.MatchingType_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 = dm.MatchingType_Subdomain - 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 = dm.MatchingType_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 = dm.MatchingType_Keyword - domainRule.Value = domain[8:] + domainRule.Value = keywordVal case strings.HasPrefix(domain, "dotless:"): domainRule.Type = dm.MatchingType_Regex diff --git a/common/matcher/geoip/conf.go b/common/matcher/geoip/conf.go index 5f46e3a4..620303e4 100644 --- a/common/matcher/geoip/conf.go +++ b/common/matcher/geoip/conf.go @@ -92,21 +92,28 @@ func find(data, code []byte) []byte { } } -func ParaseIPList(ips []string) ([]*GeoIP, error) { +func ParseIPList(ips []string) ([]*GeoIP, error) { var geoipList []*GeoIP var customCidrs []*CIDR for _, ip := range ips { if strings.HasPrefix(ip, "geoip:") { country := ip[6:] + isReverseMatch := false + if strings.HasPrefix(country, "!") { + country = country[1:] + isReverseMatch = true + } + geoipc, err := LoadGeoIP(strings.ToUpper(country)) if err != nil { return nil, newError("failed to load GeoIP: ", country).Base(err) } geoipList = append(geoipList, &GeoIP{ - CountryCode: strings.ToUpper(country), - Cidr: geoipc, + CountryCode: strings.ToUpper(country), + Cidr: geoipc, + ReverseMatch: isReverseMatch, }) continue } @@ -129,14 +136,24 @@ func ParaseIPList(ips []string) ([]*GeoIP, error) { 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 + } geoipc, err := LoadIPFile(filename, strings.ToUpper(country)) if err != nil { return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err) } geoipList = append(geoipList, &GeoIP{ - CountryCode: strings.ToUpper(filename + "_" + country), - Cidr: geoipc, + CountryCode: strings.ToUpper(filename + "_" + country), + Cidr: geoipc, + ReverseMatch: isReverseMatch, }) continue diff --git a/common/matcher/geoip/geoip.go b/common/matcher/geoip/geoip.go index 08eaf26a..f702c682 100644 --- a/common/matcher/geoip/geoip.go +++ b/common/matcher/geoip/geoip.go @@ -15,11 +15,16 @@ type ipv6 struct { } type GeoIPMatcher struct { - countryCode string - ip4 []uint32 - prefix4 []uint8 - ip6 []ipv6 - prefix6 []uint8 + countryCode string + reverseMatch bool + ip4 []uint32 + prefix4 []uint8 + ip6 []ipv6 + prefix6 []uint8 +} + +func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) { + m.reverseMatch = isReverseMatch } func normalize4(ip uint32, prefix uint8) uint32 { @@ -149,8 +154,17 @@ func (m *GeoIPMatcher) match6(ip ipv6) bool { func (m *GeoIPMatcher) Match(ip net.IP) bool { switch len(ip) { case 4: + if m.reverseMatch { + return !m.match4(binary.BigEndian.Uint32(ip)) + } return m.match4(binary.BigEndian.Uint32(ip)) case 16: + if m.reverseMatch { + return !m.match6(ipv6{ + a: binary.BigEndian.Uint64(ip[0:8]), + b: binary.BigEndian.Uint64(ip[8:16]), + }) + } return m.match6(ipv6{ a: binary.BigEndian.Uint64(ip[0:8]), b: binary.BigEndian.Uint64(ip[8:16]), @@ -170,14 +184,15 @@ type GeoIPMatcherContainer struct { func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) { if len(geoip.CountryCode) > 0 { for _, m := range c.matchers { - if m.countryCode == geoip.CountryCode { + if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch { return m, nil } } } m := &GeoIPMatcher{ - countryCode: geoip.CountryCode, + countryCode: geoip.CountryCode, + reverseMatch: geoip.ReverseMatch, } if err := m.Init(geoip.Cidr); err != nil { return nil, err diff --git a/common/matcher/geoip/geoip.pb.go b/common/matcher/geoip/geoip.pb.go index 4176127f..589ccd49 100644 --- a/common/matcher/geoip/geoip.pb.go +++ b/common/matcher/geoip/geoip.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.25.0 -// protoc v3.15.6 +// protoc v3.15.7 // source: common/matcher/geoip/geoip.proto package geoip @@ -88,8 +88,9 @@ type GeoIP struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` - Cidr []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"` + CountryCode string `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"` + Cidr []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"` + ReverseMatch bool `protobuf:"varint,3,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"` } func (x *GeoIP) Reset() { @@ -138,6 +139,13 @@ func (x *GeoIP) GetCidr() []*CIDR { return nil } +func (x *GeoIP) GetReverseMatch() bool { + if x != nil { + return x.ReverseMatch + } + return false +} + type GeoIPList struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -194,25 +202,27 @@ var file_common_matcher_geoip_geoip_proto_rawDesc = []byte{ 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x22, 0x2e, 0x0a, 0x04, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x5f, 0x0a, - 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, - 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x63, 0x69, 0x64, - 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, - 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, - 0x6f, 0x69, 0x70, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x22, 0x43, - 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x05, 0x65, - 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, - 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x65, 0x6e, - 0x74, 0x72, 0x79, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x84, 0x01, + 0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, + 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x63, 0x69, + 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, - 0x65, 0x6f, 0x69, 0x70, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, - 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, - 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0xaa, 0x02, 0x19, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, - 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, - 0x49, 0x50, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x12, + 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4d, + 0x61, 0x74, 0x63, 0x68, 0x22, 0x43, 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x36, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, + 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x47, 0x65, 0x6f, + 0x49, 0x50, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, + 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, + 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, + 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, + 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0xaa, 0x02, 0x19, 0x58, + 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, + 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/common/matcher/geoip/geoip.proto b/common/matcher/geoip/geoip.proto index a781ff09..d8a28398 100644 --- a/common/matcher/geoip/geoip.proto +++ b/common/matcher/geoip/geoip.proto @@ -18,6 +18,7 @@ message CIDR { message GeoIP { string country_code = 1; repeated CIDR cidr = 2; + bool reverse_match =3; } message GeoIPList { diff --git a/common/matcher/geoip/geoip_test.go b/common/matcher/geoip/geoip_test.go index f959b944..48bc5286 100644 --- a/common/matcher/geoip/geoip_test.go +++ b/common/matcher/geoip/geoip_test.go @@ -5,12 +5,12 @@ import ( "path/filepath" "testing" - "github.com/golang/protobuf/proto" "github.com/xtls/xray-core/common" . "github.com/xtls/xray-core/common/matcher/geoip" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/common/platform" "github.com/xtls/xray-core/common/platform/filesystem" + "google.golang.org/protobuf/proto" ) func init() { @@ -20,11 +20,31 @@ func init() { if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) { common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geoip.dat"))) } + if _, err := os.Stat(platform.GetAssetLocation("geoiptestrouter.dat")); err != nil && os.IsNotExist(err) { + common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoiptestrouter.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geoip.dat"))) + } if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) { common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geosite.dat"))) } } +func TestParseIPList(t *testing.T) { + ips := []string{ + "geoip:us", + "geoip:cn", + "geoip:!cn", + "ext:geoiptestrouter.dat:!cn", + "ext:geoiptestrouter.dat:ca", + "ext-ip:geoiptestrouter.dat:!cn", + "ext-ip:geoiptestrouter.dat:!ca", + } + + _, err := ParseIPList(ips) + if err != nil { + t.Fatalf("Failed to parse geoip list, got %s", err) + } +} + func TestGeoIPMatcherContainer(t *testing.T) { container := &GeoIPMatcherContainer{} @@ -123,6 +143,42 @@ func TestGeoIPMatcher(t *testing.T) { } } +func TestGeoIPReverseMatcher(t *testing.T) { + cidrList := CIDRList{ + {Ip: []byte{8, 8, 8, 8}, Prefix: 32}, + {Ip: []byte{91, 108, 4, 0}, Prefix: 16}, + } + matcher := &GeoIPMatcher{} + matcher.SetReverseMatch(true) // Reverse match + common.Must(matcher.Init(cidrList)) + + testCases := []struct { + Input string + Output bool + }{ + { + Input: "8.8.8.8", + Output: false, + }, + { + Input: "2001:cdba::3257:9652", + Output: true, + }, + { + Input: "91.108.255.254", + Output: false, + }, + } + + for _, testCase := range testCases { + ip := net.ParseAddress(testCase.Input).IP() + actual := matcher.Match(ip) + if actual != testCase.Output { + t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual) + } + } +} + func TestGeoIPMatcher4CN(t *testing.T) { ips, err := loadGeoIP("CN") common.Must(err) diff --git a/common/matcher/geosite/attribute.go b/common/matcher/geosite/attribute.go index a16361c1..c2fc0b30 100644 --- a/common/matcher/geosite/attribute.go +++ b/common/matcher/geosite/attribute.go @@ -1,5 +1,7 @@ package geosite +import "strings" + type AttributeList struct { matcher []AttributeMatcher } @@ -25,7 +27,7 @@ type BooleanMatcher string func (m BooleanMatcher) Match(domain *Domain) bool { for _, attr := range domain.Attribute { - if attr.Key == string(m) { + if strings.EqualFold(attr.GetKey(), string(m)) { return true } } diff --git a/infra/conf/dns.go b/infra/conf/dns.go index 0cb3b766..0e685dc5 100644 --- a/infra/conf/dns.go +++ b/infra/conf/dns.go @@ -74,7 +74,7 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) { }) } - geoipList, err := geoip.ParaseIPList(c.ExpectIPs) + geoipList, err := geoip.ParseIPList(c.ExpectIPs) if err != nil { return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err) } diff --git a/infra/conf/router.go b/infra/conf/router.go index 1e005bf2..8a59bddf 100644 --- a/infra/conf/router.go +++ b/infra/conf/router.go @@ -203,7 +203,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { } if rawFieldRule.IP != nil { - geoipList, err := geoip.ParaseIPList(*rawFieldRule.IP) + geoipList, err := geoip.ParseIPList(*rawFieldRule.IP) if err != nil { return nil, err } @@ -219,7 +219,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { } if rawFieldRule.SourceIP != nil { - geoipList, err := geoip.ParaseIPList(*rawFieldRule.SourceIP) + geoipList, err := geoip.ParseIPList(*rawFieldRule.SourceIP) if err != nil { return nil, err } @@ -261,21 +261,21 @@ 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" { + if strings.EqualFold(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" { + if strings.EqualFold(rawRule.Type, "chinasites") { chinasitesrule, err := parseChinaSitesRule(msg) if err != nil { return nil, newError("invalid chinasites rule").Base(err) diff --git a/infra/conf/xray.go b/infra/conf/xray.go index 1f89ebad..b8b1ba8a 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -101,7 +101,7 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) { var exIP []*geoip.GeoIP if c.IPsExcluded != nil { - exip, err := geoip.ParaseIPList(*c.IPsExcluded) + exip, err := geoip.ParseIPList(*c.IPsExcluded) if err != nil { return nil, newError("failed to parse excluded ip").Base(err) }