From b11429eaeef798e3a99cb738c48603fe5b576b93 Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Wed, 24 Mar 2021 23:01:20 +0800
Subject: [PATCH 01/20] Refactor: GeoSite & GeoIP

---
 app/dns/config.go                             |  35 +-
 app/dns/config.pb.go                          | 389 +++-----
 app/dns/config.proto                          |  21 +-
 app/dns/dns.go                                |  12 +-
 app/dns/dns_test.go                           |  81 +-
 app/dns/hosts.go                              |   8 +-
 app/dns/hosts_test.go                         |   7 +-
 app/dns/nameserver.go                         |  12 +-
 app/router/command/command_test.go            |   6 +-
 app/router/condition.go                       |  62 +-
 app/router/condition_test.go                  |  60 +-
 app/router/config.go                          |  48 +-
 app/router/config.pb.go                       | 891 +++---------------
 app/router/config.proto                       |  72 +-
 app/router/router_test.go                     |   7 +-
 app/stats/command/command.go                  |   4 +-
 common/matcher/conf/conf.go                   |   3 +
 common/matcher/conf/domain.go                 |  78 ++
 common/matcher/conf/errors.generated.go       |   9 +
 common/matcher/domain/domain.go               |   3 +
 common/matcher/domain/domain.pb.go            | 229 +++++
 common/matcher/domain/domain.proto            |  39 +
 common/matcher/domain/errors.generated.go     |   9 +
 common/matcher/geoip/conf.go                  |  90 ++
 common/matcher/geoip/crid.go                  |  40 +
 common/matcher/geoip/errors.generated.go      |   9 +
 .../matcher/geoip/geoip.go                    |   6 +-
 common/matcher/geoip/geoip.pb.go              | 307 ++++++
 common/matcher/geoip/geoip.proto              |  25 +
 .../matcher/geoip/geoip_test.go               |  32 +-
 common/matcher/geoip/matcher.go               |  47 +
 common/matcher/geosite/attribute.go           |  33 +
 common/matcher/geosite/conf.go                |  42 +
 common/matcher/geosite/errors.generated.go    |   9 +
 common/matcher/geosite/file.go                |  86 ++
 common/matcher/geosite/geosite.go             |  19 +
 common/matcher/geosite/geosite.pb.go          | 443 +++++++++
 common/matcher/geosite/geosite.proto          |  38 +
 .../str}/benchmark_test.go                    |   4 +-
 .../str}/domain_matcher.go                    |   2 +-
 .../str}/domain_matcher_test.go               |   4 +-
 .../str}/full_matcher.go                      |   2 +-
 .../str}/full_matcher_test.go                 |   4 +-
 .../{strmatcher => matcher/str}/matchers.go   |   2 +-
 .../str}/matchers_test.go                     |   4 +-
 .../{strmatcher => matcher/str}/strmatcher.go |   2 +-
 .../str}/strmatcher_test.go                   |   4 +-
 infra/conf/dns.go                             |  56 +-
 infra/conf/dns_test.go                        |  27 +-
 infra/conf/router.go                          | 264 +-----
 infra/conf/router_test.go                     |  32 +-
 infra/conf/xray_test.go                       |   5 +-
 testing/scenarios/dns_test.go                 |   3 +-
 testing/scenarios/reverse_test.go             |  17 +-
 54 files changed, 2110 insertions(+), 1633 deletions(-)
 create mode 100644 common/matcher/conf/conf.go
 create mode 100644 common/matcher/conf/domain.go
 create mode 100644 common/matcher/conf/errors.generated.go
 create mode 100644 common/matcher/domain/domain.go
 create mode 100644 common/matcher/domain/domain.pb.go
 create mode 100644 common/matcher/domain/domain.proto
 create mode 100644 common/matcher/domain/errors.generated.go
 create mode 100644 common/matcher/geoip/conf.go
 create mode 100644 common/matcher/geoip/crid.go
 create mode 100644 common/matcher/geoip/errors.generated.go
 rename app/router/condition_geoip.go => common/matcher/geoip/geoip.go (96%)
 create mode 100644 common/matcher/geoip/geoip.pb.go
 create mode 100644 common/matcher/geoip/geoip.proto
 rename app/router/condition_geoip_test.go => common/matcher/geoip/geoip_test.go (85%)
 create mode 100644 common/matcher/geoip/matcher.go
 create mode 100644 common/matcher/geosite/attribute.go
 create mode 100644 common/matcher/geosite/conf.go
 create mode 100644 common/matcher/geosite/errors.generated.go
 create mode 100644 common/matcher/geosite/file.go
 create mode 100644 common/matcher/geosite/geosite.go
 create mode 100644 common/matcher/geosite/geosite.pb.go
 create mode 100644 common/matcher/geosite/geosite.proto
 rename common/{strmatcher => matcher/str}/benchmark_test.go (91%)
 rename common/{strmatcher => matcher/str}/domain_matcher.go (98%)
 rename common/{strmatcher => matcher/str}/domain_matcher_test.go (94%)
 rename common/{strmatcher => matcher/str}/full_matcher.go (96%)
 rename common/{strmatcher => matcher/str}/full_matcher_test.go (91%)
 rename common/{strmatcher => matcher/str}/matchers.go (97%)
 rename common/{strmatcher => matcher/str}/matchers_test.go (94%)
 rename common/{strmatcher => matcher/str}/strmatcher.go (99%)
 rename common/{strmatcher => matcher/str}/strmatcher_test.go (95%)

diff --git a/app/dns/config.go b/app/dns/config.go
index 6236f7b5336e..caa7304cdd08 100644
--- a/app/dns/config.go
+++ b/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()
diff --git a/app/dns/config.pb.go b/app/dns/config.pb.go
index 1f812c4340ef..7937b35bffa5 100644
--- a/app/dns/config.pb.go
+++ b/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,
 		},
diff --git a/app/dns/config.proto b/app/dns/config.proto
index 5ba5c4c272c3..d0ed5766eaad 100644
--- a/app/dns/config.proto
+++ b/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;
diff --git a/app/dns/dns.go b/app/dns/dns.go
index 90c342bc31d8..47b7f9d8f8fb 100644
--- a/app/dns/dns.go
+++ b/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),
diff --git a/app/dns/dns_test.go b/app/dns/dns_test.go
index 8c96eeea4438..484b244043b8 100644
--- a/app/dns/dns_test.go
+++ b/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},
 								},
 							},
diff --git a/app/dns/hosts.go b/app/dns/hosts.go
index af3158033967..1e0783fc3415 100644
--- a/app/dns/hosts.go
+++ b/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)
 
diff --git a/app/dns/hosts_test.go b/app/dns/hosts_test.go
index 87e487bc65c0..01ede8ab504a 100644
--- a/app/dns/hosts_test.go
+++ b/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},
diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go
index 36341f656c7f..1bdda0add21a 100644
--- a/app/dns/nameserver.go
+++ b/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 {
diff --git a/app/router/command/command_test.go b/app/router/command/command_test.go
index e8e50cc65ba2..459af45092b0 100644
--- a/app/router/command/command_test.go
+++ b/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"},
 			},
 			{
diff --git a/app/router/condition.go b/app/router/condition.go
index 8c5d1de87b1b..c6e344f10247 100644
--- a/app/router/condition.go
+++ b/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
diff --git a/app/router/condition_test.go b/app/router/condition_test.go
index 3f7185e6633c..2ee1c0202cae 100644
--- a/app/router/condition_test.go
+++ b/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)})
diff --git a/app/router/config.go b/app/router/config.go
index 07167a530198..6fdfeda8b994 100644
--- a/app/router/config.go
+++ b/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
 		}
diff --git a/app/router/config.pb.go b/app/router/config.pb.go
index 69abd3706158..ce69e5328412 100644
--- a/app/router/config.pb.go
+++ b/app/router/config.pb.go
@@ -1,13 +1,15 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
 // 	protoc-gen-go v1.25.0
-// 	protoc        v3.14.0
+// 	protoc        v3.15.6
 // source: app/router/config.proto
 
 package router
 
 import (
 	proto "github.com/golang/protobuf/proto"
+	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"
@@ -26,63 +28,6 @@ const (
 // of the legacy proto package is being used.
 const _ = proto.ProtoPackageIsVersion4
 
-// Type of domain value.
-type Domain_Type int32
-
-const (
-	// The value is used as is.
-	Domain_Plain Domain_Type = 0
-	// The value is used as a regular expression.
-	Domain_Regex Domain_Type = 1
-	// The value is a root domain.
-	Domain_Domain Domain_Type = 2
-	// The value is a domain.
-	Domain_Full Domain_Type = 3
-)
-
-// Enum value maps for Domain_Type.
-var (
-	Domain_Type_name = map[int32]string{
-		0: "Plain",
-		1: "Regex",
-		2: "Domain",
-		3: "Full",
-	}
-	Domain_Type_value = map[string]int32{
-		"Plain":  0,
-		"Regex":  1,
-		"Domain": 2,
-		"Full":   3,
-	}
-)
-
-func (x Domain_Type) Enum() *Domain_Type {
-	p := new(Domain_Type)
-	*p = x
-	return p
-}
-
-func (x Domain_Type) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (Domain_Type) Descriptor() protoreflect.EnumDescriptor {
-	return file_app_router_config_proto_enumTypes[0].Descriptor()
-}
-
-func (Domain_Type) Type() protoreflect.EnumType {
-	return &file_app_router_config_proto_enumTypes[0]
-}
-
-func (x Domain_Type) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use Domain_Type.Descriptor instead.
-func (Domain_Type) EnumDescriptor() ([]byte, []int) {
-	return file_app_router_config_proto_rawDescGZIP(), []int{0, 0}
-}
-
 type Config_DomainStrategy int32
 
 const (
@@ -123,11 +68,11 @@ func (x Config_DomainStrategy) String() string {
 }
 
 func (Config_DomainStrategy) Descriptor() protoreflect.EnumDescriptor {
-	return file_app_router_config_proto_enumTypes[1].Descriptor()
+	return file_app_router_config_proto_enumTypes[0].Descriptor()
 }
 
 func (Config_DomainStrategy) Type() protoreflect.EnumType {
-	return &file_app_router_config_proto_enumTypes[1]
+	return &file_app_router_config_proto_enumTypes[0]
 }
 
 func (x Config_DomainStrategy) Number() protoreflect.EnumNumber {
@@ -136,336 +81,7 @@ func (x Config_DomainStrategy) Number() protoreflect.EnumNumber {
 
 // Deprecated: Use Config_DomainStrategy.Descriptor instead.
 func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) {
-	return file_app_router_config_proto_rawDescGZIP(), []int{8, 0}
-}
-
-// Domain for routing decision.
-type Domain struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// Domain matching type.
-	Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=xray.app.router.Domain_Type" 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_app_router_config_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_app_router_config_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_app_router_config_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *Domain) GetType() Domain_Type {
-	if x != nil {
-		return x.Type
-	}
-	return Domain_Plain
-}
-
-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
-}
-
-// 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_app_router_config_proto_msgTypes[1]
-		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_app_router_config_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 CIDR.ProtoReflect.Descriptor instead.
-func (*CIDR) Descriptor() ([]byte, []int) {
-	return file_app_router_config_proto_rawDescGZIP(), []int{1}
-}
-
-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_app_router_config_proto_msgTypes[2]
-		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_app_router_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 GeoIP.ProtoReflect.Descriptor instead.
-func (*GeoIP) Descriptor() ([]byte, []int) {
-	return file_app_router_config_proto_rawDescGZIP(), []int{2}
-}
-
-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_app_router_config_proto_msgTypes[3]
-		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_app_router_config_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 GeoIPList.ProtoReflect.Descriptor instead.
-func (*GeoIPList) Descriptor() ([]byte, []int) {
-	return file_app_router_config_proto_rawDescGZIP(), []int{3}
-}
-
-func (x *GeoIPList) GetEntry() []*GeoIP {
-	if x != nil {
-		return x.Entry
-	}
-	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_app_router_config_proto_msgTypes[4]
-		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_app_router_config_proto_msgTypes[4]
-	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_app_router_config_proto_rawDescGZIP(), []int{4}
-}
-
-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_app_router_config_proto_msgTypes[5]
-		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_app_router_config_proto_msgTypes[5]
-	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_app_router_config_proto_rawDescGZIP(), []int{5}
-}
-
-func (x *GeoSiteList) GetEntry() []*GeoSite {
-	if x != nil {
-		return x.Entry
-	}
-	return nil
+	return file_app_router_config_proto_rawDescGZIP(), []int{2, 0}
 }
 
 type RoutingRule struct {
@@ -478,17 +94,17 @@ type RoutingRule struct {
 	//	*RoutingRule_BalancingTag
 	TargetTag isRoutingRule_TargetTag `protobuf_oneof:"target_tag"`
 	// List of domains for target domain matching.
-	Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
+	Domain []*domain.Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
 	// List of CIDRs for target IP address matching.
 	// Deprecated. Use geoip below.
 	//
 	// Deprecated: Do not use.
-	Cidr []*CIDR `protobuf:"bytes,3,rep,name=cidr,proto3" json:"cidr,omitempty"`
+	Cidr []*geoip.CIDR `protobuf:"bytes,3,rep,name=cidr,proto3" json:"cidr,omitempty"`
 	// 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.
-	Geoip []*GeoIP `protobuf:"bytes,10,rep,name=geoip,proto3" json:"geoip,omitempty"`
+	Geoip []*geoip.GeoIP `protobuf:"bytes,10,rep,name=geoip,proto3" json:"geoip,omitempty"`
 	// A range of port [from, to]. If the destination port is in this range, this
 	// rule takes effect. Deprecated. Use port_list.
 	//
@@ -505,10 +121,10 @@ type RoutingRule struct {
 	// List of CIDRs for source IP address matching.
 	//
 	// Deprecated: Do not use.
-	SourceCidr []*CIDR `protobuf:"bytes,6,rep,name=source_cidr,json=sourceCidr,proto3" json:"source_cidr,omitempty"`
+	SourceCidr []*geoip.CIDR `protobuf:"bytes,6,rep,name=source_cidr,json=sourceCidr,proto3" json:"source_cidr,omitempty"`
 	// List of GeoIPs for source IP address matching. If this entry exists, the
 	// source_cidr above will have no effect.
-	SourceGeoip []*GeoIP `protobuf:"bytes,11,rep,name=source_geoip,json=sourceGeoip,proto3" json:"source_geoip,omitempty"`
+	SourceGeoip []*geoip.GeoIP `protobuf:"bytes,11,rep,name=source_geoip,json=sourceGeoip,proto3" json:"source_geoip,omitempty"`
 	// List of ports for source port matching.
 	SourcePortList *net.PortList `protobuf:"bytes,16,opt,name=source_port_list,json=sourcePortList,proto3" json:"source_port_list,omitempty"`
 	UserEmail      []string      `protobuf:"bytes,7,rep,name=user_email,json=userEmail,proto3" json:"user_email,omitempty"`
@@ -520,7 +136,7 @@ type RoutingRule struct {
 func (x *RoutingRule) Reset() {
 	*x = RoutingRule{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_app_router_config_proto_msgTypes[6]
+		mi := &file_app_router_config_proto_msgTypes[0]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -533,7 +149,7 @@ func (x *RoutingRule) String() string {
 func (*RoutingRule) ProtoMessage() {}
 
 func (x *RoutingRule) ProtoReflect() protoreflect.Message {
-	mi := &file_app_router_config_proto_msgTypes[6]
+	mi := &file_app_router_config_proto_msgTypes[0]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -546,7 +162,7 @@ func (x *RoutingRule) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use RoutingRule.ProtoReflect.Descriptor instead.
 func (*RoutingRule) Descriptor() ([]byte, []int) {
-	return file_app_router_config_proto_rawDescGZIP(), []int{6}
+	return file_app_router_config_proto_rawDescGZIP(), []int{0}
 }
 
 func (m *RoutingRule) GetTargetTag() isRoutingRule_TargetTag {
@@ -570,7 +186,7 @@ func (x *RoutingRule) GetBalancingTag() string {
 	return ""
 }
 
-func (x *RoutingRule) GetDomain() []*Domain {
+func (x *RoutingRule) GetDomain() []*domain.Domain {
 	if x != nil {
 		return x.Domain
 	}
@@ -578,14 +194,14 @@ func (x *RoutingRule) GetDomain() []*Domain {
 }
 
 // Deprecated: Do not use.
-func (x *RoutingRule) GetCidr() []*CIDR {
+func (x *RoutingRule) GetCidr() []*geoip.CIDR {
 	if x != nil {
 		return x.Cidr
 	}
 	return nil
 }
 
-func (x *RoutingRule) GetGeoip() []*GeoIP {
+func (x *RoutingRule) GetGeoip() []*geoip.GeoIP {
 	if x != nil {
 		return x.Geoip
 	}
@@ -623,14 +239,14 @@ func (x *RoutingRule) GetNetworks() []net.Network {
 }
 
 // Deprecated: Do not use.
-func (x *RoutingRule) GetSourceCidr() []*CIDR {
+func (x *RoutingRule) GetSourceCidr() []*geoip.CIDR {
 	if x != nil {
 		return x.SourceCidr
 	}
 	return nil
 }
 
-func (x *RoutingRule) GetSourceGeoip() []*GeoIP {
+func (x *RoutingRule) GetSourceGeoip() []*geoip.GeoIP {
 	if x != nil {
 		return x.SourceGeoip
 	}
@@ -702,7 +318,7 @@ type BalancingRule struct {
 func (x *BalancingRule) Reset() {
 	*x = BalancingRule{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_app_router_config_proto_msgTypes[7]
+		mi := &file_app_router_config_proto_msgTypes[1]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -715,7 +331,7 @@ func (x *BalancingRule) String() string {
 func (*BalancingRule) ProtoMessage() {}
 
 func (x *BalancingRule) ProtoReflect() protoreflect.Message {
-	mi := &file_app_router_config_proto_msgTypes[7]
+	mi := &file_app_router_config_proto_msgTypes[1]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -728,7 +344,7 @@ func (x *BalancingRule) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use BalancingRule.ProtoReflect.Descriptor instead.
 func (*BalancingRule) Descriptor() ([]byte, []int) {
-	return file_app_router_config_proto_rawDescGZIP(), []int{7}
+	return file_app_router_config_proto_rawDescGZIP(), []int{1}
 }
 
 func (x *BalancingRule) GetTag() string {
@@ -758,7 +374,7 @@ type Config struct {
 func (x *Config) Reset() {
 	*x = Config{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_app_router_config_proto_msgTypes[8]
+		mi := &file_app_router_config_proto_msgTypes[2]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -771,7 +387,7 @@ func (x *Config) String() string {
 func (*Config) ProtoMessage() {}
 
 func (x *Config) ProtoReflect() protoreflect.Message {
-	mi := &file_app_router_config_proto_msgTypes[8]
+	mi := &file_app_router_config_proto_msgTypes[2]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -784,7 +400,7 @@ func (x *Config) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use Config.ProtoReflect.Descriptor instead.
 func (*Config) Descriptor() ([]byte, []int) {
-	return file_app_router_config_proto_rawDescGZIP(), []int{8}
+	return file_app_router_config_proto_rawDescGZIP(), []int{2}
 }
 
 func (x *Config) GetDomainStrategy() Config_DomainStrategy {
@@ -808,94 +424,6 @@ func (x *Config) GetBalancingRule() []*BalancingRule {
 	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_app_router_config_proto_msgTypes[9]
-		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_app_router_config_proto_msgTypes[9]
-	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_app_router_config_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_app_router_config_proto protoreflect.FileDescriptor
 
 var file_app_router_config_proto_rawDesc = []byte{
@@ -904,126 +432,92 @@ var file_app_router_config_proto_rawDesc = []byte{
 	0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x1a, 0x15, 0x63, 0x6f, 0x6d, 0x6d,
 	0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
 	0x6f, 0x1a, 0x18, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x6e, 0x65,
-	0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb3, 0x02, 0x0a, 0x06,
-	0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x30, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
-	0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x2e, 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, 0x3f,
-	0x0a, 0x09, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x21, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75,
-	0x74, 0x65, 0x72, 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, 0x32, 0x0a,
-	0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x10, 0x00,
-	0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44,
-	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10,
-	0x03, 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, 0x55, 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, 0x29, 0x0a,
-	0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x78, 0x72,
-	0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49,
-	0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x22, 0x39, 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49,
-	0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01,
-	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, 0x65, 0x6e,
-	0x74, 0x72, 0x79, 0x22, 0x5d, 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, 0x2f, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x17, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75,
-	0x74, 0x65, 0x72, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61,
-	0x69, 0x6e, 0x22, 0x3d, 0x0a, 0x0b, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73,
-	0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74,
-	0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72,
-	0x79, 0x22, 0x8e, 0x06, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c,
+	0x74, 0x77, 0x6f, 0x72, 0x6b, 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, 0xc1, 0x06, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c,
 	0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,
 	0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69,
 	0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c,
-	0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x12, 0x2f, 0x0a, 0x06,
-	0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x78,
-	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x44,
-	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2d, 0x0a,
-	0x04, 0x63, 0x69, 0x64, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x78, 0x72,
-	0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x49,
-	0x44, 0x52, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x12, 0x2c, 0x0a, 0x05,
-	0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0a, 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, 0x3d, 0x0a, 0x0a, 0x70, 0x6f,
-	0x72, 0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a,
-	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74,
-	0x2e, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09,
-	0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x6f, 0x72,
-	0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78,
-	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50,
-	0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73,
-	0x74, 0x12, 0x43, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x69, 0x73,
-	0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
-	0x6b, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f,
-	0x72, 0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,
-	0x6b, 0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
-	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f,
-	0x72, 0x6b, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x3a, 0x0a, 0x0b,
-	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x06, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x15, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75,
-	0x74, 0x65, 0x72, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x6f,
-	0x75, 0x72, 0x63, 0x65, 0x43, 0x69, 0x64, 0x72, 0x12, 0x39, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72,
-	0x63, 0x65, 0x5f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0b, 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, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x47, 0x65,
-	0x6f, 0x69, 0x70, 0x12, 0x43, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f,
-	0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e,
+	0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x54, 0x61, 0x67, 0x12, 0x3a, 0x0a, 0x06,
+	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, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x37, 0x0a, 0x04, 0x63, 0x69, 0x64, 0x72,
+	0x18, 0x03, 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, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x63, 0x69, 0x64,
+	0x72, 0x12, 0x36, 0x0a, 0x05, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x18, 0x0a, 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, 0x3d, 0x0a, 0x0a, 0x70, 0x6f, 0x72,
+	0x74, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
 	0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e,
-	0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
-	0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x73, 0x65, 0x72,
-	0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x75, 0x73,
-	0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x62, 0x6f, 0x75,
-	0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e,
-	0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74,
-	0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62,
-	0x75, 0x74, 0x65, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74,
-	0x61, 0x67, 0x22, 0x4e, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52,
-	0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e,
-	0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
-	0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74,
-	0x6f, 0x72, 0x22, 0x9b, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a,
-	0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
-	0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e,
-	0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e,
-	0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x30,
-	0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78,
-	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52,
-	0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65,
-	0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75,
-	0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
-	0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e,
-	0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63,
-	0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
-	0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49,
-	0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, 0x10,
-	0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02,
-	0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03,
-	0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
-	0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, 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, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa,
-	0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65,
-	0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x50, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x70,
+	0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x36, 0x0a, 0x09, 0x70, 0x6f, 0x72, 0x74,
+	0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x78, 0x72,
+	0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f,
+	0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x08, 0x70, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74,
+	0x12, 0x43, 0x0a, 0x0c, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x6c, 0x69, 0x73, 0x74,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f,
+	0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+	0x4c, 0x69, 0x73, 0x74, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0b, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+	0x6b, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b,
+	0x73, 0x18, 0x0d, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72,
+	0x6b, 0x52, 0x08, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x12, 0x44, 0x0a, 0x0b, 0x73,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, 0x69, 0x64, 0x72, 0x18, 0x06, 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, 0x42, 0x02, 0x18, 0x01, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x69, 0x64,
+	0x72, 0x12, 0x43, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x67, 0x65, 0x6f, 0x69,
+	0x70, 0x18, 0x0b, 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, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x47, 0x65, 0x6f, 0x69, 0x70, 0x12, 0x43, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x19, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e,
+	0x65, 0x74, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0e, 0x73, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x75,
+	0x73, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x09, 0x75, 0x73, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e,
+	0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x0a, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x61, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69,
+	0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74,
+	0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x4e, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69,
+	0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62,
+	0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c,
+	0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x9b, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x12, 0x4f, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74,
+	0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
+	0x79, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
+	0x79, 0x12, 0x30, 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65,
+	0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72,
+	0x75, 0x6c, 0x65, 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67,
+	0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72,
+	0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61,
+	0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c,
+	0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f,
+	0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04,
+	0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10,
+	0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63,
+	0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e,
+	0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
+	0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, 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, 0x72, 0x6f, 0x75, 0x74,
+	0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f,
+	0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -1038,51 +532,40 @@ func file_app_router_config_proto_rawDescGZIP() []byte {
 	return file_app_router_config_proto_rawDescData
 }
 
-var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
-var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 10)
+var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
 var file_app_router_config_proto_goTypes = []interface{}{
-	(Domain_Type)(0),           // 0: xray.app.router.Domain.Type
-	(Config_DomainStrategy)(0), // 1: xray.app.router.Config.DomainStrategy
-	(*Domain)(nil),             // 2: xray.app.router.Domain
-	(*CIDR)(nil),               // 3: xray.app.router.CIDR
-	(*GeoIP)(nil),              // 4: xray.app.router.GeoIP
-	(*GeoIPList)(nil),          // 5: xray.app.router.GeoIPList
-	(*GeoSite)(nil),            // 6: xray.app.router.GeoSite
-	(*GeoSiteList)(nil),        // 7: xray.app.router.GeoSiteList
-	(*RoutingRule)(nil),        // 8: xray.app.router.RoutingRule
-	(*BalancingRule)(nil),      // 9: xray.app.router.BalancingRule
-	(*Config)(nil),             // 10: xray.app.router.Config
-	(*Domain_Attribute)(nil),   // 11: xray.app.router.Domain.Attribute
-	(*net.PortRange)(nil),      // 12: xray.common.net.PortRange
-	(*net.PortList)(nil),       // 13: xray.common.net.PortList
-	(*net.NetworkList)(nil),    // 14: xray.common.net.NetworkList
-	(net.Network)(0),           // 15: xray.common.net.Network
+	(Config_DomainStrategy)(0), // 0: xray.app.router.Config.DomainStrategy
+	(*RoutingRule)(nil),        // 1: xray.app.router.RoutingRule
+	(*BalancingRule)(nil),      // 2: xray.app.router.BalancingRule
+	(*Config)(nil),             // 3: xray.app.router.Config
+	(*domain.Domain)(nil),      // 4: xray.common.matcher.domain.Domain
+	(*geoip.CIDR)(nil),         // 5: xray.common.matcher.geoip.CIDR
+	(*geoip.GeoIP)(nil),        // 6: xray.common.matcher.geoip.GeoIP
+	(*net.PortRange)(nil),      // 7: xray.common.net.PortRange
+	(*net.PortList)(nil),       // 8: xray.common.net.PortList
+	(*net.NetworkList)(nil),    // 9: xray.common.net.NetworkList
+	(net.Network)(0),           // 10: xray.common.net.Network
 }
 var file_app_router_config_proto_depIdxs = []int32{
-	0,  // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type
-	11, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute
-	3,  // 2: xray.app.router.GeoIP.cidr:type_name -> xray.app.router.CIDR
-	4,  // 3: xray.app.router.GeoIPList.entry:type_name -> xray.app.router.GeoIP
-	2,  // 4: xray.app.router.GeoSite.domain:type_name -> xray.app.router.Domain
-	6,  // 5: xray.app.router.GeoSiteList.entry:type_name -> xray.app.router.GeoSite
-	2,  // 6: xray.app.router.RoutingRule.domain:type_name -> xray.app.router.Domain
-	3,  // 7: xray.app.router.RoutingRule.cidr:type_name -> xray.app.router.CIDR
-	4,  // 8: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP
-	12, // 9: xray.app.router.RoutingRule.port_range:type_name -> xray.common.net.PortRange
-	13, // 10: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList
-	14, // 11: xray.app.router.RoutingRule.network_list:type_name -> xray.common.net.NetworkList
-	15, // 12: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network
-	3,  // 13: xray.app.router.RoutingRule.source_cidr:type_name -> xray.app.router.CIDR
-	4,  // 14: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP
-	13, // 15: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList
-	1,  // 16: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
-	8,  // 17: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
-	9,  // 18: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
-	19, // [19:19] is the sub-list for method output_type
-	19, // [19:19] is the sub-list for method input_type
-	19, // [19:19] is the sub-list for extension type_name
-	19, // [19:19] is the sub-list for extension extendee
-	0,  // [0:19] is the sub-list for field type_name
+	4,  // 0: xray.app.router.RoutingRule.domain:type_name -> xray.common.matcher.domain.Domain
+	5,  // 1: xray.app.router.RoutingRule.cidr:type_name -> xray.common.matcher.geoip.CIDR
+	6,  // 2: xray.app.router.RoutingRule.geoip:type_name -> xray.common.matcher.geoip.GeoIP
+	7,  // 3: xray.app.router.RoutingRule.port_range:type_name -> xray.common.net.PortRange
+	8,  // 4: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList
+	9,  // 5: xray.app.router.RoutingRule.network_list:type_name -> xray.common.net.NetworkList
+	10, // 6: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network
+	5,  // 7: xray.app.router.RoutingRule.source_cidr:type_name -> xray.common.matcher.geoip.CIDR
+	6,  // 8: xray.app.router.RoutingRule.source_geoip:type_name -> xray.common.matcher.geoip.GeoIP
+	8,  // 9: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList
+	0,  // 10: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
+	1,  // 11: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
+	2,  // 12: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
+	13, // [13:13] is the sub-list for method output_type
+	13, // [13:13] is the sub-list for method input_type
+	13, // [13:13] is the sub-list for extension type_name
+	13, // [13:13] is the sub-list for extension extendee
+	0,  // [0:13] is the sub-list for field type_name
 }
 
 func init() { file_app_router_config_proto_init() }
@@ -1092,78 +575,6 @@ func file_app_router_config_proto_init() {
 	}
 	if !protoimpl.UnsafeEnabled {
 		file_app_router_config_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_app_router_config_proto_msgTypes[1].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_app_router_config_proto_msgTypes[2].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_app_router_config_proto_msgTypes[3].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
-			}
-		}
-		file_app_router_config_proto_msgTypes[4].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_app_router_config_proto_msgTypes[5].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_app_router_config_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*RoutingRule); i {
 			case 0:
 				return &v.state
@@ -1175,7 +586,7 @@ func file_app_router_config_proto_init() {
 				return nil
 			}
 		}
-		file_app_router_config_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+		file_app_router_config_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*BalancingRule); i {
 			case 0:
 				return &v.state
@@ -1187,7 +598,7 @@ func file_app_router_config_proto_init() {
 				return nil
 			}
 		}
-		file_app_router_config_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+		file_app_router_config_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Config); i {
 			case 0:
 				return &v.state
@@ -1199,34 +610,18 @@ func file_app_router_config_proto_init() {
 				return nil
 			}
 		}
-		file_app_router_config_proto_msgTypes[9].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_app_router_config_proto_msgTypes[6].OneofWrappers = []interface{}{
+	file_app_router_config_proto_msgTypes[0].OneofWrappers = []interface{}{
 		(*RoutingRule_Tag)(nil),
 		(*RoutingRule_BalancingTag)(nil),
 	}
-	file_app_router_config_proto_msgTypes[9].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_app_router_config_proto_rawDesc,
-			NumEnums:      2,
-			NumMessages:   10,
+			NumEnums:      1,
+			NumMessages:   3,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/app/router/config.proto b/app/router/config.proto
index c5e655adeafc..ab243445af5a 100644
--- a/app/router/config.proto
+++ b/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;
diff --git a/app/router/router_test.go b/app/router/router_test.go
index cd0b7154c992..f4e4cab0e602 100644
--- a/app/router/router_test.go
+++ b/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,
diff --git a/app/stats/command/command.go b/app/stats/command/command.go
index 3c9f54243696..ae227728d4fd 100644
--- a/app/stats/command/command.go
+++ b/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
 	}
diff --git a/common/matcher/conf/conf.go b/common/matcher/conf/conf.go
new file mode 100644
index 000000000000..9bd3f6674d51
--- /dev/null
+++ b/common/matcher/conf/conf.go
@@ -0,0 +1,3 @@
+package conf
+
+//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
diff --git a/common/matcher/conf/domain.go b/common/matcher/conf/domain.go
new file mode 100644
index 000000000000..e71a9cc6f1c3
--- /dev/null
+++ b/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
+}
diff --git a/common/matcher/conf/errors.generated.go b/common/matcher/conf/errors.generated.go
new file mode 100644
index 000000000000..a80cc1405061
--- /dev/null
+++ b/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{})
+}
diff --git a/common/matcher/domain/domain.go b/common/matcher/domain/domain.go
new file mode 100644
index 000000000000..29651ac936e2
--- /dev/null
+++ b/common/matcher/domain/domain.go
@@ -0,0 +1,3 @@
+package domain
+
+//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
diff --git a/common/matcher/domain/domain.pb.go b/common/matcher/domain/domain.pb.go
new file mode 100644
index 000000000000..94e94f9d6f80
--- /dev/null
+++ b/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
+}
diff --git a/common/matcher/domain/domain.proto b/common/matcher/domain/domain.proto
new file mode 100644
index 000000000000..f028b1edf698
--- /dev/null
+++ b/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")
+	}
+}
+ */
\ No newline at end of file
diff --git a/common/matcher/domain/errors.generated.go b/common/matcher/domain/errors.generated.go
new file mode 100644
index 000000000000..f0676c1fd86f
--- /dev/null
+++ b/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{})
+}
diff --git a/common/matcher/geoip/conf.go b/common/matcher/geoip/conf.go
new file mode 100644
index 000000000000..d5be09cce17f
--- /dev/null
+++ b/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:]
+	}
+}
diff --git a/common/matcher/geoip/crid.go b/common/matcher/geoip/crid.go
new file mode 100644
index 000000000000..3716acd37458
--- /dev/null
+++ b/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]
+}
diff --git a/common/matcher/geoip/errors.generated.go b/common/matcher/geoip/errors.generated.go
new file mode 100644
index 000000000000..c75fe73282c0
--- /dev/null
+++ b/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{})
+}
diff --git a/app/router/condition_geoip.go b/common/matcher/geoip/geoip.go
similarity index 96%
rename from app/router/condition_geoip.go
rename to common/matcher/geoip/geoip.go
index 90ad7da7bb8b..08eaf26ac538 100644
--- a/app/router/condition_geoip.go
+++ b/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
 )
diff --git a/common/matcher/geoip/geoip.pb.go b/common/matcher/geoip/geoip.pb.go
new file mode 100644
index 000000000000..4176127ff102
--- /dev/null
+++ b/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
+}
diff --git a/common/matcher/geoip/geoip.proto b/common/matcher/geoip/geoip.proto
new file mode 100644
index 000000000000..a781ff09504f
--- /dev/null
+++ b/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;
+}
diff --git a/app/router/condition_geoip_test.go b/common/matcher/geoip/geoip_test.go
similarity index 85%
rename from app/router/condition_geoip_test.go
rename to common/matcher/geoip/geoip_test.go
index 596478c664ff..f959b9447809 100644
--- a/app/router/condition_geoip_test.go
+++ b/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()
diff --git a/common/matcher/geoip/matcher.go b/common/matcher/geoip/matcher.go
new file mode 100644
index 000000000000..c98ed115493d
--- /dev/null
+++ b/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
+}
diff --git a/common/matcher/geosite/attribute.go b/common/matcher/geosite/attribute.go
new file mode 100644
index 000000000000..a16361c169e2
--- /dev/null
+++ b/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
+}
diff --git a/common/matcher/geosite/conf.go b/common/matcher/geosite/conf.go
new file mode 100644
index 000000000000..5061b1427752
--- /dev/null
+++ b/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
+}
diff --git a/common/matcher/geosite/errors.generated.go b/common/matcher/geosite/errors.generated.go
new file mode 100644
index 000000000000..c1457eee2ad6
--- /dev/null
+++ b/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{})
+}
diff --git a/common/matcher/geosite/file.go b/common/matcher/geosite/file.go
new file mode 100644
index 000000000000..e9a6ec384e11
--- /dev/null
+++ b/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:]
+	}
+}
diff --git a/common/matcher/geosite/geosite.go b/common/matcher/geosite/geosite.go
new file mode 100644
index 000000000000..47480e5c89d5
--- /dev/null
+++ b/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}
+}
diff --git a/common/matcher/geosite/geosite.pb.go b/common/matcher/geosite/geosite.pb.go
new file mode 100644
index 000000000000..d8d166980c81
--- /dev/null
+++ b/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
+}
diff --git a/common/matcher/geosite/geosite.proto b/common/matcher/geosite/geosite.proto
new file mode 100644
index 000000000000..3a5816567b68
--- /dev/null
+++ b/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;
+}
\ No newline at end of file
diff --git a/common/strmatcher/benchmark_test.go b/common/matcher/str/benchmark_test.go
similarity index 91%
rename from common/strmatcher/benchmark_test.go
rename to common/matcher/str/benchmark_test.go
index 3e70ca0421a6..e9432325a3dd 100644
--- a/common/strmatcher/benchmark_test.go
+++ b/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) {
diff --git a/common/strmatcher/domain_matcher.go b/common/matcher/str/domain_matcher.go
similarity index 98%
rename from common/strmatcher/domain_matcher.go
rename to common/matcher/str/domain_matcher.go
index ae8e65bc211e..742d2e980410 100644
--- a/common/strmatcher/domain_matcher.go
+++ b/common/matcher/str/domain_matcher.go
@@ -1,4 +1,4 @@
-package strmatcher
+package str
 
 import "strings"
 
diff --git a/common/strmatcher/domain_matcher_test.go b/common/matcher/str/domain_matcher_test.go
similarity index 94%
rename from common/strmatcher/domain_matcher_test.go
rename to common/matcher/str/domain_matcher_test.go
index 5a8ca35b2460..6d4d29d71308 100644
--- a/common/strmatcher/domain_matcher_test.go
+++ b/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) {
diff --git a/common/strmatcher/full_matcher.go b/common/matcher/str/full_matcher.go
similarity index 96%
rename from common/strmatcher/full_matcher.go
rename to common/matcher/str/full_matcher.go
index e00d02aa9c7d..578af84b2bb4 100644
--- a/common/strmatcher/full_matcher.go
+++ b/common/matcher/str/full_matcher.go
@@ -1,4 +1,4 @@
-package strmatcher
+package str
 
 type FullMatcherGroup struct {
 	matchers map[string][]uint32
diff --git a/common/strmatcher/full_matcher_test.go b/common/matcher/str/full_matcher_test.go
similarity index 91%
rename from common/strmatcher/full_matcher_test.go
rename to common/matcher/str/full_matcher_test.go
index 73d60d51f17b..75bb2bd5ad28 100644
--- a/common/strmatcher/full_matcher_test.go
+++ b/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) {
diff --git a/common/strmatcher/matchers.go b/common/matcher/str/matchers.go
similarity index 97%
rename from common/strmatcher/matchers.go
rename to common/matcher/str/matchers.go
index b5ab09c4cb9f..1d3771fa1c9d 100644
--- a/common/strmatcher/matchers.go
+++ b/common/matcher/str/matchers.go
@@ -1,4 +1,4 @@
-package strmatcher
+package str
 
 import (
 	"regexp"
diff --git a/common/strmatcher/matchers_test.go b/common/matcher/str/matchers_test.go
similarity index 94%
rename from common/strmatcher/matchers_test.go
rename to common/matcher/str/matchers_test.go
index d39c522c8143..c793fdd3c922 100644
--- a/common/strmatcher/matchers_test.go
+++ b/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) {
diff --git a/common/strmatcher/strmatcher.go b/common/matcher/str/strmatcher.go
similarity index 99%
rename from common/strmatcher/strmatcher.go
rename to common/matcher/str/strmatcher.go
index 9728047d5632..bcadec6e4b34 100644
--- a/common/strmatcher/strmatcher.go
+++ b/common/matcher/str/strmatcher.go
@@ -1,4 +1,4 @@
-package strmatcher
+package str
 
 import (
 	"regexp"
diff --git a/common/strmatcher/strmatcher_test.go b/common/matcher/str/strmatcher_test.go
similarity index 95%
rename from common/strmatcher/strmatcher_test.go
rename to common/matcher/str/strmatcher_test.go
index 87d6c5b34f2e..c3e967301fab 100644
--- a/common/strmatcher/strmatcher_test.go
+++ b/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) {
diff --git a/infra/conf/dns.go b/infra/conf/dns.go
index b1c988251c53..b96230401c07 100644
--- a/infra/conf/dns.go
+++ b/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)
 			}
diff --git a/infra/conf/dns_test.go b/infra/conf/dns_test.go
index 00284fbbd1f8..cc1779afa9f2 100644
--- a/infra/conf/dns_test.go
+++ b/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}},
 					},
diff --git a/infra/conf/router.go b/infra/conf/router.go
index a93da6b11500..de6246cbf1a9 100644
--- a/infra/conf/router.go
+++ b/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)
 	}
diff --git a/infra/conf/router_test.go b/infra/conf/router_test.go
index bb4fdc7d41a6..95a5332dc7cf 100644
--- a/infra/conf/router_test.go
+++ b/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,
diff --git a/infra/conf/xray_test.go b/infra/conf/xray_test.go
index d4f0ba6b51fa..96df370ee514 100644
--- a/infra/conf/xray_test.go
+++ b/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,
diff --git a/testing/scenarios/dns_test.go b/testing/scenarios/dns_test.go
index 886068d769e0..26daa4dcd36b 100644
--- a/testing/scenarios/dns_test.go
+++ b/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,
diff --git a/testing/scenarios/reverse_test.go b/testing/scenarios/reverse_test.go
index cbe9abdf6004..acf061fbfffb 100644
--- a/testing/scenarios/reverse_test.go
+++ b/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",

From 14189eba071ebc7bb4b502924e495cec13b5f166 Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Fri, 26 Mar 2021 15:28:27 +0800
Subject: [PATCH 02/20] Replace: domain conf loader

---
 common/matcher/{ => domain}/conf/conf.go             | 0
 common/matcher/{ => domain}/conf/domain.go           | 0
 common/matcher/{ => domain}/conf/errors.generated.go | 0
 infra/conf/dns.go                                    | 2 +-
 infra/conf/router.go                                 | 2 +-
 5 files changed, 2 insertions(+), 2 deletions(-)
 rename common/matcher/{ => domain}/conf/conf.go (100%)
 rename common/matcher/{ => domain}/conf/domain.go (100%)
 rename common/matcher/{ => domain}/conf/errors.generated.go (100%)

diff --git a/common/matcher/conf/conf.go b/common/matcher/domain/conf/conf.go
similarity index 100%
rename from common/matcher/conf/conf.go
rename to common/matcher/domain/conf/conf.go
diff --git a/common/matcher/conf/domain.go b/common/matcher/domain/conf/domain.go
similarity index 100%
rename from common/matcher/conf/domain.go
rename to common/matcher/domain/conf/domain.go
diff --git a/common/matcher/conf/errors.generated.go b/common/matcher/domain/conf/errors.generated.go
similarity index 100%
rename from common/matcher/conf/errors.generated.go
rename to common/matcher/domain/conf/errors.generated.go
diff --git a/infra/conf/dns.go b/infra/conf/dns.go
index b96230401c07..ce7bc24ea97e 100644
--- a/infra/conf/dns.go
+++ b/infra/conf/dns.go
@@ -6,8 +6,8 @@ import (
 	"strings"
 
 	"github.com/xtls/xray-core/app/dns"
-	"github.com/xtls/xray-core/common/matcher/conf"
 	dm "github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/domain/conf"
 	"github.com/xtls/xray-core/common/matcher/geosite"
 	"github.com/xtls/xray-core/common/net"
 )
diff --git a/infra/conf/router.go b/infra/conf/router.go
index de6246cbf1a9..128096ab67e6 100644
--- a/infra/conf/router.go
+++ b/infra/conf/router.go
@@ -6,7 +6,7 @@ import (
 	"strings"
 
 	"github.com/xtls/xray-core/app/router"
-	"github.com/xtls/xray-core/common/matcher/conf"
+	"github.com/xtls/xray-core/common/matcher/domain/conf"
 	"github.com/xtls/xray-core/common/matcher/geoip"
 	"github.com/xtls/xray-core/common/matcher/geosite"
 	"github.com/xtls/xray-core/common/net"

From 06fc82bad13315d8b16bea7ed91d739042afb14c Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Fri, 26 Mar 2021 16:56:43 +0800
Subject: [PATCH 03/20] Feat: sniffer exclude domain & ip

---
 app/dispatcher/default.go              |  20 +-
 app/proxyman/config.go                 |  32 +++
 app/proxyman/config.pb.go              | 323 +++++++++++++------------
 app/proxyman/config.proto              |   6 +-
 app/proxyman/errors.generated.go       |   9 +
 app/proxyman/inbound/always.go         |  18 +-
 app/proxyman/inbound/worker.go         |   8 +-
 app/router/condition.go                |  55 -----
 app/router/config.go                   |   3 +-
 common/matcher/domain/conf/domain.go   |   2 +-
 common/matcher/domain/matcher.go       |  60 +++++
 common/matcher/geoip/conf.go           | 120 ++++++++-
 common/matcher/geoip/matcher.go        |  10 +
 common/session/session.go              |   5 +-
 infra/conf/{ => common}/common.go      |   4 +-
 infra/conf/{ => common}/common_test.go |   4 +-
 infra/conf/common/errors.generated.go  |   9 +
 infra/conf/dns.go                      |  34 +--
 infra/conf/dns_proxy.go                |   7 +-
 infra/conf/dokodemo.go                 |  13 +-
 infra/conf/http.go                     |   3 +-
 infra/conf/router.go                   | 124 ++--------
 infra/conf/shadowsocks.go              |  15 +-
 infra/conf/socks.go                    |   5 +-
 infra/conf/transport_authenticators.go |  19 +-
 infra/conf/transport_internet.go       |  45 ++--
 infra/conf/trojan.go                   |  13 +-
 infra/conf/vless.go                    |   3 +-
 infra/conf/vmess.go                    |   3 +-
 infra/conf/xray.go                     |  45 +++-
 infra/conf/xray_test.go                |  47 ++++
 31 files changed, 653 insertions(+), 411 deletions(-)
 create mode 100644 app/proxyman/errors.generated.go
 create mode 100644 common/matcher/domain/matcher.go
 rename infra/conf/{ => common}/common.go (98%)
 rename infra/conf/{ => common}/common_test.go (98%)
 create mode 100644 infra/conf/common/errors.generated.go

diff --git a/app/dispatcher/default.go b/app/dispatcher/default.go
index 1c0ba79cb03e..97322ab2d586 100644
--- a/app/dispatcher/default.go
+++ b/app/dispatcher/default.go
@@ -178,8 +178,8 @@ func (d *DefaultDispatcher) getLink(ctx context.Context) (*transport.Link, *tran
 
 func shouldOverride(ctx context.Context, result SniffResult, request session.SniffingRequest, destination net.Destination) bool {
 	domain := result.Domain()
-	for _, d := range request.ExcludeForDomain {
-		if domain == d {
+	if request.ExcludedDomainMatcher != nil {
+		if request.ExcludedDomainMatcher.ApplyDomain(domain) {
 			return false
 		}
 	}
@@ -205,6 +205,16 @@ func shouldOverride(ctx context.Context, result SniffResult, request session.Sni
 	return false
 }
 
+func canSniff(ctx context.Context, req session.SniffingRequest, destination net.Destination) bool {
+	if destination.Address.Family().IsIP() && req.ExcludedIPMatcher != nil {
+		if req.ExcludedIPMatcher.MatchIP(destination.Address.IP()) {
+			return false
+		}
+	}
+
+	return true
+}
+
 // Dispatch implements routing.Dispatcher.
 func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destination) (*transport.Link, error) {
 	if !destination.IsValid() {
@@ -222,6 +232,12 @@ func (d *DefaultDispatcher) Dispatch(ctx context.Context, destination net.Destin
 		ctx = session.ContextWithContent(ctx, content)
 	}
 	sniffingRequest := content.SniffingRequest
+
+	if !canSniff(ctx, sniffingRequest, destination) {
+		newError("skip sniffing for ip ", destination.Address.String()).AtDebug().WriteToLog()
+		sniffingRequest.Enabled = false
+	}
+
 	switch {
 	case !sniffingRequest.Enabled:
 		go d.routedDispatch(ctx, outbound, destination)
diff --git a/app/proxyman/config.go b/app/proxyman/config.go
index 73c00c74a14e..302d208f61de 100644
--- a/app/proxyman/config.go
+++ b/app/proxyman/config.go
@@ -1,5 +1,12 @@
 package proxyman
 
+import (
+	"github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/geoip"
+)
+
+//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
+
 func (s *AllocationStrategy) GetConcurrencyValue() uint32 {
 	if s == nil || s.Concurrency == nil {
 		return 3
@@ -37,3 +44,28 @@ func (c *ReceiverConfig) GetEffectiveSniffingSettings() *SniffingConfig {
 
 	return nil
 }
+
+type SniffingMatcher struct {
+	ExDomain *domain.DomainMatcher
+	ExIP     *geoip.MultiGeoIPMatcher
+}
+
+func NewSniffingMatcher(sc *SniffingConfig) (*SniffingMatcher, error) {
+	m := new(SniffingMatcher)
+
+	if sc.DomainsExcluded != nil {
+		exDomain, err := domain.NewDomainMatcher(sc.DomainsExcluded)
+		if err != nil {
+			return nil, newError("failed to parse domain").Base(err)
+		}
+		m.ExDomain = exDomain
+	}
+	if sc.IpsExcluded != nil {
+		exIP, err := geoip.NewMultiGeoIPMatcher(sc.IpsExcluded, true)
+		if err != nil {
+			return nil, newError("failed to parse ip").Base(err)
+		}
+		m.ExIP = exIP
+	}
+	return m, nil
+}
diff --git a/app/proxyman/config.pb.go b/app/proxyman/config.pb.go
index 4e42cfd56c4a..d8c82e65b11b 100644
--- a/app/proxyman/config.pb.go
+++ b/app/proxyman/config.pb.go
@@ -1,13 +1,15 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
 // 	protoc-gen-go v1.25.0
-// 	protoc        (unknown)
+// 	protoc        v3.15.6
 // source: app/proxyman/config.proto
 
 package proxyman
 
 import (
 	proto "github.com/golang/protobuf/proto"
+	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"
 	serial "github.com/xtls/xray-core/common/serial"
 	internet "github.com/xtls/xray-core/transport/internet"
@@ -240,8 +242,9 @@ type SniffingConfig struct {
 	Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"`
 	// Override target destination if sniff'ed protocol is in the given list.
 	// Supported values are "http", "tls", "fakedns".
-	DestinationOverride []string `protobuf:"bytes,2,rep,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"`
-	DomainsExcluded     []string `protobuf:"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3" json:"domains_excluded,omitempty"`
+	DestinationOverride []string         `protobuf:"bytes,2,rep,name=destination_override,json=destinationOverride,proto3" json:"destination_override,omitempty"`
+	DomainsExcluded     []*domain.Domain `protobuf:"bytes,3,rep,name=domains_excluded,json=domainsExcluded,proto3" json:"domains_excluded,omitempty"`
+	IpsExcluded         []*geoip.GeoIP   `protobuf:"bytes,5,rep,name=ips_excluded,json=ipsExcluded,proto3" json:"ips_excluded,omitempty"`
 	// Whether should only try to sniff metadata without waiting for client input.
 	// Can be used to support SMTP like protocol where server send the first message.
 	MetadataOnly bool `protobuf:"varint,4,opt,name=metadata_only,json=metadataOnly,proto3" json:"metadata_only,omitempty"`
@@ -293,13 +296,20 @@ func (x *SniffingConfig) GetDestinationOverride() []string {
 	return nil
 }
 
-func (x *SniffingConfig) GetDomainsExcluded() []string {
+func (x *SniffingConfig) GetDomainsExcluded() []*domain.Domain {
 	if x != nil {
 		return x.DomainsExcluded
 	}
 	return nil
 }
 
+func (x *SniffingConfig) GetIpsExcluded() []*geoip.GeoIP {
+	if x != nil {
+		return x.IpsExcluded
+	}
+	return nil
+}
+
 func (x *SniffingConfig) GetMetadataOnly() bool {
 	if x != nil {
 		return x.MetadataOnly
@@ -738,133 +748,144 @@ var File_app_proxyman_config_proto protoreflect.FileDescriptor
 var file_app_proxyman_config_proto_rawDesc = []byte{
 	0x0a, 0x19, 0x61, 0x70, 0x70, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2f, 0x63,
 	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x78, 0x72, 0x61,
-	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 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, 0x15, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e,
-	0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a,
-	0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72,
-	0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f,
-	0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x22, 0x0f, 0x0a, 0x0d, 0x49, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x22, 0xae, 0x03, 0x0a, 0x12, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x3e, 0x0a, 0x04, 0x74,
-	0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 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, 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, 0x15,
+	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6e, 0x65, 0x74, 0x2f, 0x70, 0x6f, 0x72, 0x74, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74,
+	0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x73,
+	0x65, 0x72, 0x69, 0x61, 0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x73, 0x73,
+	0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x0f, 0x0a, 0x0d, 0x49, 0x6e, 0x62,
+	0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xae, 0x03, 0x0a, 0x12, 0x41,
+	0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
+	0x79, 0x12, 0x3e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x2a, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79,
+	0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74,
+	0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70,
+	0x65, 0x12, 0x65, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
+	0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c,
+	0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
+	0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e,
+	0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x59, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72,
+	0x65, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x78, 0x72, 0x61, 0x79,
 	0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c,
 	0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
-	0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x65, 0x0a, 0x0b, 0x63,
-	0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x43, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78,
-	0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53,
-	0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72,
-	0x72, 0x65, 0x6e, 0x63, 0x79, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
-	0x63, 0x79, 0x12, 0x59, 0x0a, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70,
-	0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x66,
-	0x72, 0x65, 0x73, 0x68, 0x52, 0x07, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x1a, 0x35, 0x0a,
-	0x1d, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74,
-	0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x14,
-	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76,
-	0x61, 0x6c, 0x75, 0x65, 0x1a, 0x31, 0x0a, 0x19, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73,
-	0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d,
-	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12,
-	0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52,
-	0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x45, 0x78, 0x74, 0x65, 0x72,
-	0x6e, 0x61, 0x6c, 0x10, 0x02, 0x22, 0xad, 0x01, 0x0a, 0x0e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69,
-	0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62,
-	0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c,
-	0x65, 0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,
-	0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65,
-	0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73,
-	0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52,
-	0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64,
-	0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6f, 0x6e, 0x6c,
-	0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74,
-	0x61, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x90, 0x04, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76,
-	0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74,
-	0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78,
-	0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50,
-	0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61,
-	0x6e, 0x67, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 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, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x12, 0x56, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f,
-	0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
-	0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c,
+	0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74,
+	0x65, 0x67, 0x79, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x07, 0x72, 0x65, 0x66, 0x72,
+	0x65, 0x73, 0x68, 0x1a, 0x35, 0x0a, 0x1d, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x43, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72,
+	0x65, 0x6e, 0x63, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x31, 0x0a, 0x19, 0x41, 0x6c,
 	0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79,
-	0x12, 0x4e, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69,
-	0x6e, 0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79,
-	0x2e, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72,
-	0x6e, 0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x52, 0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
-	0x12, 0x40, 0x0a, 0x1c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67,
-	0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f,
-	0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x12, 0x4e, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x76, 0x65,
-	0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x78, 0x72,
-	0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e,
-	0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x42, 0x02,
-	0x18, 0x01, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69,
-	0x64, 0x65, 0x12, 0x4e, 0x0a, 0x11, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x5f, 0x73,
-	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e,
-	0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61,
-	0x6e, 0x2e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x52, 0x10, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
-	0x67, 0x73, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xc0, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x62,
-	0x6f, 0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-	0x67, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03,
-	0x74, 0x61, 0x67, 0x12, 0x4d, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f,
-	0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20,
-	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72,
-	0x69, 0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-	0x52, 0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e,
-	0x67, 0x73, 0x12, 0x47, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74,
-	0x69, 0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
-	0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e,
-	0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72,
-	0x6f, 0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f,
-	0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xb0, 0x02,
-	0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d,
-	0x0a, 0x03, 0x76, 0x69, 0x61, 0x18, 0x01, 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, 0x03, 0x76, 0x69, 0x61, 0x12, 0x4e, 0x0a,
-	0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72,
-	0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74,
-	0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73,
-	0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4b, 0x0a,
-	0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61,
-	0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
-	0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f,
-	0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x54, 0x0a, 0x12, 0x6d, 0x75,
-	0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70,
-	0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69,
-	0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d,
-	0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73,
-	0x22, 0x50, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67,
-	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
-	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
-	0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e,
-	0x63, 0x79, 0x2a, 0x23, 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f,
-	0x63, 0x6f, 0x6c, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x07,
-	0x0a, 0x03, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78,
+	0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x2c, 0x0a,
+	0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10,
+	0x00, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x10, 0x01, 0x12, 0x0c, 0x0a,
+	0x08, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x10, 0x02, 0x22, 0x96, 0x02, 0x0a, 0x0e,
+	0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18,
+	0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x31, 0x0a, 0x14, 0x64, 0x65, 0x73, 0x74,
+	0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65,
+	0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x4d, 0x0a, 0x10, 0x64,
+	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x73, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18,
+	0x03, 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, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69,
+	0x6e, 0x73, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x0c, 0x69, 0x70,
+	0x73, 0x5f, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x18, 0x05, 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, 0x0b, 0x69, 0x70, 0x73, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x12,
+	0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6f, 0x6e, 0x6c, 0x79,
+	0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
+	0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x90, 0x04, 0x0a, 0x0e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65,
+	0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x39, 0x0a, 0x0a, 0x70, 0x6f, 0x72, 0x74, 0x5f,
+	0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x78, 0x72,
+	0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6e, 0x65, 0x74, 0x2e, 0x50, 0x6f,
+	0x72, 0x74, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x09, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x61, 0x6e,
+	0x67, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 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,
+	0x06, 0x6c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x12, 0x56, 0x0a, 0x13, 0x61, 0x6c, 0x6c, 0x6f, 0x63,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e,
+	0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x12, 0x61, 0x6c, 0x6c,
+	0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12,
+	0x4e, 0x0a, 0x0f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e,
+	0x67, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
+	0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e,
+	0x65, 0x74, 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
+	0x0e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12,
+	0x40, 0x0a, 0x1c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x5f, 0x6f, 0x72, 0x69, 0x67, 0x69,
+	0x6e, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
+	0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x4f, 0x72,
+	0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x12, 0x4e, 0x0a, 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x6f, 0x76, 0x65, 0x72,
+	0x72, 0x69, 0x64, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x78, 0x72, 0x61,
+	0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4b,
+	0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x73, 0x42, 0x02, 0x18,
+	0x01, 0x52, 0x0e, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64,
+	0x65, 0x12, 0x4e, 0x0a, 0x11, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x65,
+	0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x78,
 	0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e,
-	0x50, 0x01, 0x5a, 0x26, 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, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61,
-	0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x2e, 0x53, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52,
+	0x10, 0x73, 0x6e, 0x69, 0x66, 0x66, 0x69, 0x6e, 0x67, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
+	0x73, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xc0, 0x01, 0x0a, 0x14, 0x49, 0x6e, 0x62, 0x6f,
+	0x75, 0x6e, 0x64, 0x48, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74,
+	0x61, 0x67, 0x12, 0x4d, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73,
+	0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,
+	0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69,
+	0x61, 0x6c, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52,
+	0x10, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67,
+	0x73, 0x12, 0x47, 0x0a, 0x0e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69,
+	0x6e, 0x67, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79,
+	0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x2e, 0x54,
+	0x79, 0x70, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x0d, 0x70, 0x72, 0x6f,
+	0x78, 0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22, 0x10, 0x0a, 0x0e, 0x4f, 0x75,
+	0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0xb0, 0x02, 0x0a,
+	0x0c, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2d, 0x0a,
+	0x03, 0x76, 0x69, 0x61, 0x18, 0x01, 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, 0x03, 0x76, 0x69, 0x61, 0x12, 0x4e, 0x0a, 0x0f,
+	0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61,
+	0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e,
+	0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0e, 0x73, 0x74,
+	0x72, 0x65, 0x61, 0x6d, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x4b, 0x0a, 0x0e,
+	0x70, 0x72, 0x6f, 0x78, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x74, 0x72, 0x61, 0x6e,
+	0x73, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x2e, 0x50,
+	0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0d, 0x70, 0x72, 0x6f, 0x78,
+	0x79, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x54, 0x0a, 0x12, 0x6d, 0x75, 0x6c,
+	0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70,
+	0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70,
+	0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x11, 0x6d, 0x75,
+	0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x22,
+	0x50, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x69, 0x6e, 0x67, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12,
+	0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63,
+	0x79, 0x2a, 0x23, 0x0a, 0x0e, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63,
+	0x6f, 0x6c, 0x73, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x54, 0x54, 0x50, 0x10, 0x00, 0x12, 0x07, 0x0a,
+	0x03, 0x54, 0x4c, 0x53, 0x10, 0x01, 0x42, 0x55, 0x0a, 0x15, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72,
+	0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x50,
+	0x01, 0x5a, 0x26, 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, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0xaa, 0x02, 0x11, 0x58, 0x72, 0x61, 0x79,
+	0x2e, 0x41, 0x70, 0x70, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x6d, 0x61, 0x6e, 0x62, 0x06, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -894,33 +915,37 @@ var file_app_proxyman_config_proto_goTypes = []interface{}{
 	(*MultiplexingConfig)(nil),                               // 9: xray.app.proxyman.MultiplexingConfig
 	(*AllocationStrategy_AllocationStrategyConcurrency)(nil), // 10: xray.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency
 	(*AllocationStrategy_AllocationStrategyRefresh)(nil),     // 11: xray.app.proxyman.AllocationStrategy.AllocationStrategyRefresh
-	(*net.PortRange)(nil),                                    // 12: xray.common.net.PortRange
-	(*net.IPOrDomain)(nil),                                   // 13: xray.common.net.IPOrDomain
-	(*internet.StreamConfig)(nil),                            // 14: xray.transport.internet.StreamConfig
-	(*serial.TypedMessage)(nil),                              // 15: xray.common.serial.TypedMessage
-	(*internet.ProxyConfig)(nil),                             // 16: xray.transport.internet.ProxyConfig
+	(*domain.Domain)(nil),                                    // 12: xray.common.matcher.domain.Domain
+	(*geoip.GeoIP)(nil),                                      // 13: xray.common.matcher.geoip.GeoIP
+	(*net.PortRange)(nil),                                    // 14: xray.common.net.PortRange
+	(*net.IPOrDomain)(nil),                                   // 15: xray.common.net.IPOrDomain
+	(*internet.StreamConfig)(nil),                            // 16: xray.transport.internet.StreamConfig
+	(*serial.TypedMessage)(nil),                              // 17: xray.common.serial.TypedMessage
+	(*internet.ProxyConfig)(nil),                             // 18: xray.transport.internet.ProxyConfig
 }
 var file_app_proxyman_config_proto_depIdxs = []int32{
 	1,  // 0: xray.app.proxyman.AllocationStrategy.type:type_name -> xray.app.proxyman.AllocationStrategy.Type
 	10, // 1: xray.app.proxyman.AllocationStrategy.concurrency:type_name -> xray.app.proxyman.AllocationStrategy.AllocationStrategyConcurrency
 	11, // 2: xray.app.proxyman.AllocationStrategy.refresh:type_name -> xray.app.proxyman.AllocationStrategy.AllocationStrategyRefresh
-	12, // 3: xray.app.proxyman.ReceiverConfig.port_range:type_name -> xray.common.net.PortRange
-	13, // 4: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain
-	3,  // 5: xray.app.proxyman.ReceiverConfig.allocation_strategy:type_name -> xray.app.proxyman.AllocationStrategy
-	14, // 6: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
-	0,  // 7: xray.app.proxyman.ReceiverConfig.domain_override:type_name -> xray.app.proxyman.KnownProtocols
-	4,  // 8: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig
-	15, // 9: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage
-	15, // 10: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
-	13, // 11: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
-	14, // 12: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
-	16, // 13: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
-	9,  // 14: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
-	15, // [15:15] is the sub-list for method output_type
-	15, // [15:15] is the sub-list for method input_type
-	15, // [15:15] is the sub-list for extension type_name
-	15, // [15:15] is the sub-list for extension extendee
-	0,  // [0:15] is the sub-list for field type_name
+	12, // 3: xray.app.proxyman.SniffingConfig.domains_excluded:type_name -> xray.common.matcher.domain.Domain
+	13, // 4: xray.app.proxyman.SniffingConfig.ips_excluded:type_name -> xray.common.matcher.geoip.GeoIP
+	14, // 5: xray.app.proxyman.ReceiverConfig.port_range:type_name -> xray.common.net.PortRange
+	15, // 6: xray.app.proxyman.ReceiverConfig.listen:type_name -> xray.common.net.IPOrDomain
+	3,  // 7: xray.app.proxyman.ReceiverConfig.allocation_strategy:type_name -> xray.app.proxyman.AllocationStrategy
+	16, // 8: xray.app.proxyman.ReceiverConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
+	0,  // 9: xray.app.proxyman.ReceiverConfig.domain_override:type_name -> xray.app.proxyman.KnownProtocols
+	4,  // 10: xray.app.proxyman.ReceiverConfig.sniffing_settings:type_name -> xray.app.proxyman.SniffingConfig
+	17, // 11: xray.app.proxyman.InboundHandlerConfig.receiver_settings:type_name -> xray.common.serial.TypedMessage
+	17, // 12: xray.app.proxyman.InboundHandlerConfig.proxy_settings:type_name -> xray.common.serial.TypedMessage
+	15, // 13: xray.app.proxyman.SenderConfig.via:type_name -> xray.common.net.IPOrDomain
+	16, // 14: xray.app.proxyman.SenderConfig.stream_settings:type_name -> xray.transport.internet.StreamConfig
+	18, // 15: xray.app.proxyman.SenderConfig.proxy_settings:type_name -> xray.transport.internet.ProxyConfig
+	9,  // 16: xray.app.proxyman.SenderConfig.multiplex_settings:type_name -> xray.app.proxyman.MultiplexingConfig
+	17, // [17:17] is the sub-list for method output_type
+	17, // [17:17] is the sub-list for method input_type
+	17, // [17:17] is the sub-list for extension type_name
+	17, // [17:17] is the sub-list for extension extendee
+	0,  // [0:17] is the sub-list for field type_name
 }
 
 func init() { file_app_proxyman_config_proto_init() }
diff --git a/app/proxyman/config.proto b/app/proxyman/config.proto
index 83d5ad5e47ca..69fa4f15f0a7 100644
--- a/app/proxyman/config.proto
+++ b/app/proxyman/config.proto
@@ -6,6 +6,8 @@ option go_package = "github.com/xtls/xray-core/app/proxyman";
 option java_package = "com.xray.app.proxyman";
 option java_multiple_files = true;
 
+import "common/matcher/domain/domain.proto";
+import "common/matcher/geoip/geoip.proto";
 import "common/net/address.proto";
 import "common/net/port.proto";
 import "transport/internet/config.proto";
@@ -56,7 +58,9 @@ message SniffingConfig {
   // Override target destination if sniff'ed protocol is in the given list.
   // Supported values are "http", "tls", "fakedns".
   repeated string destination_override = 2;
-  repeated string domains_excluded = 3;
+
+  repeated xray.common.matcher.domain.Domain domains_excluded = 3;
+  repeated xray.common.matcher.geoip.GeoIP ips_excluded = 5;
   
   // Whether should only try to sniff metadata without waiting for client input.
   // Can be used to support SMTP like protocol where server send the first message.
diff --git a/app/proxyman/errors.generated.go b/app/proxyman/errors.generated.go
new file mode 100644
index 000000000000..56dc500fb90f
--- /dev/null
+++ b/app/proxyman/errors.generated.go
@@ -0,0 +1,9 @@
+package proxyman
+
+import "github.com/xtls/xray-core/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/app/proxyman/inbound/always.go b/app/proxyman/inbound/always.go
index a29e7d320b97..8918ba515db9 100644
--- a/app/proxyman/inbound/always.go
+++ b/app/proxyman/inbound/always.go
@@ -91,13 +91,20 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
 		if net.HasNetwork(nl, net.Network_UNIX) {
 			newError("creating unix domain socket worker on ", address).AtDebug().WriteToLog()
 
+			sc := receiverConfig.GetEffectiveSniffingSettings()
+			sm, err := proxyman.NewSniffingMatcher(sc)
+			if err != nil {
+				return nil, err
+			}
+
 			worker := &dsWorker{
 				address:         address,
 				proxy:           p,
 				stream:          mss,
 				tag:             tag,
 				dispatcher:      h.mux,
-				sniffingConfig:  receiverConfig.GetEffectiveSniffingSettings(),
+				sniffingConfig:  sc,
+				sniffingMatcher: sm,
 				uplinkCounter:   uplinkCounter,
 				downlinkCounter: downlinkCounter,
 				ctx:             ctx,
@@ -110,6 +117,12 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
 			if net.HasNetwork(nl, net.Network_TCP) {
 				newError("creating stream worker on ", address, ":", port).AtDebug().WriteToLog()
 
+				sc := receiverConfig.GetEffectiveSniffingSettings()
+				sm, err := proxyman.NewSniffingMatcher(sc)
+				if err != nil {
+					return nil, err
+				}
+
 				worker := &tcpWorker{
 					address:         address,
 					port:            net.Port(port),
@@ -118,7 +131,8 @@ func NewAlwaysOnInboundHandler(ctx context.Context, tag string, receiverConfig *
 					recvOrigDest:    receiverConfig.ReceiveOriginalDestination,
 					tag:             tag,
 					dispatcher:      h.mux,
-					sniffingConfig:  receiverConfig.GetEffectiveSniffingSettings(),
+					sniffingConfig:  sc,
+					sniffingMatcher: sm,
 					uplinkCounter:   uplinkCounter,
 					downlinkCounter: downlinkCounter,
 					ctx:             ctx,
diff --git a/app/proxyman/inbound/worker.go b/app/proxyman/inbound/worker.go
index 04094a4b00e2..7735c1330b59 100644
--- a/app/proxyman/inbound/worker.go
+++ b/app/proxyman/inbound/worker.go
@@ -39,6 +39,7 @@ type tcpWorker struct {
 	tag             string
 	dispatcher      routing.Dispatcher
 	sniffingConfig  *proxyman.SniffingConfig
+	sniffingMatcher *proxyman.SniffingMatcher
 	uplinkCounter   stats.Counter
 	downlinkCounter stats.Counter
 
@@ -97,7 +98,8 @@ func (w *tcpWorker) callback(conn internet.Connection) {
 	if w.sniffingConfig != nil {
 		content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
 		content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
-		content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
+		content.SniffingRequest.ExcludedDomainMatcher = w.sniffingMatcher.ExDomain
+		content.SniffingRequest.ExcludedIPMatcher = w.sniffingMatcher.ExIP
 		content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
 	}
 	ctx = session.ContextWithContent(ctx, content)
@@ -428,6 +430,7 @@ type dsWorker struct {
 	tag             string
 	dispatcher      routing.Dispatcher
 	sniffingConfig  *proxyman.SniffingConfig
+	sniffingMatcher *proxyman.SniffingMatcher
 	uplinkCounter   stats.Counter
 	downlinkCounter stats.Counter
 
@@ -459,7 +462,8 @@ func (w *dsWorker) callback(conn internet.Connection) {
 	if w.sniffingConfig != nil {
 		content.SniffingRequest.Enabled = w.sniffingConfig.Enabled
 		content.SniffingRequest.OverrideDestinationForProtocol = w.sniffingConfig.DestinationOverride
-		content.SniffingRequest.ExcludeForDomain = w.sniffingConfig.DomainsExcluded
+		content.SniffingRequest.ExcludedDomainMatcher = w.sniffingMatcher.ExDomain
+		content.SniffingRequest.ExcludedIPMatcher = w.sniffingMatcher.ExIP
 		content.SniffingRequest.MetadataOnly = w.sniffingConfig.MetadataOnly
 	}
 	ctx = session.ContextWithContent(ctx, content)
diff --git a/app/router/condition.go b/app/router/condition.go
index c6e344f10247..8236e8a76c94 100644
--- a/app/router/condition.go
+++ b/app/router/condition.go
@@ -6,8 +6,6 @@ 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/features/routing"
 )
@@ -42,59 +40,6 @@ func (v *ConditionChan) Len() int {
 	return len(*v)
 }
 
-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 *dm.Domain) (str.Matcher, error) {
-	matcherType, f := matcherTypeMap[domain.Type]
-	if !f {
-		return nil, newError("unsupported domain type", domain.Type)
-	}
-
-	matcher, err := matcherType.New(domain.Value)
-	if err != nil {
-		return nil, newError("failed to create domain matcher").Base(err)
-	}
-
-	return matcher, nil
-}
-
-type DomainMatcher struct {
-	matchers str.IndexMatcher
-}
-
-func NewDomainMatcher(domains []*dm.Domain) (*DomainMatcher, error) {
-	g := new(str.MatcherGroup)
-	for _, d := range domains {
-		m, err := domainToMatcher(d)
-		if err != nil {
-			return nil, err
-		}
-		g.Add(m)
-	}
-
-	return &DomainMatcher{
-		matchers: g,
-	}, nil
-}
-
-func (m *DomainMatcher) ApplyDomain(domain string) bool {
-	return len(m.matchers.Match(domain)) > 0
-}
-
-// Apply implements Condition.
-func (m *DomainMatcher) Apply(ctx routing.Context) bool {
-	domain := ctx.GetTargetDomain()
-	if len(domain) == 0 {
-		return false
-	}
-	return m.ApplyDomain(strings.ToLower(domain))
-}
-
 type PortMatcher struct {
 	port     net.MemoryPortList
 	onSource bool
diff --git a/app/router/config.go b/app/router/config.go
index 6fdfeda8b994..838e2296aa32 100644
--- a/app/router/config.go
+++ b/app/router/config.go
@@ -1,6 +1,7 @@
 package router
 
 import (
+	dm "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/outbound"
@@ -29,7 +30,7 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
 	conds := NewConditionChan()
 
 	if len(rr.Domain) > 0 {
-		matcher, err := NewDomainMatcher(rr.Domain)
+		matcher, err := dm.NewDomainMatcher(rr.Domain)
 		if err != nil {
 			return nil, newError("failed to build domain condition").Base(err)
 		}
diff --git a/common/matcher/domain/conf/domain.go b/common/matcher/domain/conf/domain.go
index e71a9cc6f1c3..10a8cf57a25c 100644
--- a/common/matcher/domain/conf/domain.go
+++ b/common/matcher/domain/conf/domain.go
@@ -7,7 +7,7 @@ import (
 	"github.com/xtls/xray-core/common/matcher/geosite"
 )
 
-func ParaseDomainRule(domain string) ([]*dm.Domain, error) {
+func ParseDomainRule(domain string) ([]*dm.Domain, error) {
 	if strings.HasPrefix(domain, "geosite:") {
 		country := strings.ToUpper(domain[8:])
 		domains, err := geosite.LoadGeositeWithAttr("geosite.dat", country)
diff --git a/common/matcher/domain/matcher.go b/common/matcher/domain/matcher.go
new file mode 100644
index 000000000000..474253439450
--- /dev/null
+++ b/common/matcher/domain/matcher.go
@@ -0,0 +1,60 @@
+package domain
+
+import (
+	"github.com/xtls/xray-core/common/matcher/str"
+	"github.com/xtls/xray-core/features/routing"
+	"strings"
+)
+
+var matcherTypeMap = map[MatchingType]str.Type{
+	MatchingType_Keyword:   str.Substr,
+	MatchingType_Regex:     str.Regex,
+	MatchingType_Subdomain: str.Domain,
+	MatchingType_Full:      str.Full,
+}
+
+func domainToMatcher(domain *Domain) (str.Matcher, error) {
+	matcherType, f := matcherTypeMap[domain.Type]
+	if !f {
+		return nil, newError("unsupported domain type", domain.Type)
+	}
+
+	matcher, err := matcherType.New(domain.Value)
+	if err != nil {
+		return nil, newError("failed to create domain matcher").Base(err)
+	}
+
+	return matcher, nil
+}
+
+type DomainMatcher struct {
+	matchers str.IndexMatcher
+}
+
+func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
+	g := new(str.MatcherGroup)
+	for _, d := range domains {
+		m, err := domainToMatcher(d)
+		if err != nil {
+			return nil, err
+		}
+		g.Add(m)
+	}
+
+	return &DomainMatcher{
+		matchers: g,
+	}, nil
+}
+
+func (m *DomainMatcher) ApplyDomain(domain string) bool {
+	return len(m.matchers.Match(domain)) > 0
+}
+
+// Apply implements Condition.
+func (m *DomainMatcher) Apply(ctx routing.Context) bool {
+	domain := ctx.GetTargetDomain()
+	if len(domain) == 0 {
+		return false
+	}
+	return m.ApplyDomain(strings.ToLower(domain))
+}
diff --git a/common/matcher/geoip/conf.go b/common/matcher/geoip/conf.go
index d5be09cce17f..c2c9b8cb0abb 100644
--- a/common/matcher/geoip/conf.go
+++ b/common/matcher/geoip/conf.go
@@ -1,7 +1,11 @@
 package geoip
 
 import (
+	"github.com/xtls/xray-core/common/net"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"runtime"
+	"strconv"
+	"strings"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common/platform/filesystem"
@@ -13,7 +17,7 @@ var (
 )
 
 func LoadGeoIP(code string) ([]*CIDR, error) {
-	return LoadIPFile("geoip.dat", code)
+	return LoadIPFile("dat", code)
 }
 
 func LoadIPFile(file, code string) ([]*CIDR, error) {
@@ -88,3 +92,117 @@ func find(data, code []byte) []byte {
 		data = data[bodyL:]
 	}
 }
+
+func ParaseIPList(ips common.StringList) ([]*GeoIP, error) {
+	var geoipList []*GeoIP
+	var customCidrs []*CIDR
+
+	for _, ip := range ips {
+		if strings.HasPrefix(ip, "geoip:") {
+			country := ip[6:]
+			geoipc, err := LoadGeoIP(strings.ToUpper(country))
+			if err != nil {
+				return nil, newError("failed to load GeoIP: ", country).Base(err)
+			}
+
+			geoipList = append(geoipList, &GeoIP{
+				CountryCode: strings.ToUpper(country),
+				Cidr:        geoipc,
+			})
+			continue
+		}
+		var isExtDatFile = 0
+		{
+			const prefix = "ext:"
+			if strings.HasPrefix(ip, prefix) {
+				isExtDatFile = len(prefix)
+			}
+			const prefixQualified = "ext-ip:"
+			if strings.HasPrefix(ip, prefixQualified) {
+				isExtDatFile = len(prefixQualified)
+			}
+		}
+		if isExtDatFile != 0 {
+			kv := strings.Split(ip[isExtDatFile:], ":")
+			if len(kv) != 2 {
+				return nil, newError("invalid external resource: ", ip)
+			}
+
+			filename := kv[0]
+			country := kv[1]
+			geoipc, err := LoadIPFile(filename, strings.ToUpper(country))
+			if err != nil {
+				return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err)
+			}
+
+			geoipList = append(geoipList, &GeoIP{
+				CountryCode: strings.ToUpper(filename + "_" + country),
+				Cidr:        geoipc,
+			})
+
+			continue
+		}
+
+		ipRule, err := ParseIP(ip)
+		if err != nil {
+			return nil, newError("invalid IP: ", ip).Base(err)
+		}
+		customCidrs = append(customCidrs, ipRule)
+	}
+
+	if len(customCidrs) > 0 {
+		geoipList = append(geoipList, &GeoIP{
+			Cidr: customCidrs,
+		})
+	}
+
+	return geoipList, nil
+}
+
+func ParseIP(s string) (*CIDR, error) {
+	var addr, mask string
+	i := strings.Index(s, "/")
+	if i < 0 {
+		addr = s
+	} else {
+		addr = s[:i]
+		mask = s[i+1:]
+	}
+	ip := net.ParseAddress(addr)
+	switch ip.Family() {
+	case net.AddressFamilyIPv4:
+		bits := uint32(32)
+		if len(mask) > 0 {
+			bits64, err := strconv.ParseUint(mask, 10, 32)
+			if err != nil {
+				return nil, newError("invalid network mask for router: ", mask).Base(err)
+			}
+			bits = uint32(bits64)
+		}
+		if bits > 32 {
+			return nil, newError("invalid network mask for router: ", bits)
+		}
+		return &CIDR{
+			Ip:     ip.IP(),
+			Prefix: bits,
+		}, nil
+	case net.AddressFamilyIPv6:
+		bits := uint32(128)
+		if len(mask) > 0 {
+			bits64, err := strconv.ParseUint(mask, 10, 32)
+			if err != nil {
+				return nil, newError("invalid network mask for router: ", mask).Base(err)
+			}
+			bits = uint32(bits64)
+		}
+		if bits > 128 {
+			return nil, newError("invalid network mask for router: ", bits)
+		}
+		return &CIDR{
+			Ip:     ip.IP(),
+			Prefix: bits,
+		}, nil
+	default:
+		return nil, newError("unsupported address for router: ", s)
+	}
+}
diff --git a/common/matcher/geoip/matcher.go b/common/matcher/geoip/matcher.go
index c98ed115493d..e2f4e1888d96 100644
--- a/common/matcher/geoip/matcher.go
+++ b/common/matcher/geoip/matcher.go
@@ -45,3 +45,13 @@ func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
 	}
 	return false
 }
+
+// MatchIP match given ip.
+func (m *MultiGeoIPMatcher) MatchIP(ip net.IP) bool {
+	for _, matcher := range m.matchers {
+		if matcher.Match(ip) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/common/session/session.go b/common/session/session.go
index 24ac4631bcc8..7c7062aa68fe 100644
--- a/common/session/session.go
+++ b/common/session/session.go
@@ -3,6 +3,8 @@ package session // import "github.com/xtls/xray-core/common/session"
 
 import (
 	"context"
+	"github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/geoip"
 	"math/rand"
 
 	"github.com/xtls/xray-core/common/errors"
@@ -60,7 +62,8 @@ type Outbound struct {
 
 // SniffingRequest controls the behavior of content sniffing.
 type SniffingRequest struct {
-	ExcludeForDomain               []string
+	ExcludedDomainMatcher          *domain.DomainMatcher
+	ExcludedIPMatcher              *geoip.MultiGeoIPMatcher
 	OverrideDestinationForProtocol []string
 	Enabled                        bool
 	MetadataOnly                   bool
diff --git a/infra/conf/common.go b/infra/conf/common/common.go
similarity index 98%
rename from infra/conf/common.go
rename to infra/conf/common/common.go
index 0de30728f65c..a24543899e4d 100644
--- a/infra/conf/common.go
+++ b/infra/conf/common/common.go
@@ -1,4 +1,4 @@
-package conf
+package common
 
 import (
 	"encoding/json"
@@ -9,6 +9,8 @@ import (
 	"github.com/xtls/xray-core/common/protocol"
 )
 
+//go:generate go run github.com/xtls/xray-core/common/errors/errorgen
+
 type StringList []string
 
 func NewStringList(raw []string) *StringList {
diff --git a/infra/conf/common_test.go b/infra/conf/common/common_test.go
similarity index 98%
rename from infra/conf/common_test.go
rename to infra/conf/common/common_test.go
index 39e4cbeac3b6..6bc73c931435 100644
--- a/infra/conf/common_test.go
+++ b/infra/conf/common/common_test.go
@@ -1,4 +1,4 @@
-package conf_test
+package common_test
 
 import (
 	"encoding/json"
@@ -11,7 +11,7 @@ import (
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/protocol"
-	. "github.com/xtls/xray-core/infra/conf"
+	. "github.com/xtls/xray-core/infra/conf/common"
 )
 
 func TestStringListUnmarshalError(t *testing.T) {
diff --git a/infra/conf/common/errors.generated.go b/infra/conf/common/errors.generated.go
new file mode 100644
index 000000000000..28b9a625522c
--- /dev/null
+++ b/infra/conf/common/errors.generated.go
@@ -0,0 +1,9 @@
+package common
+
+import "github.com/xtls/xray-core/common/errors"
+
+type errPathObjHolder struct{}
+
+func newError(values ...interface{}) *errors.Error {
+	return errors.New(values...).WithPathObj(errPathObjHolder{})
+}
diff --git a/infra/conf/dns.go b/infra/conf/dns.go
index ce7bc24ea97e..807600ecf162 100644
--- a/infra/conf/dns.go
+++ b/infra/conf/dns.go
@@ -2,6 +2,8 @@ package conf
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/common/matcher/geoip"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"sort"
 	"strings"
 
@@ -13,24 +15,24 @@ import (
 )
 
 type NameServerConfig struct {
-	Address   *Address
+	Address   *common.Address
 	Port      uint16
 	Domains   []string
-	ExpectIPs StringList
+	ExpectIPs common.StringList
 }
 
 func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
-	var address Address
+	var address common.Address
 	if err := json.Unmarshal(data, &address); err == nil {
 		c.Address = &address
 		return nil
 	}
 
 	var advanced struct {
-		Address   *Address   `json:"address"`
-		Port      uint16     `json:"port"`
-		Domains   []string   `json:"domains"`
-		ExpectIPs StringList `json:"expectIps"`
+		Address   *common.Address   `json:"address"`
+		Port      uint16            `json:"port"`
+		Domains   []string          `json:"domains"`
+		ExpectIPs common.StringList `json:"expectIps"`
 	}
 	if err := json.Unmarshal(data, &advanced); err == nil {
 		c.Address = advanced.Address
@@ -52,7 +54,7 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
 	var originalRules []*dns.NameServer_OriginalRule
 
 	for _, rule := range c.Domains {
-		parsedDomain, err := conf.ParaseDomainRule(rule)
+		parsedDomain, err := conf.ParseDomainRule(rule)
 		if err != nil {
 			return nil, newError("invalid domain rule: ", rule).Base(err)
 		}
@@ -69,7 +71,7 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
 		})
 	}
 
-	geoipList, err := toCidrList(c.ExpectIPs)
+	geoipList, err := geoip.ParaseIPList(c.ExpectIPs)
 	if err != nil {
 		return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
 	}
@@ -88,15 +90,15 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
 
 // DNSConfig is a JSON serializable object for dns.Config.
 type DNSConfig struct {
-	Servers       []*NameServerConfig `json:"servers"`
-	Hosts         map[string]*Address `json:"hosts"`
-	ClientIP      *Address            `json:"clientIp"`
-	Tag           string              `json:"tag"`
-	QueryStrategy string              `json:"queryStrategy"`
-	DisableCache  bool                `json:"disableCache"`
+	Servers       []*NameServerConfig        `json:"servers"`
+	Hosts         map[string]*common.Address `json:"hosts"`
+	ClientIP      *common.Address            `json:"clientIp"`
+	Tag           string                     `json:"tag"`
+	QueryStrategy string                     `json:"queryStrategy"`
+	DisableCache  bool                       `json:"disableCache"`
 }
 
-func getHostMapping(addr *Address) *dns.Config_HostMapping {
+func getHostMapping(addr *common.Address) *dns.Config_HostMapping {
 	if addr.Family().IsIP() {
 		return &dns.Config_HostMapping{
 			Ip: [][]byte{[]byte(addr.IP())},
diff --git a/infra/conf/dns_proxy.go b/infra/conf/dns_proxy.go
index 922d32bafcae..4b1f3b915401 100644
--- a/infra/conf/dns_proxy.go
+++ b/infra/conf/dns_proxy.go
@@ -3,13 +3,14 @@ package conf
 import (
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common/net"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/dns"
 )
 
 type DNSOutboundConfig struct {
-	Network Network  `json:"network"`
-	Address *Address `json:"address"`
-	Port    uint16   `json:"port"`
+	Network common.Network  `json:"network"`
+	Address *common.Address `json:"address"`
+	Port    uint16          `json:"port"`
 }
 
 func (c *DNSOutboundConfig) Build() (proto.Message, error) {
diff --git a/infra/conf/dokodemo.go b/infra/conf/dokodemo.go
index 03a21d71192f..c5b42e657f0d 100644
--- a/infra/conf/dokodemo.go
+++ b/infra/conf/dokodemo.go
@@ -2,16 +2,17 @@ package conf
 
 import (
 	"github.com/golang/protobuf/proto"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/dokodemo"
 )
 
 type DokodemoConfig struct {
-	Host         *Address     `json:"address"`
-	PortValue    uint16       `json:"port"`
-	NetworkList  *NetworkList `json:"network"`
-	TimeoutValue uint32       `json:"timeout"`
-	Redirect     bool         `json:"followRedirect"`
-	UserLevel    uint32       `json:"userLevel"`
+	Host         *common.Address     `json:"address"`
+	PortValue    uint16              `json:"port"`
+	NetworkList  *common.NetworkList `json:"network"`
+	TimeoutValue uint32              `json:"timeout"`
+	Redirect     bool                `json:"followRedirect"`
+	UserLevel    uint32              `json:"userLevel"`
 }
 
 func (v *DokodemoConfig) Build() (proto.Message, error) {
diff --git a/infra/conf/http.go b/infra/conf/http.go
index e6969582b7e6..7b9acaa771f9 100644
--- a/infra/conf/http.go
+++ b/infra/conf/http.go
@@ -2,6 +2,7 @@ package conf
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/infra/conf/common"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common/protocol"
@@ -46,7 +47,7 @@ func (c *HTTPServerConfig) Build() (proto.Message, error) {
 }
 
 type HTTPRemoteConfig struct {
-	Address *Address          `json:"address"`
+	Address *common.Address   `json:"address"`
 	Port    uint16            `json:"port"`
 	Users   []json.RawMessage `json:"users"`
 }
diff --git a/infra/conf/router.go b/infra/conf/router.go
index 128096ab67e6..1f68e3049082 100644
--- a/infra/conf/router.go
+++ b/infra/conf/router.go
@@ -2,6 +2,7 @@ package conf
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"strconv"
 	"strings"
 
@@ -10,7 +11,6 @@ import (
 	"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"
 )
 
 type RouterRulesConfig struct {
@@ -19,8 +19,8 @@ type RouterRulesConfig struct {
 }
 
 type BalancingRule struct {
-	Tag       string     `json:"tag"`
-	Selectors StringList `json:"selector"`
+	Tag       string            `json:"tag"`
+	Selectors common.StringList `json:"selector"`
 }
 
 func (r *BalancingRule) Build() (*router.BalancingRule, error) {
@@ -148,108 +148,20 @@ func ParseIP(s string) (*geoip.CIDR, error) {
 	}
 }
 
-var (
-	FileCache = make(map[string][]byte)
-	IPCache   = make(map[string]*geoip.GeoIP)
-)
-
-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 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:]
-			geoipc, err := geoip.LoadGeoIP(strings.ToUpper(country))
-			if err != nil {
-				return nil, newError("failed to load GeoIP: ", country).Base(err)
-			}
-
-			geoipList = append(geoipList, &geoip.GeoIP{
-				CountryCode: strings.ToUpper(country),
-				Cidr:        geoipc,
-			})
-			continue
-		}
-		var isExtDatFile = 0
-		{
-			const prefix = "ext:"
-			if strings.HasPrefix(ip, prefix) {
-				isExtDatFile = len(prefix)
-			}
-			const prefixQualified = "ext-ip:"
-			if strings.HasPrefix(ip, prefixQualified) {
-				isExtDatFile = len(prefixQualified)
-			}
-		}
-		if isExtDatFile != 0 {
-			kv := strings.Split(ip[isExtDatFile:], ":")
-			if len(kv) != 2 {
-				return nil, newError("invalid external resource: ", ip)
-			}
-
-			filename := kv[0]
-			country := kv[1]
-			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, &geoip.GeoIP{
-				CountryCode: strings.ToUpper(filename + "_" + country),
-				Cidr:        geoipc,
-			})
-
-			continue
-		}
-
-		ipRule, err := ParseIP(ip)
-		if err != nil {
-			return nil, newError("invalid IP: ", ip).Base(err)
-		}
-		customCidrs = append(customCidrs, ipRule)
-	}
-
-	if len(customCidrs) > 0 {
-		geoipList = append(geoipList, &geoip.GeoIP{
-			Cidr: customCidrs,
-		})
-	}
-
-	return geoipList, nil
-}
-
 func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 	type RawFieldRule struct {
 		RouterRule
-		Domain     *StringList  `json:"domain"`
-		Domains    *StringList  `json:"domains"`
-		IP         *StringList  `json:"ip"`
-		Port       *PortList    `json:"port"`
-		Network    *NetworkList `json:"network"`
-		SourceIP   *StringList  `json:"source"`
-		SourcePort *PortList    `json:"sourcePort"`
-		User       *StringList  `json:"user"`
-		InboundTag *StringList  `json:"inboundTag"`
-		Protocols  *StringList  `json:"protocol"`
-		Attributes string       `json:"attrs"`
+		Domain     *common.StringList  `json:"domain"`
+		Domains    *common.StringList  `json:"domains"`
+		IP         *common.StringList  `json:"ip"`
+		Port       *common.PortList    `json:"port"`
+		Network    *common.NetworkList `json:"network"`
+		SourceIP   *common.StringList  `json:"source"`
+		SourcePort *common.PortList    `json:"sourcePort"`
+		User       *common.StringList  `json:"user"`
+		InboundTag *common.StringList  `json:"inboundTag"`
+		Protocols  *common.StringList  `json:"protocol"`
+		Attributes string              `json:"attrs"`
 	}
 	rawFieldRule := new(RawFieldRule)
 	err := json.Unmarshal(msg, rawFieldRule)
@@ -273,7 +185,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 
 	if rawFieldRule.Domain != nil {
 		for _, domain := range *rawFieldRule.Domain {
-			rules, err := conf.ParaseDomainRule(domain)
+			rules, err := conf.ParseDomainRule(domain)
 			if err != nil {
 				return nil, newError("failed to parse domain rule: ", domain).Base(err)
 			}
@@ -283,7 +195,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 
 	if rawFieldRule.Domains != nil {
 		for _, domain := range *rawFieldRule.Domains {
-			rules, err := conf.ParaseDomainRule(domain)
+			rules, err := conf.ParseDomainRule(domain)
 			if err != nil {
 				return nil, newError("failed to parse domain rule: ", domain).Base(err)
 			}
@@ -292,7 +204,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 	}
 
 	if rawFieldRule.IP != nil {
-		geoipList, err := toCidrList(*rawFieldRule.IP)
+		geoipList, err := geoip.ParaseIPList(*rawFieldRule.IP)
 		if err != nil {
 			return nil, err
 		}
@@ -308,7 +220,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 	}
 
 	if rawFieldRule.SourceIP != nil {
-		geoipList, err := toCidrList(*rawFieldRule.SourceIP)
+		geoipList, err := geoip.ParaseIPList(*rawFieldRule.SourceIP)
 		if err != nil {
 			return nil, err
 		}
diff --git a/infra/conf/shadowsocks.go b/infra/conf/shadowsocks.go
index fbb2a79d583a..42b9f038815f 100644
--- a/infra/conf/shadowsocks.go
+++ b/infra/conf/shadowsocks.go
@@ -1,6 +1,7 @@
 package conf
 
 import (
+	"github.com/xtls/xray-core/infra/conf/common"
 	"strings"
 
 	"github.com/golang/protobuf/proto"
@@ -46,7 +47,7 @@ type ShadowsocksServerConfig struct {
 	Level       byte                     `json:"level"`
 	Email       string                   `json:"email"`
 	Users       []*ShadowsocksUserConfig `json:"clients"`
-	NetworkList *NetworkList             `json:"network"`
+	NetworkList *common.NetworkList      `json:"network"`
 }
 
 func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
@@ -93,12 +94,12 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
 }
 
 type ShadowsocksServerTarget struct {
-	Address  *Address `json:"address"`
-	Port     uint16   `json:"port"`
-	Cipher   string   `json:"method"`
-	Password string   `json:"password"`
-	Email    string   `json:"email"`
-	Level    byte     `json:"level"`
+	Address  *common.Address `json:"address"`
+	Port     uint16          `json:"port"`
+	Cipher   string          `json:"method"`
+	Password string          `json:"password"`
+	Email    string          `json:"email"`
+	Level    byte            `json:"level"`
 }
 
 type ShadowsocksClientConfig struct {
diff --git a/infra/conf/socks.go b/infra/conf/socks.go
index 05dad7bc18fb..ea2a41893888 100644
--- a/infra/conf/socks.go
+++ b/infra/conf/socks.go
@@ -2,6 +2,7 @@ package conf
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/infra/conf/common"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common/protocol"
@@ -30,7 +31,7 @@ type SocksServerConfig struct {
 	AuthMethod string          `json:"auth"`
 	Accounts   []*SocksAccount `json:"accounts"`
 	UDP        bool            `json:"udp"`
-	Host       *Address        `json:"ip"`
+	Host       *common.Address `json:"ip"`
 	Timeout    uint32          `json:"timeout"`
 	UserLevel  uint32          `json:"userLevel"`
 }
@@ -65,7 +66,7 @@ func (v *SocksServerConfig) Build() (proto.Message, error) {
 }
 
 type SocksRemoteConfig struct {
-	Address *Address          `json:"address"`
+	Address *common.Address   `json:"address"`
 	Port    uint16            `json:"port"`
 	Users   []json.RawMessage `json:"users"`
 }
diff --git a/infra/conf/transport_authenticators.go b/infra/conf/transport_authenticators.go
index c9e42605b10d..f000e8d4707e 100644
--- a/infra/conf/transport_authenticators.go
+++ b/infra/conf/transport_authenticators.go
@@ -1,6 +1,7 @@
 package conf
 
 import (
+	"github.com/xtls/xray-core/infra/conf/common"
 	"sort"
 
 	"github.com/golang/protobuf/proto"
@@ -57,13 +58,13 @@ func (DTLSAuthenticator) Build() (proto.Message, error) {
 }
 
 type AuthenticatorRequest struct {
-	Version string                 `json:"version"`
-	Method  string                 `json:"method"`
-	Path    StringList             `json:"path"`
-	Headers map[string]*StringList `json:"headers"`
+	Version string                        `json:"version"`
+	Method  string                        `json:"method"`
+	Path    common.StringList             `json:"path"`
+	Headers map[string]*common.StringList `json:"headers"`
 }
 
-func sortMapKeys(m map[string]*StringList) []string {
+func sortMapKeys(m map[string]*common.StringList) []string {
 	var keys []string
 	for key := range m {
 		keys = append(keys, key)
@@ -133,10 +134,10 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
 }
 
 type AuthenticatorResponse struct {
-	Version string                 `json:"version"`
-	Status  string                 `json:"status"`
-	Reason  string                 `json:"reason"`
-	Headers map[string]*StringList `json:"headers"`
+	Version string                        `json:"version"`
+	Status  string                        `json:"status"`
+	Reason  string                        `json:"reason"`
+	Headers map[string]*common.StringList `json:"headers"`
 }
 
 func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go
index 15d5dd3257d3..980a49a829de 100644
--- a/infra/conf/transport_internet.go
+++ b/infra/conf/transport_internet.go
@@ -2,6 +2,7 @@ package conf
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"math"
 	"net/url"
 	"strconv"
@@ -179,8 +180,8 @@ func (c *WebSocketConfig) Build() (proto.Message, error) {
 }
 
 type HTTPConfig struct {
-	Host *StringList `json:"host"`
-	Path string      `json:"path"`
+	Host *common.StringList `json:"host"`
+	Path string             `json:"path"`
 }
 
 // Build implements Buildable.
@@ -311,16 +312,16 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
 }
 
 type TLSConfig struct {
-	Insecure                 bool             `json:"allowInsecure"`
-	Certs                    []*TLSCertConfig `json:"certificates"`
-	ServerName               string           `json:"serverName"`
-	ALPN                     *StringList      `json:"alpn"`
-	EnableSessionResumption  bool             `json:"enableSessionResumption"`
-	DisableSystemRoot        bool             `json:"disableSystemRoot"`
-	MinVersion               string           `json:"minVersion"`
-	MaxVersion               string           `json:"maxVersion"`
-	CipherSuites             string           `json:"cipherSuites"`
-	PreferServerCipherSuites bool             `json:"preferServerCipherSuites"`
+	Insecure                 bool               `json:"allowInsecure"`
+	Certs                    []*TLSCertConfig   `json:"certificates"`
+	ServerName               string             `json:"serverName"`
+	ALPN                     *common.StringList `json:"alpn"`
+	EnableSessionResumption  bool               `json:"enableSessionResumption"`
+	DisableSystemRoot        bool               `json:"disableSystemRoot"`
+	MinVersion               string             `json:"minVersion"`
+	MaxVersion               string             `json:"maxVersion"`
+	CipherSuites             string             `json:"cipherSuites"`
+	PreferServerCipherSuites bool               `json:"preferServerCipherSuites"`
 }
 
 // Build implements Buildable.
@@ -401,16 +402,16 @@ func (c *XTLSCertConfig) Build() (*xtls.Certificate, error) {
 }
 
 type XTLSConfig struct {
-	Insecure                 bool              `json:"allowInsecure"`
-	Certs                    []*XTLSCertConfig `json:"certificates"`
-	ServerName               string            `json:"serverName"`
-	ALPN                     *StringList       `json:"alpn"`
-	EnableSessionResumption  bool              `json:"enableSessionResumption"`
-	DisableSystemRoot        bool              `json:"disableSystemRoot"`
-	MinVersion               string            `json:"minVersion"`
-	MaxVersion               string            `json:"maxVersion"`
-	CipherSuites             string            `json:"cipherSuites"`
-	PreferServerCipherSuites bool              `json:"preferServerCipherSuites"`
+	Insecure                 bool               `json:"allowInsecure"`
+	Certs                    []*XTLSCertConfig  `json:"certificates"`
+	ServerName               string             `json:"serverName"`
+	ALPN                     *common.StringList `json:"alpn"`
+	EnableSessionResumption  bool               `json:"enableSessionResumption"`
+	DisableSystemRoot        bool               `json:"disableSystemRoot"`
+	MinVersion               string             `json:"minVersion"`
+	MaxVersion               string             `json:"maxVersion"`
+	CipherSuites             string             `json:"cipherSuites"`
+	PreferServerCipherSuites bool               `json:"preferServerCipherSuites"`
 }
 
 // Build implements Buildable.
diff --git a/infra/conf/trojan.go b/infra/conf/trojan.go
index 4fe96c071585..b59894cb16f7 100644
--- a/infra/conf/trojan.go
+++ b/infra/conf/trojan.go
@@ -2,6 +2,7 @@ package conf
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"runtime"
 	"strconv"
 	"syscall"
@@ -16,12 +17,12 @@ import (
 
 // TrojanServerTarget is configuration of a single trojan server
 type TrojanServerTarget struct {
-	Address  *Address `json:"address"`
-	Port     uint16   `json:"port"`
-	Password string   `json:"password"`
-	Email    string   `json:"email"`
-	Level    byte     `json:"level"`
-	Flow     string   `json:"flow"`
+	Address  *common.Address `json:"address"`
+	Port     uint16          `json:"port"`
+	Password string          `json:"password"`
+	Email    string          `json:"email"`
+	Level    byte            `json:"level"`
+	Flow     string          `json:"flow"`
 }
 
 // TrojanClientConfig is configuration of trojan servers
diff --git a/infra/conf/vless.go b/infra/conf/vless.go
index f5ded7a00607..33a5c3b11ebc 100644
--- a/infra/conf/vless.go
+++ b/infra/conf/vless.go
@@ -2,6 +2,7 @@ package conf
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"runtime"
 	"strconv"
 	"syscall"
@@ -137,7 +138,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
 }
 
 type VLessOutboundVnext struct {
-	Address *Address          `json:"address"`
+	Address *common.Address   `json:"address"`
 	Port    uint16            `json:"port"`
 	Users   []json.RawMessage `json:"users"`
 }
diff --git a/infra/conf/vmess.go b/infra/conf/vmess.go
index 1200be07047a..62084c092eff 100644
--- a/infra/conf/vmess.go
+++ b/infra/conf/vmess.go
@@ -2,6 +2,7 @@ package conf
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"strings"
 
 	"github.com/golang/protobuf/proto"
@@ -123,7 +124,7 @@ func (c *VMessInboundConfig) Build() (proto.Message, error) {
 }
 
 type VMessOutboundTarget struct {
-	Address *Address          `json:"address"`
+	Address *common.Address   `json:"address"`
 	Port    uint16            `json:"port"`
 	Users   []json.RawMessage `json:"users"`
 }
diff --git a/infra/conf/xray.go b/infra/conf/xray.go
index 0b139792e5d8..f871222107a8 100644
--- a/infra/conf/xray.go
+++ b/infra/conf/xray.go
@@ -2,6 +2,10 @@ package conf
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/domain/conf"
+	"github.com/xtls/xray-core/common/matcher/geoip"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/transport/internet"
 	"log"
 	"os"
@@ -59,10 +63,11 @@ func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) {
 }
 
 type SniffingConfig struct {
-	Enabled         bool        `json:"enabled"`
-	DestOverride    *StringList `json:"destOverride"`
-	DomainsExcluded *StringList `json:"domainsExcluded"`
-	MetadataOnly    bool        `json:"metadataOnly"`
+	Enabled         bool               `json:"enabled"`
+	DestOverride    *common.StringList `json:"destOverride"`
+	DomainsExcluded *common.StringList `json:"domainsExcluded"`
+	IPsExcluded     *common.StringList `json:"ipsExcluded"`
+	MetadataOnly    bool               `json:"metadataOnly"`
 }
 
 // Build implements Buildable.
@@ -83,17 +88,31 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) {
 		}
 	}
 
-	var d []string
+	var exDomain []*domain.Domain
 	if c.DomainsExcluded != nil {
-		for _, domain := range *c.DomainsExcluded {
-			d = append(d, strings.ToLower(domain))
+		for _, dmr := range *c.DomainsExcluded {
+			if dm, err := conf.ParseDomainRule(dmr); err == nil {
+				exDomain = append(exDomain, dm...)
+			} else {
+				return nil, newError("failed to parse excluded domain").Base(err)
+			}
+		}
+	}
+
+	var exIP []*geoip.GeoIP
+	if c.IPsExcluded != nil {
+		exip, err := geoip.ParaseIPList(*c.IPsExcluded)
+		if err != nil {
+			return nil, newError("failed to parse excluded ip").Base(err)
 		}
+		exIP = exip
 	}
 
 	return &proxyman.SniffingConfig{
 		Enabled:             c.Enabled,
 		DestinationOverride: p,
-		DomainsExcluded:     d,
+		DomainsExcluded:     exDomain,
+		IpsExcluded:         exIP,
 		MetadataOnly:        c.MetadataOnly,
 	}, nil
 }
@@ -156,13 +175,13 @@ func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, e
 
 type InboundDetourConfig struct {
 	Protocol       string                         `json:"protocol"`
-	PortRange      *PortRange                     `json:"port"`
-	ListenOn       *Address                       `json:"listen"`
+	PortRange      *common.PortRange              `json:"port"`
+	ListenOn       *common.Address                `json:"listen"`
 	Settings       *json.RawMessage               `json:"settings"`
 	Tag            string                         `json:"tag"`
 	Allocation     *InboundDetourAllocationConfig `json:"allocate"`
 	StreamSetting  *StreamConfig                  `json:"streamSettings"`
-	DomainOverride *StringList                    `json:"domainOverride"`
+	DomainOverride *common.StringList             `json:"domainOverride"`
 	SniffingConfig *SniffingConfig                `json:"sniffing"`
 }
 
@@ -264,7 +283,7 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
 
 type OutboundDetourConfig struct {
 	Protocol      string           `json:"protocol"`
-	SendThrough   *Address         `json:"sendThrough"`
+	SendThrough   *common.Address  `json:"sendThrough"`
 	Tag           string           `json:"tag"`
 	Settings      *json.RawMessage `json:"settings"`
 	StreamSetting *StreamConfig    `json:"streamSettings"`
@@ -622,7 +641,7 @@ func (c *Config) Build() (*core.Config, error) {
 
 	// Backward compatibility.
 	if len(inbounds) > 0 && inbounds[0].PortRange == nil && c.Port > 0 {
-		inbounds[0].PortRange = &PortRange{
+		inbounds[0].PortRange = &common.PortRange{
 			From: uint32(c.Port),
 			To:   uint32(c.Port),
 		}
diff --git a/infra/conf/xray_test.go b/infra/conf/xray_test.go
index 96df370ee514..bf7e2c519de9 100644
--- a/infra/conf/xray_test.go
+++ b/infra/conf/xray_test.go
@@ -2,6 +2,7 @@ package conf_test
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/common/matcher/domain"
 	"reflect"
 	"testing"
 
@@ -372,6 +373,52 @@ func TestMuxConfig_Build(t *testing.T) {
 	}
 }
 
+func TestSniffingConfig_Build(t *testing.T) {
+	tests := []struct {
+		name   string
+		fields string
+		want   *proxyman.SniffingConfig
+	}{
+		{"default", `
+{
+	"enabled": true, 
+	"destOverride": ["tls"],
+	"domainsExcluded": ["domain:google.com"],
+	"ipsExcluded": ["8.8.8.8"]
+}`, &proxyman.SniffingConfig{
+			Enabled:             true,
+			DestinationOverride: []string{"tls"},
+			DomainsExcluded: []*domain.Domain{
+				{
+					Type:  domain.MatchingType_Subdomain,
+					Value: "google.com",
+				},
+			},
+			IpsExcluded: []*geoip.GeoIP{
+				{
+					Cidr: []*geoip.CIDR{{Ip: []byte{8, 8, 8, 8}, Prefix: 32}},
+				},
+			},
+		}},
+		{"empty def", `{}`, &proxyman.SniffingConfig{
+			Enabled: false,
+		}},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			m := &SniffingConfig{}
+			common.Must(json.Unmarshal([]byte(tt.fields), m))
+			got, err := m.Build()
+			if err != nil {
+				t.Errorf("%v", err)
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("SniffingConfig.Build() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
 func TestConfig_Override(t *testing.T) {
 	tests := []struct {
 		name string

From fc8b58001754ec543f6738adc9efbea6f88c2d71 Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Fri, 26 Mar 2021 17:19:09 +0800
Subject: [PATCH 04/20] Fix: tests

---
 app/proxyman/config.go       | 4 ++++
 app/router/condition_test.go | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/app/proxyman/config.go b/app/proxyman/config.go
index 302d208f61de..16506627fa6c 100644
--- a/app/proxyman/config.go
+++ b/app/proxyman/config.go
@@ -53,6 +53,10 @@ type SniffingMatcher struct {
 func NewSniffingMatcher(sc *SniffingConfig) (*SniffingMatcher, error) {
 	m := new(SniffingMatcher)
 
+	if sc == nil {
+		return m, nil
+	}
+
 	if sc.DomainsExcluded != nil {
 		exDomain, err := domain.NewDomainMatcher(sc.DomainsExcluded)
 		if err != nil {
diff --git a/app/router/condition_test.go b/app/router/condition_test.go
index 2ee1c0202cae..e4f203411932 100644
--- a/app/router/condition_test.go
+++ b/app/router/condition_test.go
@@ -359,7 +359,7 @@ func TestChinaSites(t *testing.T) {
 	domains, err := loadGeoSite("CN")
 	common.Must(err)
 
-	matcher, err := NewDomainMatcher(domains)
+	matcher, err := domain.NewDomainMatcher(domains)
 	common.Must(err)
 
 	type TestCase struct {

From 4eb3acb4ddff44f964473440e336baed502c8643 Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Fri, 26 Mar 2021 17:30:24 +0800
Subject: [PATCH 05/20] Fix: format

---
 common/matcher/domain/matcher.go       |  3 ++-
 common/matcher/geoip/conf.go           |  5 ++---
 common/session/session.go              |  4 ++--
 infra/conf/dns.go                      |  5 +++--
 infra/conf/http.go                     |  2 +-
 infra/conf/router.go                   |  2 +-
 infra/conf/shadowsocks.go              |  2 +-
 infra/conf/socks.go                    |  2 +-
 infra/conf/transport_authenticators.go |  2 +-
 infra/conf/transport_internet.go       |  2 +-
 infra/conf/trojan.go                   |  2 +-
 infra/conf/vless.go                    |  2 +-
 infra/conf/vmess.go                    |  2 +-
 infra/conf/xray.go                     | 11 ++++++-----
 14 files changed, 24 insertions(+), 22 deletions(-)

diff --git a/common/matcher/domain/matcher.go b/common/matcher/domain/matcher.go
index 474253439450..1abad45ca712 100644
--- a/common/matcher/domain/matcher.go
+++ b/common/matcher/domain/matcher.go
@@ -1,9 +1,10 @@
 package domain
 
 import (
+	"strings"
+
 	"github.com/xtls/xray-core/common/matcher/str"
 	"github.com/xtls/xray-core/features/routing"
-	"strings"
 )
 
 var matcherTypeMap = map[MatchingType]str.Type{
diff --git a/common/matcher/geoip/conf.go b/common/matcher/geoip/conf.go
index c2c9b8cb0abb..e7d89ac65179 100644
--- a/common/matcher/geoip/conf.go
+++ b/common/matcher/geoip/conf.go
@@ -1,13 +1,12 @@
 package geoip
 
 import (
-	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"runtime"
 	"strconv"
 	"strings"
 
 	"github.com/golang/protobuf/proto"
+	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/platform/filesystem"
 )
 
@@ -93,7 +92,7 @@ func find(data, code []byte) []byte {
 	}
 }
 
-func ParaseIPList(ips common.StringList) ([]*GeoIP, error) {
+func ParaseIPList(ips []string) ([]*GeoIP, error) {
 	var geoipList []*GeoIP
 	var customCidrs []*CIDR
 
diff --git a/common/session/session.go b/common/session/session.go
index 7c7062aa68fe..9b32773f9437 100644
--- a/common/session/session.go
+++ b/common/session/session.go
@@ -3,11 +3,11 @@ package session // import "github.com/xtls/xray-core/common/session"
 
 import (
 	"context"
-	"github.com/xtls/xray-core/common/matcher/domain"
-	"github.com/xtls/xray-core/common/matcher/geoip"
 	"math/rand"
 
 	"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/net"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/signal"
diff --git a/infra/conf/dns.go b/infra/conf/dns.go
index 807600ecf162..9a70e3e82ec5 100644
--- a/infra/conf/dns.go
+++ b/infra/conf/dns.go
@@ -2,16 +2,17 @@ package conf
 
 import (
 	"encoding/json"
-	"github.com/xtls/xray-core/common/matcher/geoip"
-	"github.com/xtls/xray-core/infra/conf/common"
+
 	"sort"
 	"strings"
 
 	"github.com/xtls/xray-core/app/dns"
 	dm "github.com/xtls/xray-core/common/matcher/domain"
 	"github.com/xtls/xray-core/common/matcher/domain/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/infra/conf/common"
 )
 
 type NameServerConfig struct {
diff --git a/infra/conf/http.go b/infra/conf/http.go
index 7b9acaa771f9..ebaaa7fdf723 100644
--- a/infra/conf/http.go
+++ b/infra/conf/http.go
@@ -2,11 +2,11 @@ package conf
 
 import (
 	"encoding/json"
-	"github.com/xtls/xray-core/infra/conf/common"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/http"
 )
 
diff --git a/infra/conf/router.go b/infra/conf/router.go
index 1f68e3049082..504632677115 100644
--- a/infra/conf/router.go
+++ b/infra/conf/router.go
@@ -2,7 +2,6 @@ package conf
 
 import (
 	"encoding/json"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"strconv"
 	"strings"
 
@@ -11,6 +10,7 @@ import (
 	"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/infra/conf/common"
 )
 
 type RouterRulesConfig struct {
diff --git a/infra/conf/shadowsocks.go b/infra/conf/shadowsocks.go
index 42b9f038815f..f5b4305d938a 100644
--- a/infra/conf/shadowsocks.go
+++ b/infra/conf/shadowsocks.go
@@ -1,13 +1,13 @@
 package conf
 
 import (
-	"github.com/xtls/xray-core/infra/conf/common"
 	"strings"
 
 	"github.com/golang/protobuf/proto"
 
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/shadowsocks"
 )
 
diff --git a/infra/conf/socks.go b/infra/conf/socks.go
index ea2a41893888..61d8295e6bbe 100644
--- a/infra/conf/socks.go
+++ b/infra/conf/socks.go
@@ -2,11 +2,11 @@ package conf
 
 import (
 	"encoding/json"
-	"github.com/xtls/xray-core/infra/conf/common"
 
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/socks"
 )
 
diff --git a/infra/conf/transport_authenticators.go b/infra/conf/transport_authenticators.go
index f000e8d4707e..f0ddffe3d3fd 100644
--- a/infra/conf/transport_authenticators.go
+++ b/infra/conf/transport_authenticators.go
@@ -1,11 +1,11 @@
 package conf
 
 import (
-	"github.com/xtls/xray-core/infra/conf/common"
 	"sort"
 
 	"github.com/golang/protobuf/proto"
 
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/transport/internet/headers/http"
 	"github.com/xtls/xray-core/transport/internet/headers/noop"
 	"github.com/xtls/xray-core/transport/internet/headers/srtp"
diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go
index 980a49a829de..9e929e97bddf 100644
--- a/infra/conf/transport_internet.go
+++ b/infra/conf/transport_internet.go
@@ -2,7 +2,6 @@ package conf
 
 import (
 	"encoding/json"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"math"
 	"net/url"
 	"strconv"
@@ -12,6 +11,7 @@ import (
 	"github.com/xtls/xray-core/common/platform/filesystem"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/transport/internet"
 	"github.com/xtls/xray-core/transport/internet/domainsocket"
 	"github.com/xtls/xray-core/transport/internet/http"
diff --git a/infra/conf/trojan.go b/infra/conf/trojan.go
index b59894cb16f7..0a71e24388d6 100644
--- a/infra/conf/trojan.go
+++ b/infra/conf/trojan.go
@@ -2,7 +2,6 @@ package conf
 
 import (
 	"encoding/json"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"runtime"
 	"strconv"
 	"syscall"
@@ -12,6 +11,7 @@ import (
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/trojan"
 )
 
diff --git a/infra/conf/vless.go b/infra/conf/vless.go
index 33a5c3b11ebc..c7a07fb29332 100644
--- a/infra/conf/vless.go
+++ b/infra/conf/vless.go
@@ -2,7 +2,6 @@ package conf
 
 import (
 	"encoding/json"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"runtime"
 	"strconv"
 	"syscall"
@@ -13,6 +12,7 @@ import (
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
 	"github.com/xtls/xray-core/common/uuid"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/vless"
 	"github.com/xtls/xray-core/proxy/vless/inbound"
 	"github.com/xtls/xray-core/proxy/vless/outbound"
diff --git a/infra/conf/vmess.go b/infra/conf/vmess.go
index 62084c092eff..9670dc299db3 100644
--- a/infra/conf/vmess.go
+++ b/infra/conf/vmess.go
@@ -2,7 +2,6 @@ package conf
 
 import (
 	"encoding/json"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"strings"
 
 	"github.com/golang/protobuf/proto"
@@ -10,6 +9,7 @@ import (
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
 	"github.com/xtls/xray-core/common/uuid"
+	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/vmess"
 	"github.com/xtls/xray-core/proxy/vmess/inbound"
 	"github.com/xtls/xray-core/proxy/vmess/outbound"
diff --git a/infra/conf/xray.go b/infra/conf/xray.go
index f871222107a8..02561c5eef37 100644
--- a/infra/conf/xray.go
+++ b/infra/conf/xray.go
@@ -2,11 +2,7 @@ package conf
 
 import (
 	"encoding/json"
-	"github.com/xtls/xray-core/common/matcher/domain"
-	"github.com/xtls/xray-core/common/matcher/domain/conf"
-	"github.com/xtls/xray-core/common/matcher/geoip"
-	"github.com/xtls/xray-core/infra/conf/common"
-	"github.com/xtls/xray-core/transport/internet"
+
 	"log"
 	"os"
 	"strings"
@@ -14,8 +10,13 @@ import (
 	"github.com/xtls/xray-core/app/dispatcher"
 	"github.com/xtls/xray-core/app/proxyman"
 	"github.com/xtls/xray-core/app/stats"
+	"github.com/xtls/xray-core/common/matcher/domain"
+	"github.com/xtls/xray-core/common/matcher/domain/conf"
+	"github.com/xtls/xray-core/common/matcher/geoip"
 	"github.com/xtls/xray-core/common/serial"
 	core "github.com/xtls/xray-core/core"
+	"github.com/xtls/xray-core/infra/conf/common"
+	"github.com/xtls/xray-core/transport/internet"
 	"github.com/xtls/xray-core/transport/internet/xtls"
 )
 

From 58bca70dfbc8bfc0a0df2281e737d69672bd9e43 Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Sat, 27 Mar 2021 15:33:20 +0800
Subject: [PATCH 06/20] Revert: move common.go to common

---
 infra/conf/{common => }/common.go      |  2 +-
 infra/conf/common/errors.generated.go  |  9 -----
 infra/conf/{common => }/common_test.go | 50 +++++++++++++-------------
 infra/conf/dns.go                      | 29 ++++++++-------
 infra/conf/dns_proxy.go                |  7 ++--
 infra/conf/dokodemo.go                 | 13 ++++---
 infra/conf/http.go                     |  3 +-
 infra/conf/router.go                   | 27 +++++++-------
 infra/conf/shadowsocks.go              | 15 ++++----
 infra/conf/socks.go                    |  5 ++-
 infra/conf/transport_authenticators.go | 19 +++++-----
 infra/conf/transport_internet.go       | 45 ++++++++++++-----------
 infra/conf/trojan.go                   | 13 ++++---
 infra/conf/vless.go                    |  3 +-
 infra/conf/vmess.go                    |  3 +-
 infra/conf/xray.go                     | 21 ++++++-----
 16 files changed, 121 insertions(+), 143 deletions(-)
 rename infra/conf/{common => }/common.go (99%)
 delete mode 100644 infra/conf/common/errors.generated.go
 rename infra/conf/{common => }/common_test.go (85%)

diff --git a/infra/conf/common/common.go b/infra/conf/common.go
similarity index 99%
rename from infra/conf/common/common.go
rename to infra/conf/common.go
index a24543899e4d..dea4e06b804c 100644
--- a/infra/conf/common/common.go
+++ b/infra/conf/common.go
@@ -1,4 +1,4 @@
-package common
+package conf
 
 import (
 	"encoding/json"
diff --git a/infra/conf/common/errors.generated.go b/infra/conf/common/errors.generated.go
deleted file mode 100644
index 28b9a625522c..000000000000
--- a/infra/conf/common/errors.generated.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package common
-
-import "github.com/xtls/xray-core/common/errors"
-
-type errPathObjHolder struct{}
-
-func newError(values ...interface{}) *errors.Error {
-	return errors.New(values...).WithPathObj(errPathObjHolder{})
-}
diff --git a/infra/conf/common/common_test.go b/infra/conf/common_test.go
similarity index 85%
rename from infra/conf/common/common_test.go
rename to infra/conf/common_test.go
index 6bc73c931435..4a49de24d703 100644
--- a/infra/conf/common/common_test.go
+++ b/infra/conf/common_test.go
@@ -1,7 +1,8 @@
-package common_test
+package conf_test
 
 import (
 	"encoding/json"
+	"github.com/xtls/xray-core/infra/conf"
 	"os"
 	"testing"
 
@@ -11,12 +12,11 @@ import (
 	"github.com/xtls/xray-core/common"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/protocol"
-	. "github.com/xtls/xray-core/infra/conf/common"
 )
 
 func TestStringListUnmarshalError(t *testing.T) {
 	rawJSON := `1234`
-	list := new(StringList)
+	list := new(conf.StringList)
 	err := json.Unmarshal([]byte(rawJSON), list)
 	if err == nil {
 		t.Error("expected error, but got nil")
@@ -25,7 +25,7 @@ func TestStringListUnmarshalError(t *testing.T) {
 
 func TestStringListLen(t *testing.T) {
 	rawJSON := `"a, b, c, d"`
-	var list StringList
+	var list conf.StringList
 	err := json.Unmarshal([]byte(rawJSON), &list)
 	common.Must(err)
 	if r := cmp.Diff([]string(list), []string{"a", " b", " c", " d"}); r != "" {
@@ -35,7 +35,7 @@ func TestStringListLen(t *testing.T) {
 
 func TestIPParsing(t *testing.T) {
 	rawJSON := "\"8.8.8.8\""
-	var address Address
+	var address conf.Address
 	err := json.Unmarshal([]byte(rawJSON), &address)
 	common.Must(err)
 	if r := cmp.Diff(address.IP(), net.IP{8, 8, 8, 8}); r != "" {
@@ -45,7 +45,7 @@ func TestIPParsing(t *testing.T) {
 
 func TestDomainParsing(t *testing.T) {
 	rawJSON := "\"example.com\""
-	var address Address
+	var address conf.Address
 	common.Must(json.Unmarshal([]byte(rawJSON), &address))
 	if address.Domain() != "example.com" {
 		t.Error("domain: ", address.Domain())
@@ -55,7 +55,7 @@ func TestDomainParsing(t *testing.T) {
 func TestURLParsing(t *testing.T) {
 	{
 		rawJSON := "\"https://dns.google/dns-query\""
-		var address Address
+		var address conf.Address
 		common.Must(json.Unmarshal([]byte(rawJSON), &address))
 		if address.Domain() != "https://dns.google/dns-query" {
 			t.Error("URL: ", address.Domain())
@@ -63,7 +63,7 @@ func TestURLParsing(t *testing.T) {
 	}
 	{
 		rawJSON := "\"https+local://dns.google/dns-query\""
-		var address Address
+		var address conf.Address
 		common.Must(json.Unmarshal([]byte(rawJSON), &address))
 		if address.Domain() != "https+local://dns.google/dns-query" {
 			t.Error("URL: ", address.Domain())
@@ -73,7 +73,7 @@ func TestURLParsing(t *testing.T) {
 
 func TestInvalidAddressJson(t *testing.T) {
 	rawJSON := "1234"
-	var address Address
+	var address conf.Address
 	err := json.Unmarshal([]byte(rawJSON), &address)
 	if err == nil {
 		t.Error("nil error")
@@ -81,7 +81,7 @@ func TestInvalidAddressJson(t *testing.T) {
 }
 
 func TestStringNetwork(t *testing.T) {
-	var network Network
+	var network conf.Network
 	common.Must(json.Unmarshal([]byte(`"tcp"`), &network))
 	if v := network.Build(); v != net.Network_TCP {
 		t.Error("network: ", v)
@@ -89,7 +89,7 @@ func TestStringNetwork(t *testing.T) {
 }
 
 func TestArrayNetworkList(t *testing.T) {
-	var list NetworkList
+	var list conf.NetworkList
 	common.Must(json.Unmarshal([]byte("[\"Tcp\"]"), &list))
 
 	nlist := list.Build()
@@ -102,7 +102,7 @@ func TestArrayNetworkList(t *testing.T) {
 }
 
 func TestStringNetworkList(t *testing.T) {
-	var list NetworkList
+	var list conf.NetworkList
 	common.Must(json.Unmarshal([]byte("\"TCP, ip\""), &list))
 
 	nlist := list.Build()
@@ -115,7 +115,7 @@ func TestStringNetworkList(t *testing.T) {
 }
 
 func TestInvalidNetworkJson(t *testing.T) {
-	var list NetworkList
+	var list conf.NetworkList
 	err := json.Unmarshal([]byte("0"), &list)
 	if err == nil {
 		t.Error("nil error")
@@ -123,10 +123,10 @@ func TestInvalidNetworkJson(t *testing.T) {
 }
 
 func TestIntPort(t *testing.T) {
-	var portRange PortRange
+	var portRange conf.PortRange
 	common.Must(json.Unmarshal([]byte("1234"), &portRange))
 
-	if r := cmp.Diff(portRange, PortRange{
+	if r := cmp.Diff(portRange, conf.PortRange{
 		From: 1234, To: 1234,
 	}); r != "" {
 		t.Error(r)
@@ -134,7 +134,7 @@ func TestIntPort(t *testing.T) {
 }
 
 func TestOverRangeIntPort(t *testing.T) {
-	var portRange PortRange
+	var portRange conf.PortRange
 	err := json.Unmarshal([]byte("70000"), &portRange)
 	if err == nil {
 		t.Error("nil error")
@@ -149,10 +149,10 @@ func TestOverRangeIntPort(t *testing.T) {
 func TestEnvPort(t *testing.T) {
 	common.Must(os.Setenv("PORT", "1234"))
 
-	var portRange PortRange
+	var portRange conf.PortRange
 	common.Must(json.Unmarshal([]byte("\"env:PORT\""), &portRange))
 
-	if r := cmp.Diff(portRange, PortRange{
+	if r := cmp.Diff(portRange, conf.PortRange{
 		From: 1234, To: 1234,
 	}); r != "" {
 		t.Error(r)
@@ -160,10 +160,10 @@ func TestEnvPort(t *testing.T) {
 }
 
 func TestSingleStringPort(t *testing.T) {
-	var portRange PortRange
+	var portRange conf.PortRange
 	common.Must(json.Unmarshal([]byte("\"1234\""), &portRange))
 
-	if r := cmp.Diff(portRange, PortRange{
+	if r := cmp.Diff(portRange, conf.PortRange{
 		From: 1234, To: 1234,
 	}); r != "" {
 		t.Error(r)
@@ -171,10 +171,10 @@ func TestSingleStringPort(t *testing.T) {
 }
 
 func TestStringPairPort(t *testing.T) {
-	var portRange PortRange
+	var portRange conf.PortRange
 	common.Must(json.Unmarshal([]byte("\"1234-5678\""), &portRange))
 
-	if r := cmp.Diff(portRange, PortRange{
+	if r := cmp.Diff(portRange, conf.PortRange{
 		From: 1234, To: 5678,
 	}); r != "" {
 		t.Error(r)
@@ -182,7 +182,7 @@ func TestStringPairPort(t *testing.T) {
 }
 
 func TestOverRangeStringPort(t *testing.T) {
-	var portRange PortRange
+	var portRange conf.PortRange
 	err := json.Unmarshal([]byte("\"65536\""), &portRange)
 	if err == nil {
 		t.Error("nil error")
@@ -205,7 +205,7 @@ func TestOverRangeStringPort(t *testing.T) {
 }
 
 func TestUserParsing(t *testing.T) {
-	user := new(User)
+	user := new(conf.User)
 	common.Must(json.Unmarshal([]byte(`{
     "id": "96edb838-6d68-42ef-a933-25f7ac3a9d09",
     "email": "love@example.com",
@@ -223,7 +223,7 @@ func TestUserParsing(t *testing.T) {
 }
 
 func TestInvalidUserJson(t *testing.T) {
-	user := new(User)
+	user := new(conf.User)
 	err := json.Unmarshal([]byte(`{"email": 1234}`), user)
 	if err == nil {
 		t.Error("nil error")
diff --git a/infra/conf/dns.go b/infra/conf/dns.go
index 9a70e3e82ec5..b192c5e5f879 100644
--- a/infra/conf/dns.go
+++ b/infra/conf/dns.go
@@ -12,28 +12,27 @@ import (
 	"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/infra/conf/common"
 )
 
 type NameServerConfig struct {
-	Address   *common.Address
+	Address   *Address
 	Port      uint16
 	Domains   []string
-	ExpectIPs common.StringList
+	ExpectIPs StringList
 }
 
 func (c *NameServerConfig) UnmarshalJSON(data []byte) error {
-	var address common.Address
+	var address Address
 	if err := json.Unmarshal(data, &address); err == nil {
 		c.Address = &address
 		return nil
 	}
 
 	var advanced struct {
-		Address   *common.Address   `json:"address"`
-		Port      uint16            `json:"port"`
-		Domains   []string          `json:"domains"`
-		ExpectIPs common.StringList `json:"expectIps"`
+		Address   *Address   `json:"address"`
+		Port      uint16     `json:"port"`
+		Domains   []string   `json:"domains"`
+		ExpectIPs StringList `json:"expectIps"`
 	}
 	if err := json.Unmarshal(data, &advanced); err == nil {
 		c.Address = advanced.Address
@@ -91,15 +90,15 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
 
 // DNSConfig is a JSON serializable object for dns.Config.
 type DNSConfig struct {
-	Servers       []*NameServerConfig        `json:"servers"`
-	Hosts         map[string]*common.Address `json:"hosts"`
-	ClientIP      *common.Address            `json:"clientIp"`
-	Tag           string                     `json:"tag"`
-	QueryStrategy string                     `json:"queryStrategy"`
-	DisableCache  bool                       `json:"disableCache"`
+	Servers       []*NameServerConfig `json:"servers"`
+	Hosts         map[string]*Address `json:"hosts"`
+	ClientIP      *Address            `json:"clientIp"`
+	Tag           string              `json:"tag"`
+	QueryStrategy string              `json:"queryStrategy"`
+	DisableCache  bool                `json:"disableCache"`
 }
 
-func getHostMapping(addr *common.Address) *dns.Config_HostMapping {
+func getHostMapping(addr *Address) *dns.Config_HostMapping {
 	if addr.Family().IsIP() {
 		return &dns.Config_HostMapping{
 			Ip: [][]byte{[]byte(addr.IP())},
diff --git a/infra/conf/dns_proxy.go b/infra/conf/dns_proxy.go
index 4b1f3b915401..922d32bafcae 100644
--- a/infra/conf/dns_proxy.go
+++ b/infra/conf/dns_proxy.go
@@ -3,14 +3,13 @@ package conf
 import (
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/dns"
 )
 
 type DNSOutboundConfig struct {
-	Network common.Network  `json:"network"`
-	Address *common.Address `json:"address"`
-	Port    uint16          `json:"port"`
+	Network Network  `json:"network"`
+	Address *Address `json:"address"`
+	Port    uint16   `json:"port"`
 }
 
 func (c *DNSOutboundConfig) Build() (proto.Message, error) {
diff --git a/infra/conf/dokodemo.go b/infra/conf/dokodemo.go
index c5b42e657f0d..03a21d71192f 100644
--- a/infra/conf/dokodemo.go
+++ b/infra/conf/dokodemo.go
@@ -2,17 +2,16 @@ package conf
 
 import (
 	"github.com/golang/protobuf/proto"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/dokodemo"
 )
 
 type DokodemoConfig struct {
-	Host         *common.Address     `json:"address"`
-	PortValue    uint16              `json:"port"`
-	NetworkList  *common.NetworkList `json:"network"`
-	TimeoutValue uint32              `json:"timeout"`
-	Redirect     bool                `json:"followRedirect"`
-	UserLevel    uint32              `json:"userLevel"`
+	Host         *Address     `json:"address"`
+	PortValue    uint16       `json:"port"`
+	NetworkList  *NetworkList `json:"network"`
+	TimeoutValue uint32       `json:"timeout"`
+	Redirect     bool         `json:"followRedirect"`
+	UserLevel    uint32       `json:"userLevel"`
 }
 
 func (v *DokodemoConfig) Build() (proto.Message, error) {
diff --git a/infra/conf/http.go b/infra/conf/http.go
index ebaaa7fdf723..e6969582b7e6 100644
--- a/infra/conf/http.go
+++ b/infra/conf/http.go
@@ -6,7 +6,6 @@ import (
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/http"
 )
 
@@ -47,7 +46,7 @@ func (c *HTTPServerConfig) Build() (proto.Message, error) {
 }
 
 type HTTPRemoteConfig struct {
-	Address *common.Address   `json:"address"`
+	Address *Address          `json:"address"`
 	Port    uint16            `json:"port"`
 	Users   []json.RawMessage `json:"users"`
 }
diff --git a/infra/conf/router.go b/infra/conf/router.go
index 504632677115..ae2ffd50e47a 100644
--- a/infra/conf/router.go
+++ b/infra/conf/router.go
@@ -10,7 +10,6 @@ import (
 	"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/infra/conf/common"
 )
 
 type RouterRulesConfig struct {
@@ -19,8 +18,8 @@ type RouterRulesConfig struct {
 }
 
 type BalancingRule struct {
-	Tag       string            `json:"tag"`
-	Selectors common.StringList `json:"selector"`
+	Tag       string     `json:"tag"`
+	Selectors StringList `json:"selector"`
 }
 
 func (r *BalancingRule) Build() (*router.BalancingRule, error) {
@@ -151,17 +150,17 @@ func ParseIP(s string) (*geoip.CIDR, error) {
 func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 	type RawFieldRule struct {
 		RouterRule
-		Domain     *common.StringList  `json:"domain"`
-		Domains    *common.StringList  `json:"domains"`
-		IP         *common.StringList  `json:"ip"`
-		Port       *common.PortList    `json:"port"`
-		Network    *common.NetworkList `json:"network"`
-		SourceIP   *common.StringList  `json:"source"`
-		SourcePort *common.PortList    `json:"sourcePort"`
-		User       *common.StringList  `json:"user"`
-		InboundTag *common.StringList  `json:"inboundTag"`
-		Protocols  *common.StringList  `json:"protocol"`
-		Attributes string              `json:"attrs"`
+		Domain     *StringList  `json:"domain"`
+		Domains    *StringList  `json:"domains"`
+		IP         *StringList  `json:"ip"`
+		Port       *PortList    `json:"port"`
+		Network    *NetworkList `json:"network"`
+		SourceIP   *StringList  `json:"source"`
+		SourcePort *PortList    `json:"sourcePort"`
+		User       *StringList  `json:"user"`
+		InboundTag *StringList  `json:"inboundTag"`
+		Protocols  *StringList  `json:"protocol"`
+		Attributes string       `json:"attrs"`
 	}
 	rawFieldRule := new(RawFieldRule)
 	err := json.Unmarshal(msg, rawFieldRule)
diff --git a/infra/conf/shadowsocks.go b/infra/conf/shadowsocks.go
index f5b4305d938a..fbb2a79d583a 100644
--- a/infra/conf/shadowsocks.go
+++ b/infra/conf/shadowsocks.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/shadowsocks"
 )
 
@@ -47,7 +46,7 @@ type ShadowsocksServerConfig struct {
 	Level       byte                     `json:"level"`
 	Email       string                   `json:"email"`
 	Users       []*ShadowsocksUserConfig `json:"clients"`
-	NetworkList *common.NetworkList      `json:"network"`
+	NetworkList *NetworkList             `json:"network"`
 }
 
 func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
@@ -94,12 +93,12 @@ func (v *ShadowsocksServerConfig) Build() (proto.Message, error) {
 }
 
 type ShadowsocksServerTarget struct {
-	Address  *common.Address `json:"address"`
-	Port     uint16          `json:"port"`
-	Cipher   string          `json:"method"`
-	Password string          `json:"password"`
-	Email    string          `json:"email"`
-	Level    byte            `json:"level"`
+	Address  *Address `json:"address"`
+	Port     uint16   `json:"port"`
+	Cipher   string   `json:"method"`
+	Password string   `json:"password"`
+	Email    string   `json:"email"`
+	Level    byte     `json:"level"`
 }
 
 type ShadowsocksClientConfig struct {
diff --git a/infra/conf/socks.go b/infra/conf/socks.go
index 61d8295e6bbe..05dad7bc18fb 100644
--- a/infra/conf/socks.go
+++ b/infra/conf/socks.go
@@ -6,7 +6,6 @@ import (
 	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/socks"
 )
 
@@ -31,7 +30,7 @@ type SocksServerConfig struct {
 	AuthMethod string          `json:"auth"`
 	Accounts   []*SocksAccount `json:"accounts"`
 	UDP        bool            `json:"udp"`
-	Host       *common.Address `json:"ip"`
+	Host       *Address        `json:"ip"`
 	Timeout    uint32          `json:"timeout"`
 	UserLevel  uint32          `json:"userLevel"`
 }
@@ -66,7 +65,7 @@ func (v *SocksServerConfig) Build() (proto.Message, error) {
 }
 
 type SocksRemoteConfig struct {
-	Address *common.Address   `json:"address"`
+	Address *Address          `json:"address"`
 	Port    uint16            `json:"port"`
 	Users   []json.RawMessage `json:"users"`
 }
diff --git a/infra/conf/transport_authenticators.go b/infra/conf/transport_authenticators.go
index f0ddffe3d3fd..c9e42605b10d 100644
--- a/infra/conf/transport_authenticators.go
+++ b/infra/conf/transport_authenticators.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/golang/protobuf/proto"
 
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/transport/internet/headers/http"
 	"github.com/xtls/xray-core/transport/internet/headers/noop"
 	"github.com/xtls/xray-core/transport/internet/headers/srtp"
@@ -58,13 +57,13 @@ func (DTLSAuthenticator) Build() (proto.Message, error) {
 }
 
 type AuthenticatorRequest struct {
-	Version string                        `json:"version"`
-	Method  string                        `json:"method"`
-	Path    common.StringList             `json:"path"`
-	Headers map[string]*common.StringList `json:"headers"`
+	Version string                 `json:"version"`
+	Method  string                 `json:"method"`
+	Path    StringList             `json:"path"`
+	Headers map[string]*StringList `json:"headers"`
 }
 
-func sortMapKeys(m map[string]*common.StringList) []string {
+func sortMapKeys(m map[string]*StringList) []string {
 	var keys []string
 	for key := range m {
 		keys = append(keys, key)
@@ -134,10 +133,10 @@ func (v *AuthenticatorRequest) Build() (*http.RequestConfig, error) {
 }
 
 type AuthenticatorResponse struct {
-	Version string                        `json:"version"`
-	Status  string                        `json:"status"`
-	Reason  string                        `json:"reason"`
-	Headers map[string]*common.StringList `json:"headers"`
+	Version string                 `json:"version"`
+	Status  string                 `json:"status"`
+	Reason  string                 `json:"reason"`
+	Headers map[string]*StringList `json:"headers"`
 }
 
 func (v *AuthenticatorResponse) Build() (*http.ResponseConfig, error) {
diff --git a/infra/conf/transport_internet.go b/infra/conf/transport_internet.go
index 9e929e97bddf..15d5dd3257d3 100644
--- a/infra/conf/transport_internet.go
+++ b/infra/conf/transport_internet.go
@@ -11,7 +11,6 @@ import (
 	"github.com/xtls/xray-core/common/platform/filesystem"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/transport/internet"
 	"github.com/xtls/xray-core/transport/internet/domainsocket"
 	"github.com/xtls/xray-core/transport/internet/http"
@@ -180,8 +179,8 @@ func (c *WebSocketConfig) Build() (proto.Message, error) {
 }
 
 type HTTPConfig struct {
-	Host *common.StringList `json:"host"`
-	Path string             `json:"path"`
+	Host *StringList `json:"host"`
+	Path string      `json:"path"`
 }
 
 // Build implements Buildable.
@@ -312,16 +311,16 @@ func (c *TLSCertConfig) Build() (*tls.Certificate, error) {
 }
 
 type TLSConfig struct {
-	Insecure                 bool               `json:"allowInsecure"`
-	Certs                    []*TLSCertConfig   `json:"certificates"`
-	ServerName               string             `json:"serverName"`
-	ALPN                     *common.StringList `json:"alpn"`
-	EnableSessionResumption  bool               `json:"enableSessionResumption"`
-	DisableSystemRoot        bool               `json:"disableSystemRoot"`
-	MinVersion               string             `json:"minVersion"`
-	MaxVersion               string             `json:"maxVersion"`
-	CipherSuites             string             `json:"cipherSuites"`
-	PreferServerCipherSuites bool               `json:"preferServerCipherSuites"`
+	Insecure                 bool             `json:"allowInsecure"`
+	Certs                    []*TLSCertConfig `json:"certificates"`
+	ServerName               string           `json:"serverName"`
+	ALPN                     *StringList      `json:"alpn"`
+	EnableSessionResumption  bool             `json:"enableSessionResumption"`
+	DisableSystemRoot        bool             `json:"disableSystemRoot"`
+	MinVersion               string           `json:"minVersion"`
+	MaxVersion               string           `json:"maxVersion"`
+	CipherSuites             string           `json:"cipherSuites"`
+	PreferServerCipherSuites bool             `json:"preferServerCipherSuites"`
 }
 
 // Build implements Buildable.
@@ -402,16 +401,16 @@ func (c *XTLSCertConfig) Build() (*xtls.Certificate, error) {
 }
 
 type XTLSConfig struct {
-	Insecure                 bool               `json:"allowInsecure"`
-	Certs                    []*XTLSCertConfig  `json:"certificates"`
-	ServerName               string             `json:"serverName"`
-	ALPN                     *common.StringList `json:"alpn"`
-	EnableSessionResumption  bool               `json:"enableSessionResumption"`
-	DisableSystemRoot        bool               `json:"disableSystemRoot"`
-	MinVersion               string             `json:"minVersion"`
-	MaxVersion               string             `json:"maxVersion"`
-	CipherSuites             string             `json:"cipherSuites"`
-	PreferServerCipherSuites bool               `json:"preferServerCipherSuites"`
+	Insecure                 bool              `json:"allowInsecure"`
+	Certs                    []*XTLSCertConfig `json:"certificates"`
+	ServerName               string            `json:"serverName"`
+	ALPN                     *StringList       `json:"alpn"`
+	EnableSessionResumption  bool              `json:"enableSessionResumption"`
+	DisableSystemRoot        bool              `json:"disableSystemRoot"`
+	MinVersion               string            `json:"minVersion"`
+	MaxVersion               string            `json:"maxVersion"`
+	CipherSuites             string            `json:"cipherSuites"`
+	PreferServerCipherSuites bool              `json:"preferServerCipherSuites"`
 }
 
 // Build implements Buildable.
diff --git a/infra/conf/trojan.go b/infra/conf/trojan.go
index 0a71e24388d6..4fe96c071585 100644
--- a/infra/conf/trojan.go
+++ b/infra/conf/trojan.go
@@ -11,18 +11,17 @@ import (
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/trojan"
 )
 
 // TrojanServerTarget is configuration of a single trojan server
 type TrojanServerTarget struct {
-	Address  *common.Address `json:"address"`
-	Port     uint16          `json:"port"`
-	Password string          `json:"password"`
-	Email    string          `json:"email"`
-	Level    byte            `json:"level"`
-	Flow     string          `json:"flow"`
+	Address  *Address `json:"address"`
+	Port     uint16   `json:"port"`
+	Password string   `json:"password"`
+	Email    string   `json:"email"`
+	Level    byte     `json:"level"`
+	Flow     string   `json:"flow"`
 }
 
 // TrojanClientConfig is configuration of trojan servers
diff --git a/infra/conf/vless.go b/infra/conf/vless.go
index c7a07fb29332..f5ded7a00607 100644
--- a/infra/conf/vless.go
+++ b/infra/conf/vless.go
@@ -12,7 +12,6 @@ import (
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
 	"github.com/xtls/xray-core/common/uuid"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/vless"
 	"github.com/xtls/xray-core/proxy/vless/inbound"
 	"github.com/xtls/xray-core/proxy/vless/outbound"
@@ -138,7 +137,7 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) {
 }
 
 type VLessOutboundVnext struct {
-	Address *common.Address   `json:"address"`
+	Address *Address          `json:"address"`
 	Port    uint16            `json:"port"`
 	Users   []json.RawMessage `json:"users"`
 }
diff --git a/infra/conf/vmess.go b/infra/conf/vmess.go
index 9670dc299db3..1200be07047a 100644
--- a/infra/conf/vmess.go
+++ b/infra/conf/vmess.go
@@ -9,7 +9,6 @@ import (
 	"github.com/xtls/xray-core/common/protocol"
 	"github.com/xtls/xray-core/common/serial"
 	"github.com/xtls/xray-core/common/uuid"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/proxy/vmess"
 	"github.com/xtls/xray-core/proxy/vmess/inbound"
 	"github.com/xtls/xray-core/proxy/vmess/outbound"
@@ -124,7 +123,7 @@ func (c *VMessInboundConfig) Build() (proto.Message, error) {
 }
 
 type VMessOutboundTarget struct {
-	Address *common.Address   `json:"address"`
+	Address *Address          `json:"address"`
 	Port    uint16            `json:"port"`
 	Users   []json.RawMessage `json:"users"`
 }
diff --git a/infra/conf/xray.go b/infra/conf/xray.go
index 02561c5eef37..1f89ebad209e 100644
--- a/infra/conf/xray.go
+++ b/infra/conf/xray.go
@@ -15,7 +15,6 @@ import (
 	"github.com/xtls/xray-core/common/matcher/geoip"
 	"github.com/xtls/xray-core/common/serial"
 	core "github.com/xtls/xray-core/core"
-	"github.com/xtls/xray-core/infra/conf/common"
 	"github.com/xtls/xray-core/transport/internet"
 	"github.com/xtls/xray-core/transport/internet/xtls"
 )
@@ -64,11 +63,11 @@ func toProtocolList(s []string) ([]proxyman.KnownProtocols, error) {
 }
 
 type SniffingConfig struct {
-	Enabled         bool               `json:"enabled"`
-	DestOverride    *common.StringList `json:"destOverride"`
-	DomainsExcluded *common.StringList `json:"domainsExcluded"`
-	IPsExcluded     *common.StringList `json:"ipsExcluded"`
-	MetadataOnly    bool               `json:"metadataOnly"`
+	Enabled         bool        `json:"enabled"`
+	DestOverride    *StringList `json:"destOverride"`
+	DomainsExcluded *StringList `json:"domainsExcluded"`
+	IPsExcluded     *StringList `json:"ipsExcluded"`
+	MetadataOnly    bool        `json:"metadataOnly"`
 }
 
 // Build implements Buildable.
@@ -176,13 +175,13 @@ func (c *InboundDetourAllocationConfig) Build() (*proxyman.AllocationStrategy, e
 
 type InboundDetourConfig struct {
 	Protocol       string                         `json:"protocol"`
-	PortRange      *common.PortRange              `json:"port"`
-	ListenOn       *common.Address                `json:"listen"`
+	PortRange      *PortRange                     `json:"port"`
+	ListenOn       *Address                       `json:"listen"`
 	Settings       *json.RawMessage               `json:"settings"`
 	Tag            string                         `json:"tag"`
 	Allocation     *InboundDetourAllocationConfig `json:"allocate"`
 	StreamSetting  *StreamConfig                  `json:"streamSettings"`
-	DomainOverride *common.StringList             `json:"domainOverride"`
+	DomainOverride *StringList                    `json:"domainOverride"`
 	SniffingConfig *SniffingConfig                `json:"sniffing"`
 }
 
@@ -284,7 +283,7 @@ func (c *InboundDetourConfig) Build() (*core.InboundHandlerConfig, error) {
 
 type OutboundDetourConfig struct {
 	Protocol      string           `json:"protocol"`
-	SendThrough   *common.Address  `json:"sendThrough"`
+	SendThrough   *Address         `json:"sendThrough"`
 	Tag           string           `json:"tag"`
 	Settings      *json.RawMessage `json:"settings"`
 	StreamSetting *StreamConfig    `json:"streamSettings"`
@@ -642,7 +641,7 @@ func (c *Config) Build() (*core.Config, error) {
 
 	// Backward compatibility.
 	if len(inbounds) > 0 && inbounds[0].PortRange == nil && c.Port > 0 {
-		inbounds[0].PortRange = &common.PortRange{
+		inbounds[0].PortRange = &PortRange{
 			From: uint32(c.Port),
 			To:   uint32(c.Port),
 		}

From 6471cfd290f97e37a62af793f59036c212beec80 Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Sat, 27 Mar 2021 16:54:42 +0800
Subject: [PATCH 07/20] Fix: typo

---
 common/matcher/geoip/conf.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/common/matcher/geoip/conf.go b/common/matcher/geoip/conf.go
index e7d89ac65179..9c5fa42b7eea 100644
--- a/common/matcher/geoip/conf.go
+++ b/common/matcher/geoip/conf.go
@@ -16,7 +16,7 @@ var (
 )
 
 func LoadGeoIP(code string) ([]*CIDR, error) {
-	return LoadIPFile("dat", code)
+	return LoadIPFile("geoip.dat", code)
 }
 
 func LoadIPFile(file, code string) ([]*CIDR, error) {

From b8196a22a0ebdd3e66da846d019014c9b468e698 Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Sat, 27 Mar 2021 17:41:46 +0800
Subject: [PATCH 08/20] Fix: matching type mismatch

---
 common/matcher/geosite/geosite.go    |  16 +++-
 common/matcher/geosite/geosite.pb.go | 128 +++++++++++++++++++--------
 common/matcher/geosite/geosite.proto |  14 ++-
 3 files changed, 119 insertions(+), 39 deletions(-)

diff --git a/common/matcher/geosite/geosite.go b/common/matcher/geosite/geosite.go
index 47480e5c89d5..4693f56bce4d 100644
--- a/common/matcher/geosite/geosite.go
+++ b/common/matcher/geosite/geosite.go
@@ -15,5 +15,19 @@ func ToDomains(dms []*Domain) []*domain.Domain {
 }
 
 func (d *Domain) ToDomain() *domain.Domain {
-	return &domain.Domain{Type: d.Type, Value: d.Value}
+	return &domain.Domain{Type: d.Type.ToMatchingType(), Value: d.Value}
+}
+
+func (t Domain_Type) ToMatchingType() domain.MatchingType {
+	switch t {
+	case Domain_Plain:
+		return domain.MatchingType_Keyword
+	case Domain_Regex:
+		return domain.MatchingType_Regex
+	case Domain_Domain:
+		return domain.MatchingType_Subdomain
+	case Domain_Full:
+		return domain.MatchingType_Full
+	}
+	panic("impossible")
 }
diff --git a/common/matcher/geosite/geosite.pb.go b/common/matcher/geosite/geosite.pb.go
index d8d166980c81..a0cd2ff5fb4c 100644
--- a/common/matcher/geosite/geosite.pb.go
+++ b/common/matcher/geosite/geosite.pb.go
@@ -8,7 +8,6 @@ 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"
@@ -26,13 +25,69 @@ const (
 // of the legacy proto package is being used.
 const _ = proto.ProtoPackageIsVersion4
 
+type Domain_Type int32
+
+const (
+	// The value is used as is.
+	Domain_Plain Domain_Type = 0
+	// The value is used as a regular expression.
+	Domain_Regex Domain_Type = 1
+	// The value is a root domain.
+	Domain_Domain Domain_Type = 2
+	// The value is a domain.
+	Domain_Full Domain_Type = 3
+)
+
+// Enum value maps for Domain_Type.
+var (
+	Domain_Type_name = map[int32]string{
+		0: "Plain",
+		1: "Regex",
+		2: "Domain",
+		3: "Full",
+	}
+	Domain_Type_value = map[string]int32{
+		"Plain":  0,
+		"Regex":  1,
+		"Domain": 2,
+		"Full":   3,
+	}
+)
+
+func (x Domain_Type) Enum() *Domain_Type {
+	p := new(Domain_Type)
+	*p = x
+	return p
+}
+
+func (x Domain_Type) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Domain_Type) Descriptor() protoreflect.EnumDescriptor {
+	return file_common_matcher_geosite_geosite_proto_enumTypes[0].Descriptor()
+}
+
+func (Domain_Type) Type() protoreflect.EnumType {
+	return &file_common_matcher_geosite_geosite_proto_enumTypes[0]
+}
+
+func (x Domain_Type) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use Domain_Type.Descriptor instead.
+func (Domain_Type) EnumDescriptor() ([]byte, []int) {
+	return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{0, 0}
+}
+
 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"`
+	Type Domain_Type `protobuf:"varint,1,opt,name=type,proto3,enum=xray.common.matcher.geosite.Domain_Type" 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.
@@ -71,11 +126,11 @@ func (*Domain) Descriptor() ([]byte, []int) {
 	return file_common_matcher_geosite_geosite_proto_rawDescGZIP(), []int{0}
 }
 
-func (x *Domain) GetType() domain.MatchingType {
+func (x *Domain) GetType() Domain_Type {
 	if x != nil {
 		return x.Type
 	}
-	return domain.MatchingType_Full
+	return Domain_Plain
 }
 
 func (x *Domain) GetValue() string {
@@ -289,27 +344,28 @@ var file_common_matcher_geosite_geosite_proto_rawDesc = []byte{
 	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,
+	0x69, 0x74, 0x65, 0x22, 0xcb, 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, 0x67, 0x65, 0x6f, 0x73, 0x69, 0x74, 0x65, 0x2e, 0x44, 0x6f, 0x6d, 0x61, 0x69,
+	0x6e, 0x2e, 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, 0x32, 0x0a,
+	0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x50, 0x6c, 0x61, 0x69, 0x6e, 0x10, 0x00,
+	0x12, 0x09, 0x0a, 0x05, 0x52, 0x65, 0x67, 0x65, 0x78, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44,
+	0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x46, 0x75, 0x6c, 0x6c, 0x10,
+	0x03, 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,
@@ -343,19 +399,20 @@ func file_common_matcher_geosite_geosite_proto_rawDescGZIP() []byte {
 	return file_common_matcher_geosite_geosite_proto_rawDescData
 }
 
+var file_common_matcher_geosite_geosite_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
 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
+	(Domain_Type)(0),         // 0: xray.common.matcher.geosite.Domain.Type
+	(*Domain)(nil),           // 1: xray.common.matcher.geosite.Domain
+	(*GeoSite)(nil),          // 2: xray.common.matcher.geosite.GeoSite
+	(*GeoSiteList)(nil),      // 3: xray.common.matcher.geosite.GeoSiteList
+	(*Domain_Attribute)(nil), // 4: xray.common.matcher.geosite.Domain.Attribute
 }
 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
+	0, // 0: xray.common.matcher.geosite.Domain.type:type_name -> xray.common.matcher.geosite.Domain.Type
+	4, // 1: xray.common.matcher.geosite.Domain.attribute:type_name -> xray.common.matcher.geosite.Domain.Attribute
+	1, // 2: xray.common.matcher.geosite.GeoSite.domain:type_name -> xray.common.matcher.geosite.Domain
+	2, // 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
@@ -427,13 +484,14 @@ func file_common_matcher_geosite_geosite_proto_init() {
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_common_matcher_geosite_geosite_proto_rawDesc,
-			NumEnums:      0,
+			NumEnums:      1,
 			NumMessages:   4,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
 		GoTypes:           file_common_matcher_geosite_geosite_proto_goTypes,
 		DependencyIndexes: file_common_matcher_geosite_geosite_proto_depIdxs,
+		EnumInfos:         file_common_matcher_geosite_geosite_proto_enumTypes,
 		MessageInfos:      file_common_matcher_geosite_geosite_proto_msgTypes,
 	}.Build()
 	File_common_matcher_geosite_geosite_proto = out.File
diff --git a/common/matcher/geosite/geosite.proto b/common/matcher/geosite/geosite.proto
index 3a5816567b68..8a8e055f63f4 100644
--- a/common/matcher/geosite/geosite.proto
+++ b/common/matcher/geosite/geosite.proto
@@ -6,11 +6,19 @@ 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 {
+  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.
-  xray.common.matcher.domain.MatchingType type = 1;
+  Type type = 1;
 
   // Domain value.
   string value = 2;

From b9c5de96e71a0f109c8adb05de83c4f155a3645e Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Sat, 27 Mar 2021 17:52:15 +0800
Subject: [PATCH 09/20] Fix: test

---
 infra/conf/dns_test.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/infra/conf/dns_test.go b/infra/conf/dns_test.go
index cc1779afa9f2..aeb1df39629b 100644
--- a/infra/conf/dns_test.go
+++ b/infra/conf/dns_test.go
@@ -36,7 +36,7 @@ func init() {
 			{
 				CountryCode: "TEST",
 				Domain: []*geosite.Domain{
-					{Type: domain.MatchingType_Full, Value: "example.com"},
+					{Type: geosite.Domain_Full, Value: "example.com"},
 				},
 			},
 		},

From 946a7a2647d93acd88a472f1b1ab2f67992e803b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=8B=E3=81=AE=E3=81=8B=E3=81=88=E3=81=A7?=
 <autmaple@protonmail.com>
Date: Wed, 7 Apr 2021 22:53:21 +0800
Subject: [PATCH 10/20] Refine DomainStrategy config

---
 infra/conf/router.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/infra/conf/router.go b/infra/conf/router.go
index ae2ffd50e47a..1e005bf257ca 100644
--- a/infra/conf/router.go
+++ b/infra/conf/router.go
@@ -52,11 +52,11 @@ func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy {
 	}
 
 	switch strings.ToLower(ds) {
-	case "alwaysip":
+	case "alwaysip", "always_ip", "always-ip":
 		return router.Config_UseIp
-	case "ipifnonmatch":
+	case "ipifnonmatch", "ip_if_non_match", "ip-if-non-match":
 		return router.Config_IpIfNonMatch
-	case "ipondemand":
+	case "ipondemand", "ip_on_demand", "ip-on-demand":
 		return router.Config_IpOnDemand
 	default:
 		return router.Config_AsIs

From 68201a88983099fee500e1d4acc7cff4bde36136 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=8B=E3=81=AE=E3=81=8B=E3=81=88=E3=81=A7?=
 <autmaple@protonmail.com>
Date: Thu, 8 Apr 2021 13:07:53 +0800
Subject: [PATCH 11/20] Feat: add reverse match for GeoIP

---
 common/matcher/geoip/conf.go       | 20 +++++++++---
 common/matcher/geoip/geoip.go      | 29 +++++++++++++----
 common/matcher/geoip/geoip.pb.go   | 52 ++++++++++++++++++------------
 common/matcher/geoip/geoip.proto   |  1 +
 common/matcher/geoip/geoip_test.go | 36 +++++++++++++++++++++
 5 files changed, 106 insertions(+), 32 deletions(-)

diff --git a/common/matcher/geoip/conf.go b/common/matcher/geoip/conf.go
index 9c5fa42b7eea..e159534045da 100644
--- a/common/matcher/geoip/conf.go
+++ b/common/matcher/geoip/conf.go
@@ -99,14 +99,16 @@ func ParaseIPList(ips []string) ([]*GeoIP, error) {
 	for _, ip := range ips {
 		if strings.HasPrefix(ip, "geoip:") {
 			country := ip[6:]
+			isReverseMatch := false
 			geoipc, err := LoadGeoIP(strings.ToUpper(country))
 			if err != nil {
 				return nil, newError("failed to load GeoIP: ", country).Base(err)
 			}
 
 			geoipList = append(geoipList, &GeoIP{
-				CountryCode: strings.ToUpper(country),
-				Cidr:        geoipc,
+				CountryCode:  strings.ToUpper(country),
+				Cidr:         geoipc,
+				ReverseMatch: isReverseMatch,
 			})
 			continue
 		}
@@ -129,14 +131,24 @@ func ParaseIPList(ips []string) ([]*GeoIP, error) {
 
 			filename := kv[0]
 			country := kv[1]
+			if len(filename) == 0 || len(country) == 0 {
+				return nil, newError("empty filename or empty country in rule")
+			}
+
+			isReverseMatch := false
+			if strings.HasPrefix(country, "!") {
+				country = country[1:]
+				isReverseMatch = true
+			}
 			geoipc, err := LoadIPFile(filename, strings.ToUpper(country))
 			if err != nil {
 				return nil, newError("failed to load IPs: ", country, " from ", filename).Base(err)
 			}
 
 			geoipList = append(geoipList, &GeoIP{
-				CountryCode: strings.ToUpper(filename + "_" + country),
-				Cidr:        geoipc,
+				CountryCode:  strings.ToUpper(filename + "_" + country),
+				Cidr:         geoipc,
+				ReverseMatch: isReverseMatch,
 			})
 
 			continue
diff --git a/common/matcher/geoip/geoip.go b/common/matcher/geoip/geoip.go
index 08eaf26ac538..f702c6824ecb 100644
--- a/common/matcher/geoip/geoip.go
+++ b/common/matcher/geoip/geoip.go
@@ -15,11 +15,16 @@ type ipv6 struct {
 }
 
 type GeoIPMatcher struct {
-	countryCode string
-	ip4         []uint32
-	prefix4     []uint8
-	ip6         []ipv6
-	prefix6     []uint8
+	countryCode  string
+	reverseMatch bool
+	ip4          []uint32
+	prefix4      []uint8
+	ip6          []ipv6
+	prefix6      []uint8
+}
+
+func (m *GeoIPMatcher) SetReverseMatch(isReverseMatch bool) {
+	m.reverseMatch = isReverseMatch
 }
 
 func normalize4(ip uint32, prefix uint8) uint32 {
@@ -149,8 +154,17 @@ func (m *GeoIPMatcher) match6(ip ipv6) bool {
 func (m *GeoIPMatcher) Match(ip net.IP) bool {
 	switch len(ip) {
 	case 4:
+		if m.reverseMatch {
+			return !m.match4(binary.BigEndian.Uint32(ip))
+		}
 		return m.match4(binary.BigEndian.Uint32(ip))
 	case 16:
+		if m.reverseMatch {
+			return !m.match6(ipv6{
+				a: binary.BigEndian.Uint64(ip[0:8]),
+				b: binary.BigEndian.Uint64(ip[8:16]),
+			})
+		}
 		return m.match6(ipv6{
 			a: binary.BigEndian.Uint64(ip[0:8]),
 			b: binary.BigEndian.Uint64(ip[8:16]),
@@ -170,14 +184,15 @@ type GeoIPMatcherContainer struct {
 func (c *GeoIPMatcherContainer) Add(geoip *GeoIP) (*GeoIPMatcher, error) {
 	if len(geoip.CountryCode) > 0 {
 		for _, m := range c.matchers {
-			if m.countryCode == geoip.CountryCode {
+			if m.countryCode == geoip.CountryCode && m.reverseMatch == geoip.ReverseMatch {
 				return m, nil
 			}
 		}
 	}
 
 	m := &GeoIPMatcher{
-		countryCode: geoip.CountryCode,
+		countryCode:  geoip.CountryCode,
+		reverseMatch: geoip.ReverseMatch,
 	}
 	if err := m.Init(geoip.Cidr); err != nil {
 		return nil, err
diff --git a/common/matcher/geoip/geoip.pb.go b/common/matcher/geoip/geoip.pb.go
index 4176127ff102..589ccd49214e 100644
--- a/common/matcher/geoip/geoip.pb.go
+++ b/common/matcher/geoip/geoip.pb.go
@@ -1,7 +1,7 @@
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
 // 	protoc-gen-go v1.25.0
-// 	protoc        v3.15.6
+// 	protoc        v3.15.7
 // source: common/matcher/geoip/geoip.proto
 
 package geoip
@@ -88,8 +88,9 @@ type GeoIP struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	CountryCode string  `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
-	Cidr        []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"`
+	CountryCode  string  `protobuf:"bytes,1,opt,name=country_code,json=countryCode,proto3" json:"country_code,omitempty"`
+	Cidr         []*CIDR `protobuf:"bytes,2,rep,name=cidr,proto3" json:"cidr,omitempty"`
+	ReverseMatch bool    `protobuf:"varint,3,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"`
 }
 
 func (x *GeoIP) Reset() {
@@ -138,6 +139,13 @@ func (x *GeoIP) GetCidr() []*CIDR {
 	return nil
 }
 
+func (x *GeoIP) GetReverseMatch() bool {
+	if x != nil {
+		return x.ReverseMatch
+	}
+	return false
+}
+
 type GeoIPList struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -194,25 +202,27 @@ var file_common_matcher_geoip_geoip_proto_rawDesc = []byte{
 	0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x22, 0x2e, 0x0a,
 	0x04, 0x43, 0x49, 0x44, 0x52, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28,
 	0x0c, 0x52, 0x02, 0x69, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x5f, 0x0a,
-	0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72,
-	0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f,
-	0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x63, 0x69, 0x64,
-	0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63,
-	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65,
-	0x6f, 0x69, 0x70, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x22, 0x43,
-	0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x36, 0x0a, 0x05, 0x65,
-	0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x78, 0x72, 0x61,
-	0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
-	0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x52, 0x05, 0x65, 0x6e,
-	0x74, 0x72, 0x79, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
+	0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x22, 0x84, 0x01,
+	0x0a, 0x05, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74,
+	0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63,
+	0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x63, 0x69,
+	0x64, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e,
 	0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67,
-	0x65, 0x6f, 0x69, 0x70, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72,
-	0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72,
-	0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0xaa, 0x02, 0x19, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f,
-	0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f,
-	0x49, 0x50, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x65, 0x6f, 0x69, 0x70, 0x2e, 0x43, 0x49, 0x44, 0x52, 0x52, 0x04, 0x63, 0x69, 0x64, 0x72, 0x12,
+	0x23, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x4d,
+	0x61, 0x74, 0x63, 0x68, 0x22, 0x43, 0x0a, 0x09, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73,
+	0x74, 0x12, 0x36, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x20, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d,
+	0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x2e, 0x47, 0x65, 0x6f,
+	0x49, 0x50, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x6d, 0x0a, 0x1d, 0x63, 0x6f, 0x6d,
+	0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x6d, 0x61, 0x74,
+	0x63, 0x68, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x6f, 0x69, 0x70, 0x50, 0x01, 0x5a, 0x2e, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72,
+	0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2f, 0x6d,
+	0x61, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6f, 0x69, 0x70, 0xaa, 0x02, 0x19, 0x58,
+	0x72, 0x61, 0x79, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68,
+	0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/common/matcher/geoip/geoip.proto b/common/matcher/geoip/geoip.proto
index a781ff09504f..d8a28398013b 100644
--- a/common/matcher/geoip/geoip.proto
+++ b/common/matcher/geoip/geoip.proto
@@ -18,6 +18,7 @@ message CIDR {
 message GeoIP {
   string country_code = 1;
   repeated CIDR cidr = 2;
+  bool reverse_match =3;
 }
 
 message GeoIPList {
diff --git a/common/matcher/geoip/geoip_test.go b/common/matcher/geoip/geoip_test.go
index f959b9447809..0e583ff35d31 100644
--- a/common/matcher/geoip/geoip_test.go
+++ b/common/matcher/geoip/geoip_test.go
@@ -135,6 +135,42 @@ func TestGeoIPMatcher4CN(t *testing.T) {
 	}
 }
 
+func TestGeoIPReverseMatcher(t *testing.T) {
+	cidrList := CIDRList{
+		{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
+		{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
+	}
+	matcher := &GeoIPMatcher{}
+	matcher.SetReverseMatch(true) // Reverse match
+	common.Must(matcher.Init(cidrList))
+
+	testCases := []struct {
+		Input  string
+		Output bool
+	}{
+		{
+			Input:  "8.8.8.8",
+			Output: false,
+		},
+		{
+			Input:  "2001:cdba::3257:9652",
+			Output: true,
+		},
+		{
+			Input:  "91.108.255.254",
+			Output: false,
+		},
+	}
+
+	for _, testCase := range testCases {
+		ip := net.ParseAddress(testCase.Input).IP()
+		actual := matcher.Match(ip)
+		if actual != testCase.Output {
+			t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual)
+		}
+	}
+}
+
 func TestGeoIPMatcher6US(t *testing.T) {
 	ips, err := loadGeoIP("US")
 	common.Must(err)

From 44317bd657eb1d4beab5fbc9f0f64fd842cce6af Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=8B=E3=81=AE=E3=81=8B=E3=81=88=E3=81=A7?=
 <autmaple@protonmail.com>
Date: Thu, 8 Apr 2021 13:19:25 +0800
Subject: [PATCH 12/20] Refine router config parse

---
 common/matcher/domain/conf/domain.go | 24 ++++++++++++++++++++----
 common/matcher/geosite/attribute.go  |  4 +++-
 infra/conf/router.go                 |  6 +++---
 3 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/common/matcher/domain/conf/domain.go b/common/matcher/domain/conf/domain.go
index 10a8cf57a25c..524b39bd8799 100644
--- a/common/matcher/domain/conf/domain.go
+++ b/common/matcher/domain/conf/domain.go
@@ -44,20 +44,36 @@ func ParseDomainRule(domain string) ([]*dm.Domain, error) {
 	domainRule := new(dm.Domain)
 	switch {
 	case strings.HasPrefix(domain, "regexp:"):
+		regexpVal := domain[7:]
+		if len(regexpVal) == 0 {
+			return nil, newError("empty regexp type of rule: ", domain)
+		}
 		domainRule.Type = dm.MatchingType_Regex
-		domainRule.Value = domain[7:]
+		domainRule.Value = regexpVal
 
 	case strings.HasPrefix(domain, "domain:"):
+		domainName := domain[7:]
+		if len(domainName) == 0 {
+			return nil, newError("empty domain type of rule: ", domain)
+		}
 		domainRule.Type = dm.MatchingType_Subdomain
-		domainRule.Value = domain[7:]
+		domainRule.Value = domainName
 
 	case strings.HasPrefix(domain, "full:"):
+		fullVal := domain[5:]
+		if len(fullVal) == 0 {
+			return nil, newError("empty full domain type of rule: ", domain)
+		}
 		domainRule.Type = dm.MatchingType_Full
-		domainRule.Value = domain[5:]
+		domainRule.Value = fullVal
 
 	case strings.HasPrefix(domain, "keyword:"):
+		keywordVal := domain[8:]
+		if len(keywordVal) == 0 {
+			return nil, newError("empty keyword type of rule: ", domain)
+		}
 		domainRule.Type = dm.MatchingType_Keyword
-		domainRule.Value = domain[8:]
+		domainRule.Value = keywordVal
 
 	case strings.HasPrefix(domain, "dotless:"):
 		domainRule.Type = dm.MatchingType_Regex
diff --git a/common/matcher/geosite/attribute.go b/common/matcher/geosite/attribute.go
index a16361c169e2..c2fc0b30de12 100644
--- a/common/matcher/geosite/attribute.go
+++ b/common/matcher/geosite/attribute.go
@@ -1,5 +1,7 @@
 package geosite
 
+import "strings"
+
 type AttributeList struct {
 	matcher []AttributeMatcher
 }
@@ -25,7 +27,7 @@ type BooleanMatcher string
 
 func (m BooleanMatcher) Match(domain *Domain) bool {
 	for _, attr := range domain.Attribute {
-		if attr.Key == string(m) {
+		if strings.EqualFold(attr.GetKey(), string(m)) {
 			return true
 		}
 	}
diff --git a/infra/conf/router.go b/infra/conf/router.go
index 1e005bf257ca..e905b67232a0 100644
--- a/infra/conf/router.go
+++ b/infra/conf/router.go
@@ -261,21 +261,21 @@ func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) {
 	if err != nil {
 		return nil, newError("invalid router rule").Base(err)
 	}
-	if rawRule.Type == "field" {
+	if strings.EqualFold(rawRule.Type, "field") {
 		fieldrule, err := parseFieldRule(msg)
 		if err != nil {
 			return nil, newError("invalid field rule").Base(err)
 		}
 		return fieldrule, nil
 	}
-	if rawRule.Type == "chinaip" {
+	if strings.EqualFold(rawRule.Type, "chinaip") {
 		chinaiprule, err := parseChinaIPRule(msg)
 		if err != nil {
 			return nil, newError("invalid chinaip rule").Base(err)
 		}
 		return chinaiprule, nil
 	}
-	if rawRule.Type == "chinasites" {
+	if strings.EqualFold(rawRule.Type, "chinasites") {
 		chinasitesrule, err := parseChinaSitesRule(msg)
 		if err != nil {
 			return nil, newError("invalid chinasites rule").Base(err)

From 5d5beb2028b9848c3c1d208b1a058d6c6db6d866 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=8B=E3=81=AE=E3=81=8B=E3=81=88=E3=81=A7?=
 <autmaple@protonmail.com>
Date: Thu, 8 Apr 2021 13:25:22 +0800
Subject: [PATCH 13/20] Add test to ParaseIPList

---
 common/matcher/geoip/geoip_test.go | 45 ++++++++++++++++++++++--------
 1 file changed, 33 insertions(+), 12 deletions(-)

diff --git a/common/matcher/geoip/geoip_test.go b/common/matcher/geoip/geoip_test.go
index 0e583ff35d31..602957823c7f 100644
--- a/common/matcher/geoip/geoip_test.go
+++ b/common/matcher/geoip/geoip_test.go
@@ -25,6 +25,27 @@ func init() {
 	}
 }
 
+func TestParaseIPList(t *testing.T) {
+	t.Log(os.Getenv("xray.location.asset"))
+
+	common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoiptestrouter.dat"), platform.GetAssetLocation("geoip.dat")))
+
+	ips := []string{
+		"geoip:us",
+		"geoip:cn",
+		"geoip:!cn",
+		"ext:geoiptestrouter.dat:!cn",
+		"ext:geoiptestrouter.dat:ca",
+		"ext-ip:geoiptestrouter.dat:!cn",
+		"ext-ip:geoiptestrouter.dat:!ca",
+	}
+
+	_, err := ParaseIPList(ips)
+	if err != nil {
+		t.Fatalf("Failed to parse geoip list, got %s", err)
+	}
+}
+
 func TestGeoIPMatcherContainer(t *testing.T) {
 	container := &GeoIPMatcherContainer{}
 
@@ -123,18 +144,6 @@ func TestGeoIPMatcher(t *testing.T) {
 	}
 }
 
-func TestGeoIPMatcher4CN(t *testing.T) {
-	ips, err := loadGeoIP("CN")
-	common.Must(err)
-
-	matcher := &GeoIPMatcher{}
-	common.Must(matcher.Init(ips))
-
-	if matcher.Match([]byte{8, 8, 8, 8}) {
-		t.Error("expect CN geoip doesn't contain 8.8.8.8, but actually does")
-	}
-}
-
 func TestGeoIPReverseMatcher(t *testing.T) {
 	cidrList := CIDRList{
 		{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
@@ -171,6 +180,18 @@ func TestGeoIPReverseMatcher(t *testing.T) {
 	}
 }
 
+func TestGeoIPMatcher4CN(t *testing.T) {
+	ips, err := loadGeoIP("CN")
+	common.Must(err)
+
+	matcher := &GeoIPMatcher{}
+	common.Must(matcher.Init(ips))
+
+	if matcher.Match([]byte{8, 8, 8, 8}) {
+		t.Error("expect CN geoip doesn't contain 8.8.8.8, but actually does")
+	}
+}
+
 func TestGeoIPMatcher6US(t *testing.T) {
 	ips, err := loadGeoIP("US")
 	common.Must(err)

From 1c3abb2ec3ad72495d61763cbcad87fa6c99e1d7 Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Thu, 8 Apr 2021 19:18:23 +0800
Subject: [PATCH 14/20] Fix: config loader

---
 common/matcher/geoip/conf.go | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/common/matcher/geoip/conf.go b/common/matcher/geoip/conf.go
index e159534045da..5760409ab854 100644
--- a/common/matcher/geoip/conf.go
+++ b/common/matcher/geoip/conf.go
@@ -100,6 +100,11 @@ func ParaseIPList(ips []string) ([]*GeoIP, error) {
 		if strings.HasPrefix(ip, "geoip:") {
 			country := ip[6:]
 			isReverseMatch := false
+			if strings.HasPrefix(country, "!") {
+				country = country[1:]
+				isReverseMatch = true
+			}
+
 			geoipc, err := LoadGeoIP(strings.ToUpper(country))
 			if err != nil {
 				return nil, newError("failed to load GeoIP: ", country).Base(err)

From 095b17da6208793903d0e51409569b3013804623 Mon Sep 17 00:00:00 2001
From: JimhHan <50871214+JimhHan@users.noreply.github.com>
Date: Thu, 8 Apr 2021 19:19:53 +0800
Subject: [PATCH 15/20] Fix typo

---
 common/matcher/geoip/conf.go       | 2 +-
 common/matcher/geoip/geoip_test.go | 4 ++--
 infra/conf/dns.go                  | 2 +-
 infra/conf/router.go               | 4 ++--
 infra/conf/xray.go                 | 2 +-
 5 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/common/matcher/geoip/conf.go b/common/matcher/geoip/conf.go
index 5760409ab854..4a997148e7d9 100644
--- a/common/matcher/geoip/conf.go
+++ b/common/matcher/geoip/conf.go
@@ -92,7 +92,7 @@ func find(data, code []byte) []byte {
 	}
 }
 
-func ParaseIPList(ips []string) ([]*GeoIP, error) {
+func ParseIPList(ips []string) ([]*GeoIP, error) {
 	var geoipList []*GeoIP
 	var customCidrs []*CIDR
 
diff --git a/common/matcher/geoip/geoip_test.go b/common/matcher/geoip/geoip_test.go
index 602957823c7f..b2bee0c4eb16 100644
--- a/common/matcher/geoip/geoip_test.go
+++ b/common/matcher/geoip/geoip_test.go
@@ -25,7 +25,7 @@ func init() {
 	}
 }
 
-func TestParaseIPList(t *testing.T) {
+func TestParseIPList(t *testing.T) {
 	t.Log(os.Getenv("xray.location.asset"))
 
 	common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoiptestrouter.dat"), platform.GetAssetLocation("geoip.dat")))
@@ -40,7 +40,7 @@ func TestParaseIPList(t *testing.T) {
 		"ext-ip:geoiptestrouter.dat:!ca",
 	}
 
-	_, err := ParaseIPList(ips)
+	_, err := ParseIPList(ips)
 	if err != nil {
 		t.Fatalf("Failed to parse geoip list, got %s", err)
 	}
diff --git a/infra/conf/dns.go b/infra/conf/dns.go
index 0cb3b7663754..0e685dc55783 100644
--- a/infra/conf/dns.go
+++ b/infra/conf/dns.go
@@ -74,7 +74,7 @@ func (c *NameServerConfig) Build() (*dns.NameServer, error) {
 		})
 	}
 
-	geoipList, err := geoip.ParaseIPList(c.ExpectIPs)
+	geoipList, err := geoip.ParseIPList(c.ExpectIPs)
 	if err != nil {
 		return nil, newError("invalid IP rule: ", c.ExpectIPs).Base(err)
 	}
diff --git a/infra/conf/router.go b/infra/conf/router.go
index e905b67232a0..8a59bddf7949 100644
--- a/infra/conf/router.go
+++ b/infra/conf/router.go
@@ -203,7 +203,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 	}
 
 	if rawFieldRule.IP != nil {
-		geoipList, err := geoip.ParaseIPList(*rawFieldRule.IP)
+		geoipList, err := geoip.ParseIPList(*rawFieldRule.IP)
 		if err != nil {
 			return nil, err
 		}
@@ -219,7 +219,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) {
 	}
 
 	if rawFieldRule.SourceIP != nil {
-		geoipList, err := geoip.ParaseIPList(*rawFieldRule.SourceIP)
+		geoipList, err := geoip.ParseIPList(*rawFieldRule.SourceIP)
 		if err != nil {
 			return nil, err
 		}
diff --git a/infra/conf/xray.go b/infra/conf/xray.go
index 1f89ebad209e..b8b1ba8a96ac 100644
--- a/infra/conf/xray.go
+++ b/infra/conf/xray.go
@@ -101,7 +101,7 @@ func (c *SniffingConfig) Build() (*proxyman.SniffingConfig, error) {
 
 	var exIP []*geoip.GeoIP
 	if c.IPsExcluded != nil {
-		exip, err := geoip.ParaseIPList(*c.IPsExcluded)
+		exip, err := geoip.ParseIPList(*c.IPsExcluded)
 		if err != nil {
 			return nil, newError("failed to parse excluded ip").Base(err)
 		}

From eeb40c9ce2df5390eef75c67cb08507dc1cdb1cb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=8B=E3=81=AE=E3=81=8B=E3=81=88=E3=81=A7?=
 <autmaple@protonmail.com>
Date: Thu, 8 Apr 2021 22:51:55 +0800
Subject: [PATCH 16/20] Refine tests

---
 common/matcher/geoip/geoip_test.go | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/common/matcher/geoip/geoip_test.go b/common/matcher/geoip/geoip_test.go
index b2bee0c4eb16..48bc52867ad6 100644
--- a/common/matcher/geoip/geoip_test.go
+++ b/common/matcher/geoip/geoip_test.go
@@ -5,12 +5,12 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/golang/protobuf/proto"
 	"github.com/xtls/xray-core/common"
 	. "github.com/xtls/xray-core/common/matcher/geoip"
 	"github.com/xtls/xray-core/common/net"
 	"github.com/xtls/xray-core/common/platform"
 	"github.com/xtls/xray-core/common/platform/filesystem"
+	"google.golang.org/protobuf/proto"
 )
 
 func init() {
@@ -20,16 +20,15 @@ func init() {
 	if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
 		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geoip.dat")))
 	}
+	if _, err := os.Stat(platform.GetAssetLocation("geoiptestrouter.dat")); err != nil && os.IsNotExist(err) {
+		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoiptestrouter.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geoip.dat")))
+	}
 	if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) {
 		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "..", "resources", "geosite.dat")))
 	}
 }
 
 func TestParseIPList(t *testing.T) {
-	t.Log(os.Getenv("xray.location.asset"))
-
-	common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoiptestrouter.dat"), platform.GetAssetLocation("geoip.dat")))
-
 	ips := []string{
 		"geoip:us",
 		"geoip:cn",

From 9721ff7a569a4a1c26f9a197cf76379f85f837f2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=8B=E3=81=AE=E3=81=8B=E3=81=88=E3=81=A7?=
 <autmaple@protonmail.com>
Date: Thu, 8 Apr 2021 23:19:43 +0800
Subject: [PATCH 17/20] Style: comment unused code

---
 common/matcher/geoip/conf.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/common/matcher/geoip/conf.go b/common/matcher/geoip/conf.go
index 9c5fa42b7eea..5f46e3a4cc28 100644
--- a/common/matcher/geoip/conf.go
+++ b/common/matcher/geoip/conf.go
@@ -36,7 +36,7 @@ func LoadIPFile(file, code string) ([]*CIDR, error) {
 		}
 		defer runtime.GC()        // or debug.FreeOSMemory()
 		return geoipdat.Cidr, nil // do not cache geoip
-		IPCache[index] = &geoipdat
+		// IPCache[index] = &geoipdat
 	}
 	return IPCache[index].Cidr, nil
 }
@@ -53,7 +53,7 @@ func loadFile(file string) ([]byte, error) {
 		// Do not cache file, may save RAM when there
 		// are many files, but consume CPU each time.
 		return bs, nil
-		FileCache[file] = bs
+		// FileCache[file] = bs
 	}
 	return FileCache[file], nil
 }

From ec9f19039e22e57d2b4dd7597ac98c6c28f87eb1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=8B=E3=81=AE=E3=81=8B=E3=81=88=E3=81=A7?=
 <autmaple@protonmail.com>
Date: Sat, 1 May 2021 08:55:27 +0800
Subject: [PATCH 18/20] remove v2ray.com

---
 common/matcher/str/benchmark_test.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/common/matcher/str/benchmark_test.go b/common/matcher/str/benchmark_test.go
index e7420ee4e815..f52317b92f64 100644
--- a/common/matcher/str/benchmark_test.go
+++ b/common/matcher/str/benchmark_test.go
@@ -11,13 +11,13 @@ import (
 func BenchmarkACAutomaton(b *testing.B) {
 	ac := NewACAutomaton()
 	for i := 1; i <= 1024; i++ {
-		ac.Add(strconv.Itoa(i)+".v2ray.com", Domain)
+		ac.Add(strconv.Itoa(i)+".xray.com", Domain)
 	}
 	ac.Build()
 
 	b.ResetTimer()
 	for i := 0; i < b.N; i++ {
-		_ = ac.Match("0.v2ray.com")
+		_ = ac.Match("0.xray.com")
 	}
 }
 

From a3e63e6928c11d174cdb29857b0a585241a35381 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A7=8B=E3=81=AE=E3=81=8B=E3=81=88=E3=81=A7?=
 <autmaple@protonmail.com>
Date: Sat, 1 May 2021 10:44:29 +0800
Subject: [PATCH 19/20] style: refine style

---
 infra/conf/router.go | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/infra/conf/router.go b/infra/conf/router.go
index accb2c4b9f48..99064543750e 100644
--- a/infra/conf/router.go
+++ b/infra/conf/router.go
@@ -94,10 +94,9 @@ func (c *RouterConfig) Build() (*router.Config, error) {
 }
 
 type RouterRule struct {
-	Type        string `json:"type"`
-	OutboundTag string `json:"outboundTag"`
-	BalancerTag string `json:"balancerTag"`
-
+	Type          string `json:"type"`
+	OutboundTag   string `json:"outboundTag"`
+	BalancerTag   string `json:"balancerTag"`
 	DomainMatcher string `json:"domainMatcher"`
 }
 

From 1ced7985d59f89673f7e17409180c93ac1885afd Mon Sep 17 00:00:00 2001
From: hmol233 <82594500+hmol233@users.noreply.github.com>
Date: Sun, 11 Jul 2021 22:02:55 +0800
Subject: [PATCH 20/20] Refine `PrioritizedDomain`, should fix
 https://github.com/XTLS/Xray-core/issues/638

---
 app/dns/dns.go        | 50 ++++++++++-------------------
 app/dns/nameserver.go | 74 ++++++++++++++++++++-----------------------
 2 files changed, 52 insertions(+), 72 deletions(-)

diff --git a/app/dns/dns.go b/app/dns/dns.go
index c638735dd405..68cb29b09bdd 100644
--- a/app/dns/dns.go
+++ b/app/dns/dns.go
@@ -12,7 +12,6 @@ import (
 	"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/features"
@@ -29,8 +28,6 @@ type DNS struct {
 	hosts           *StaticHosts
 	clients         []*Client
 	ctx             context.Context
-	domainMatcher   str.IndexMatcher
-	matcherInfos    []DomainMatcherInfo
 }
 
 // DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
@@ -89,9 +86,6 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
 		domainRuleCount += len(ns.PrioritizedDomain)
 	}
 
-	// MatcherInfos is ensured to cover the maximum index domainMatcher could return, where matcher's index starts from 1
-	matcherInfos := make([]DomainMatcherInfo, domainRuleCount+1)
-	domainMatcher := &str.MatcherGroup{}
 	geoipContainer := geoip.GeoIPMatcherContainer{}
 
 	for _, endpoint := range config.NameServers {
@@ -104,22 +98,13 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
 	}
 
 	for _, ns := range config.NameServer {
-		clientIdx := len(clients)
-		updateDomain := func(domainRule str.Matcher, originalRuleIdx int, matcherInfos []DomainMatcherInfo) error {
-			midx := domainMatcher.Add(domainRule)
-			matcherInfos[midx] = DomainMatcherInfo{
-				clientIdx:     uint16(clientIdx),
-				domainRuleIdx: uint16(originalRuleIdx),
-			}
-			return nil
-		}
 
 		myClientIP := clientIP
 		switch len(ns.ClientIp) {
 		case net.IPv4len, net.IPv6len:
-			myClientIP = net.IP(ns.ClientIp)
+			myClientIP = ns.ClientIp
 		}
-		client, err := NewClient(ctx, ns, myClientIP, geoipContainer, &matcherInfos, updateDomain)
+		client, err := NewClient(ctx, ns, myClientIP, geoipContainer)
 		if err != nil {
 			return nil, newError("failed to create client").Base(err)
 		}
@@ -137,8 +122,6 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
 		ipOption:        ipOption,
 		clients:         clients,
 		ctx:             ctx,
-		domainMatcher:   domainMatcher,
-		matcherInfos:    matcherInfos,
 		cacheStrategy:   config.CacheStrategy,
 		disableFallback: config.DisableFallback,
 	}, nil
@@ -268,21 +251,22 @@ func (s *DNS) sortClients(domain string, option *dns.IPOption) []*Client {
 	}()
 
 	// Priority domain matching
-	for _, match := range s.domainMatcher.Match(domain) {
-		info := s.matcherInfos[match]
-		client := s.clients[info.clientIdx]
-		domainRule := client.domains[info.domainRuleIdx]
-		if !canQueryOnClient(option, client) {
-			newError("skipping the client " + client.Name()).AtDebug().WriteToLog()
-			continue
-		}
-		domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
-		if clientUsed[info.clientIdx] {
-			continue
+	for clientIdx, client := range s.clients {
+		if ids := client.domainMatcher.Match(domain); len(ids) > 0 {
+			if !canQueryOnClient(option, client) {
+				newError("skipping the client " + client.Name()).AtDebug().WriteToLog()
+				continue
+			}
+			for _, id := range ids {
+				rule := client.findRule(id)
+				domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", rule, clientIdx))
+			}
+			if clientUsed[clientIdx] {
+				continue
+			}
+			clients = append(clients, client)
+			clientNames = append(clientNames, client.Name())
 		}
-		clientUsed[info.clientIdx] = true
-		clients = append(clients, client)
-		clientNames = append(clientNames, client.Name())
 	}
 
 	if !s.disableFallback {
diff --git a/app/dns/nameserver.go b/app/dns/nameserver.go
index ac0e405aca13..acc842b36171 100644
--- a/app/dns/nameserver.go
+++ b/app/dns/nameserver.go
@@ -25,11 +25,23 @@ type Server interface {
 
 // Client is the interface for DNS client.
 type Client struct {
-	server       Server
-	clientIP     net.IP
-	skipFallback bool
-	domains      []string
-	expectIPs    []*geoip.GeoIPMatcher
+	server        Server
+	clientIP      net.IP
+	skipFallback  bool
+	expectIPs     []*geoip.GeoIPMatcher
+	domainMatcher str.MatcherGroup
+	originRules   []*NameServer_OriginalRule
+}
+
+func (c Client) findRule(idx uint32) string {
+	for _, r := range c.originRules {
+		if idx <= r.Size {
+			return r.Rule
+		}
+		idx -= r.Size
+	}
+
+	return "unknown rule"
 }
 
 var errExpectedIPNonMatch = errors.New("expectIPs not match")
@@ -64,7 +76,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 geoip.GeoIPMatcherContainer, matcherInfos *[]DomainMatcherInfo, updateDomainRule func(str.Matcher, int, []DomainMatcherInfo) error) (*Client, error) {
+func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container geoip.GeoIPMatcherContainer) (*Client, error) {
 	client := &Client{}
 
 	err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
@@ -79,55 +91,38 @@ func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container g
 			ns.PrioritizedDomain = append(ns.PrioritizedDomain, localTLDsAndDotlessDomains...)
 			ns.OriginalRules = append(ns.OriginalRules, localTLDsAndDotlessDomainsRule)
 			// The following lines is a solution to avoid core panics(rule index out of range) when setting `localhost` DNS client in config.
-			// Because the `localhost` DNS client will apend len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
+			// Because the `localhost` DNS client will append len(localTLDsAndDotlessDomains) rules into matcherInfos to match `geosite:private` default rule.
 			// But `matcherInfos` has no enough length to add rules, which leads to core panics (rule index out of range).
 			// To avoid this, the length of `matcherInfos` must be equal to the expected, so manually append it with Golang default zero value first for later modification.
-			for i := 0; i < len(localTLDsAndDotlessDomains); i++ {
-				*matcherInfos = append(*matcherInfos, DomainMatcherInfo{
-					clientIdx:     uint16(0),
-					domainRuleIdx: uint16(0),
-				})
-			}
+			// ;)
+			/*
+				for i := 0; i < len(localTLDsAndDotlessDomains); i++ {
+					*matcherInfos = append(*matcherInfos, DomainMatcherInfo{
+						clientIdx:     uint16(0),
+						domainRuleIdx: uint16(0),
+					})
+				}
+			*/
 		}
 
 		// Establish domain rules
-		var rules []string
-		ruleCurr := 0
-		ruleIter := 0
+		var domainMatcher = str.MatcherGroup{}
 		for _, domain := range ns.PrioritizedDomain {
 			domainRule, err := toStrMatcher(domain.Type, domain.Value)
 			if err != nil {
 				return newError("failed to create prioritized domain").Base(err).AtWarning()
 			}
-			originalRuleIdx := ruleCurr
-			if ruleCurr < len(ns.OriginalRules) {
-				rule := ns.OriginalRules[ruleCurr]
-				if ruleCurr >= len(rules) {
-					rules = append(rules, rule.Rule)
-				}
-				ruleIter++
-				if ruleIter >= int(rule.Size) {
-					ruleIter = 0
-					ruleCurr++
-				}
-			} else { // No original rule, generate one according to current domain matcher (majorly for compatibility with tests)
-				rules = append(rules, domainRule.String())
-				ruleCurr++
-			}
-			err = updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
-			if err != nil {
-				return newError("failed to create prioritized domain").Base(err).AtWarning()
-			}
+			domainMatcher.Add(domainRule)
 		}
 
 		// Establish expected IPs
-		var matchers []*geoip.GeoIPMatcher
+		var ipMatchers []*geoip.GeoIPMatcher
 		for _, geoip := range ns.Geoip {
 			matcher, err := container.Add(geoip)
 			if err != nil {
 				return newError("failed to create ip matcher").Base(err).AtWarning()
 			}
-			matchers = append(matchers, matcher)
+			ipMatchers = append(ipMatchers, matcher)
 		}
 
 		if len(clientIP) > 0 {
@@ -141,8 +136,9 @@ func NewClient(ctx context.Context, ns *NameServer, clientIP net.IP, container g
 
 		client.server = server
 		client.clientIP = clientIP
-		client.domains = rules
-		client.expectIPs = matchers
+		client.expectIPs = ipMatchers
+		client.originRules = ns.OriginalRules
+		client.domainMatcher = domainMatcher
 		return nil
 	})
 	return client, err