Răsfoiți Sursa

Refactor: GeoSite & GeoIP

JimhHan 4 ani în urmă
părinte
comite
b11429eaee
54 a modificat fișierele cu 2105 adăugiri și 1612 ștergeri
  1. 18 17
      app/dns/config.go
  2. 132 257
      app/dns/config.pb.go
  3. 5 16
      app/dns/config.proto
  4. 6 6
      app/dns/dns.go
  5. 41 40
      app/dns/dns_test.go
  6. 4 4
      app/dns/hosts.go
  7. 4 3
      app/dns/hosts_test.go
  8. 6 6
      app/dns/nameserver.go
  9. 4 2
      app/router/command/command_test.go
  10. 11 51
      app/router/condition.go
  11. 41 19
      app/router/condition_test.go
  12. 5 43
      app/router/config.go
  13. 138 727
      app/router/config.pb.go
  14. 7 65
      app/router/config.proto
  15. 4 3
      app/router/router_test.go
  16. 2 2
      app/stats/command/command.go
  17. 3 0
      common/matcher/conf/conf.go
  18. 78 0
      common/matcher/conf/domain.go
  19. 9 0
      common/matcher/conf/errors.generated.go
  20. 3 0
      common/matcher/domain/domain.go
  21. 229 0
      common/matcher/domain/domain.pb.go
  22. 39 0
      common/matcher/domain/domain.proto
  23. 9 0
      common/matcher/domain/errors.generated.go
  24. 90 0
      common/matcher/geoip/conf.go
  25. 40 0
      common/matcher/geoip/crid.go
  26. 9 0
      common/matcher/geoip/errors.generated.go
  27. 4 2
      common/matcher/geoip/geoip.go
  28. 307 0
      common/matcher/geoip/geoip.pb.go
  29. 25 0
      common/matcher/geoip/geoip.proto
  30. 16 16
      common/matcher/geoip/geoip_test.go
  31. 47 0
      common/matcher/geoip/matcher.go
  32. 33 0
      common/matcher/geosite/attribute.go
  33. 42 0
      common/matcher/geosite/conf.go
  34. 9 0
      common/matcher/geosite/errors.generated.go
  35. 86 0
      common/matcher/geosite/file.go
  36. 19 0
      common/matcher/geosite/geosite.go
  37. 443 0
      common/matcher/geosite/geosite.pb.go
  38. 38 0
      common/matcher/geosite/geosite.proto
  39. 2 2
      common/matcher/str/benchmark_test.go
  40. 1 1
      common/matcher/str/domain_matcher.go
  41. 2 2
      common/matcher/str/domain_matcher_test.go
  42. 1 1
      common/matcher/str/full_matcher.go
  43. 2 2
      common/matcher/str/full_matcher_test.go
  44. 1 1
      common/matcher/str/matchers.go
  45. 2 2
      common/matcher/str/matchers_test.go
  46. 1 1
      common/matcher/str/strmatcher.go
  47. 2 2
      common/matcher/str/strmatcher_test.go
  48. 18 38
      infra/conf/dns.go
  49. 14 13
      infra/conf/dns_test.go
  50. 22 242
      infra/conf/router.go
  51. 17 15
      infra/conf/router_test.go
  52. 3 2
      infra/conf/xray_test.go
  53. 2 1
      testing/scenarios/dns_test.go
  54. 9 8
      testing/scenarios/reverse_test.go

+ 18 - 17
app/dns/config.go

@@ -1,31 +1,32 @@
 package dns
 
 import (
+	dm "github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/str"
 	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/common/strmatcher"
 	"github.com/xtls/xray-core/common/uuid"
 )
 
-var typeMap = map[DomainMatchingType]strmatcher.Type{
-	DomainMatchingType_Full:      strmatcher.Full,
-	DomainMatchingType_Subdomain: strmatcher.Domain,
-	DomainMatchingType_Keyword:   strmatcher.Substr,
-	DomainMatchingType_Regex:     strmatcher.Regex,
+var typeMap = map[dm.MatchingType]str.Type{
+	dm.MatchingType_Keyword:   str.Substr,
+	dm.MatchingType_Regex:     str.Regex,
+	dm.MatchingType_Subdomain: str.Domain,
+	dm.MatchingType_Full:      str.Full,
 }
 
 // References:
 // https://www.iana.org/assignments/special-use-domain-names/special-use-domain-names.xhtml
 // https://unix.stackexchange.com/questions/92441/whats-the-difference-between-local-home-and-lan
-var localTLDsAndDotlessDomains = []*NameServer_PriorityDomain{
-	{Type: DomainMatchingType_Regex, Domain: "^[^.]+$"}, // This will only match domains without any dot
-	{Type: DomainMatchingType_Subdomain, Domain: "local"},
-	{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
-	{Type: DomainMatchingType_Subdomain, Domain: "localhost"},
-	{Type: DomainMatchingType_Subdomain, Domain: "lan"},
-	{Type: DomainMatchingType_Subdomain, Domain: "home.arpa"},
-	{Type: DomainMatchingType_Subdomain, Domain: "example"},
-	{Type: DomainMatchingType_Subdomain, Domain: "invalid"},
-	{Type: DomainMatchingType_Subdomain, Domain: "test"},
+var localTLDsAndDotlessDomains = []*dm.Domain{
+	{Type: dm.MatchingType_Regex, Value: "^[^.]+$"}, // This will only match domains without any dot
+	{Type: dm.MatchingType_Subdomain, Value: "local"},
+	{Type: dm.MatchingType_Subdomain, Value: "localdomain"},
+	{Type: dm.MatchingType_Subdomain, Value: "localhost"},
+	{Type: dm.MatchingType_Subdomain, Value: "lan"},
+	{Type: dm.MatchingType_Subdomain, Value: "home.arpa"},
+	{Type: dm.MatchingType_Subdomain, Value: "example"},
+	{Type: dm.MatchingType_Subdomain, Value: "invalid"},
+	{Type: dm.MatchingType_Subdomain, Value: "test"},
 }
 
 var localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{
@@ -33,7 +34,7 @@ var localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{
 	Size: uint32(len(localTLDsAndDotlessDomains)),
 }
 
-func toStrMatcher(t DomainMatchingType, domain string) (strmatcher.Matcher, error) {
+func toStrMatcher(t dm.MatchingType, domain string) (str.Matcher, error) {
 	strMType, f := typeMap[t]
 	if !f {
 		return nil, newError("unknown mapping type", t).AtWarning()

+ 132 - 257
app/dns/config.pb.go

@@ -8,7 +8,8 @@ package dns
 
 import (
 	proto "github.com/golang/protobuf/proto"
-	router "github.com/xtls/xray-core/app/router"
+	domain "github.com/xtls/xray-core/common/matcher/domain"
+	geoip "github.com/xtls/xray-core/common/matcher/geoip"
 	net "github.com/xtls/xray-core/common/net"
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
 	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
@@ -27,58 +28,6 @@ const (
 // of the legacy proto package is being used.
 const _ = proto.ProtoPackageIsVersion4
 
-type DomainMatchingType int32
-
-const (
-	DomainMatchingType_Full      DomainMatchingType = 0
-	DomainMatchingType_Subdomain DomainMatchingType = 1
-	DomainMatchingType_Keyword   DomainMatchingType = 2
-	DomainMatchingType_Regex     DomainMatchingType = 3
-)
-
-// Enum value maps for DomainMatchingType.
-var (
-	DomainMatchingType_name = map[int32]string{
-		0: "Full",
-		1: "Subdomain",
-		2: "Keyword",
-		3: "Regex",
-	}
-	DomainMatchingType_value = map[string]int32{
-		"Full":      0,
-		"Subdomain": 1,
-		"Keyword":   2,
-		"Regex":     3,
-	}
-)
-
-func (x DomainMatchingType) Enum() *DomainMatchingType {
-	p := new(DomainMatchingType)
-	*p = x
-	return p
-}
-
-func (x DomainMatchingType) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (DomainMatchingType) Descriptor() protoreflect.EnumDescriptor {
-	return file_app_dns_config_proto_enumTypes[0].Descriptor()
-}
-
-func (DomainMatchingType) Type() protoreflect.EnumType {
-	return &file_app_dns_config_proto_enumTypes[0]
-}
-
-func (x DomainMatchingType) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use DomainMatchingType.Descriptor instead.
-func (DomainMatchingType) EnumDescriptor() ([]byte, []int) {
-	return file_app_dns_config_proto_rawDescGZIP(), []int{0}
-}
-
 type QueryStrategy int32
 
 const (
@@ -112,11 +61,11 @@ func (x QueryStrategy) String() string {
 }
 
 func (QueryStrategy) Descriptor() protoreflect.EnumDescriptor {
-	return file_app_dns_config_proto_enumTypes[1].Descriptor()
+	return file_app_dns_config_proto_enumTypes[0].Descriptor()
 }
 
 func (QueryStrategy) Type() protoreflect.EnumType {
-	return &file_app_dns_config_proto_enumTypes[1]
+	return &file_app_dns_config_proto_enumTypes[0]
 }
 
 func (x QueryStrategy) Number() protoreflect.EnumNumber {
@@ -125,7 +74,7 @@ func (x QueryStrategy) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use QueryStrategy.Descriptor instead.
 func (QueryStrategy) EnumDescriptor() ([]byte, []int) {
-	return file_app_dns_config_proto_rawDescGZIP(), []int{1}
+	return file_app_dns_config_proto_rawDescGZIP(), []int{0}
 }
 
 type NameServer struct {
@@ -133,11 +82,11 @@ type NameServer struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Address           *net.Endpoint                `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
-	ClientIp          []byte                       `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
-	PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
-	Geoip             []*router.GeoIP              `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"`
-	OriginalRules     []*NameServer_OriginalRule   `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
+	Address           *net.Endpoint              `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
+	ClientIp          []byte                     `protobuf:"bytes,5,opt,name=client_ip,json=clientIp,proto3" json:"client_ip,omitempty"`
+	PrioritizedDomain []*domain.Domain           `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
+	Geoip             []*geoip.GeoIP             `protobuf:"bytes,3,rep,name=geoip,proto3" json:"geoip,omitempty"`
+	OriginalRules     []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
 }
 
 func (x *NameServer) Reset() {
@@ -186,14 +135,14 @@ func (x *NameServer) GetClientIp() []byte {
 	return nil
 }
 
-func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
+func (x *NameServer) GetPrioritizedDomain() []*domain.Domain {
 	if x != nil {
 		return x.PrioritizedDomain
 	}
 	return nil
 }
 
-func (x *NameServer) GetGeoip() []*router.GeoIP {
+func (x *NameServer) GetGeoip() []*geoip.GeoIP {
 	if x != nil {
 		return x.Geoip
 	}
@@ -326,61 +275,6 @@ func (x *Config) GetQueryStrategy() QueryStrategy {
 	return QueryStrategy_USE_IP
 }
 
-type NameServer_PriorityDomain struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	Type   DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"`
-	Domain string             `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
-}
-
-func (x *NameServer_PriorityDomain) Reset() {
-	*x = NameServer_PriorityDomain{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_app_dns_config_proto_msgTypes[2]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *NameServer_PriorityDomain) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*NameServer_PriorityDomain) ProtoMessage() {}
-
-func (x *NameServer_PriorityDomain) ProtoReflect() protoreflect.Message {
-	mi := &file_app_dns_config_proto_msgTypes[2]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use NameServer_PriorityDomain.ProtoReflect.Descriptor instead.
-func (*NameServer_PriorityDomain) Descriptor() ([]byte, []int) {
-	return file_app_dns_config_proto_rawDescGZIP(), []int{0, 0}
-}
-
-func (x *NameServer_PriorityDomain) GetType() DomainMatchingType {
-	if x != nil {
-		return x.Type
-	}
-	return DomainMatchingType_Full
-}
-
-func (x *NameServer_PriorityDomain) GetDomain() string {
-	if x != nil {
-		return x.Domain
-	}
-	return ""
-}
-
 type NameServer_OriginalRule struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -393,7 +287,7 @@ type NameServer_OriginalRule struct {
 func (x *NameServer_OriginalRule) Reset() {
 	*x = NameServer_OriginalRule{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_app_dns_config_proto_msgTypes[3]
+		mi := &file_app_dns_config_proto_msgTypes[2]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -406,7 +300,7 @@ func (x *NameServer_OriginalRule) String() string {
 func (*NameServer_OriginalRule) ProtoMessage() {}
 
 func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {
-	mi := &file_app_dns_config_proto_msgTypes[3]
+	mi := &file_app_dns_config_proto_msgTypes[2]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -419,7 +313,7 @@ func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use NameServer_OriginalRule.ProtoReflect.Descriptor instead.
 func (*NameServer_OriginalRule) Descriptor() ([]byte, []int) {
-	return file_app_dns_config_proto_rawDescGZIP(), []int{0, 1}
+	return file_app_dns_config_proto_rawDescGZIP(), []int{0, 0}
 }
 
 func (x *NameServer_OriginalRule) GetRule() string {
@@ -441,9 +335,9 @@ type Config_HostMapping struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	Type   DomainMatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.dns.DomainMatchingType" json:"type,omitempty"`
-	Domain string             `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
-	Ip     [][]byte           `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
+	Type   domain.MatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.common.matcher.domain.MatchingType" json:"type,omitempty"`
+	Domain string              `protobuf:"bytes,2,opt,name=domain,proto3" json:"domain,omitempty"`
+	Ip     [][]byte            `protobuf:"bytes,3,rep,name=ip,proto3" json:"ip,omitempty"`
 	// ProxiedDomain indicates the mapped domain has the same IP address on this
 	// domain. Xray will use this domain for IP queries. This field is only
 	// effective if ip is empty.
@@ -453,7 +347,7 @@ type Config_HostMapping struct {
 func (x *Config_HostMapping) Reset() {
 	*x = Config_HostMapping{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_app_dns_config_proto_msgTypes[5]
+		mi := &file_app_dns_config_proto_msgTypes[4]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -466,7 +360,7 @@ func (x *Config_HostMapping) String() string {
 func (*Config_HostMapping) ProtoMessage() {}
 
 func (x *Config_HostMapping) ProtoReflect() protoreflect.Message {
-	mi := &file_app_dns_config_proto_msgTypes[5]
+	mi := &file_app_dns_config_proto_msgTypes[4]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -482,11 +376,11 @@ func (*Config_HostMapping) Descriptor() ([]byte, []int) {
 	return file_app_dns_config_proto_rawDescGZIP(), []int{1, 1}
 }
 
-func (x *Config_HostMapping) GetType() DomainMatchingType {
+func (x *Config_HostMapping) GetType() domain.MatchingType {
 	if x != nil {
 		return x.Type
 	}
-	return DomainMatchingType_Full
+	return domain.MatchingType_Full
 }
 
 func (x *Config_HostMapping) GetDomain() string {
@@ -518,91 +412,85 @@ var file_app_dns_config_proto_rawDesc = []byte{
 	0x2e, 0x64, 0x6e, 0x73, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74,
 	0x2f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c,
 	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x64, 0x65, 0x73, 0x74, 0x69,
-	0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x61, 0x70,
-	0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xca, 0x03, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65,
-	0x72, 0x76, 0x65, 0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
-	0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74,
-	0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69,
-	0x65, 0x6e, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c,
-	0x69, 0x65, 0x6e, 0x74, 0x49, 0x70, 0x12, 0x56, 0x0a, 0x12, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69,
-	0x74, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03,
-	0x28, 0x0b, 0x32, 0x27, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e,
-	0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x69,
-	0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69,
-	0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2c,
-	0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x16, 0x2e,
-	0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e,
-	0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x4c, 0x0a, 0x0e,
-	0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x04,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
-	0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x4f,
-	0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x6f, 0x72, 0x69,
-	0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x5e, 0x0a, 0x0e, 0x50, 0x72,
-	0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x34, 0x0a, 0x04,
-	0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
-	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
-	0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79,
-	0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x1a, 0x36, 0x0a, 0x0c, 0x4f, 0x72,
-	0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x72, 0x75,
-	0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, 0x12,
-	0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04, 0x73, 0x69,
-	0x7a, 0x65, 0x22, 0x8d, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3f, 0x0a,
-	0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x22, 0x63, 0x6f,
+	0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x64, 0x6f, 0x6d,
+	0x61, 0x69, 0x6e, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x1a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
+	0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x22, 0xef, 0x02, 0x0a, 0x0a, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65,
+	0x72, 0x12, 0x33, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01,
 	0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
-	0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42, 0x02, 0x18,
-	0x01, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x12, 0x39,
-	0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x05, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64,
-	0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x0a, 0x6e,
-	0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x05, 0x48, 0x6f, 0x73,
-	0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
-	0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48,
-	0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x48,
-	0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69,
-	0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,
-	0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68, 0x6f, 0x73, 0x74,
-	0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61,
-	0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x48, 0x6f,
-	0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x69,
-	0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x06, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x61,
-	0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c,
-	0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x42, 0x0a, 0x0e,
-	0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x09,
-	0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
-	0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
-	0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
-	0x1a, 0x55, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
-	0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
-	0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65,
-	0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x92, 0x01, 0x0a, 0x0b, 0x48, 0x6f, 0x73, 0x74,
-	0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
-	0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68,
-	0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a,
-	0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64,
-	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28,
-	0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64,
-	0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70,
-	0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07,
-	0x10, 0x08, 0x2a, 0x45, 0x0a, 0x12, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4d, 0x61, 0x74, 0x63,
-	0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c,
-	0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10,
-	0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f, 0x72, 0x64, 0x10, 0x02, 0x12, 0x09,
-	0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x2a, 0x35, 0x0a, 0x0d, 0x51, 0x75, 0x65,
-	0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0a, 0x0a, 0x06, 0x55, 0x53,
-	0x45, 0x5f, 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
-	0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x36, 0x10, 0x02,
-	0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
-	0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 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, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58, 0x72, 0x61, 0x79,
-	0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x07, 0x61,
+	0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
+	0x5f, 0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e,
+	0x74, 0x49, 0x70, 0x12, 0x51, 0x0a, 0x12, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a,
+	0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x22, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61,
+	0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x44, 0x6f, 0x6d,
+	0x61, 0x69, 0x6e, 0x52, 0x11, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x69, 0x7a, 0x65, 0x64,
+	0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x36, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18,
+	0x03, 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, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x4c,
+	0x0a, 0x0e, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73,
+	0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
+	0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
+	0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x6f,
+	0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x1a, 0x36, 0x0a, 0x0c,
+	0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04,
+	0x72, 0x75, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65,
+	0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x04,
+	0x73, 0x69, 0x7a, 0x65, 0x22, 0x95, 0x05, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
+	0x3f, 0x0a, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73, 0x18, 0x01,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42,
+	0x02, 0x18, 0x01, 0x52, 0x0b, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x73,
+	0x12, 0x39, 0x0a, 0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18,
+	0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
+	0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52,
+	0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x39, 0x0a, 0x05, 0x48,
+	0x6f, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61,
+	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x2e, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x02, 0x18, 0x01, 0x52,
+	0x05, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,
+	0x5f, 0x69, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e,
+	0x74, 0x49, 0x70, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x68, 0x6f,
+	0x73, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
+	0x48, 0x6f, 0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x52, 0x0b, 0x73, 0x74, 0x61,
+	0x74, 0x69, 0x63, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x22, 0x0a, 0x0c, 0x64, 0x69,
+	0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08,
+	0x52, 0x0c, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x42,
+	0x0a, 0x0e, 0x71, 0x75, 0x65, 0x72, 0x79, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
+	0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
+	0x70, 0x2e, 0x64, 0x6e, 0x73, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74,
+	0x65, 0x67, 0x79, 0x52, 0x0d, 0x71, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65,
+	0x67, 0x79, 0x1a, 0x55, 0x0a, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
+	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
+	0x65, 0x79, 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1b, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e,
+	0x6e, 0x65, 0x74, 0x2e, 0x49, 0x50, 0x4f, 0x72, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x9a, 0x01, 0x0a, 0x0b, 0x48, 0x6f,
+	0x73, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x3c, 0x0a, 0x04, 0x74, 0x79, 0x70,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f,
+	0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70,
+	0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
+	0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12,
+	0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x02, 0x69, 0x70, 0x12,
+	0x25, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64, 0x5f, 0x64, 0x6f, 0x6d, 0x61, 0x69,
+	0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x64,
+	0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x2a, 0x35, 0x0a, 0x0d,
+	0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0a, 0x0a,
+	0x06, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45,
+	0x5f, 0x49, 0x50, 0x34, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x53, 0x45, 0x5f, 0x49, 0x50,
+	0x36, 0x10, 0x02, 0x42, 0x46, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
+	0x61, 0x70, 0x70, 0x2e, 0x64, 0x6e, 0x73, 0x50, 0x01, 0x5a, 0x21, 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, 0x61, 0x70, 0x70, 0x2f, 0x64, 0x6e, 0x73, 0xaa, 0x02, 0x0c, 0x58,
+	0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x44, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x33,
 }
 
 var (
@@ -617,39 +505,38 @@ func file_app_dns_config_proto_rawDescGZIP() []byte {
 	return file_app_dns_config_proto_rawDescData
 }
 
-var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
-var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
+var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
 var file_app_dns_config_proto_goTypes = []interface{}{
-	(DomainMatchingType)(0),           // 0: xray.app.dns.DomainMatchingType
-	(QueryStrategy)(0),                // 1: xray.app.dns.QueryStrategy
-	(*NameServer)(nil),                // 2: xray.app.dns.NameServer
-	(*Config)(nil),                    // 3: xray.app.dns.Config
-	(*NameServer_PriorityDomain)(nil), // 4: xray.app.dns.NameServer.PriorityDomain
-	(*NameServer_OriginalRule)(nil),   // 5: xray.app.dns.NameServer.OriginalRule
-	nil,                               // 6: xray.app.dns.Config.HostsEntry
-	(*Config_HostMapping)(nil),        // 7: xray.app.dns.Config.HostMapping
-	(*net.Endpoint)(nil),              // 8: xray.common.net.Endpoint
-	(*router.GeoIP)(nil),              // 9: xray.app.router.GeoIP
-	(*net.IPOrDomain)(nil),            // 10: xray.common.net.IPOrDomain
+	(QueryStrategy)(0),              // 0: xray.app.dns.QueryStrategy
+	(*NameServer)(nil),              // 1: xray.app.dns.NameServer
+	(*Config)(nil),                  // 2: xray.app.dns.Config
+	(*NameServer_OriginalRule)(nil), // 3: xray.app.dns.NameServer.OriginalRule
+	nil,                             // 4: xray.app.dns.Config.HostsEntry
+	(*Config_HostMapping)(nil),      // 5: xray.app.dns.Config.HostMapping
+	(*net.Endpoint)(nil),            // 6: xray.common.net.Endpoint
+	(*domain.Domain)(nil),           // 7: xray.common.matcher.domain.Domain
+	(*geoip.GeoIP)(nil),             // 8: xray.common.matcher.geoip.GeoIP
+	(*net.IPOrDomain)(nil),          // 9: xray.common.net.IPOrDomain
+	(domain.MatchingType)(0),        // 10: xray.common.matcher.domain.MatchingType
 }
 var file_app_dns_config_proto_depIdxs = []int32{
-	8,  // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
-	4,  // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.app.dns.NameServer.PriorityDomain
-	9,  // 2: xray.app.dns.NameServer.geoip:type_name -> xray.app.router.GeoIP
-	5,  // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
-	8,  // 4: xray.app.dns.Config.NameServers:type_name -> xray.common.net.Endpoint
-	2,  // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
-	6,  // 6: xray.app.dns.Config.Hosts:type_name -> xray.app.dns.Config.HostsEntry
-	7,  // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
-	1,  // 8: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
-	0,  // 9: xray.app.dns.NameServer.PriorityDomain.type:type_name -> xray.app.dns.DomainMatchingType
-	10, // 10: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain
-	0,  // 11: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
-	12, // [12:12] is the sub-list for method output_type
-	12, // [12:12] is the sub-list for method input_type
-	12, // [12:12] is the sub-list for extension type_name
-	12, // [12:12] is the sub-list for extension extendee
-	0,  // [0:12] is the sub-list for field type_name
+	6,  // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
+	7,  // 1: xray.app.dns.NameServer.prioritized_domain:type_name -> xray.common.matcher.domain.Domain
+	8,  // 2: xray.app.dns.NameServer.geoip:type_name -> xray.common.matcher.geoip.GeoIP
+	3,  // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
+	6,  // 4: xray.app.dns.Config.NameServers:type_name -> xray.common.net.Endpoint
+	1,  // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
+	4,  // 6: xray.app.dns.Config.Hosts:type_name -> xray.app.dns.Config.HostsEntry
+	5,  // 7: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
+	0,  // 8: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
+	9,  // 9: xray.app.dns.Config.HostsEntry.value:type_name -> xray.common.net.IPOrDomain
+	10, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.common.matcher.domain.MatchingType
+	11, // [11:11] is the sub-list for method output_type
+	11, // [11:11] is the sub-list for method input_type
+	11, // [11:11] is the sub-list for extension type_name
+	11, // [11:11] is the sub-list for extension extendee
+	0,  // [0:11] is the sub-list for field type_name
 }
 
 func init() { file_app_dns_config_proto_init() }
@@ -683,18 +570,6 @@ func file_app_dns_config_proto_init() {
 			}
 		}
 		file_app_dns_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*NameServer_PriorityDomain); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_app_dns_config_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*NameServer_OriginalRule); i {
 			case 0:
 				return &v.state
@@ -706,7 +581,7 @@ func file_app_dns_config_proto_init() {
 				return nil
 			}
 		}
-		file_app_dns_config_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+		file_app_dns_config_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Config_HostMapping); i {
 			case 0:
 				return &v.state
@@ -724,8 +599,8 @@ func file_app_dns_config_proto_init() {
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_app_dns_config_proto_rawDesc,
-			NumEnums:      2,
-			NumMessages:   6,
+			NumEnums:      1,
+			NumMessages:   5,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 5 - 16
app/dns/config.proto

@@ -8,34 +8,23 @@ option java_multiple_files = true;
 
 import "common/net/address.proto";
 import "common/net/destination.proto";
-import "app/router/config.proto";
+import "common/matcher/domain/domain.proto";
+import "common/matcher/geoip/geoip.proto";
 
 message NameServer {
   xray.common.net.Endpoint address = 1;
   bytes client_ip = 5;
 
-  message PriorityDomain {
-    DomainMatchingType type = 1;
-    string domain = 2;
-  }
-
   message OriginalRule {
     string rule = 1;
     uint32 size = 2;
   }
 
-  repeated PriorityDomain prioritized_domain = 2;
-  repeated xray.app.router.GeoIP geoip = 3;
+  repeated xray.common.matcher.domain.Domain prioritized_domain = 2;
+  repeated xray.common.matcher.geoip.GeoIP geoip = 3;
   repeated OriginalRule original_rules = 4;
 }
 
-enum DomainMatchingType {
-  Full = 0;
-  Subdomain = 1;
-  Keyword = 2;
-  Regex = 3;
-}
-
 enum QueryStrategy {
   USE_IP = 0;
   USE_IP4 = 1;
@@ -60,7 +49,7 @@ message Config {
   bytes client_ip = 3;
 
   message HostMapping {
-    DomainMatchingType type = 1;
+    xray.common.matcher.domain.MatchingType type = 1;
     string domain = 2;
 
     repeated bytes ip = 3;

+ 6 - 6
app/dns/dns.go

@@ -9,12 +9,12 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/xtls/xray-core/app/router"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/errors"
+	"github.com/xtls/xray-core/common/matcher/geoip"
+	"github.com/xtls/xray-core/common/matcher/str"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/session"
-	"github.com/xtls/xray-core/common/strmatcher"
 	"github.com/xtls/xray-core/features"
 	"github.com/xtls/xray-core/features/dns"
 )
@@ -28,7 +28,7 @@ type DNS struct {
 	hosts         *StaticHosts
 	clients       []*Client
 	ctx           context.Context
-	domainMatcher strmatcher.IndexMatcher
+	domainMatcher str.IndexMatcher
 	matcherInfos  []DomainMatcherInfo
 }
 
@@ -90,8 +90,8 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
 
 	// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
 	matcherInfos := make([]DomainMatcherInfo, domainRuleCount+1)
-	domainMatcher := &strmatcher.MatcherGroup{}
-	geoipContainer := router.GeoIPMatcherContainer{}
+	domainMatcher := &str.MatcherGroup{}
+	geoipContainer := geoip.GeoIPMatcherContainer{}
 
 	for _, endpoint := range config.NameServers {
 		features.PrintDeprecatedFeatureWarning("simple DNS server")
@@ -104,7 +104,7 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
 
 	for _, ns := range config.NameServer {
 		clientIdx := len(clients)
-		updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []DomainMatcherInfo) error {
+		updateDomain := func(domainRule str.Matcher, originalRuleIdx int, matcherInfos []DomainMatcherInfo) error {
 			midx := domainMatcher.Add(domainRule)
 			matcherInfos[midx] = DomainMatcherInfo{
 				clientIdx:     uint16(clientIdx),

+ 41 - 40
app/dns/dns_test.go

@@ -12,8 +12,9 @@ import (
 	"github.com/xtls/xray-core/app/policy"
 	"github.com/xtls/xray-core/app/proxyman"
 	_ "github.com/xtls/xray-core/app/proxyman/outbound"
-	"github.com/xtls/xray-core/app/router"
 	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/geoip"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/serial"
 	"github.com/xtls/xray-core/core"
@@ -303,10 +304,10 @@ func TestPrioritizedDomain(t *testing.T) {
 							},
 							Port: uint32(port),
 						},
-						PrioritizedDomain: []*NameServer_PriorityDomain{
+						PrioritizedDomain: []*domain.Domain{
 							{
-								Type:   DomainMatchingType_Full,
-								Domain: "google.com",
+								Type:  domain.MatchingType_Full,
+								Value: "google.com",
 							},
 						},
 					},
@@ -432,7 +433,7 @@ func TestStaticHostDomain(t *testing.T) {
 				},
 				StaticHosts: []*Config_HostMapping{
 					{
-						Type:          DomainMatchingType_Full,
+						Type:          domain.MatchingType_Full,
 						Domain:        "example.com",
 						ProxiedDomain: "google.com",
 					},
@@ -496,10 +497,10 @@ func TestIPMatch(t *testing.T) {
 							},
 							Port: uint32(port),
 						},
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{
 								CountryCode: "local",
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{
 										// inner ip, will not match
 										Ip:     []byte{192, 168, 11, 1},
@@ -520,10 +521,10 @@ func TestIPMatch(t *testing.T) {
 							},
 							Port: uint32(port),
 						},
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{
 								CountryCode: "test",
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{
 										Ip:     []byte{8, 8, 8, 8},
 										Prefix: 32,
@@ -532,7 +533,7 @@ func TestIPMatch(t *testing.T) {
 							},
 							{
 								CountryCode: "test",
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{
 										Ip:     []byte{8, 8, 8, 4},
 										Prefix: 32,
@@ -616,14 +617,14 @@ func TestLocalDomain(t *testing.T) {
 							},
 							Port: uint32(port),
 						},
-						PrioritizedDomain: []*NameServer_PriorityDomain{
+						PrioritizedDomain: []*domain.Domain{
 							// Equivalent of dotless:localhost
-							{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"},
+							{Type: domain.MatchingType_Regex, Value: "^[^.]*localhost[^.]*$"},
 						},
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{ // Will match localhost, localhost-a and localhost-b,
 								CountryCode: "local",
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{Ip: []byte{127, 0, 0, 2}, Prefix: 32},
 									{Ip: []byte{127, 0, 0, 3}, Prefix: 32},
 									{Ip: []byte{127, 0, 0, 4}, Prefix: 32},
@@ -641,22 +642,22 @@ func TestLocalDomain(t *testing.T) {
 							},
 							Port: uint32(port),
 						},
-						PrioritizedDomain: []*NameServer_PriorityDomain{
+						PrioritizedDomain: []*domain.Domain{
 							// Equivalent of dotless: and domain:local
-							{Type: DomainMatchingType_Regex, Domain: "^[^.]*$"},
-							{Type: DomainMatchingType_Subdomain, Domain: "local"},
-							{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
+							{Type: domain.MatchingType_Regex, Value: "^[^.]*$"},
+							{Type: domain.MatchingType_Subdomain, Value: "local"},
+							{Type: domain.MatchingType_Subdomain, Value: "localdomain"},
 						},
 					},
 				},
 				StaticHosts: []*Config_HostMapping{
 					{
-						Type:   DomainMatchingType_Full,
+						Type:   domain.MatchingType_Full,
 						Domain: "hostnamestatic",
 						Ip:     [][]byte{{127, 0, 0, 53}},
 					},
 					{
-						Type:          DomainMatchingType_Full,
+						Type:          domain.MatchingType_Full,
 						Domain:        "hostnamealias",
 						ProxiedDomain: "hostname.localdomain",
 					},
@@ -812,15 +813,15 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 							},
 							Port: uint32(port),
 						},
-						PrioritizedDomain: []*NameServer_PriorityDomain{
+						PrioritizedDomain: []*domain.Domain{
 							{
-								Type:   DomainMatchingType_Subdomain,
-								Domain: "google.com",
+								Type:  domain.MatchingType_Subdomain,
+								Value: "google.com",
 							},
 						},
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{ // Will only match 8.8.8.8 and 8.8.4.4
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
 									{Ip: []byte{8, 8, 4, 4}, Prefix: 32},
 								},
@@ -837,15 +838,15 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 							},
 							Port: uint32(port),
 						},
-						PrioritizedDomain: []*NameServer_PriorityDomain{
+						PrioritizedDomain: []*domain.Domain{
 							{
-								Type:   DomainMatchingType_Subdomain,
-								Domain: "google.com",
+								Type:  domain.MatchingType_Subdomain,
+								Value: "google.com",
 							},
 						},
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{ // Will match 8.8.8.8 and 8.8.8.7, etc
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{Ip: []byte{8, 8, 8, 7}, Prefix: 24},
 								},
 							},
@@ -861,15 +862,15 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 							},
 							Port: uint32(port),
 						},
-						PrioritizedDomain: []*NameServer_PriorityDomain{
+						PrioritizedDomain: []*domain.Domain{
 							{
-								Type:   DomainMatchingType_Subdomain,
-								Domain: "api.google.com",
+								Type:  domain.MatchingType_Subdomain,
+								Value: "api.google.com",
 							},
 						},
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{ // Will only match 8.8.7.7 (api.google.com)
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{Ip: []byte{8, 8, 7, 7}, Prefix: 32},
 								},
 							},
@@ -885,15 +886,15 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
 							},
 							Port: uint32(port),
 						},
-						PrioritizedDomain: []*NameServer_PriorityDomain{
+						PrioritizedDomain: []*domain.Domain{
 							{
-								Type:   DomainMatchingType_Full,
-								Domain: "v2.api.google.com",
+								Type:  domain.MatchingType_Full,
+								Value: "v2.api.google.com",
 							},
 						},
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{ // Will only match 8.8.7.8 (v2.api.google.com)
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{Ip: []byte{8, 8, 7, 8}, Prefix: 32},
 								},
 							},

+ 4 - 4
app/dns/hosts.go

@@ -2,8 +2,8 @@ package dns
 
 import (
 	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/matcher/str"
 	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/common/strmatcher"
 	"github.com/xtls/xray-core/features"
 	"github.com/xtls/xray-core/features/dns"
 )
@@ -11,12 +11,12 @@ import (
 // StaticHosts represents static domain-ip mapping in DNS server.
 type StaticHosts struct {
 	ips      [][]net.Address
-	matchers *strmatcher.MatcherGroup
+	matchers *str.MatcherGroup
 }
 
 // NewStaticHosts creates a new StaticHosts instance.
 func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDomain) (*StaticHosts, error) {
-	g := new(strmatcher.MatcherGroup)
+	g := new(str.MatcherGroup)
 	sh := &StaticHosts{
 		ips:      make([][]net.Address, len(hosts)+len(legacy)+16),
 		matchers: g,
@@ -26,7 +26,7 @@ func NewStaticHosts(hosts []*Config_HostMapping, legacy map[string]*net.IPOrDoma
 		features.PrintDeprecatedFeatureWarning("simple host mapping")
 
 		for domain, ip := range legacy {
-			matcher, err := strmatcher.Full.New(domain)
+			matcher, err := str.Full.New(domain)
 			common.Must(err)
 			id := g.Add(matcher)
 

+ 4 - 3
app/dns/hosts_test.go

@@ -7,6 +7,7 @@ import (
 
 	. "github.com/xtls/xray-core/app/dns"
 	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/matcher/domain"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/features/dns"
 )
@@ -14,21 +15,21 @@ import (
 func TestStaticHosts(t *testing.T) {
 	pb := []*Config_HostMapping{
 		{
-			Type:   DomainMatchingType_Full,
+			Type:   domain.MatchingType_Full,
 			Domain: "example.com",
 			Ip: [][]byte{
 				{1, 1, 1, 1},
 			},
 		},
 		{
-			Type:   DomainMatchingType_Subdomain,
+			Type:   domain.MatchingType_Subdomain,
 			Domain: "example.cn",
 			Ip: [][]byte{
 				{2, 2, 2, 2},
 			},
 		},
 		{
-			Type:   DomainMatchingType_Subdomain,
+			Type:   domain.MatchingType_Subdomain,
 			Domain: "baidu.com",
 			Ip: [][]byte{
 				{127, 0, 0, 1},

+ 6 - 6
app/dns/nameserver.go

@@ -6,10 +6,10 @@ import (
 	"strings"
 	"time"
 
-	"github.com/xtls/xray-core/app/router"
 	"github.com/xtls/xray-core/common/errors"
+	"github.com/xtls/xray-core/common/matcher/geoip"
+	"github.com/xtls/xray-core/common/matcher/str"
 	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/common/strmatcher"
 	core "github.com/xtls/xray-core/core"
 	"github.com/xtls/xray-core/features/dns"
 	"github.com/xtls/xray-core/features/routing"
@@ -28,7 +28,7 @@ type Client struct {
 	server    Server
 	clientIP  net.IP
 	domains   []string
-	expectIPs []*router.GeoIPMatcher
+	expectIPs []*geoip.GeoIPMatcher
 }
 
 var errExpectedIPNonMatch = errors.New("expectIPs not match")
@@ -63,7 +63,7 @@ func NewServer(dest net.Destination, dispatcher routing.Dispatcher) (Server, err
 }
 
 // NewClient creates a DNS client managing a name server with client IP, domain rules and expected IPs.
-func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container router.GeoIPMatcherContainer, matcherInfos *[]DomainMatcherInfo, updateDomainRule func(strmatcher.Matcher, int, []DomainMatcherInfo) error) (*Client, error) {
+func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container geoip.GeoIPMatcherContainer, matcherInfos *[]DomainMatcherInfo, updateDomainRule func(str.Matcher, int, []DomainMatcherInfo) error) (*Client, error) {
 	client := &Client{}
 	err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
 		// Create a new server for each client for now
@@ -93,7 +93,7 @@ func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container r
 		ruleCurr := 0
 		ruleIter := 0
 		for _, domain := range ns.PrioritizedDomain {
-			domainRule, err := toStrMatcher(domain.Type, domain.Domain)
+			domainRule, err := toStrMatcher(domain.Type, domain.Value)
 			if err != nil {
 				return newError("failed to create prioritized domain").Base(err).AtWarning()
 			}
@@ -119,7 +119,7 @@ func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container r
 		}
 
 		// Establish expected IPs
-		var matchers []*router.GeoIPMatcher
+		var matchers []*geoip.GeoIPMatcher
 		for _, geoip := range ns.Geoip {
 			matcher, err := container.Add(geoip)
 			if err != nil {

+ 4 - 2
app/router/command/command_test.go

@@ -12,6 +12,8 @@ import (
 	. "github.com/xtls/xray-core/app/router/command"
 	"github.com/xtls/xray-core/app/stats"
 	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/geoip"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/features/routing"
 	"github.com/xtls/xray-core/testing/mocks"
@@ -231,11 +233,11 @@ func TestSerivceTestRoute(t *testing.T) {
 				TargetTag:      &router.RoutingRule_Tag{Tag: "out"},
 			},
 			{
-				Domain:    []*router.Domain{{Type: router.Domain_Domain, Value: "com"}},
+				Domain:    []*domain.Domain{{Type: domain.MatchingType_Subdomain, Value: "com"}},
 				TargetTag: &router.RoutingRule_Tag{Tag: "out"},
 			},
 			{
-				SourceGeoip: []*router.GeoIP{{CountryCode: "private", Cidr: []*router.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},
+				SourceGeoip: []*geoip.GeoIP{{CountryCode: "private", Cidr: []*geoip.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},
 				TargetTag:   &router.RoutingRule_Tag{Tag: "out"},
 			},
 			{

+ 11 - 51
app/router/condition.go

@@ -6,8 +6,9 @@ import (
 	"go.starlark.net/starlark"
 	"go.starlark.net/syntax"
 
+	dm "github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/str"
 	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/common/strmatcher"
 	"github.com/xtls/xray-core/features/routing"
 )
 
@@ -41,14 +42,14 @@ func (v *ConditionChan) Len() int {
 	return len(*v)
 }
 
-var matcherTypeMap = map[Domain_Type]strmatcher.Type{
-	Domain_Plain:  strmatcher.Substr,
-	Domain_Regex:  strmatcher.Regex,
-	Domain_Domain: strmatcher.Domain,
-	Domain_Full:   strmatcher.Full,
+var matcherTypeMap = map[dm.MatchingType]str.Type{
+	dm.MatchingType_Keyword:   str.Substr,
+	dm.MatchingType_Regex:     str.Regex,
+	dm.MatchingType_Subdomain: str.Domain,
+	dm.MatchingType_Full:      str.Full,
 }
 
-func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
+func domainToMatcher(domain *dm.Domain) (str.Matcher, error) {
 	matcherType, f := matcherTypeMap[domain.Type]
 	if !f {
 		return nil, newError("unsupported domain type", domain.Type)
@@ -63,11 +64,11 @@ func domainToMatcher(domain *Domain) (strmatcher.Matcher, error) {
 }
 
 type DomainMatcher struct {
-	matchers strmatcher.IndexMatcher
+	matchers str.IndexMatcher
 }
 
-func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
-	g := new(strmatcher.MatcherGroup)
+func NewDomainMatcher(domains []*dm.Domain) (*DomainMatcher, error) {
+	g := new(str.MatcherGroup)
 	for _, d := range domains {
 		m, err := domainToMatcher(d)
 		if err != nil {
@@ -94,47 +95,6 @@ func (m *DomainMatcher) Apply(ctx routing.Context) bool {
 	return m.ApplyDomain(strings.ToLower(domain))
 }
 
-type MultiGeoIPMatcher struct {
-	matchers []*GeoIPMatcher
-	onSource bool
-}
-
-func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
-	var matchers []*GeoIPMatcher
-	for _, geoip := range geoips {
-		matcher, err := globalGeoIPContainer.Add(geoip)
-		if err != nil {
-			return nil, err
-		}
-		matchers = append(matchers, matcher)
-	}
-
-	matcher := &MultiGeoIPMatcher{
-		matchers: matchers,
-		onSource: onSource,
-	}
-
-	return matcher, nil
-}
-
-// Apply implements Condition.
-func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
-	var ips []net.IP
-	if m.onSource {
-		ips = ctx.GetSourceIPs()
-	} else {
-		ips = ctx.GetTargetIPs()
-	}
-	for _, ip := range ips {
-		for _, matcher := range m.matchers {
-			if matcher.Match(ip) {
-				return true
-			}
-		}
-	}
-	return false
-}
-
 type PortMatcher struct {
 	port     net.MemoryPortList
 	onSource bool

+ 41 - 19
app/router/condition_test.go

@@ -11,6 +11,9 @@ import (
 	. "github.com/xtls/xray-core/app/router"
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/errors"
+	"github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/geoip"
+	"github.com/xtls/xray-core/common/matcher/geosite"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/platform"
 	"github.com/xtls/xray-core/common/platform/filesystem"
@@ -26,10 +29,10 @@ func init() {
 	common.Must(err)
 
 	if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
-		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
+		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.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, "..", "..", "release", "config", "geosite.dat")))
+		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "resources", "geosite.dat")))
 	}
 }
 
@@ -61,18 +64,18 @@ func TestRoutingRule(t *testing.T) {
 	}{
 		{
 			rule: &RoutingRule{
-				Domain: []*Domain{
+				Domain: []*domain.Domain{
 					{
 						Value: "example.com",
-						Type:  Domain_Plain,
+						Type:  domain.MatchingType_Keyword,
 					},
 					{
 						Value: "google.com",
-						Type:  Domain_Domain,
+						Type:  domain.MatchingType_Subdomain,
 					},
 					{
 						Value: "^facebook\\.com$",
-						Type:  Domain_Regex,
+						Type:  domain.MatchingType_Regex,
 					},
 				},
 			},
@@ -109,7 +112,7 @@ func TestRoutingRule(t *testing.T) {
 		},
 		{
 			rule: &RoutingRule{
-				Cidr: []*CIDR{
+				Cidr: []*geoip.CIDR{
 					{
 						Ip:     []byte{8, 8, 8, 8},
 						Prefix: 32,
@@ -145,9 +148,9 @@ func TestRoutingRule(t *testing.T) {
 		},
 		{
 			rule: &RoutingRule{
-				Geoip: []*GeoIP{
+				Geoip: []*geoip.GeoIP{
 					{
-						Cidr: []*CIDR{
+						Cidr: []*geoip.CIDR{
 							{
 								Ip:     []byte{8, 8, 8, 8},
 								Prefix: 32,
@@ -185,7 +188,7 @@ func TestRoutingRule(t *testing.T) {
 		},
 		{
 			rule: &RoutingRule{
-				SourceCidr: []*CIDR{
+				SourceCidr: []*geoip.CIDR{
 					{
 						Ip:     []byte{192, 168, 0, 0},
 						Prefix: 16,
@@ -333,19 +336,19 @@ func TestRoutingRule(t *testing.T) {
 	}
 }
 
-func loadGeoSite(country string) ([]*Domain, error) {
+func loadGeoSite(country string) ([]*domain.Domain, error) {
 	geositeBytes, err := filesystem.ReadAsset("geosite.dat")
 	if err != nil {
 		return nil, err
 	}
-	var geositeList GeoSiteList
+	var geositeList geosite.GeoSiteList
 	if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
 		return nil, err
 	}
 
 	for _, site := range geositeList.Entry {
 		if site.CountryCode == country {
-			return site.Domain, nil
+			return geosite.ToDomains(site.Domain), nil
 		}
 	}
 
@@ -394,13 +397,32 @@ func TestChinaSites(t *testing.T) {
 	}
 }
 
+func loadGeoIP(country string) ([]*geoip.CIDR, error) {
+	geoipBytes, err := filesystem.ReadAsset("dat")
+	if err != nil {
+		return nil, err
+	}
+	var geoipList geoip.GeoIPList
+	if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
+		return nil, err
+	}
+
+	for _, geoip := range geoipList.Entry {
+		if geoip.CountryCode == country {
+			return geoip.Cidr, nil
+		}
+	}
+
+	panic("country not found: " + country)
+}
+
 func BenchmarkMultiGeoIPMatcher(b *testing.B) {
-	var geoips []*GeoIP
+	var geoips []*geoip.GeoIP
 
 	{
 		ips, err := loadGeoIP("CN")
 		common.Must(err)
-		geoips = append(geoips, &GeoIP{
+		geoips = append(geoips, &geoip.GeoIP{
 			CountryCode: "CN",
 			Cidr:        ips,
 		})
@@ -409,7 +431,7 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
 	{
 		ips, err := loadGeoIP("JP")
 		common.Must(err)
-		geoips = append(geoips, &GeoIP{
+		geoips = append(geoips, &geoip.GeoIP{
 			CountryCode: "JP",
 			Cidr:        ips,
 		})
@@ -418,7 +440,7 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
 	{
 		ips, err := loadGeoIP("CA")
 		common.Must(err)
-		geoips = append(geoips, &GeoIP{
+		geoips = append(geoips, &geoip.GeoIP{
 			CountryCode: "CA",
 			Cidr:        ips,
 		})
@@ -427,13 +449,13 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) {
 	{
 		ips, err := loadGeoIP("US")
 		common.Must(err)
-		geoips = append(geoips, &GeoIP{
+		geoips = append(geoips, &geoip.GeoIP{
 			CountryCode: "US",
 			Cidr:        ips,
 		})
 	}
 
-	matcher, err := NewMultiGeoIPMatcher(geoips, false)
+	matcher, err := geoip.NewMultiGeoIPMatcher(geoips, false)
 	common.Must(err)
 
 	ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})

+ 5 - 43
app/router/config.go

@@ -1,50 +1,12 @@
 package router
 
 import (
+	"github.com/xtls/xray-core/common/matcher/geoip"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/features/outbound"
 	"github.com/xtls/xray-core/features/routing"
 )
 
-// CIDRList is an alias of []*CIDR to provide sort.Interface.
-type CIDRList []*CIDR
-
-// Len implements sort.Interface.
-func (l *CIDRList) Len() int {
-	return len(*l)
-}
-
-// Less implements sort.Interface.
-func (l *CIDRList) Less(i int, j int) bool {
-	ci := (*l)[i]
-	cj := (*l)[j]
-
-	if len(ci.Ip) < len(cj.Ip) {
-		return true
-	}
-
-	if len(ci.Ip) > len(cj.Ip) {
-		return false
-	}
-
-	for k := 0; k < len(ci.Ip); k++ {
-		if ci.Ip[k] < cj.Ip[k] {
-			return true
-		}
-
-		if ci.Ip[k] > cj.Ip[k] {
-			return false
-		}
-	}
-
-	return ci.Prefix < cj.Prefix
-}
-
-// Swap implements sort.Interface.
-func (l *CIDRList) Swap(i int, j int) {
-	(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
-}
-
 type Rule struct {
 	Tag       string
 	Balancer  *Balancer
@@ -99,13 +61,13 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
 	}
 
 	if len(rr.Geoip) > 0 {
-		cond, err := NewMultiGeoIPMatcher(rr.Geoip, false)
+		cond, err := geoip.NewMultiGeoIPMatcher(rr.Geoip, false)
 		if err != nil {
 			return nil, err
 		}
 		conds.Add(cond)
 	} else if len(rr.Cidr) > 0 {
-		cond, err := NewMultiGeoIPMatcher([]*GeoIP{{Cidr: rr.Cidr}}, false)
+		cond, err := geoip.NewMultiGeoIPMatcher([]*geoip.GeoIP{{Cidr: rr.Cidr}}, false)
 		if err != nil {
 			return nil, err
 		}
@@ -113,13 +75,13 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
 	}
 
 	if len(rr.SourceGeoip) > 0 {
-		cond, err := NewMultiGeoIPMatcher(rr.SourceGeoip, true)
+		cond, err := geoip.NewMultiGeoIPMatcher(rr.SourceGeoip, true)
 		if err != nil {
 			return nil, err
 		}
 		conds.Add(cond)
 	} else if len(rr.SourceCidr) > 0 {
-		cond, err := NewMultiGeoIPMatcher([]*GeoIP{{Cidr: rr.SourceCidr}}, true)
+		cond, err := geoip.NewMultiGeoIPMatcher([]*geoip.GeoIP{{Cidr: rr.SourceCidr}}, true)
 		if err != nil {
 			return nil, err
 		}

Fișier diff suprimat deoarece este prea mare
+ 138 - 727
app/router/config.pb.go


+ 7 - 65
app/router/config.proto

@@ -8,66 +8,8 @@ option java_multiple_files = true;
 
 import "common/net/port.proto";
 import "common/net/network.proto";
-
-// Domain for routing decision.
-message Domain {
-  // Type of domain value.
-  enum Type {
-    // The value is used as is.
-    Plain = 0;
-    // The value is used as a regular expression.
-    Regex = 1;
-    // The value is a root domain.
-    Domain = 2;
-    // The value is a domain.
-    Full = 3;
-  }
-
-  // Domain matching type.
-  Type type = 1;
-
-  // Domain value.
-  string value = 2;
-
-  message Attribute {
-    string key = 1;
-
-    oneof typed_value {
-      bool bool_value = 2;
-      int64 int_value = 3;
-    }
-  }
-
-  // Attributes of this domain. May be used for filtering.
-  repeated Attribute attribute = 3;
-}
-
-// IP for routing decision, in CIDR form.
-message CIDR {
-  // IP address, should be either 4 or 16 bytes.
-  bytes ip = 1;
-
-  // Number of leading ones in the network mask.
-  uint32 prefix = 2;
-}
-
-message GeoIP {
-  string country_code = 1;
-  repeated CIDR cidr = 2;
-}
-
-message GeoIPList {
-  repeated GeoIP entry = 1;
-}
-
-message GeoSite {
-  string country_code = 1;
-  repeated Domain domain = 2;
-}
-
-message GeoSiteList {
-  repeated GeoSite entry = 1;
-}
+import "common/matcher/domain/domain.proto";
+import "common/matcher/geoip/geoip.proto";
 
 message RoutingRule {
   oneof target_tag {
@@ -79,17 +21,17 @@ message RoutingRule {
   }
 
   // List of domains for target domain matching.
-  repeated Domain domain = 2;
+  repeated xray.common.matcher.domain.Domain domain = 2;
 
   // List of CIDRs for target IP address matching.
   // Deprecated. Use geoip below.
-  repeated CIDR cidr = 3 [deprecated = true];
+  repeated xray.common.matcher.geoip.CIDR cidr = 3 [deprecated = true];
 
   // List of GeoIPs for target IP address matching. If this entry exists, the
   // cidr above will have no effect. GeoIP fields with the same country code are
   // supposed to contain exactly same content. They will be merged during
   // runtime. For customized GeoIPs, please leave country code empty.
-  repeated GeoIP geoip = 10;
+  repeated xray.common.matcher.geoip.GeoIP geoip = 10;
 
   // A range of port [from, to]. If the destination port is in this range, this
   // rule takes effect. Deprecated. Use port_list.
@@ -105,11 +47,11 @@ message RoutingRule {
   repeated xray.common.net.Network networks = 13;
 
   // List of CIDRs for source IP address matching.
-  repeated CIDR source_cidr = 6 [deprecated = true];
+  repeated xray.common.matcher.geoip.CIDR source_cidr = 6 [deprecated = true];
 
   // List of GeoIPs for source IP address matching. If this entry exists, the
   // source_cidr above will have no effect.
-  repeated GeoIP source_geoip = 11;
+  repeated xray.common.matcher.geoip.GeoIP source_geoip = 11;
 
   // List of ports for source port matching.
   xray.common.net.PortList source_port_list = 16;

+ 4 - 3
app/router/router_test.go

@@ -7,6 +7,7 @@ import (
 	"github.com/golang/mock/gomock"
 	. "github.com/xtls/xray-core/app/router"
 	"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/session"
 	"github.com/xtls/xray-core/features/outbound"
@@ -101,7 +102,7 @@ func TestIPOnDemand(t *testing.T) {
 				TargetTag: &RoutingRule_Tag{
 					Tag: "test",
 				},
-				Cidr: []*CIDR{
+				Cidr: []*geoip.CIDR{
 					{
 						Ip:     []byte{192, 168, 0, 0},
 						Prefix: 16,
@@ -136,7 +137,7 @@ func TestIPIfNonMatchDomain(t *testing.T) {
 				TargetTag: &RoutingRule_Tag{
 					Tag: "test",
 				},
-				Cidr: []*CIDR{
+				Cidr: []*geoip.CIDR{
 					{
 						Ip:     []byte{192, 168, 0, 0},
 						Prefix: 16,
@@ -171,7 +172,7 @@ func TestIPIfNonMatchIP(t *testing.T) {
 				TargetTag: &RoutingRule_Tag{
 					Tag: "test",
 				},
-				Cidr: []*CIDR{
+				Cidr: []*geoip.CIDR{
 					{
 						Ip:     []byte{127, 0, 0, 0},
 						Prefix: 8,

+ 2 - 2
app/stats/command/command.go

@@ -11,7 +11,7 @@ import (
 
 	"github.com/xtls/xray-core/app/stats"
 	"github.com/xtls/xray-core/common"
-	"github.com/xtls/xray-core/common/strmatcher"
+	"github.com/xtls/xray-core/common/matcher/str"
 	"github.com/xtls/xray-core/core"
 	feature_stats "github.com/xtls/xray-core/features/stats"
 )
@@ -49,7 +49,7 @@ func (s *statsServer) GetStats(ctx context.Context, request *GetStatsRequest) (*
 }
 
 func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
-	matcher, err := strmatcher.Substr.New(request.Pattern)
+	matcher, err := str.Substr.New(request.Pattern)
 	if err != nil {
 		return nil, err
 	}

+ 3 - 0
common/matcher/conf/conf.go

@@ -0,0 +1,3 @@
+package conf
+
+//go:generate go run github.com/xtls/xray-core/common/errors/errorgen

+ 78 - 0
common/matcher/conf/domain.go

@@ -0,0 +1,78 @@
+package conf
+
+import (
+	"strings"
+
+	dm "github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/geosite"
+)
+
+func ParaseDomainRule(domain string) ([]*dm.Domain, error) {
+	if strings.HasPrefix(domain, "geosite:") {
+		country := strings.ToUpper(domain[8:])
+		domains, err := geosite.LoadGeositeWithAttr("geosite.dat", country)
+		if err != nil {
+			return nil, newError("failed to load geosite: ", country).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]
+		country := kv[1]
+		domains, err := geosite.LoadGeositeWithAttr(filename, country)
+		if err != nil {
+			return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err)
+		}
+		return domains, nil
+	}
+
+	domainRule := new(dm.Domain)
+	switch {
+	case strings.HasPrefix(domain, "regexp:"):
+		domainRule.Type = dm.MatchingType_Regex
+		domainRule.Value = domain[7:]
+
+	case strings.HasPrefix(domain, "domain:"):
+		domainRule.Type = dm.MatchingType_Subdomain
+		domainRule.Value = domain[7:]
+
+	case strings.HasPrefix(domain, "full:"):
+		domainRule.Type = dm.MatchingType_Full
+		domainRule.Value = domain[5:]
+
+	case strings.HasPrefix(domain, "keyword:"):
+		domainRule.Type = dm.MatchingType_Keyword
+		domainRule.Value = domain[8:]
+
+	case strings.HasPrefix(domain, "dotless:"):
+		domainRule.Type = dm.MatchingType_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 = dm.MatchingType_Keyword
+		domainRule.Value = domain
+	}
+	return []*dm.Domain{domainRule}, nil
+}

+ 9 - 0
common/matcher/conf/errors.generated.go

@@ -0,0 +1,9 @@
+package conf
+
+import "github.com/xtls/xray-core/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}

+ 3 - 0
common/matcher/domain/domain.go

@@ -0,0 +1,3 @@
+package domain
+
+//go:generate go run github.com/xtls/xray-core/common/errors/errorgen

+ 229 - 0
common/matcher/domain/domain.pb.go

@@ -0,0 +1,229 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.25.0
+// 	protoc        v3.15.6
+// source: common/matcher/domain/domain.proto
+
+package domain
+
+import (
+	proto "github.com/golang/protobuf/proto"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// This is a compile-time assertion that a sufficiently up-to-date version
+// of the legacy proto package is being used.
+const _ = proto.ProtoPackageIsVersion4
+
+type MatchingType int32
+
+const (
+	MatchingType_Full      MatchingType = 0
+	MatchingType_Subdomain MatchingType = 1
+	MatchingType_Keyword   MatchingType = 2
+	MatchingType_Regex     MatchingType = 3
+)
+
+// Enum value maps for MatchingType.
+var (
+	MatchingType_name = map[int32]string{
+		0: "Full",
+		1: "Subdomain",
+		2: "Keyword",
+		3: "Regex",
+	}
+	MatchingType_value = map[string]int32{
+		"Full":      0,
+		"Subdomain": 1,
+		"Keyword":   2,
+		"Regex":     3,
+	}
+)
+
+func (x MatchingType) Enum() *MatchingType {
+	p := new(MatchingType)
+	*p = x
+	return p
+}
+
+func (x MatchingType) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (MatchingType) Descriptor() protoreflect.EnumDescriptor {
+	return file_common_matcher_domain_domain_proto_enumTypes[0].Descriptor()
+}
+
+func (MatchingType) Type() protoreflect.EnumType {
+	return &file_common_matcher_domain_domain_proto_enumTypes[0]
+}
+
+func (x MatchingType) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use MatchingType.Descriptor instead.
+func (MatchingType) EnumDescriptor() ([]byte, []int) {
+	return file_common_matcher_domain_domain_proto_rawDescGZIP(), []int{0}
+}
+
+type Domain struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Domain matching type.
+	Type MatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.common.matcher.domain.MatchingType" json:"type,omitempty"`
+	// Domain value.
+	Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
+}
+
+func (x *Domain) Reset() {
+	*x = Domain{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_matcher_domain_domain_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Domain) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Domain) ProtoMessage() {}
+
+func (x *Domain) ProtoReflect() protoreflect.Message {
+	mi := &file_common_matcher_domain_domain_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Domain.ProtoReflect.Descriptor instead.
+func (*Domain) Descriptor() ([]byte, []int) {
+	return file_common_matcher_domain_domain_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Domain) GetType() MatchingType {
+	if x != nil {
+		return x.Type
+	}
+	return MatchingType_Full
+}
+
+func (x *Domain) GetValue() string {
+	if x != nil {
+		return x.Value
+	}
+	return ""
+}
+
+var File_common_matcher_domain_domain_proto protoreflect.FileDescriptor
+
+var file_common_matcher_domain_domain_proto_rawDesc = []byte{
+	0x0a, 0x22, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
+	0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
+	0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e,
+	0x22, 0x5c, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3c, 0x0a, 0x04, 0x74, 0x79,
+	0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
+	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64,
+	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79,
+	0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x3f,
+	0x0a, 0x0c, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x08,
+	0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x75, 0x62, 0x64,
+	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x4b, 0x65, 0x79, 0x77, 0x6f,
+	0x72, 0x64, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x03, 0x42,
+	0x70, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69,
+	0x6e, 0x50, 0x01, 0x5a, 0x2f, 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, 0x64, 0x6f,
+	0x6d, 0x61, 0x69, 0x6e, 0xaa, 0x02, 0x1a, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
+	0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_common_matcher_domain_domain_proto_rawDescOnce sync.Once
+	file_common_matcher_domain_domain_proto_rawDescData = file_common_matcher_domain_domain_proto_rawDesc
+)
+
+func file_common_matcher_domain_domain_proto_rawDescGZIP() []byte {
+	file_common_matcher_domain_domain_proto_rawDescOnce.Do(func() {
+		file_common_matcher_domain_domain_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_matcher_domain_domain_proto_rawDescData)
+	})
+	return file_common_matcher_domain_domain_proto_rawDescData
+}
+
+var file_common_matcher_domain_domain_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_common_matcher_domain_domain_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_common_matcher_domain_domain_proto_goTypes = []interface{}{
+	(MatchingType)(0), // 0: xray.common.matcher.domain.MatchingType
+	(*Domain)(nil),    // 1: xray.common.matcher.domain.Domain
+}
+var file_common_matcher_domain_domain_proto_depIdxs = []int32{
+	0, // 0: xray.common.matcher.domain.Domain.type:type_name -> xray.common.matcher.domain.MatchingType
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_common_matcher_domain_domain_proto_init() }
+func file_common_matcher_domain_domain_proto_init() {
+	if File_common_matcher_domain_domain_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_common_matcher_domain_domain_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Domain); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_common_matcher_domain_domain_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_common_matcher_domain_domain_proto_goTypes,
+		DependencyIndexes: file_common_matcher_domain_domain_proto_depIdxs,
+		EnumInfos:         file_common_matcher_domain_domain_proto_enumTypes,
+		MessageInfos:      file_common_matcher_domain_domain_proto_msgTypes,
+	}.Build()
+	File_common_matcher_domain_domain_proto = out.File
+	file_common_matcher_domain_domain_proto_rawDesc = nil
+	file_common_matcher_domain_domain_proto_goTypes = nil
+	file_common_matcher_domain_domain_proto_depIdxs = nil
+}

+ 39 - 0
common/matcher/domain/domain.proto

@@ -0,0 +1,39 @@
+syntax = "proto3";
+
+package xray.common.matcher.domain;
+option csharp_namespace = "Xray.Common.Matcher.Domain";
+option go_package = "github.com/xtls/xray-core/common/matcher/domain";
+option java_package = "com.xray.common.matcher.domain";
+option java_multiple_files = true;
+
+enum MatchingType {
+  Full = 0;
+  Subdomain = 1;
+  Keyword = 2;
+  Regex = 3;
+}
+
+message Domain {
+  // Domain matching type.
+  MatchingType type = 1;
+
+  // Domain value.
+  string value = 2;
+}
+
+/*
+func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
+	switch t {
+	case router.Domain_Domain:
+		return dns.DomainMatchingType_Subdomain
+	case router.Domain_Full:
+		return dns.DomainMatchingType_Full
+	case router.Domain_Plain:
+		return dns.DomainMatchingType_Keyword
+	case router.Domain_Regex:
+		return dns.DomainMatchingType_Regex
+	default:
+		panic("unknown domain type")
+	}
+}
+ */

+ 9 - 0
common/matcher/domain/errors.generated.go

@@ -0,0 +1,9 @@
+package domain
+
+import "github.com/xtls/xray-core/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}

+ 90 - 0
common/matcher/geoip/conf.go

@@ -0,0 +1,90 @@
+package geoip
+
+import (
+	"runtime"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/xtls/xray-core/common/platform/filesystem"
+)
+
+var (
+	FileCache = make(map[string][]byte)
+	IPCache   = make(map[string]*GeoIP)
+)
+
+func LoadGeoIP(code string) ([]*CIDR, error) {
+	return LoadIPFile("geoip.dat", code)
+}
+
+func LoadIPFile(file, code string) ([]*CIDR, error) {
+	index := file + ":" + code
+	if IPCache[index] == nil {
+		bs, err := loadFile(file)
+		if err != nil {
+			return nil, newError("failed to load file: ", file).Base(err)
+		}
+		bs = find(bs, []byte(code))
+		if bs == nil {
+			return nil, newError("code not found in ", file, ": ", code)
+		}
+		var geoipdat GeoIP
+		if err := proto.Unmarshal(bs, &geoipdat); err != nil {
+			return nil, newError("error unmarshal IP in ", file, ": ", code).Base(err)
+		}
+		defer runtime.GC()        // or debug.FreeOSMemory()
+		return geoipdat.Cidr, nil // do not cache geoip
+		IPCache[index] = &geoipdat
+	}
+	return IPCache[index].Cidr, nil
+}
+
+func loadFile(file string) ([]byte, error) {
+	if FileCache[file] == nil {
+		bs, err := filesystem.ReadAsset(file)
+		if err != nil {
+			return nil, newError("failed to open file: ", file).Base(err)
+		}
+		if len(bs) == 0 {
+			return nil, newError("empty file: ", file)
+		}
+		// Do not cache file, may save RAM when there
+		// are many files, but consume CPU each time.
+		return bs, nil
+		FileCache[file] = bs
+	}
+	return FileCache[file], nil
+}
+
+func find(data, code []byte) []byte {
+	codeL := len(code)
+	if codeL == 0 {
+		return nil
+	}
+	for {
+		dataL := len(data)
+		if dataL < 2 {
+			return nil
+		}
+		x, y := proto.DecodeVarint(data[1:])
+		if x == 0 && y == 0 {
+			return nil
+		}
+		headL, bodyL := 1+y, int(x)
+		dataL -= headL
+		if dataL < bodyL {
+			return nil
+		}
+		data = data[headL:]
+		if int(data[1]) == codeL {
+			for i := 0; i < codeL && data[2+i] == code[i]; i++ {
+				if i+1 == codeL {
+					return data[:bodyL]
+				}
+			}
+		}
+		if dataL == bodyL {
+			return nil
+		}
+		data = data[bodyL:]
+	}
+}

+ 40 - 0
common/matcher/geoip/crid.go

@@ -0,0 +1,40 @@
+package geoip
+
+// CIDRList is an alias of []*CIDR to provide sort.Interface.
+type CIDRList []*CIDR
+
+// Len implements sort.Interface.
+func (l *CIDRList) Len() int {
+	return len(*l)
+}
+
+// Less implements sort.Interface.
+func (l *CIDRList) Less(i int, j int) bool {
+	ci := (*l)[i]
+	cj := (*l)[j]
+
+	if len(ci.Ip) < len(cj.Ip) {
+		return true
+	}
+
+	if len(ci.Ip) > len(cj.Ip) {
+		return false
+	}
+
+	for k := 0; k < len(ci.Ip); k++ {
+		if ci.Ip[k] < cj.Ip[k] {
+			return true
+		}
+
+		if ci.Ip[k] > cj.Ip[k] {
+			return false
+		}
+	}
+
+	return ci.Prefix < cj.Prefix
+}
+
+// Swap implements sort.Interface.
+func (l *CIDRList) Swap(i int, j int) {
+	(*l)[i], (*l)[j] = (*l)[j], (*l)[i]
+}

+ 9 - 0
common/matcher/geoip/errors.generated.go

@@ -0,0 +1,9 @@
+package geoip
+
+import "github.com/xtls/xray-core/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}

+ 4 - 2
app/router/condition_geoip.go → common/matcher/geoip/geoip.go

@@ -1,4 +1,4 @@
-package router
+package geoip
 
 import (
 	"encoding/binary"
@@ -7,6 +7,8 @@ import (
 	"github.com/xtls/xray-core/common/net"
 )
 
+//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
+
 type ipv6 struct {
 	a uint64
 	b uint64
@@ -187,5 +189,5 @@ func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
 }
 
 var (
-	globalGeoIPContainer GeoIPMatcherContainer
+	GlobalGeoIPContainer GeoIPMatcherContainer
 )

+ 307 - 0
common/matcher/geoip/geoip.pb.go

@@ -0,0 +1,307 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.25.0
+// 	protoc        v3.15.6
+// source: common/matcher/geoip/geoip.proto
+
+package geoip
+
+import (
+	proto "github.com/golang/protobuf/proto"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// This is a compile-time assertion that a sufficiently up-to-date version
+// of the legacy proto package is being used.
+const _ = proto.ProtoPackageIsVersion4
+
+// IP for routing decision, in CIDR form.
+type CIDR struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// IP address, should be either 4 or 16 bytes.
+	Ip []byte `protobuf:"bytes,1,opt,name=ip,proto3" json:"ip,omitempty"`
+	// Number of leading ones in the network mask.
+	Prefix uint32 `protobuf:"varint,2,opt,name=prefix,proto3" json:"prefix,omitempty"`
+}
+
+func (x *CIDR) Reset() {
+	*x = CIDR{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_matcher_geoip_geoip_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CIDR) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CIDR) ProtoMessage() {}
+
+func (x *CIDR) ProtoReflect() protoreflect.Message {
+	mi := &file_common_matcher_geoip_geoip_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CIDR.ProtoReflect.Descriptor instead.
+func (*CIDR) Descriptor() ([]byte, []int) {
+	return file_common_matcher_geoip_geoip_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *CIDR) GetIp() []byte {
+	if x != nil {
+		return x.Ip
+	}
+	return nil
+}
+
+func (x *CIDR) GetPrefix() uint32 {
+	if x != nil {
+		return x.Prefix
+	}
+	return 0
+}
+
+type GeoIP struct {
+	state         protoimpl.MessageState
+	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"`
+}
+
+func (x *GeoIP) Reset() {
+	*x = GeoIP{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_matcher_geoip_geoip_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GeoIP) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GeoIP) ProtoMessage() {}
+
+func (x *GeoIP) ProtoReflect() protoreflect.Message {
+	mi := &file_common_matcher_geoip_geoip_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
+func (*GeoIP) Descriptor() ([]byte, []int) {
+	return file_common_matcher_geoip_geoip_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GeoIP) GetCountryCode() string {
+	if x != nil {
+		return x.CountryCode
+	}
+	return ""
+}
+
+func (x *GeoIP) GetCidr() []*CIDR {
+	if x != nil {
+		return x.Cidr
+	}
+	return nil
+}
+
+type GeoIPList struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Entry []*GeoIP `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
+}
+
+func (x *GeoIPList) Reset() {
+	*x = GeoIPList{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_matcher_geoip_geoip_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GeoIPList) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GeoIPList) ProtoMessage() {}
+
+func (x *GeoIPList) ProtoReflect() protoreflect.Message {
+	mi := &file_common_matcher_geoip_geoip_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.
+func (*GeoIPList) Descriptor() ([]byte, []int) {
+	return file_common_matcher_geoip_geoip_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *GeoIPList) GetEntry() []*GeoIP {
+	if x != nil {
+		return x.Entry
+	}
+	return nil
+}
+
+var File_common_matcher_geoip_geoip_proto protoreflect.FileDescriptor
+
+var file_common_matcher_geoip_geoip_proto_rawDesc = []byte{
+	0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
+	0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x12, 0x19, 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, 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,
+	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 (
+	file_common_matcher_geoip_geoip_proto_rawDescOnce sync.Once
+	file_common_matcher_geoip_geoip_proto_rawDescData = file_common_matcher_geoip_geoip_proto_rawDesc
+)
+
+func file_common_matcher_geoip_geoip_proto_rawDescGZIP() []byte {
+	file_common_matcher_geoip_geoip_proto_rawDescOnce.Do(func() {
+		file_common_matcher_geoip_geoip_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_matcher_geoip_geoip_proto_rawDescData)
+	})
+	return file_common_matcher_geoip_geoip_proto_rawDescData
+}
+
+var file_common_matcher_geoip_geoip_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
+var file_common_matcher_geoip_geoip_proto_goTypes = []interface{}{
+	(*CIDR)(nil),      // 0: xray.common.matcher.geoip.CIDR
+	(*GeoIP)(nil),     // 1: xray.common.matcher.geoip.GeoIP
+	(*GeoIPList)(nil), // 2: xray.common.matcher.geoip.GeoIPList
+}
+var file_common_matcher_geoip_geoip_proto_depIdxs = []int32{
+	0, // 0: xray.common.matcher.geoip.GeoIP.cidr:type_name -> xray.common.matcher.geoip.CIDR
+	1, // 1: xray.common.matcher.geoip.GeoIPList.entry:type_name -> xray.common.matcher.geoip.GeoIP
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
+}
+
+func init() { file_common_matcher_geoip_geoip_proto_init() }
+func file_common_matcher_geoip_geoip_proto_init() {
+	if File_common_matcher_geoip_geoip_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_common_matcher_geoip_geoip_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CIDR); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_common_matcher_geoip_geoip_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GeoIP); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_common_matcher_geoip_geoip_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GeoIPList); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_common_matcher_geoip_geoip_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   3,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_common_matcher_geoip_geoip_proto_goTypes,
+		DependencyIndexes: file_common_matcher_geoip_geoip_proto_depIdxs,
+		MessageInfos:      file_common_matcher_geoip_geoip_proto_msgTypes,
+	}.Build()
+	File_common_matcher_geoip_geoip_proto = out.File
+	file_common_matcher_geoip_geoip_proto_rawDesc = nil
+	file_common_matcher_geoip_geoip_proto_goTypes = nil
+	file_common_matcher_geoip_geoip_proto_depIdxs = nil
+}

+ 25 - 0
common/matcher/geoip/geoip.proto

@@ -0,0 +1,25 @@
+syntax = "proto3";
+
+package xray.common.matcher.geoip;
+option csharp_namespace = "Xray.Common.Matcher.GeoIP";
+option go_package = "github.com/xtls/xray-core/common/matcher/geoip";
+option java_package = "com.xray.common.matcher.geoip";
+option java_multiple_files = true;
+
+// IP for routing decision, in CIDR form.
+message CIDR {
+  // IP address, should be either 4 or 16 bytes.
+  bytes ip = 1;
+
+  // Number of leading ones in the network mask.
+  uint32 prefix = 2;
+}
+
+message GeoIP {
+  string country_code = 1;
+  repeated CIDR cidr = 2;
+}
+
+message GeoIPList {
+  repeated GeoIP entry = 1;
+}

+ 16 - 16
app/router/condition_geoip_test.go → common/matcher/geoip/geoip_test.go

@@ -1,4 +1,4 @@
-package router_test
+package geoip_test
 
 import (
 	"os"
@@ -6,8 +6,8 @@ import (
 	"testing"
 
 	"github.com/golang/protobuf/proto"
-	"github.com/xtls/xray-core/app/router"
 	"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"
@@ -18,27 +18,27 @@ func init() {
 	common.Must(err)
 
 	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")))
+		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.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")))
+		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geosite.dat")))
 	}
 }
 
 func TestGeoIPMatcherContainer(t *testing.T) {
-	container := &router.GeoIPMatcherContainer{}
+	container := &GeoIPMatcherContainer{}
 
-	m1, err := container.Add(&router.GeoIP{
+	m1, err := container.Add(&GeoIP{
 		CountryCode: "CN",
 	})
 	common.Must(err)
 
-	m2, err := container.Add(&router.GeoIP{
+	m2, err := container.Add(&GeoIP{
 		CountryCode: "US",
 	})
 	common.Must(err)
 
-	m3, err := container.Add(&router.GeoIP{
+	m3, err := container.Add(&GeoIP{
 		CountryCode: "CN",
 	})
 	common.Must(err)
@@ -53,7 +53,7 @@ func TestGeoIPMatcherContainer(t *testing.T) {
 }
 
 func TestGeoIPMatcher(t *testing.T) {
-	cidrList := router.CIDRList{
+	cidrList := CIDRList{
 		{Ip: []byte{0, 0, 0, 0}, Prefix: 8},
 		{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
 		{Ip: []byte{100, 64, 0, 0}, Prefix: 10},
@@ -70,7 +70,7 @@ func TestGeoIPMatcher(t *testing.T) {
 		{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
 	}
 
-	matcher := &router.GeoIPMatcher{}
+	matcher := &GeoIPMatcher{}
 	common.Must(matcher.Init(cidrList))
 
 	testCases := []struct {
@@ -127,7 +127,7 @@ func TestGeoIPMatcher4CN(t *testing.T) {
 	ips, err := loadGeoIP("CN")
 	common.Must(err)
 
-	matcher := &router.GeoIPMatcher{}
+	matcher := &GeoIPMatcher{}
 	common.Must(matcher.Init(ips))
 
 	if matcher.Match([]byte{8, 8, 8, 8}) {
@@ -139,7 +139,7 @@ func TestGeoIPMatcher6US(t *testing.T) {
 	ips, err := loadGeoIP("US")
 	common.Must(err)
 
-	matcher := &router.GeoIPMatcher{}
+	matcher := &GeoIPMatcher{}
 	common.Must(matcher.Init(ips))
 
 	if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) {
@@ -147,12 +147,12 @@ func TestGeoIPMatcher6US(t *testing.T) {
 	}
 }
 
-func loadGeoIP(country string) ([]*router.CIDR, error) {
+func loadGeoIP(country string) ([]*CIDR, error) {
 	geoipBytes, err := filesystem.ReadAsset("geoip.dat")
 	if err != nil {
 		return nil, err
 	}
-	var geoipList router.GeoIPList
+	var geoipList GeoIPList
 	if err := proto.Unmarshal(geoipBytes, &geoipList); err != nil {
 		return nil, err
 	}
@@ -170,7 +170,7 @@ func BenchmarkGeoIPMatcher4CN(b *testing.B) {
 	ips, err := loadGeoIP("CN")
 	common.Must(err)
 
-	matcher := &router.GeoIPMatcher{}
+	matcher := &GeoIPMatcher{}
 	common.Must(matcher.Init(ips))
 
 	b.ResetTimer()
@@ -184,7 +184,7 @@ func BenchmarkGeoIPMatcher6US(b *testing.B) {
 	ips, err := loadGeoIP("US")
 	common.Must(err)
 
-	matcher := &router.GeoIPMatcher{}
+	matcher := &GeoIPMatcher{}
 	common.Must(matcher.Init(ips))
 
 	b.ResetTimer()

+ 47 - 0
common/matcher/geoip/matcher.go

@@ -0,0 +1,47 @@
+package geoip
+
+import (
+	"github.com/xtls/xray-core/common/net"
+	"github.com/xtls/xray-core/features/routing"
+)
+
+type MultiGeoIPMatcher struct {
+	matchers []*GeoIPMatcher
+	onSource bool
+}
+
+func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
+	var matchers []*GeoIPMatcher
+	for _, geoip := range geoips {
+		matcher, err := GlobalGeoIPContainer.Add(geoip)
+		if err != nil {
+			return nil, err
+		}
+		matchers = append(matchers, matcher)
+	}
+
+	matcher := &MultiGeoIPMatcher{
+		matchers: matchers,
+		onSource: onSource,
+	}
+
+	return matcher, nil
+}
+
+// Apply implements Condition.
+func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
+	var ips []net.IP
+	if m.onSource {
+		ips = ctx.GetSourceIPs()
+	} else {
+		ips = ctx.GetTargetIPs()
+	}
+	for _, ip := range ips {
+		for _, matcher := range m.matchers {
+			if matcher.Match(ip) {
+				return true
+			}
+		}
+	}
+	return false
+}

+ 33 - 0
common/matcher/geosite/attribute.go

@@ -0,0 +1,33 @@
+package geosite
+
+type AttributeList struct {
+	matcher []AttributeMatcher
+}
+
+func (al *AttributeList) Match(domain *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
+}
+
+type AttributeMatcher interface {
+	Match(*Domain) bool
+}
+
+type BooleanMatcher string
+
+func (m BooleanMatcher) Match(domain *Domain) bool {
+	for _, attr := range domain.Attribute {
+		if attr.Key == string(m) {
+			return true
+		}
+	}
+	return false
+}

+ 42 - 0
common/matcher/geosite/conf.go

@@ -0,0 +1,42 @@
+package geosite
+
+import (
+	"strings"
+
+	dm "github.com/xtls/xray-core/common/matcher/domain"
+)
+
+func LoadGeositeWithAttr(file string, siteWithAttr string) ([]*dm.Domain, error) {
+	parts := strings.Split(siteWithAttr, "@")
+	if len(parts) == 0 {
+		return nil, newError("empty site")
+	}
+	country := strings.ToUpper(parts[0])
+	attrs := parseAttrs(parts[1:])
+	domains, err := loadSite(file, country)
+	if err != nil {
+		return nil, err
+	}
+
+	if attrs.IsEmpty() {
+		return ToDomains(domains), nil
+	}
+
+	filteredDomains := make([]*dm.Domain, 0, len(domains))
+	for _, domain := range domains {
+		if attrs.Match(domain) {
+			filteredDomains = append(filteredDomains, domain.ToDomain())
+		}
+	}
+
+	return filteredDomains, nil
+}
+
+func parseAttrs(attrs []string) *AttributeList {
+	al := new(AttributeList)
+	for _, attr := range attrs {
+		lc := strings.ToLower(attr)
+		al.matcher = append(al.matcher, BooleanMatcher(lc))
+	}
+	return al
+}

+ 9 - 0
common/matcher/geosite/errors.generated.go

@@ -0,0 +1,9 @@
+package geosite
+
+import "github.com/xtls/xray-core/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}

+ 86 - 0
common/matcher/geosite/file.go

@@ -0,0 +1,86 @@
+package geosite
+
+import (
+	"runtime"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/xtls/xray-core/common/platform/filesystem"
+)
+
+var (
+	SiteCache = make(map[string]*GeoSite)
+	FileCache = make(map[string][]byte)
+)
+
+func loadFile(file string) ([]byte, error) {
+	if FileCache[file] == nil {
+		bs, err := filesystem.ReadAsset(file)
+		if err != nil {
+			return nil, newError("failed to open file: ", file).Base(err)
+		}
+		if len(bs) == 0 {
+			return nil, newError("empty file: ", file)
+		}
+		// Do not cache file, may save RAM when there
+		// are many files, but consume CPU each time.
+		return bs, nil
+		FileCache[file] = bs
+	}
+	return FileCache[file], nil
+}
+
+func loadSite(file, code string) ([]*Domain, error) {
+	index := file + ":" + code
+	if SiteCache[index] == nil {
+		bs, err := loadFile(file)
+		if err != nil {
+			return nil, newError("failed to load file: ", file).Base(err)
+		}
+		bs = find(bs, []byte(code))
+		if bs == nil {
+			return nil, newError("list not found in ", file, ": ", code)
+		}
+		var ges GeoSite
+		if err := proto.Unmarshal(bs, &ges); err != nil {
+			return nil, newError("error unmarshal Site in ", file, ": ", code).Base(err)
+		}
+		defer runtime.GC()     // or debug.FreeOSMemory()
+		return ges.Domain, nil // do not cache geosite
+		SiteCache[index] = &ges
+	}
+	return SiteCache[index].Domain, nil
+}
+
+func find(data, code []byte) []byte {
+	codeL := len(code)
+	if codeL == 0 {
+		return nil
+	}
+	for {
+		dataL := len(data)
+		if dataL < 2 {
+			return nil
+		}
+		x, y := proto.DecodeVarint(data[1:])
+		if x == 0 && y == 0 {
+			return nil
+		}
+		headL, bodyL := 1+y, int(x)
+		dataL -= headL
+		if dataL < bodyL {
+			return nil
+		}
+		data = data[headL:]
+		if int(data[1]) == codeL {
+			for i := 0; i < codeL && data[2+i] == code[i]; i++ {
+				if i+1 == codeL {
+					return data[:bodyL]
+				}
+			}
+		}
+		if dataL == bodyL {
+			return nil
+		}
+		data = data[bodyL:]
+	}
+}

+ 19 - 0
common/matcher/geosite/geosite.go

@@ -0,0 +1,19 @@
+package geosite
+
+import "github.com/xtls/xray-core/common/matcher/domain"
+
+//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
+
+func ToDomains(dms []*Domain) []*domain.Domain {
+	dm := make([]*domain.Domain, len(dms))
+
+	for idx, entry := range dms {
+		dm[idx] = entry.ToDomain()
+	}
+
+	return dm
+}
+
+func (d *Domain) ToDomain() *domain.Domain {
+	return &domain.Domain{Type: d.Type, Value: d.Value}
+}

+ 443 - 0
common/matcher/geosite/geosite.pb.go

@@ -0,0 +1,443 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.25.0
+// 	protoc        v3.15.6
+// source: common/matcher/geosite/geosite.proto
+
+package geosite
+
+import (
+	proto "github.com/golang/protobuf/proto"
+	domain "github.com/xtls/xray-core/common/matcher/domain"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// This is a compile-time assertion that a sufficiently up-to-date version
+// of the legacy proto package is being used.
+const _ = proto.ProtoPackageIsVersion4
+
+type Domain struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Domain matching type.
+	Type domain.MatchingType `protobuf:"varint,1,opt,name=type,proto3,enum=xray.common.matcher.domain.MatchingType" json:"type,omitempty"`
+	// Domain value.
+	Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
+	// Attributes of this domain. May be used for filtering.
+	Attribute []*Domain_Attribute `protobuf:"bytes,3,rep,name=attribute,proto3" json:"attribute,omitempty"`
+}
+
+func (x *Domain) Reset() {
+	*x = Domain{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_matcher_geosite_geosite_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Domain) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Domain) ProtoMessage() {}
+
+func (x *Domain) ProtoReflect() protoreflect.Message {
+	mi := &file_common_matcher_geosite_geosite_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Domain.ProtoReflect.Descriptor instead.
+func (*Domain) Descriptor() ([]byte, []int) {
+	return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Domain) GetType() domain.MatchingType {
+	if x != nil {
+		return x.Type
+	}
+	return domain.MatchingType_Full
+}
+
+func (x *Domain) GetValue() string {
+	if x != nil {
+		return x.Value
+	}
+	return ""
+}
+
+func (x *Domain) GetAttribute() []*Domain_Attribute {
+	if x != nil {
+		return x.Attribute
+	}
+	return nil
+}
+
+type GeoSite struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	CountryCode string    `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
+	Domain      []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
+}
+
+func (x *GeoSite) Reset() {
+	*x = GeoSite{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_matcher_geosite_geosite_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GeoSite) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GeoSite) ProtoMessage() {}
+
+func (x *GeoSite) ProtoReflect() protoreflect.Message {
+	mi := &file_common_matcher_geosite_geosite_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GeoSite.ProtoReflect.Descriptor instead.
+func (*GeoSite) Descriptor() ([]byte, []int) {
+	return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GeoSite) GetCountryCode() string {
+	if x != nil {
+		return x.CountryCode
+	}
+	return ""
+}
+
+func (x *GeoSite) GetDomain() []*Domain {
+	if x != nil {
+		return x.Domain
+	}
+	return nil
+}
+
+type GeoSiteList struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Entry []*GeoSite `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
+}
+
+func (x *GeoSiteList) Reset() {
+	*x = GeoSiteList{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_matcher_geosite_geosite_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *GeoSiteList) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GeoSiteList) ProtoMessage() {}
+
+func (x *GeoSiteList) ProtoReflect() protoreflect.Message {
+	mi := &file_common_matcher_geosite_geosite_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GeoSiteList.ProtoReflect.Descriptor instead.
+func (*GeoSiteList) Descriptor() ([]byte, []int) {
+	return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *GeoSiteList) GetEntry() []*GeoSite {
+	if x != nil {
+		return x.Entry
+	}
+	return nil
+}
+
+type Domain_Attribute struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+	// Types that are assignable to TypedValue:
+	//	*Domain_Attribute_BoolValue
+	//	*Domain_Attribute_IntValue
+	TypedValue isDomain_Attribute_TypedValue `protobuf_oneof:"typed_value"`
+}
+
+func (x *Domain_Attribute) Reset() {
+	*x = Domain_Attribute{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_common_matcher_geosite_geosite_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Domain_Attribute) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Domain_Attribute) ProtoMessage() {}
+
+func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
+	mi := &file_common_matcher_geosite_geosite_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Domain_Attribute.ProtoReflect.Descriptor instead.
+func (*Domain_Attribute) Descriptor() ([]byte, []int) {
+	return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{0, 0}
+}
+
+func (x *Domain_Attribute) GetKey() string {
+	if x != nil {
+		return x.Key
+	}
+	return ""
+}
+
+func (m *Domain_Attribute) GetTypedValue() isDomain_Attribute_TypedValue {
+	if m != nil {
+		return m.TypedValue
+	}
+	return nil
+}
+
+func (x *Domain_Attribute) GetBoolValue() bool {
+	if x, ok := x.GetTypedValue().(*Domain_Attribute_BoolValue); ok {
+		return x.BoolValue
+	}
+	return false
+}
+
+func (x *Domain_Attribute) GetIntValue() int64 {
+	if x, ok := x.GetTypedValue().(*Domain_Attribute_IntValue); ok {
+		return x.IntValue
+	}
+	return 0
+}
+
+type isDomain_Attribute_TypedValue interface {
+	isDomain_Attribute_TypedValue()
+}
+
+type Domain_Attribute_BoolValue struct {
+	BoolValue bool `protobuf:"varint,2,opt,name=bool_value,json=boolValue,proto3,oneof"`
+}
+
+type Domain_Attribute_IntValue struct {
+	IntValue int64 `protobuf:"varint,3,opt,name=int_value,json=intValue,proto3,oneof"`
+}
+
+func (*Domain_Attribute_BoolValue) isDomain_Attribute_TypedValue() {}
+
+func (*Domain_Attribute_IntValue) isDomain_Attribute_TypedValue() {}
+
+var File_common_matcher_geosite_geosite_proto protoreflect.FileDescriptor
+
+var file_common_matcher_geosite_geosite_proto_rawDesc = []byte{
+	0x0a, 0x24, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
+	0x2f, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x2f, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1b, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d,
+	0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x73,
+	0x69, 0x74, 0x65, 0x1a, 0x22, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63,
+	0x68, 0x65, 0x72, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2f, 0x64, 0x6f, 0x6d, 0x61, 0x69,
+	0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x97, 0x02, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61,
+	0x69, 0x6e, 0x12, 0x3c, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x28, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d,
+	0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x4d, 0x61,
+	0x74, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
+	0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4b, 0x0a, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
+	0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e,
+	0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 0x41,
+	0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x52, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
+	0x75, 0x74, 0x65, 0x1a, 0x6c, 0x0a, 0x09, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65,
+	0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b,
+	0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61,
+	0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x09, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x56, 0x61, 0x6c,
+	0x75, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x22, 0x69, 0x0a, 0x07, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 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,
+	0x3b, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x23, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61,
+	0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x44, 0x6f,
+	0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x22, 0x49, 0x0a, 0x0b,
+	0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x05, 0x65,
+	0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61,
+	0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
+	0x2e, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65,
+	0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x73, 0x0a, 0x1f, 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, 0x73, 0x69, 0x74, 0x65, 0x50, 0x01, 0x5a, 0x30, 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, 0x73, 0x69, 0x74, 0x65, 0xaa, 0x02,
+	0x1b, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74,
+	0x63, 0x68, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x62, 0x06, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_common_matcher_geosite_geosite_proto_rawDescOnce sync.Once
+	file_common_matcher_geosite_geosite_proto_rawDescData = file_common_matcher_geosite_geosite_proto_rawDesc
+)
+
+func file_common_matcher_geosite_geosite_proto_rawDescGZIP() []byte {
+	file_common_matcher_geosite_geosite_proto_rawDescOnce.Do(func() {
+		file_common_matcher_geosite_geosite_proto_rawDescData = protoimpl.X.CompressGZIP(file_common_matcher_geosite_geosite_proto_rawDescData)
+	})
+	return file_common_matcher_geosite_geosite_proto_rawDescData
+}
+
+var file_common_matcher_geosite_geosite_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_common_matcher_geosite_geosite_proto_goTypes = []interface{}{
+	(*Domain)(nil),           // 0: xray.common.matcher.geosite.Domain
+	(*GeoSite)(nil),          // 1: xray.common.matcher.geosite.GeoSite
+	(*GeoSiteList)(nil),      // 2: xray.common.matcher.geosite.GeoSiteList
+	(*Domain_Attribute)(nil), // 3: xray.common.matcher.geosite.Domain.Attribute
+	(domain.MatchingType)(0), // 4: xray.common.matcher.domain.MatchingType
+}
+var file_common_matcher_geosite_geosite_proto_depIdxs = []int32{
+	4, // 0: xray.common.matcher.geosite.Domain.type:type_name -> xray.common.matcher.domain.MatchingType
+	3, // 1: xray.common.matcher.geosite.Domain.attribute:type_name -> xray.common.matcher.geosite.Domain.Attribute
+	0, // 2: xray.common.matcher.geosite.GeoSite.domain:type_name -> xray.common.matcher.geosite.Domain
+	1, // 3: xray.common.matcher.geosite.GeoSiteList.entry:type_name -> xray.common.matcher.geosite.GeoSite
+	4, // [4:4] is the sub-list for method output_type
+	4, // [4:4] is the sub-list for method input_type
+	4, // [4:4] is the sub-list for extension type_name
+	4, // [4:4] is the sub-list for extension extendee
+	0, // [0:4] is the sub-list for field type_name
+}
+
+func init() { file_common_matcher_geosite_geosite_proto_init() }
+func file_common_matcher_geosite_geosite_proto_init() {
+	if File_common_matcher_geosite_geosite_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_common_matcher_geosite_geosite_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Domain); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_common_matcher_geosite_geosite_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GeoSite); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_common_matcher_geosite_geosite_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*GeoSiteList); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_common_matcher_geosite_geosite_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Domain_Attribute); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	file_common_matcher_geosite_geosite_proto_msgTypes[3].OneofWrappers = []interface{}{
+		(*Domain_Attribute_BoolValue)(nil),
+		(*Domain_Attribute_IntValue)(nil),
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_common_matcher_geosite_geosite_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   4,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_common_matcher_geosite_geosite_proto_goTypes,
+		DependencyIndexes: file_common_matcher_geosite_geosite_proto_depIdxs,
+		MessageInfos:      file_common_matcher_geosite_geosite_proto_msgTypes,
+	}.Build()
+	File_common_matcher_geosite_geosite_proto = out.File
+	file_common_matcher_geosite_geosite_proto_rawDesc = nil
+	file_common_matcher_geosite_geosite_proto_goTypes = nil
+	file_common_matcher_geosite_geosite_proto_depIdxs = nil
+}

+ 38 - 0
common/matcher/geosite/geosite.proto

@@ -0,0 +1,38 @@
+syntax = "proto3";
+
+package xray.common.matcher.geosite;
+option csharp_namespace = "Xray.Common.Matcher.GeoSite";
+option go_package = "github.com/xtls/xray-core/common/matcher/geosite";
+option java_package = "com.xray.common.matcher.geosite";
+option java_multiple_files = true;
+
+import "common/matcher/domain/domain.proto";
+
+message Domain {
+  // Domain matching type.
+  xray.common.matcher.domain.MatchingType type = 1;
+
+  // Domain value.
+  string value = 2;
+
+  message Attribute {
+    string key = 1;
+
+    oneof typed_value {
+      bool bool_value = 2;
+      int64 int_value = 3;
+    }
+  }
+
+  // Attributes of this domain. May be used for filtering.
+  repeated Attribute attribute = 3;
+}
+
+message GeoSite {
+  string country_code = 1;
+  repeated Domain domain = 2;
+}
+
+message GeoSiteList {
+  repeated GeoSite entry = 1;
+}

+ 2 - 2
common/strmatcher/benchmark_test.go → common/matcher/str/benchmark_test.go

@@ -1,11 +1,11 @@
-package strmatcher_test
+package str_test
 
 import (
 	"strconv"
 	"testing"
 
 	"github.com/xtls/xray-core/common"
-	. "github.com/xtls/xray-core/common/strmatcher"
+	. "github.com/xtls/xray-core/common/matcher/str"
 )
 
 func BenchmarkDomainMatcherGroup(b *testing.B) {

+ 1 - 1
common/strmatcher/domain_matcher.go → common/matcher/str/domain_matcher.go

@@ -1,4 +1,4 @@
-package strmatcher
+package str
 
 import "strings"
 

+ 2 - 2
common/strmatcher/domain_matcher_test.go → common/matcher/str/domain_matcher_test.go

@@ -1,10 +1,10 @@
-package strmatcher_test
+package str_test
 
 import (
 	"reflect"
 	"testing"
 
-	. "github.com/xtls/xray-core/common/strmatcher"
+	. "github.com/xtls/xray-core/common/matcher/str"
 )
 
 func TestDomainMatcherGroup(t *testing.T) {

+ 1 - 1
common/strmatcher/full_matcher.go → common/matcher/str/full_matcher.go

@@ -1,4 +1,4 @@
-package strmatcher
+package str
 
 type FullMatcherGroup struct {
 	matchers map[string][]uint32

+ 2 - 2
common/strmatcher/full_matcher_test.go → common/matcher/str/full_matcher_test.go

@@ -1,10 +1,10 @@
-package strmatcher_test
+package str_test
 
 import (
 	"reflect"
 	"testing"
 
-	. "github.com/xtls/xray-core/common/strmatcher"
+	. "github.com/xtls/xray-core/common/matcher/str"
 )
 
 func TestFullMatcherGroup(t *testing.T) {

+ 1 - 1
common/strmatcher/matchers.go → common/matcher/str/matchers.go

@@ -1,4 +1,4 @@
-package strmatcher
+package str
 
 import (
 	"regexp"

+ 2 - 2
common/strmatcher/matchers_test.go → common/matcher/str/matchers_test.go

@@ -1,10 +1,10 @@
-package strmatcher_test
+package str_test
 
 import (
 	"testing"
 
 	"github.com/xtls/xray-core/common"
-	. "github.com/xtls/xray-core/common/strmatcher"
+	. "github.com/xtls/xray-core/common/matcher/str"
 )
 
 func TestMatcher(t *testing.T) {

+ 1 - 1
common/strmatcher/strmatcher.go → common/matcher/str/strmatcher.go

@@ -1,4 +1,4 @@
-package strmatcher
+package str
 
 import (
 	"regexp"

+ 2 - 2
common/strmatcher/strmatcher_test.go → common/matcher/str/strmatcher_test.go

@@ -1,11 +1,11 @@
-package strmatcher_test
+package str_test
 
 import (
 	"reflect"
 	"testing"
 
 	"github.com/xtls/xray-core/common"
-	. "github.com/xtls/xray-core/common/strmatcher"
+	. "github.com/xtls/xray-core/common/matcher/str"
 )
 
 func TestMatcherGroup(t *testing.T) {

+ 18 - 38
infra/conf/dns.go

@@ -6,7 +6,9 @@ import (
 	"strings"
 
 	"github.com/xtls/xray-core/app/dns"
-	"github.com/xtls/xray-core/app/router"
+	"github.com/xtls/xray-core/common/matcher/conf"
+	dm "github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/geosite"
 	"github.com/xtls/xray-core/common/net"
 )
 
@@ -41,39 +43,24 @@ func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
 	return newError("failed to parse name server: ", string(data))
 }
 
-func toDomainMatchingType(t router.Domain_Type) dns.DomainMatchingType {
-	switch t {
-	case router.Domain_Domain:
-		return dns.DomainMatchingType_Subdomain
-	case router.Domain_Full:
-		return dns.DomainMatchingType_Full
-	case router.Domain_Plain:
-		return dns.DomainMatchingType_Keyword
-	case router.Domain_Regex:
-		return dns.DomainMatchingType_Regex
-	default:
-		panic("unknown domain type")
-	}
-}
-
 func (c *NameServerConfig) Build() (*dns.NameServer, error) {
 	if c.Address == nil {
 		return nil, newError("NameServer address is not specified.")
 	}
 
-	var domains []*dns.NameServer_PriorityDomain
+	var domains []*dm.Domain
 	var originalRules []*dns.NameServer_OriginalRule
 
 	for _, rule := range c.Domains {
-		parsedDomain, err := parseDomainRule(rule)
+		parsedDomain, err := conf.ParaseDomainRule(rule)
 		if err != nil {
 			return nil, newError("invalid domain rule: ", rule).Base(err)
 		}
 
 		for _, pd := range parsedDomain {
-			domains = append(domains, &dns.NameServer_PriorityDomain{
-				Type:   toDomainMatchingType(pd.Type),
-				Domain: pd.Value,
+			domains = append(domains, &dm.Domain{
+				Type:  pd.Type,
+				Value: pd.Value,
 			})
 		}
 		originalRules = append(originalRules, &dns.NameServer_OriginalRule{
@@ -99,13 +86,6 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
 	}, nil
 }
 
-var typeMap = map[router.Domain_Type]dns.DomainMatchingType{
-	router.Domain_Full:   dns.DomainMatchingType_Full,
-	router.Domain_Domain: dns.DomainMatchingType_Subdomain,
-	router.Domain_Plain:  dns.DomainMatchingType_Keyword,
-	router.Domain_Regex:  dns.DomainMatchingType_Regex,
-}
-
 // DNSConfig is a JSON serializable object for dns.Config.
 type DNSConfig struct {
 	Servers       []*NameServerConfig `json:"servers"`
@@ -177,7 +157,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
 					return nil, newError("empty domain type of rule: ", domain)
 				}
 				mapping := getHostMapping(addr)
-				mapping.Type = dns.DomainMatchingType_Subdomain
+				mapping.Type = dm.MatchingType_Subdomain
 				mapping.Domain = domainName
 				mappings = append(mappings, mapping)
 
@@ -186,13 +166,13 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
 				if len(listName) == 0 {
 					return nil, newError("empty geosite rule: ", domain)
 				}
-				domains, err := loadGeositeWithAttr("geosite.dat", listName)
+				domains, err := geosite.LoadGeositeWithAttr("geosite.dat", listName)
 				if err != nil {
 					return nil, newError("failed to load geosite: ", listName).Base(err)
 				}
 				for _, d := range domains {
 					mapping := getHostMapping(addr)
-					mapping.Type = typeMap[d.Type]
+					mapping.Type = d.Type
 					mapping.Domain = d.Value
 					mappings = append(mappings, mapping)
 				}
@@ -203,7 +183,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
 					return nil, newError("empty regexp type of rule: ", domain)
 				}
 				mapping := getHostMapping(addr)
-				mapping.Type = dns.DomainMatchingType_Regex
+				mapping.Type = dm.MatchingType_Regex
 				mapping.Domain = regexpVal
 				mappings = append(mappings, mapping)
 
@@ -213,7 +193,7 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
 					return nil, newError("empty keyword type of rule: ", domain)
 				}
 				mapping := getHostMapping(addr)
-				mapping.Type = dns.DomainMatchingType_Keyword
+				mapping.Type = dm.MatchingType_Keyword
 				mapping.Domain = keywordVal
 				mappings = append(mappings, mapping)
 
@@ -223,13 +203,13 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
 					return nil, newError("empty full domain type of rule: ", domain)
 				}
 				mapping := getHostMapping(addr)
-				mapping.Type = dns.DomainMatchingType_Full
+				mapping.Type = dm.MatchingType_Full
 				mapping.Domain = fullVal
 				mappings = append(mappings, mapping)
 
 			case strings.HasPrefix(domain, "dotless:"):
 				mapping := getHostMapping(addr)
-				mapping.Type = dns.DomainMatchingType_Regex
+				mapping.Type = dm.MatchingType_Regex
 				switch substr := domain[8:]; {
 				case substr == "":
 					mapping.Domain = "^[^.]*$"
@@ -247,20 +227,20 @@ func (c *DNSConfig) Build() (*dns.Config, error) {
 				}
 				filename := kv[0]
 				list := kv[1]
-				domains, err := loadGeositeWithAttr(filename, list)
+				domains, err := geosite.LoadGeositeWithAttr(filename, list)
 				if err != nil {
 					return nil, newError("failed to load domain list: ", list, " from ", filename).Base(err)
 				}
 				for _, d := range domains {
 					mapping := getHostMapping(addr)
-					mapping.Type = typeMap[d.Type]
+					mapping.Type = d.Type
 					mapping.Domain = d.Value
 					mappings = append(mappings, mapping)
 				}
 
 			default:
 				mapping := getHostMapping(addr)
-				mapping.Type = dns.DomainMatchingType_Full
+				mapping.Type = dm.MatchingType_Full
 				mapping.Domain = domain
 				mappings = append(mappings, mapping)
 			}

+ 14 - 13
infra/conf/dns_test.go

@@ -8,8 +8,9 @@ import (
 
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/app/dns"
-	"github.com/xtls/xray-core/app/router"
 	"github.com/xtls/xray-core/common"
+	"github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/geosite"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/platform"
 	"github.com/xtls/xray-core/common/platform/filesystem"
@@ -30,12 +31,12 @@ func init() {
 	common.Must(err)
 	defer geositeFile.Close()
 
-	list := &router.GeoSiteList{
-		Entry: []*router.GeoSite{
+	list := &geosite.GeoSiteList{
+		Entry: []*geosite.GeoSite{
 			{
 				CountryCode: "TEST",
-				Domain: []*router.Domain{
-					{Type: router.Domain_Full, Value: "example.com"},
+				Domain: []*geosite.Domain{
+					{Type: domain.MatchingType_Full, Value: "example.com"},
 				},
 			},
 		},
@@ -94,10 +95,10 @@ func TestDNSConfigParsing(t *testing.T) {
 							Network: net.Network_UDP,
 							Port:    5353,
 						},
-						PrioritizedDomain: []*dns.NameServer_PriorityDomain{
+						PrioritizedDomain: []*domain.Domain{
 							{
-								Type:   dns.DomainMatchingType_Subdomain,
-								Domain: "example.com",
+								Type:  domain.MatchingType_Subdomain,
+								Value: "example.com",
 							},
 						},
 						OriginalRules: []*dns.NameServer_OriginalRule{
@@ -110,27 +111,27 @@ func TestDNSConfigParsing(t *testing.T) {
 				},
 				StaticHosts: []*dns.Config_HostMapping{
 					{
-						Type:          dns.DomainMatchingType_Subdomain,
+						Type:          domain.MatchingType_Subdomain,
 						Domain:        "example.com",
 						ProxiedDomain: "google.com",
 					},
 					{
-						Type:   dns.DomainMatchingType_Full,
+						Type:   domain.MatchingType_Full,
 						Domain: "example.com",
 						Ip:     [][]byte{{127, 0, 0, 1}},
 					},
 					{
-						Type:   dns.DomainMatchingType_Full,
+						Type:   domain.MatchingType_Full,
 						Domain: "example.com",
 						Ip:     [][]byte{{10, 0, 0, 1}},
 					},
 					{
-						Type:   dns.DomainMatchingType_Keyword,
+						Type:   domain.MatchingType_Keyword,
 						Domain: "google",
 						Ip:     [][]byte{{8, 8, 8, 8}},
 					},
 					{
-						Type:   dns.DomainMatchingType_Regex,
+						Type:   domain.MatchingType_Regex,
 						Domain: ".*\\.com",
 						Ip:     [][]byte{{8, 8, 4, 4}},
 					},

+ 22 - 242
infra/conf/router.go

@@ -2,13 +2,13 @@ package conf
 
 import (
 	"encoding/json"
-	"runtime"
 	"strconv"
 	"strings"
 
-	"github.com/golang/protobuf/proto"
-
 	"github.com/xtls/xray-core/app/router"
+	"github.com/xtls/xray-core/common/matcher/conf"
+	"github.com/xtls/xray-core/common/matcher/geoip"
+	"github.com/xtls/xray-core/common/matcher/geosite"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/platform/filesystem"
 )
@@ -100,7 +100,7 @@ type RouterRule struct {
 	BalancerTag string `json:"balancerTag"`
 }
 
-func ParseIP(s string) (*router.CIDR, error) {
+func ParseIP(s string) (*geoip.CIDR, error) {
 	var addr, mask string
 	i := strings.Index(s, "/")
 	if i < 0 {
@@ -123,7 +123,7 @@ func ParseIP(s string) (*router.CIDR, error) {
 		if bits > 32 {
 			return nil, newError("invalid network mask for router: ", bits)
 		}
-		return &router.CIDR{
+		return &geoip.CIDR{
 			Ip:     []byte(ip.IP()),
 			Prefix: bits,
 		}, nil
@@ -139,8 +139,8 @@ func ParseIP(s string) (*router.CIDR, error) {
 		if bits > 128 {
 			return nil, newError("invalid network mask for router: ", bits)
 		}
-		return &router.CIDR{
-			Ip:     []byte(ip.IP()),
+		return &geoip.CIDR{
+			Ip:     ip.IP(),
 			Prefix: bits,
 		}, nil
 	default:
@@ -148,14 +148,9 @@ func ParseIP(s string) (*router.CIDR, error) {
 	}
 }
 
-func loadGeoIP(code string) ([]*router.CIDR, error) {
-	return loadIP("geoip.dat", code)
-}
-
 var (
 	FileCache = make(map[string][]byte)
-	IPCache   = make(map[string]*router.GeoIP)
-	SiteCache = make(map[string]*router.GeoSite)
+	IPCache   = make(map[string]*geoip.GeoIP)
 )
 
 func loadFile(file string) ([]byte, error) {
@@ -175,236 +170,21 @@ func loadFile(file string) ([]byte, error) {
 	return FileCache[file], nil
 }
 
-func loadIP(file, code string) ([]*router.CIDR, error) {
-	index := file + ":" + code
-	if IPCache[index] == nil {
-		bs, err := loadFile(file)
-		if err != nil {
-			return nil, newError("failed to load file: ", file).Base(err)
-		}
-		bs = find(bs, []byte(code))
-		if bs == nil {
-			return nil, newError("code not found in ", file, ": ", code)
-		}
-		var geoip router.GeoIP
-		if err := proto.Unmarshal(bs, &geoip); err != nil {
-			return nil, newError("error unmarshal IP in ", file, ": ", code).Base(err)
-		}
-		defer runtime.GC()     // or debug.FreeOSMemory()
-		return geoip.Cidr, nil // do not cache geoip
-		IPCache[index] = &geoip
-	}
-	return IPCache[index].Cidr, nil
-}
-
-func loadSite(file, code string) ([]*router.Domain, error) {
-	index := file + ":" + code
-	if SiteCache[index] == nil {
-		bs, err := loadFile(file)
-		if err != nil {
-			return nil, newError("failed to load file: ", file).Base(err)
-		}
-		bs = find(bs, []byte(code))
-		if bs == nil {
-			return nil, newError("list not found in ", file, ": ", code)
-		}
-		var geosite router.GeoSite
-		if err := proto.Unmarshal(bs, &geosite); err != nil {
-			return nil, newError("error unmarshal Site in ", file, ": ", code).Base(err)
-		}
-		defer runtime.GC()         // or debug.FreeOSMemory()
-		return geosite.Domain, nil // do not cache geosite
-		SiteCache[index] = &geosite
-	}
-	return SiteCache[index].Domain, nil
-}
-
-func find(data, code []byte) []byte {
-	codeL := len(code)
-	if codeL == 0 {
-		return nil
-	}
-	for {
-		dataL := len(data)
-		if dataL < 2 {
-			return nil
-		}
-		x, y := proto.DecodeVarint(data[1:])
-		if x == 0 && y == 0 {
-			return nil
-		}
-		headL, bodyL := 1+y, int(x)
-		dataL -= headL
-		if dataL < bodyL {
-			return nil
-		}
-		data = data[headL:]
-		if int(data[1]) == codeL {
-			for i := 0; i < codeL && data[2+i] == code[i]; i++ {
-				if i+1 == codeL {
-					return data[:bodyL]
-				}
-			}
-		}
-		if dataL == bodyL {
-			return nil
-		}
-		data = data[bodyL:]
-	}
-}
-
-type AttributeMatcher interface {
-	Match(*router.Domain) bool
-}
-
-type BooleanMatcher string
-
-func (m BooleanMatcher) Match(domain *router.Domain) bool {
-	for _, attr := range domain.Attribute {
-		if attr.Key == 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 {
-		lc := strings.ToLower(attr)
-		al.matcher = append(al.matcher, BooleanMatcher(lc))
-	}
-	return al
-}
-
-func loadGeositeWithAttr(file string, siteWithAttr string) ([]*router.Domain, error) {
-	parts := strings.Split(siteWithAttr, "@")
-	if len(parts) == 0 {
-		return nil, newError("empty site")
-	}
-	country := strings.ToUpper(parts[0])
-	attrs := parseAttrs(parts[1:])
-	domains, err := loadSite(file, country)
-	if err != nil {
-		return nil, err
-	}
-
-	if attrs.IsEmpty() {
-		return domains, nil
-	}
-
-	filteredDomains := make([]*router.Domain, 0, len(domains))
-	for _, domain := range domains {
-		if attrs.Match(domain) {
-			filteredDomains = append(filteredDomains, domain)
-		}
-	}
-
-	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)
-		}
-		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]
-		country := kv[1]
-		domains, err := loadGeositeWithAttr(filename, country)
-		if err != nil {
-			return nil, newError("failed to load external sites: ", country, " from ", filename).Base(err)
-		}
-		return domains, nil
-	}
-
-	domainRule := new(router.Domain)
-	switch {
-	case strings.HasPrefix(domain, "regexp:"):
-		domainRule.Type = router.Domain_Regex
-		domainRule.Value = domain[7:]
-
-	case strings.HasPrefix(domain, "domain:"):
-		domainRule.Type = router.Domain_Domain
-		domainRule.Value = domain[7:]
-
-	case strings.HasPrefix(domain, "full:"):
-		domainRule.Type = router.Domain_Full
-		domainRule.Value = domain[5:]
-
-	case strings.HasPrefix(domain, "keyword:"):
-		domainRule.Type = router.Domain_Plain
-		domainRule.Value = domain[8:]
-
-	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
+func toCidrList(ips StringList) ([]*geoip.GeoIP, error) {
+	var geoipList []*geoip.GeoIP
+	var customCidrs []*geoip.CIDR
 
 	for _, ip := range ips {
 		if strings.HasPrefix(ip, "geoip:") {
 			country := ip[6:]
-			geoip, err := loadGeoIP(strings.ToUpper(country))
+			geoipc, err := geoip.LoadGeoIP(strings.ToUpper(country))
 			if err != nil {
 				return nil, newError("failed to load GeoIP: ", country).Base(err)
 			}
 
-			geoipList = append(geoipList, &router.GeoIP{
+			geoipList = append(geoipList, &geoip.GeoIP{
 				CountryCode: strings.ToUpper(country),
-				Cidr:        geoip,
+				Cidr:        geoipc,
 			})
 			continue
 		}
@@ -427,14 +207,14 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) {
 
 			filename := kv[0]
 			country := kv[1]
-			geoip, err := loadIP(filename, strings.ToUpper(country))
+			geoipc, err := geoip.LoadIPFile(filename, strings.ToUpper(country))
 			if err != nil {
 				return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err)
 			}
 
-			geoipList = append(geoipList, &router.GeoIP{
+			geoipList = append(geoipList, &geoip.GeoIP{
 				CountryCode: strings.ToUpper(filename + "_" + country),
-				Cidr:        geoip,
+				Cidr:        geoipc,
 			})
 
 			continue
@@ -448,7 +228,7 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) {
 	}
 
 	if len(customCidrs) > 0 {
-		geoipList = append(geoipList, &router.GeoIP{
+		geoipList = append(geoipList, &geoip.GeoIP{
 			Cidr: customCidrs,
 		})
 	}
@@ -493,7 +273,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 
 	if rawFieldRule.Domain != nil {
 		for _, domain := range *rawFieldRule.Domain {
-			rules, err := parseDomainRule(domain)
+			rules, err := conf.ParaseDomainRule(domain)
 			if err != nil {
 				return nil, newError("failed to parse domain rule: ", domain).Base(err)
 			}
@@ -503,7 +283,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 
 	if rawFieldRule.Domains != nil {
 		for _, domain := range *rawFieldRule.Domains {
-			rules, err := parseDomainRule(domain)
+			rules, err := conf.ParaseDomainRule(domain)
 			if err != nil {
 				return nil, newError("failed to parse domain rule: ", domain).Base(err)
 			}
@@ -600,7 +380,7 @@ func parseChinaIPRule(data []byte) (*router.RoutingRule, error) {
 	if err != nil {
 		return nil, newError("invalid router rule").Base(err)
 	}
-	chinaIPs, err := loadGeoIP("CN")
+	chinaIPs, err := geoip.LoadGeoIP("CN")
 	if err != nil {
 		return nil, newError("failed to load geoip:cn").Base(err)
 	}
@@ -618,7 +398,7 @@ func parseChinaSitesRule(data []byte) (*router.RoutingRule, error) {
 	if err != nil {
 		return nil, newError("invalid router rule").Base(err).AtError()
 	}
-	domains, err := loadGeositeWithAttr("geosite.dat", "CN")
+	domains, err := geosite.LoadGeositeWithAttr("geosite.dat", "CN")
 	if err != nil {
 		return nil, newError("failed to load geosite:cn.").Base(err)
 	}

+ 17 - 15
infra/conf/router_test.go

@@ -7,6 +7,8 @@ import (
 	"github.com/golang/protobuf/proto"
 
 	"github.com/xtls/xray-core/app/router"
+	"github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/geoip"
 	"github.com/xtls/xray-core/common/net"
 	. "github.com/xtls/xray-core/infra/conf"
 )
@@ -73,13 +75,13 @@ func TestRouterConfig(t *testing.T) {
 				},
 				Rule: []*router.RoutingRule{
 					{
-						Domain: []*router.Domain{
+						Domain: []*domain.Domain{
 							{
-								Type:  router.Domain_Plain,
+								Type:  domain.MatchingType_Keyword,
 								Value: "baidu.com",
 							},
 							{
-								Type:  router.Domain_Plain,
+								Type:  domain.MatchingType_Keyword,
 								Value: "qq.com",
 							},
 						},
@@ -88,9 +90,9 @@ func TestRouterConfig(t *testing.T) {
 						},
 					},
 					{
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{
 										Ip:     []byte{10, 0, 0, 0},
 										Prefix: 8,
@@ -161,13 +163,13 @@ func TestRouterConfig(t *testing.T) {
 				DomainStrategy: router.Config_IpIfNonMatch,
 				Rule: []*router.RoutingRule{
 					{
-						Domain: []*router.Domain{
+						Domain: []*domain.Domain{
 							{
-								Type:  router.Domain_Plain,
+								Type:  domain.MatchingType_Keyword,
 								Value: "baidu.com",
 							},
 							{
-								Type:  router.Domain_Plain,
+								Type:  domain.MatchingType_Keyword,
 								Value: "qq.com",
 							},
 						},
@@ -176,9 +178,9 @@ func TestRouterConfig(t *testing.T) {
 						},
 					},
 					{
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{
 										Ip:     []byte{10, 0, 0, 0},
 										Prefix: 8,
@@ -224,13 +226,13 @@ func TestRouterConfig(t *testing.T) {
 				DomainStrategy: router.Config_AsIs,
 				Rule: []*router.RoutingRule{
 					{
-						Domain: []*router.Domain{
+						Domain: []*domain.Domain{
 							{
-								Type:  router.Domain_Plain,
+								Type:  domain.MatchingType_Keyword,
 								Value: "baidu.com",
 							},
 							{
-								Type:  router.Domain_Plain,
+								Type:  domain.MatchingType_Keyword,
 								Value: "qq.com",
 							},
 						},
@@ -239,9 +241,9 @@ func TestRouterConfig(t *testing.T) {
 						},
 					},
 					{
-						Geoip: []*router.GeoIP{
+						Geoip: []*geoip.GeoIP{
 							{
-								Cidr: []*router.CIDR{
+								Cidr: []*geoip.CIDR{
 									{
 										Ip:     []byte{10, 0, 0, 0},
 										Prefix: 8,

+ 3 - 2
infra/conf/xray_test.go

@@ -13,6 +13,7 @@ import (
 	"github.com/xtls/xray-core/app/router"
 	"github.com/xtls/xray-core/common"
 	clog "github.com/xtls/xray-core/common/log"
+	"github.com/xtls/xray-core/common/matcher/geoip"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
@@ -154,9 +155,9 @@ func TestXrayConfig(t *testing.T) {
 						DomainStrategy: router.Config_AsIs,
 						Rule: []*router.RoutingRule{
 							{
-								Geoip: []*router.GeoIP{
+								Geoip: []*geoip.GeoIP{
 									{
-										Cidr: []*router.CIDR{
+										Cidr: []*geoip.CIDR{
 											{
 												Ip:     []byte{10, 0, 0, 0},
 												Prefix: 8,

+ 2 - 1
testing/scenarios/dns_test.go

@@ -9,6 +9,7 @@ import (
 	"github.com/xtls/xray-core/app/proxyman"
 	"github.com/xtls/xray-core/app/router"
 	"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/serial"
 	"github.com/xtls/xray-core/core"
@@ -39,7 +40,7 @@ func TestResolveIP(t *testing.T) {
 				DomainStrategy: router.Config_IpIfNonMatch,
 				Rule: []*router.RoutingRule{
 					{
-						Cidr: []*router.CIDR{
+						Cidr: []*geoip.CIDR{
 							{
 								Ip:     []byte{127, 0, 0, 0},
 								Prefix: 8,

+ 9 - 8
testing/scenarios/reverse_test.go

@@ -13,6 +13,7 @@ import (
 	"github.com/xtls/xray-core/app/router"
 	"github.com/xtls/xray-core/common"
 	clog "github.com/xtls/xray-core/common/log"
+	"github.com/xtls/xray-core/common/matcher/domain"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
@@ -53,8 +54,8 @@ func TestReverseProxy(t *testing.T) {
 			serial.ToTypedMessage(&router.Config{
 				Rule: []*router.RoutingRule{
 					{
-						Domain: []*router.Domain{
-							{Type: router.Domain_Full, Value: "test.example.com"},
+						Domain: []*domain.Domain{
+							{Type: domain.MatchingType_Full, Value: "test.example.com"},
 						},
 						TargetTag: &router.RoutingRule_Tag{
 							Tag: "portal",
@@ -122,8 +123,8 @@ func TestReverseProxy(t *testing.T) {
 			serial.ToTypedMessage(&router.Config{
 				Rule: []*router.RoutingRule{
 					{
-						Domain: []*router.Domain{
-							{Type: router.Domain_Full, Value: "test.example.com"},
+						Domain: []*domain.Domain{
+							{Type: domain.MatchingType_Full, Value: "test.example.com"},
 						},
 						TargetTag: &router.RoutingRule_Tag{
 							Tag: "reverse",
@@ -238,8 +239,8 @@ func TestReverseProxyLongRunning(t *testing.T) {
 			serial.ToTypedMessage(&router.Config{
 				Rule: []*router.RoutingRule{
 					{
-						Domain: []*router.Domain{
-							{Type: router.Domain_Full, Value: "test.example.com"},
+						Domain: []*domain.Domain{
+							{Type: domain.MatchingType_Full, Value: "test.example.com"},
 						},
 						TargetTag: &router.RoutingRule_Tag{
 							Tag: "portal",
@@ -321,8 +322,8 @@ func TestReverseProxyLongRunning(t *testing.T) {
 			serial.ToTypedMessage(&router.Config{
 				Rule: []*router.RoutingRule{
 					{
-						Domain: []*router.Domain{
-							{Type: router.Domain_Full, Value: "test.example.com"},
+						Domain: []*domain.Domain{
+							{Type: domain.MatchingType_Full, Value: "test.example.com"},
 						},
 						TargetTag: &router.RoutingRule_Tag{
 							Tag: "reverse",

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff