Meo597 před 3 měsíci
rodič
revize
c3e553bd2e

+ 387 - 70
common/geodata/geodat.pb.go

@@ -141,6 +141,244 @@ func (x *Domain) GetAttribute() []*Domain_Attribute {
 	return nil
 	return nil
 }
 }
 
 
+type GeoSite struct {
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Code          string                 `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
+	Domain        []*Domain              `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
+}
+
+func (x *GeoSite) Reset() {
+	*x = GeoSite{}
+	mi := &file_common_geodata_geodat_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_geodata_geodat_proto_msgTypes[1]
+	if 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_geodata_geodat_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *GeoSite) GetCode() string {
+	if x != nil {
+		return x.Code
+	}
+	return ""
+}
+
+func (x *GeoSite) GetDomain() []*Domain {
+	if x != nil {
+		return x.Domain
+	}
+	return nil
+}
+
+type GeoSiteList struct {
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Entry         []*GeoSite             `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
+}
+
+func (x *GeoSiteList) Reset() {
+	*x = GeoSiteList{}
+	mi := &file_common_geodata_geodat_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_geodata_geodat_proto_msgTypes[2]
+	if 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_geodata_geodat_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *GeoSiteList) GetEntry() []*GeoSite {
+	if x != nil {
+		return x.Entry
+	}
+	return nil
+}
+
+type GeoSiteRule struct {
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	File          string                 `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
+	Code          string                 `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"`
+	Attrs         string                 `protobuf:"bytes,3,opt,name=attrs,proto3" json:"attrs,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
+}
+
+func (x *GeoSiteRule) Reset() {
+	*x = GeoSiteRule{}
+	mi := &file_common_geodata_geodat_proto_msgTypes[3]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *GeoSiteRule) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*GeoSiteRule) ProtoMessage() {}
+
+func (x *GeoSiteRule) ProtoReflect() protoreflect.Message {
+	mi := &file_common_geodata_geodat_proto_msgTypes[3]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use GeoSiteRule.ProtoReflect.Descriptor instead.
+func (*GeoSiteRule) Descriptor() ([]byte, []int) {
+	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *GeoSiteRule) GetFile() string {
+	if x != nil {
+		return x.File
+	}
+	return ""
+}
+
+func (x *GeoSiteRule) GetCode() string {
+	if x != nil {
+		return x.Code
+	}
+	return ""
+}
+
+func (x *GeoSiteRule) GetAttrs() string {
+	if x != nil {
+		return x.Attrs
+	}
+	return ""
+}
+
+type DomainRule struct {
+	state protoimpl.MessageState `protogen:"open.v1"`
+	// Types that are valid to be assigned to Value:
+	//
+	//	*DomainRule_Geosite
+	//	*DomainRule_Custom
+	Value         isDomainRule_Value `protobuf_oneof:"value"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
+}
+
+func (x *DomainRule) Reset() {
+	*x = DomainRule{}
+	mi := &file_common_geodata_geodat_proto_msgTypes[4]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *DomainRule) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DomainRule) ProtoMessage() {}
+
+func (x *DomainRule) ProtoReflect() protoreflect.Message {
+	mi := &file_common_geodata_geodat_proto_msgTypes[4]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DomainRule.ProtoReflect.Descriptor instead.
+func (*DomainRule) Descriptor() ([]byte, []int) {
+	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *DomainRule) GetValue() isDomainRule_Value {
+	if x != nil {
+		return x.Value
+	}
+	return nil
+}
+
+func (x *DomainRule) GetGeosite() *GeoSiteRule {
+	if x != nil {
+		if x, ok := x.Value.(*DomainRule_Geosite); ok {
+			return x.Geosite
+		}
+	}
+	return nil
+}
+
+func (x *DomainRule) GetCustom() *Domain {
+	if x != nil {
+		if x, ok := x.Value.(*DomainRule_Custom); ok {
+			return x.Custom
+		}
+	}
+	return nil
+}
+
+type isDomainRule_Value interface {
+	isDomainRule_Value()
+}
+
+type DomainRule_Geosite struct {
+	Geosite *GeoSiteRule `protobuf:"bytes,1,opt,name=geosite,proto3,oneof"`
+}
+
+type DomainRule_Custom struct {
+	Custom *Domain `protobuf:"bytes,2,opt,name=custom,proto3,oneof"`
+}
+
+func (*DomainRule_Geosite) isDomainRule_Value() {}
+
+func (*DomainRule_Custom) isDomainRule_Value() {}
+
 type CIDR struct {
 type CIDR struct {
 	state protoimpl.MessageState `protogen:"open.v1"`
 	state protoimpl.MessageState `protogen:"open.v1"`
 	// IP address, should be either 4 or 16 bytes.
 	// IP address, should be either 4 or 16 bytes.
@@ -153,7 +391,7 @@ type CIDR struct {
 
 
 func (x *CIDR) Reset() {
 func (x *CIDR) Reset() {
 	*x = CIDR{}
 	*x = CIDR{}
-	mi := &file_common_geodata_geodat_proto_msgTypes[1]
+	mi := &file_common_geodata_geodat_proto_msgTypes[5]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 	ms.StoreMessageInfo(mi)
 }
 }
@@ -165,7 +403,7 @@ func (x *CIDR) String() string {
 func (*CIDR) ProtoMessage() {}
 func (*CIDR) ProtoMessage() {}
 
 
 func (x *CIDR) ProtoReflect() protoreflect.Message {
 func (x *CIDR) ProtoReflect() protoreflect.Message {
-	mi := &file_common_geodata_geodat_proto_msgTypes[1]
+	mi := &file_common_geodata_geodat_proto_msgTypes[5]
 	if x != nil {
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 		if ms.LoadMessageInfo() == nil {
@@ -178,7 +416,7 @@ func (x *CIDR) ProtoReflect() protoreflect.Message {
 
 
 // Deprecated: Use CIDR.ProtoReflect.Descriptor instead.
 // Deprecated: Use CIDR.ProtoReflect.Descriptor instead.
 func (*CIDR) Descriptor() ([]byte, []int) {
 func (*CIDR) Descriptor() ([]byte, []int) {
-	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{1}
+	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{5}
 }
 }
 
 
 func (x *CIDR) GetIp() []byte {
 func (x *CIDR) GetIp() []byte {
@@ -206,7 +444,7 @@ type GeoIP struct {
 
 
 func (x *GeoIP) Reset() {
 func (x *GeoIP) Reset() {
 	*x = GeoIP{}
 	*x = GeoIP{}
-	mi := &file_common_geodata_geodat_proto_msgTypes[2]
+	mi := &file_common_geodata_geodat_proto_msgTypes[6]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 	ms.StoreMessageInfo(mi)
 }
 }
@@ -218,7 +456,7 @@ func (x *GeoIP) String() string {
 func (*GeoIP) ProtoMessage() {}
 func (*GeoIP) ProtoMessage() {}
 
 
 func (x *GeoIP) ProtoReflect() protoreflect.Message {
 func (x *GeoIP) ProtoReflect() protoreflect.Message {
-	mi := &file_common_geodata_geodat_proto_msgTypes[2]
+	mi := &file_common_geodata_geodat_proto_msgTypes[6]
 	if x != nil {
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 		if ms.LoadMessageInfo() == nil {
@@ -231,7 +469,7 @@ func (x *GeoIP) ProtoReflect() protoreflect.Message {
 
 
 // Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
 // Deprecated: Use GeoIP.ProtoReflect.Descriptor instead.
 func (*GeoIP) Descriptor() ([]byte, []int) {
 func (*GeoIP) Descriptor() ([]byte, []int) {
-	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{2}
+	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{6}
 }
 }
 
 
 func (x *GeoIP) GetCode() string {
 func (x *GeoIP) GetCode() string {
@@ -264,7 +502,7 @@ type GeoIPList struct {
 
 
 func (x *GeoIPList) Reset() {
 func (x *GeoIPList) Reset() {
 	*x = GeoIPList{}
 	*x = GeoIPList{}
-	mi := &file_common_geodata_geodat_proto_msgTypes[3]
+	mi := &file_common_geodata_geodat_proto_msgTypes[7]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 	ms.StoreMessageInfo(mi)
 }
 }
@@ -276,7 +514,7 @@ func (x *GeoIPList) String() string {
 func (*GeoIPList) ProtoMessage() {}
 func (*GeoIPList) ProtoMessage() {}
 
 
 func (x *GeoIPList) ProtoReflect() protoreflect.Message {
 func (x *GeoIPList) ProtoReflect() protoreflect.Message {
-	mi := &file_common_geodata_geodat_proto_msgTypes[3]
+	mi := &file_common_geodata_geodat_proto_msgTypes[7]
 	if x != nil {
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 		if ms.LoadMessageInfo() == nil {
@@ -289,7 +527,7 @@ func (x *GeoIPList) ProtoReflect() protoreflect.Message {
 
 
 // Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.
 // Deprecated: Use GeoIPList.ProtoReflect.Descriptor instead.
 func (*GeoIPList) Descriptor() ([]byte, []int) {
 func (*GeoIPList) Descriptor() ([]byte, []int) {
-	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{3}
+	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{7}
 }
 }
 
 
 func (x *GeoIPList) GetEntry() []*GeoIP {
 func (x *GeoIPList) GetEntry() []*GeoIP {
@@ -299,29 +537,30 @@ func (x *GeoIPList) GetEntry() []*GeoIP {
 	return nil
 	return nil
 }
 }
 
 
-type GeoSite struct {
+type GeoIPRule struct {
 	state         protoimpl.MessageState `protogen:"open.v1"`
 	state         protoimpl.MessageState `protogen:"open.v1"`
-	Code          string                 `protobuf:"bytes,1,opt,name=code,proto3" json:"code,omitempty"`
-	Domain        []*Domain              `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
+	File          string                 `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
+	Code          string                 `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"`
+	ReverseMatch  bool                   `protobuf:"varint,3,opt,name=reverse_match,json=reverseMatch,proto3" json:"reverse_match,omitempty"`
 	unknownFields protoimpl.UnknownFields
 	unknownFields protoimpl.UnknownFields
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
 }
 }
 
 
-func (x *GeoSite) Reset() {
-	*x = GeoSite{}
-	mi := &file_common_geodata_geodat_proto_msgTypes[4]
+func (x *GeoIPRule) Reset() {
+	*x = GeoIPRule{}
+	mi := &file_common_geodata_geodat_proto_msgTypes[8]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 	ms.StoreMessageInfo(mi)
 }
 }
 
 
-func (x *GeoSite) String() string {
+func (x *GeoIPRule) String() string {
 	return protoimpl.X.MessageStringOf(x)
 	return protoimpl.X.MessageStringOf(x)
 }
 }
 
 
-func (*GeoSite) ProtoMessage() {}
+func (*GeoIPRule) ProtoMessage() {}
 
 
-func (x *GeoSite) ProtoReflect() protoreflect.Message {
-	mi := &file_common_geodata_geodat_proto_msgTypes[4]
+func (x *GeoIPRule) ProtoReflect() protoreflect.Message {
+	mi := &file_common_geodata_geodat_proto_msgTypes[8]
 	if x != nil {
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 		if ms.LoadMessageInfo() == nil {
@@ -332,47 +571,58 @@ func (x *GeoSite) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 	return mi.MessageOf(x)
 }
 }
 
 
-// Deprecated: Use GeoSite.ProtoReflect.Descriptor instead.
-func (*GeoSite) Descriptor() ([]byte, []int) {
-	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{4}
+// Deprecated: Use GeoIPRule.ProtoReflect.Descriptor instead.
+func (*GeoIPRule) Descriptor() ([]byte, []int) {
+	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{8}
 }
 }
 
 
-func (x *GeoSite) GetCode() string {
+func (x *GeoIPRule) GetFile() string {
+	if x != nil {
+		return x.File
+	}
+	return ""
+}
+
+func (x *GeoIPRule) GetCode() string {
 	if x != nil {
 	if x != nil {
 		return x.Code
 		return x.Code
 	}
 	}
 	return ""
 	return ""
 }
 }
 
 
-func (x *GeoSite) GetDomain() []*Domain {
+func (x *GeoIPRule) GetReverseMatch() bool {
 	if x != nil {
 	if x != nil {
-		return x.Domain
+		return x.ReverseMatch
 	}
 	}
-	return nil
+	return false
 }
 }
 
 
-type GeoSiteList struct {
-	state         protoimpl.MessageState `protogen:"open.v1"`
-	Entry         []*GeoSite             `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
+type IPRule struct {
+	state protoimpl.MessageState `protogen:"open.v1"`
+	// Types that are valid to be assigned to Value:
+	//
+	//	*IPRule_Geoip
+	//	*IPRule_Custom
+	Value         isIPRule_Value `protobuf_oneof:"value"`
 	unknownFields protoimpl.UnknownFields
 	unknownFields protoimpl.UnknownFields
 	sizeCache     protoimpl.SizeCache
 	sizeCache     protoimpl.SizeCache
 }
 }
 
 
-func (x *GeoSiteList) Reset() {
-	*x = GeoSiteList{}
-	mi := &file_common_geodata_geodat_proto_msgTypes[5]
+func (x *IPRule) Reset() {
+	*x = IPRule{}
+	mi := &file_common_geodata_geodat_proto_msgTypes[9]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 	ms.StoreMessageInfo(mi)
 }
 }
 
 
-func (x *GeoSiteList) String() string {
+func (x *IPRule) String() string {
 	return protoimpl.X.MessageStringOf(x)
 	return protoimpl.X.MessageStringOf(x)
 }
 }
 
 
-func (*GeoSiteList) ProtoMessage() {}
+func (*IPRule) ProtoMessage() {}
 
 
-func (x *GeoSiteList) ProtoReflect() protoreflect.Message {
-	mi := &file_common_geodata_geodat_proto_msgTypes[5]
+func (x *IPRule) ProtoReflect() protoreflect.Message {
+	mi := &file_common_geodata_geodat_proto_msgTypes[9]
 	if x != nil {
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 		if ms.LoadMessageInfo() == nil {
@@ -383,18 +633,52 @@ func (x *GeoSiteList) ProtoReflect() protoreflect.Message {
 	return mi.MessageOf(x)
 	return mi.MessageOf(x)
 }
 }
 
 
-// Deprecated: Use GeoSiteList.ProtoReflect.Descriptor instead.
-func (*GeoSiteList) Descriptor() ([]byte, []int) {
-	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{5}
+// Deprecated: Use IPRule.ProtoReflect.Descriptor instead.
+func (*IPRule) Descriptor() ([]byte, []int) {
+	return file_common_geodata_geodat_proto_rawDescGZIP(), []int{9}
 }
 }
 
 
-func (x *GeoSiteList) GetEntry() []*GeoSite {
+func (x *IPRule) GetValue() isIPRule_Value {
 	if x != nil {
 	if x != nil {
-		return x.Entry
+		return x.Value
+	}
+	return nil
+}
+
+func (x *IPRule) GetGeoip() *GeoIPRule {
+	if x != nil {
+		if x, ok := x.Value.(*IPRule_Geoip); ok {
+			return x.Geoip
+		}
+	}
+	return nil
+}
+
+func (x *IPRule) GetCustom() *CIDR {
+	if x != nil {
+		if x, ok := x.Value.(*IPRule_Custom); ok {
+			return x.Custom
+		}
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
+type isIPRule_Value interface {
+	isIPRule_Value()
+}
+
+type IPRule_Geoip struct {
+	Geoip *GeoIPRule `protobuf:"bytes,1,opt,name=geoip,proto3,oneof"`
+}
+
+type IPRule_Custom struct {
+	Custom *CIDR `protobuf:"bytes,2,opt,name=custom,proto3,oneof"`
+}
+
+func (*IPRule_Geoip) isIPRule_Value() {}
+
+func (*IPRule_Custom) isIPRule_Value() {}
+
 type Domain_Attribute struct {
 type Domain_Attribute struct {
 	state protoimpl.MessageState `protogen:"open.v1"`
 	state protoimpl.MessageState `protogen:"open.v1"`
 	Key   string                 `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
 	Key   string                 `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
@@ -409,7 +693,7 @@ type Domain_Attribute struct {
 
 
 func (x *Domain_Attribute) Reset() {
 func (x *Domain_Attribute) Reset() {
 	*x = Domain_Attribute{}
 	*x = Domain_Attribute{}
-	mi := &file_common_geodata_geodat_proto_msgTypes[6]
+	mi := &file_common_geodata_geodat_proto_msgTypes[10]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 	ms.StoreMessageInfo(mi)
 }
 }
@@ -421,7 +705,7 @@ func (x *Domain_Attribute) String() string {
 func (*Domain_Attribute) ProtoMessage() {}
 func (*Domain_Attribute) ProtoMessage() {}
 
 
 func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
 func (x *Domain_Attribute) ProtoReflect() protoreflect.Message {
-	mi := &file_common_geodata_geodat_proto_msgTypes[6]
+	mi := &file_common_geodata_geodat_proto_msgTypes[10]
 	if x != nil {
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
 		if ms.LoadMessageInfo() == nil {
@@ -506,7 +790,21 @@ const file_common_geodata_geodat_proto_rawDesc = "" +
 	"\x05Regex\x10\x01\x12\n" +
 	"\x05Regex\x10\x01\x12\n" +
 	"\n" +
 	"\n" +
 	"\x06Domain\x10\x02\x12\b\n" +
 	"\x06Domain\x10\x02\x12\b\n" +
-	"\x04Full\x10\x03\".\n" +
+	"\x04Full\x10\x03\"R\n" +
+	"\aGeoSite\x12\x12\n" +
+	"\x04code\x18\x01 \x01(\tR\x04code\x123\n" +
+	"\x06domain\x18\x02 \x03(\v2\x1b.xray.common.geodata.DomainR\x06domain\"A\n" +
+	"\vGeoSiteList\x122\n" +
+	"\x05entry\x18\x01 \x03(\v2\x1c.xray.common.geodata.GeoSiteR\x05entry\"K\n" +
+	"\vGeoSiteRule\x12\x12\n" +
+	"\x04file\x18\x01 \x01(\tR\x04file\x12\x12\n" +
+	"\x04code\x18\x02 \x01(\tR\x04code\x12\x14\n" +
+	"\x05attrs\x18\x03 \x01(\tR\x05attrs\"\x8a\x01\n" +
+	"\n" +
+	"DomainRule\x12<\n" +
+	"\ageosite\x18\x01 \x01(\v2 .xray.common.geodata.GeoSiteRuleH\x00R\ageosite\x125\n" +
+	"\x06custom\x18\x02 \x01(\v2\x1b.xray.common.geodata.DomainH\x00R\x06customB\a\n" +
+	"\x05value\".\n" +
 	"\x04CIDR\x12\x0e\n" +
 	"\x04CIDR\x12\x0e\n" +
 	"\x02ip\x18\x01 \x01(\fR\x02ip\x12\x16\n" +
 	"\x02ip\x18\x01 \x01(\fR\x02ip\x12\x16\n" +
 	"\x06prefix\x18\x02 \x01(\rR\x06prefix\"o\n" +
 	"\x06prefix\x18\x02 \x01(\rR\x06prefix\"o\n" +
@@ -515,12 +813,15 @@ const file_common_geodata_geodat_proto_rawDesc = "" +
 	"\x04cidr\x18\x02 \x03(\v2\x19.xray.common.geodata.CIDRR\x04cidr\x12#\n" +
 	"\x04cidr\x18\x02 \x03(\v2\x19.xray.common.geodata.CIDRR\x04cidr\x12#\n" +
 	"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"=\n" +
 	"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"=\n" +
 	"\tGeoIPList\x120\n" +
 	"\tGeoIPList\x120\n" +
-	"\x05entry\x18\x01 \x03(\v2\x1a.xray.common.geodata.GeoIPR\x05entry\"R\n" +
-	"\aGeoSite\x12\x12\n" +
-	"\x04code\x18\x01 \x01(\tR\x04code\x123\n" +
-	"\x06domain\x18\x02 \x03(\v2\x1b.xray.common.geodata.DomainR\x06domain\"A\n" +
-	"\vGeoSiteList\x122\n" +
-	"\x05entry\x18\x01 \x03(\v2\x1c.xray.common.geodata.GeoSiteR\x05entryB[\n" +
+	"\x05entry\x18\x01 \x03(\v2\x1a.xray.common.geodata.GeoIPR\x05entry\"X\n" +
+	"\tGeoIPRule\x12\x12\n" +
+	"\x04file\x18\x01 \x01(\tR\x04file\x12\x12\n" +
+	"\x04code\x18\x02 \x01(\tR\x04code\x12#\n" +
+	"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"~\n" +
+	"\x06IPRule\x126\n" +
+	"\x05geoip\x18\x01 \x01(\v2\x1e.xray.common.geodata.GeoIPRuleH\x00R\x05geoip\x123\n" +
+	"\x06custom\x18\x02 \x01(\v2\x19.xray.common.geodata.CIDRH\x00R\x06customB\a\n" +
+	"\x05valueB[\n" +
 	"\x17com.xray.common.geodataP\x01Z(github.com/xtls/xray-core/common/geodata\xaa\x02\x13Xray.Common.Geodatab\x06proto3"
 	"\x17com.xray.common.geodataP\x01Z(github.com/xtls/xray-core/common/geodata\xaa\x02\x13Xray.Common.Geodatab\x06proto3"
 
 
 var (
 var (
@@ -536,29 +837,37 @@ func file_common_geodata_geodat_proto_rawDescGZIP() []byte {
 }
 }
 
 
 var file_common_geodata_geodat_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
 var file_common_geodata_geodat_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_common_geodata_geodat_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
+var file_common_geodata_geodat_proto_msgTypes = make([]protoimpl.MessageInfo, 11)
 var file_common_geodata_geodat_proto_goTypes = []any{
 var file_common_geodata_geodat_proto_goTypes = []any{
 	(Domain_Type)(0),         // 0: xray.common.geodata.Domain.Type
 	(Domain_Type)(0),         // 0: xray.common.geodata.Domain.Type
 	(*Domain)(nil),           // 1: xray.common.geodata.Domain
 	(*Domain)(nil),           // 1: xray.common.geodata.Domain
-	(*CIDR)(nil),             // 2: xray.common.geodata.CIDR
-	(*GeoIP)(nil),            // 3: xray.common.geodata.GeoIP
-	(*GeoIPList)(nil),        // 4: xray.common.geodata.GeoIPList
-	(*GeoSite)(nil),          // 5: xray.common.geodata.GeoSite
-	(*GeoSiteList)(nil),      // 6: xray.common.geodata.GeoSiteList
-	(*Domain_Attribute)(nil), // 7: xray.common.geodata.Domain.Attribute
+	(*GeoSite)(nil),          // 2: xray.common.geodata.GeoSite
+	(*GeoSiteList)(nil),      // 3: xray.common.geodata.GeoSiteList
+	(*GeoSiteRule)(nil),      // 4: xray.common.geodata.GeoSiteRule
+	(*DomainRule)(nil),       // 5: xray.common.geodata.DomainRule
+	(*CIDR)(nil),             // 6: xray.common.geodata.CIDR
+	(*GeoIP)(nil),            // 7: xray.common.geodata.GeoIP
+	(*GeoIPList)(nil),        // 8: xray.common.geodata.GeoIPList
+	(*GeoIPRule)(nil),        // 9: xray.common.geodata.GeoIPRule
+	(*IPRule)(nil),           // 10: xray.common.geodata.IPRule
+	(*Domain_Attribute)(nil), // 11: xray.common.geodata.Domain.Attribute
 }
 }
 var file_common_geodata_geodat_proto_depIdxs = []int32{
 var file_common_geodata_geodat_proto_depIdxs = []int32{
-	0, // 0: xray.common.geodata.Domain.type:type_name -> xray.common.geodata.Domain.Type
-	7, // 1: xray.common.geodata.Domain.attribute:type_name -> xray.common.geodata.Domain.Attribute
-	2, // 2: xray.common.geodata.GeoIP.cidr:type_name -> xray.common.geodata.CIDR
-	3, // 3: xray.common.geodata.GeoIPList.entry:type_name -> xray.common.geodata.GeoIP
-	1, // 4: xray.common.geodata.GeoSite.domain:type_name -> xray.common.geodata.Domain
-	5, // 5: xray.common.geodata.GeoSiteList.entry:type_name -> xray.common.geodata.GeoSite
-	6, // [6:6] is the sub-list for method output_type
-	6, // [6:6] is the sub-list for method input_type
-	6, // [6:6] is the sub-list for extension type_name
-	6, // [6:6] is the sub-list for extension extendee
-	0, // [0:6] is the sub-list for field type_name
+	0,  // 0: xray.common.geodata.Domain.type:type_name -> xray.common.geodata.Domain.Type
+	11, // 1: xray.common.geodata.Domain.attribute:type_name -> xray.common.geodata.Domain.Attribute
+	1,  // 2: xray.common.geodata.GeoSite.domain:type_name -> xray.common.geodata.Domain
+	2,  // 3: xray.common.geodata.GeoSiteList.entry:type_name -> xray.common.geodata.GeoSite
+	4,  // 4: xray.common.geodata.DomainRule.geosite:type_name -> xray.common.geodata.GeoSiteRule
+	1,  // 5: xray.common.geodata.DomainRule.custom:type_name -> xray.common.geodata.Domain
+	6,  // 6: xray.common.geodata.GeoIP.cidr:type_name -> xray.common.geodata.CIDR
+	7,  // 7: xray.common.geodata.GeoIPList.entry:type_name -> xray.common.geodata.GeoIP
+	9,  // 8: xray.common.geodata.IPRule.geoip:type_name -> xray.common.geodata.GeoIPRule
+	6,  // 9: xray.common.geodata.IPRule.custom:type_name -> xray.common.geodata.CIDR
+	10, // [10:10] is the sub-list for method output_type
+	10, // [10:10] is the sub-list for method input_type
+	10, // [10:10] is the sub-list for extension type_name
+	10, // [10:10] is the sub-list for extension extendee
+	0,  // [0:10] is the sub-list for field type_name
 }
 }
 
 
 func init() { file_common_geodata_geodat_proto_init() }
 func init() { file_common_geodata_geodat_proto_init() }
@@ -566,7 +875,15 @@ func file_common_geodata_geodat_proto_init() {
 	if File_common_geodata_geodat_proto != nil {
 	if File_common_geodata_geodat_proto != nil {
 		return
 		return
 	}
 	}
-	file_common_geodata_geodat_proto_msgTypes[6].OneofWrappers = []any{
+	file_common_geodata_geodat_proto_msgTypes[4].OneofWrappers = []any{
+		(*DomainRule_Geosite)(nil),
+		(*DomainRule_Custom)(nil),
+	}
+	file_common_geodata_geodat_proto_msgTypes[9].OneofWrappers = []any{
+		(*IPRule_Geoip)(nil),
+		(*IPRule_Custom)(nil),
+	}
+	file_common_geodata_geodat_proto_msgTypes[10].OneofWrappers = []any{
 		(*Domain_Attribute_BoolValue)(nil),
 		(*Domain_Attribute_BoolValue)(nil),
 		(*Domain_Attribute_IntValue)(nil),
 		(*Domain_Attribute_IntValue)(nil),
 	}
 	}
@@ -576,7 +893,7 @@ func file_common_geodata_geodat_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_geodata_geodat_proto_rawDesc), len(file_common_geodata_geodat_proto_rawDesc)),
 			RawDescriptor: unsafe.Slice(unsafe.StringData(file_common_geodata_geodat_proto_rawDesc), len(file_common_geodata_geodat_proto_rawDesc)),
 			NumEnums:      1,
 			NumEnums:      1,
-			NumMessages:   7,
+			NumMessages:   11,
 			NumExtensions: 0,
 			NumExtensions: 0,
 			NumServices:   0,
 			NumServices:   0,
 		},
 		},

+ 31 - 5
common/geodata/geodat.proto

@@ -36,6 +36,28 @@ message Domain {
   repeated Attribute attribute = 3;
   repeated Attribute attribute = 3;
 }
 }
 
 
+message GeoSite {
+  string code = 1;
+  repeated Domain domain = 2;
+}
+
+message GeoSiteList {
+  repeated GeoSite entry = 1;
+}
+
+message GeoSiteRule {
+  string file = 1;
+  string code = 2;
+  string attrs = 3;
+}
+
+message DomainRule {
+  oneof value {
+    GeoSiteRule geosite = 1;
+    Domain custom = 2;
+  }
+}
+
 message CIDR {
 message CIDR {
   // IP address, should be either 4 or 16 bytes.
   // IP address, should be either 4 or 16 bytes.
   bytes ip = 1;
   bytes ip = 1;
@@ -54,11 +76,15 @@ message GeoIPList {
   repeated GeoIP entry = 1;
   repeated GeoIP entry = 1;
 }
 }
 
 
-message GeoSite {
-  string code = 1;
-  repeated Domain domain = 2;
+message GeoIPRule {
+  string file = 1;
+  string code = 2;
+  bool reverse_match = 3;
 }
 }
 
 
-message GeoSiteList {
-  repeated GeoSite entry = 1;
+message IPRule {
+  oneof value {
+    GeoIPRule geoip = 1;
+    CIDR custom = 2;
+  }
 }
 }

+ 207 - 0
common/geodata/geodat_loader.go

@@ -0,0 +1,207 @@
+package geodata
+
+import (
+	"bufio"
+	"bytes"
+	"io"
+	"runtime"
+	"strings"
+
+	"github.com/xtls/xray-core/common/errors"
+	"github.com/xtls/xray-core/common/platform/filesystem"
+
+	"google.golang.org/protobuf/proto"
+)
+
+func checkFile(file, code string) error {
+	r, err := filesystem.OpenAsset(file)
+	if err != nil {
+		return errors.New("failed to open ", file).Base(err)
+	}
+	defer r.Close()
+	if _, err := find(r, []byte(code), false); err != nil {
+		return errors.New("failed to check code ", code, " from ", file).Base(err)
+	}
+	return nil
+}
+
+func loadFile(file, code string) ([]byte, error) {
+	runtime.GC() // peak mem
+	r, err := filesystem.OpenAsset(file)
+	if err != nil {
+		return nil, errors.New("failed to open ", file).Base(err)
+	}
+	defer r.Close()
+	bs, err := find(r, []byte(code), true)
+	if err != nil {
+		return nil, errors.New("failed to load code ", code, " from ", file).Base(err)
+	}
+	return bs, nil
+}
+
+func loadIP(file, code string) ([]*CIDR, error) {
+	bs, err := loadFile(file, code)
+	if err != nil {
+		return nil, err
+	}
+	defer runtime.GC() // peak mem
+	var geoip GeoIP
+	if err := proto.Unmarshal(bs, &geoip); err != nil {
+		return nil, errors.New("error unmarshal IP in ", file, ":", code).Base(err)
+	}
+	return geoip.Cidr, nil
+}
+
+func loadSite(file, code string) ([]*Domain, error) {
+	bs, err := loadFile(file, code)
+	if err != nil {
+		return nil, err
+	}
+	defer runtime.GC() // peak mem
+	var geosite GeoSite
+	if err := proto.Unmarshal(bs, &geosite); err != nil {
+		return nil, errors.New("error unmarshal Site in ", file, ":", code).Base(err)
+	}
+	return geosite.Domain, nil
+}
+
+func decodeVarint(br *bufio.Reader) (uint64, error) {
+	var x uint64
+	for shift := uint(0); shift < 64; shift += 7 {
+		b, err := br.ReadByte()
+		if err != nil {
+			return 0, err
+		}
+		x |= (uint64(b) & 0x7F) << shift
+		if (b & 0x80) == 0 {
+			return x, nil
+		}
+	}
+	// The number is too large to represent in a 64-bit value.
+	return 0, errors.New("varint overflow")
+}
+
+func find(r io.Reader, code []byte, readBody bool) ([]byte, error) {
+	codeL := len(code)
+	if codeL == 0 {
+		return nil, errors.New("empty code")
+	}
+
+	br := bufio.NewReaderSize(r, 64*1024)
+	need := 2 + codeL // TODO: if code too long
+	prefixBuf := make([]byte, need)
+
+	for {
+		if _, err := br.ReadByte(); err != nil {
+			return nil, err
+		}
+
+		x, err := decodeVarint(br)
+		if err != nil {
+			return nil, err
+		}
+		bodyL := int(x)
+		if bodyL <= 0 {
+			return nil, errors.New("invalid body length: ", bodyL)
+		}
+
+		prefixL := bodyL
+		if prefixL > need {
+			prefixL = need
+		}
+		prefix := prefixBuf[:prefixL]
+		if _, err := io.ReadFull(br, prefix); err != nil {
+			return nil, err
+		}
+
+		match := false
+		if bodyL >= need {
+			if int(prefix[1]) == codeL && bytes.Equal(prefix[2:need], code) {
+				if !readBody {
+					return nil, nil
+				}
+				match = true
+			}
+		}
+
+		remain := bodyL - prefixL
+		if match {
+			out := make([]byte, bodyL)
+			copy(out, prefix)
+			if remain > 0 {
+				if _, err := io.ReadFull(br, out[prefixL:]); err != nil {
+					return nil, err
+				}
+			}
+			return out, nil
+		}
+
+		if remain > 0 {
+			if _, err := br.Discard(remain); err != nil {
+				return nil, err
+			}
+		}
+	}
+}
+
+type AttributeMatcher interface {
+	Match(*Domain) bool
+}
+
+type HasAttrMatcher string
+
+// Match reports whether this matcher matches any attribute on the domain.
+func (m HasAttrMatcher) Match(domain *Domain) bool {
+	for _, attr := range domain.Attribute {
+		if attr.Key == string(m) {
+			return true
+		}
+	}
+	return false
+}
+
+type AllAttrsMatcher struct {
+	matchers []AttributeMatcher
+}
+
+// Match reports whether the domain matches every matcher in the list.
+func (m *AllAttrsMatcher) Match(domain *Domain) bool {
+	for _, matcher := range m.matchers {
+		if !matcher.Match(domain) {
+			return false
+		}
+	}
+	return true
+}
+
+func NewAllAttrsMatcher(attrs string) AttributeMatcher {
+	if attrs == "" {
+		return nil
+	}
+	m := new(AllAttrsMatcher)
+	for _, attr := range strings.Split(attrs, "@") {
+		m.matchers = append(m.matchers, HasAttrMatcher(attr))
+	}
+	return m
+}
+
+func loadSiteWithAttrs(file, code, attrs string) ([]*Domain, error) {
+	domains, err := loadSite(file, code)
+	if err != nil {
+		return nil, err
+	}
+
+	matcher := NewAllAttrsMatcher(attrs)
+	if matcher == nil {
+		return domains, nil
+	}
+
+	filtered := make([]*Domain, 0, len(domains))
+	for _, d := range domains {
+		if matcher.Match(d) {
+			filtered = append(filtered, d)
+		}
+	}
+
+	return filtered, nil
+}

+ 111 - 80
common/geodata/ip_matcher.go

@@ -3,6 +3,7 @@ package geodata
 import (
 import (
 	"context"
 	"context"
 	"net/netip"
 	"net/netip"
+	"slices"
 	"sort"
 	"sort"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
@@ -59,9 +60,15 @@ func (m *HeuristicGeoIPMatcher) Match(ip net.IP) bool {
 
 
 func (m *HeuristicGeoIPMatcher) matchAddr(ipx netip.Addr) bool {
 func (m *HeuristicGeoIPMatcher) matchAddr(ipx netip.Addr) bool {
 	if ipx.Is4() {
 	if ipx.Is4() {
+		if m.ipset.max4 == 0xff {
+			return false
+		}
 		return m.ipset.ipv4.Contains(ipx) != m.reverse
 		return m.ipset.ipv4.Contains(ipx) != m.reverse
 	}
 	}
 	if ipx.Is6() {
 	if ipx.Is6() {
+		if m.ipset.max6 == 0xff {
+			return false
+		}
 		return m.ipset.ipv6.Contains(ipx) != m.reverse
 		return m.ipset.ipv6.Contains(ipx) != m.reverse
 	}
 	}
 	return false
 	return false
@@ -801,7 +808,9 @@ type GeoIPSetFactory struct {
 	shared map[string]*GeoIPSet // TODO: cleanup
 	shared map[string]*GeoIPSet // TODO: cleanup
 }
 }
 
 
-func (f *GeoIPSetFactory) GetOrCreate(key string, cidrGroups [][]*CIDR) (*GeoIPSet, error) {
+func (f *GeoIPSetFactory) GetOrCreateFromGeoIPRules(rules []*GeoIPRule) (*GeoIPSet, error) {
+	key := buildGeoIPRulesKey(rules)
+
 	f.Lock()
 	f.Lock()
 	defer f.Unlock()
 	defer f.Unlock()
 
 
@@ -809,40 +818,87 @@ func (f *GeoIPSetFactory) GetOrCreate(key string, cidrGroups [][]*CIDR) (*GeoIPS
 		return ipset, nil
 		return ipset, nil
 	}
 	}
 
 
-	ipset, err := f.Create(cidrGroups...)
+	ipset, err := f.createFrom(func(add func(*CIDR)) error {
+		for _, r := range rules {
+			cidrs, err := loadIP(r.File, r.Code)
+			if err != nil {
+				return err
+			}
+			for i, c := range cidrs {
+				add(c)
+				cidrs[i] = nil // peak mem
+			}
+		}
+		return nil
+	})
 	if err == nil {
 	if err == nil {
 		f.shared[key] = ipset
 		f.shared[key] = ipset
 	}
 	}
 	return ipset, err
 	return ipset, err
 }
 }
 
 
-func (f *GeoIPSetFactory) Create(cidrGroups ...[]*CIDR) (*GeoIPSet, error) {
+func buildGeoIPRulesKey(rules []*GeoIPRule) string {
+	rules = slices.Clone(rules)
+
+	sort.Slice(rules, func(i, j int) bool {
+		ri, rj := rules[i], rules[j]
+		if ri.File != rj.File {
+			return ri.File < rj.File
+		}
+		return ri.Code < rj.Code
+	})
+
+	var sb strings.Builder
+	sb.Grow(len(rules) * 20) // geoip.dat:xx,
+	var last *GeoIPRule
+	for i, r := range rules {
+		if i == 0 || (r.File != last.File || r.Code != last.Code) {
+			last = r
+			sb.WriteString(r.File)
+			sb.WriteString(":")
+			sb.WriteString(r.Code)
+			sb.WriteString(",")
+		}
+	}
+	return sb.String()
+}
+
+func (f *GeoIPSetFactory) CreateFromCIDRs(cidrs []*CIDR) (*GeoIPSet, error) {
+	return f.createFrom(func(add func(*CIDR)) error {
+		for _, c := range cidrs {
+			add(c)
+		}
+		return nil
+	})
+}
+
+func (f *GeoIPSetFactory) createFrom(yield func(func(*CIDR)) error) (*GeoIPSet, error) {
 	var ipv4Builder, ipv6Builder netipx.IPSetBuilder
 	var ipv4Builder, ipv6Builder netipx.IPSetBuilder
 
 
-	for _, cidrGroup := range cidrGroups {
-		for i, cidrEntry := range cidrGroup {
-			cidrGroup[i] = nil
-			ipBytes := cidrEntry.GetIp()
-			prefixLen := int(cidrEntry.GetPrefix())
+	err := yield(func(c *CIDR) {
+		ipBytes := c.GetIp()
+		prefixLen := int(c.GetPrefix())
 
 
-			addr, ok := netip.AddrFromSlice(ipBytes)
-			if !ok {
-				errors.LogError(context.Background(), "ignore invalid IP byte slice: ", ipBytes)
-				continue
-			}
+		addr, ok := netip.AddrFromSlice(ipBytes)
+		if !ok {
+			errors.LogError(context.Background(), "ignore invalid IP byte slice: ", ipBytes)
+			return
+		}
 
 
-			prefix := netip.PrefixFrom(addr, prefixLen)
-			if !prefix.IsValid() {
-				errors.LogError(context.Background(), "ignore created invalid prefix from addr ", addr, " and length ", prefixLen)
-				continue
-			}
+		prefix := netip.PrefixFrom(addr, prefixLen)
+		if !prefix.IsValid() {
+			errors.LogError(context.Background(), "ignore created invalid prefix from addr ", addr, " and length ", prefixLen)
+			return
+		}
 
 
-			if addr.Is4() {
-				ipv4Builder.AddPrefix(prefix)
-			} else if addr.Is6() {
-				ipv6Builder.AddPrefix(prefix)
-			}
+		if addr.Is4() {
+			ipv4Builder.AddPrefix(prefix)
+		} else if addr.Is6() {
+			ipv6Builder.AddPrefix(prefix)
 		}
 		}
+	})
+	if err != nil {
+		return nil, err
 	}
 	}
 
 
 	ipv4, err := ipv4Builder.IPSet()
 	ipv4, err := ipv4Builder.IPSet()
@@ -877,75 +933,50 @@ func (f *GeoIPSetFactory) Create(cidrGroups ...[]*CIDR) (*GeoIPSet, error) {
 	return &GeoIPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil
 	return &GeoIPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil
 }
 }
 
 
-func (r *GeoIPRegistry) buildOptimizedGeoIPMatcher(geoips ...*GeoIP) (GeoIPMatcher, error) {
-	n := len(geoips)
-	if n == 0 {
-		return nil, errors.New("no geoip configs provided")
-	}
-
-	var subs []*HeuristicGeoIPMatcher
-	pos := make([]*GeoIP, 0, n)
-	neg := make([]*GeoIP, 0, n/2)
+func buildOptimizedGeoIPMatcher(f *GeoIPSetFactory, rules []*IPRule) (GeoIPMatcher, error) {
+	n := len(rules)
+	custom := make([]*CIDR, 0, n)
+	pos := make([]*GeoIPRule, 0, n)
+	neg := make([]*GeoIPRule, 0, n)
 
 
-	for _, geoip := range geoips {
-		if geoip == nil {
-			return nil, errors.New("geoip entry is nil")
-		}
-		if geoip.Code == "" {
-			ipset, err := r.ipsetFactory.Create(geoip.Cidr)
-			if err != nil {
-				return nil, err
+	for _, r := range rules {
+		switch v := r.Value.(type) {
+		case *IPRule_Custom:
+			custom = append(custom, v.Custom)
+		case *IPRule_Geoip:
+			if !v.Geoip.ReverseMatch {
+				pos = append(pos, v.Geoip)
+			} else {
+				neg = append(neg, v.Geoip)
 			}
 			}
-			subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: geoip.ReverseMatch})
-			continue
-		}
-		if !geoip.ReverseMatch {
-			pos = append(pos, geoip)
-		} else {
-			neg = append(neg, geoip)
+		default:
+			panic("unknown ip rule type")
 		}
 		}
 	}
 	}
 
 
-	buildIPSet := func(mergeables []*GeoIP) (*GeoIPSet, error) {
-		n := len(mergeables)
-		if n == 0 {
-			return nil, nil
-		}
-
-		sort.Slice(mergeables, func(i, j int) bool {
-			gi, gj := mergeables[i], mergeables[j]
-			return gi.Code < gj.Code
-		})
+	subs := make([]*HeuristicGeoIPMatcher, 0, 3)
 
 
-		var sb strings.Builder
-		sb.Grow(n * 3) // xx,
-		cidrGroups := make([][]*CIDR, 0, n)
-		var last *GeoIP
-		for i, geoip := range mergeables {
-			if i == 0 || (geoip.Code != last.Code) {
-				last = geoip
-				sb.WriteString(geoip.Code)
-				sb.WriteString(",")
-				cidrGroups = append(cidrGroups, geoip.Cidr)
-			}
+	if len(custom) > 0 {
+		ipset, err := f.CreateFromCIDRs(custom)
+		if err != nil {
+			return nil, err
 		}
 		}
-
-		return r.ipsetFactory.GetOrCreate(sb.String(), cidrGroups)
+		subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: false})
 	}
 	}
 
 
-	ipset, err := buildIPSet(pos)
-	if err != nil {
-		return nil, err
-	}
-	if ipset != nil {
+	if len(pos) > 0 {
+		ipset, err := f.GetOrCreateFromGeoIPRules(pos)
+		if err != nil {
+			return nil, err
+		}
 		subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: false})
 		subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: false})
 	}
 	}
 
 
-	ipset, err = buildIPSet(neg)
-	if err != nil {
-		return nil, err
-	}
-	if ipset != nil {
+	if len(neg) > 0 {
+		ipset, err := f.GetOrCreateFromGeoIPRules(neg)
+		if err != nil {
+			return nil, err
+		}
 		subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: true})
 		subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: true})
 	}
 	}
 
 

+ 6 - 2
common/geodata/ip_registry.go

@@ -4,10 +4,14 @@ type GeoIPRegistry struct {
 	ipsetFactory *GeoIPSetFactory
 	ipsetFactory *GeoIPSetFactory
 }
 }
 
 
-func NewGeoIPRegistry() *GeoIPRegistry {
+func (r *GeoIPRegistry) BuildGeoIPMatcher(rules []*IPRule) (GeoIPMatcher, error) {
+	return buildOptimizedGeoIPMatcher(r.ipsetFactory, rules)
+}
+
+func newGeoIPRegistry() *GeoIPRegistry {
 	return &GeoIPRegistry{
 	return &GeoIPRegistry{
 		ipsetFactory: &GeoIPSetFactory{shared: make(map[string]*GeoIPSet)},
 		ipsetFactory: &GeoIPSetFactory{shared: make(map[string]*GeoIPSet)},
 	}
 	}
 }
 }
 
 
-var IPRegistry = NewGeoIPRegistry()
+var IPRegistry = newGeoIPRegistry()

+ 228 - 0
common/geodata/rule_parser.go

@@ -0,0 +1,228 @@
+package geodata
+
+import (
+	"strconv"
+	"strings"
+
+	"github.com/xtls/xray-core/common/errors"
+	"github.com/xtls/xray-core/common/net"
+)
+
+const (
+	DefaultGeoIPDat   = "geoip.dat"
+	DefaultGeoSiteDat = "geosite.dat"
+)
+
+func ParseIPRules(rules []string) ([]*IPRule, error) {
+	var ipRules []*IPRule
+
+	for i, r := range rules {
+		if strings.HasPrefix(r, "geoip:") {
+			r = "ext:" + DefaultGeoIPDat + ":" + r[len("geoip:"):]
+		}
+
+		prefix := 0
+		for _, ext := range [...]string{"ext:", "ext-ip:"} {
+			if strings.HasPrefix(r, ext) {
+				prefix = len(ext)
+				break
+			}
+		}
+
+		var rule isIPRule_Value
+		var err error
+		if prefix > 0 {
+			rule, err = parseGeoIPRule(r[prefix:])
+		} else {
+			rule, err = parseCustomIPRule(r)
+		}
+		if err != nil {
+			return nil, errors.New("illegal ip rule: ", rules[i]).Base(err)
+		}
+		ipRules = append(ipRules, &IPRule{Value: rule})
+	}
+
+	return ipRules, nil
+}
+
+func parseGeoIPRule(rule string) (*IPRule_Geoip, error) {
+	file, code, ok := strings.Cut(rule, ":")
+	if !ok {
+		return nil, errors.New("syntax error")
+	}
+
+	if file == "" {
+		return nil, errors.New("empty file")
+	}
+
+	reverse := false
+	if strings.HasPrefix(code, "!") {
+		code = code[1:]
+		reverse = true
+	}
+	if code == "" {
+		return nil, errors.New("empty code")
+	}
+	code = strings.ToUpper(code)
+
+	if err := checkFile(file, code); err != nil {
+		return nil, err
+	}
+
+	return &IPRule_Geoip{
+		Geoip: &GeoIPRule{
+			File:         file,
+			Code:         code,
+			ReverseMatch: reverse,
+		},
+	}, nil
+}
+
+func parseCustomIPRule(rule string) (*IPRule_Custom, error) {
+	cidr, err := parseCIDR(rule)
+	if err != nil {
+		return nil, err
+	}
+	return &IPRule_Custom{
+		Custom: cidr,
+	}, nil
+}
+
+func parseCIDR(s string) (*CIDR, error) {
+	ipStr, prefixStr, _ := strings.Cut(s, "/")
+
+	ipAddr := net.ParseAddress(ipStr)
+
+	var maxPrefix uint32
+	switch ipAddr.Family() {
+	case net.AddressFamilyIPv4:
+		maxPrefix = 32
+	case net.AddressFamilyIPv6:
+		maxPrefix = 128
+	default:
+		return nil, errors.New("unsupported address family")
+	}
+
+	prefixBits := maxPrefix
+	if prefixStr != "" {
+		parsedPrefix, err := strconv.ParseUint(prefixStr, 10, 32)
+		if err != nil {
+			return nil, errors.New("invalid CIDR prefix length: ", prefixStr).Base(err)
+		}
+		prefixBits = uint32(parsedPrefix)
+	}
+	if prefixBits > maxPrefix {
+		return nil, errors.New("CIDR prefix length ", prefixBits, " exceeds max ", maxPrefix)
+	}
+
+	return &CIDR{
+		Ip:     []byte(ipAddr.IP()),
+		Prefix: prefixBits,
+	}, nil
+}
+
+func ParseDomainRules(rules []string, defaultType Domain_Type) ([]*DomainRule, error) {
+	var domainRules []*DomainRule
+
+	for i, r := range rules {
+		if strings.HasPrefix(r, "geosite:") {
+			r = "ext:" + DefaultGeoSiteDat + ":" + r[len("geosite:"):]
+		}
+
+		prefix := 0
+		for _, ext := range [...]string{"ext:", "ext-domain:"} {
+			if strings.HasPrefix(r, ext) {
+				prefix = len(ext)
+				break
+			}
+		}
+
+		var rule isDomainRule_Value
+		var err error
+		if prefix > 0 {
+			rule, err = parseGeoSiteRule(r[prefix:])
+		} else {
+			rule, err = parseCustomDomainRule(r, defaultType)
+		}
+		if err != nil {
+			return nil, errors.New("illegal domain rule: ", rules[i]).Base(err)
+		}
+		domainRules = append(domainRules, &DomainRule{Value: rule})
+	}
+
+	return domainRules, nil
+}
+
+func parseGeoSiteRule(rule string) (*DomainRule_Geosite, error) {
+	file, codeWithAttrs, ok := strings.Cut(rule, ":")
+	if !ok {
+		return nil, errors.New("syntax error")
+	}
+
+	if file == "" {
+		return nil, errors.New("empty file")
+	}
+
+	if strings.HasSuffix(codeWithAttrs, "@") || strings.Contains(codeWithAttrs, "@@") {
+		return nil, errors.New("empty attr")
+	}
+	code, attrs, _ := strings.Cut(codeWithAttrs, "@")
+
+	if code == "" {
+		return nil, errors.New("empty code")
+	}
+	code = strings.ToUpper(code)
+
+	if err := checkFile(file, code); err != nil {
+		return nil, err
+	}
+
+	return &DomainRule_Geosite{
+		Geosite: &GeoSiteRule{
+			File:  file,
+			Code:  code,
+			Attrs: strings.ToLower(attrs),
+		},
+	}, nil
+}
+
+func parseCustomDomainRule(rule string, defaultType Domain_Type) (*DomainRule_Custom, error) {
+	domain := new(Domain)
+
+	switch {
+	case strings.HasPrefix(rule, "regexp:"):
+		domain.Type = Domain_Regex
+		domain.Value = rule[7:]
+
+	case strings.HasPrefix(rule, "domain:"):
+		domain.Type = Domain_Domain
+		domain.Value = rule[7:]
+
+	case strings.HasPrefix(rule, "full:"):
+		domain.Type = Domain_Full
+		domain.Value = rule[5:]
+
+	case strings.HasPrefix(rule, "keyword:"):
+		domain.Type = Domain_Substr
+		domain.Value = rule[8:]
+
+	case strings.HasPrefix(rule, "dotless:"):
+		domain.Type = Domain_Regex
+		switch substr := rule[8:]; {
+		case substr == "":
+			domain.Value = "^[^.]*$"
+		case !strings.Contains(substr, "."):
+			domain.Value = "^[^.]*" + substr + "[^.]*$"
+		default:
+			return nil, errors.New("substr in dotless rule should not contain a dot")
+		}
+
+	default:
+		domain.Type = defaultType
+		domain.Value = rule
+	}
+
+	return &DomainRule_Custom{
+		Custom: domain,
+	}, nil
+}

+ 0 - 393
infra/conf/router.go

@@ -1,18 +1,11 @@
 package conf
 package conf
 
 
 import (
 import (
-	"bufio"
-	"bytes"
 	"encoding/json"
 	"encoding/json"
-	"io"
-	"runtime"
-	"strconv"
 	"strings"
 	"strings"
 
 
 	"github.com/xtls/xray-core/app/router"
 	"github.com/xtls/xray-core/app/router"
 	"github.com/xtls/xray-core/common/errors"
 	"github.com/xtls/xray-core/common/errors"
-	"github.com/xtls/xray-core/common/net"
-	"github.com/xtls/xray-core/common/platform/filesystem"
 	"github.com/xtls/xray-core/common/serial"
 	"github.com/xtls/xray-core/common/serial"
 	"google.golang.org/protobuf/proto"
 	"google.golang.org/protobuf/proto"
 )
 )
@@ -128,392 +121,6 @@ type RouterRule struct {
 	BalancerTag string `json:"balancerTag"`
 	BalancerTag string `json:"balancerTag"`
 }
 }
 
 
-func parseIP(s string) (*router.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, errors.New("invalid network mask for router: ", mask).Base(err)
-			}
-			bits = uint32(bits64)
-		}
-		if bits > 32 {
-			return nil, errors.New("invalid network mask for router: ", bits)
-		}
-		return &router.CIDR{
-			Ip:     []byte(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, errors.New("invalid network mask for router: ", mask).Base(err)
-			}
-			bits = uint32(bits64)
-		}
-		if bits > 128 {
-			return nil, errors.New("invalid network mask for router: ", bits)
-		}
-		return &router.CIDR{
-			Ip:     []byte(ip.IP()),
-			Prefix: bits,
-		}, nil
-	default:
-		return nil, errors.New("unsupported address for router: ", s)
-	}
-}
-
-func loadFile(file, code string) ([]byte, error) {
-	runtime.GC()
-	r, err := filesystem.OpenAsset(file)
-	defer r.Close()
-	if err != nil {
-		return nil, errors.New("failed to open file: ", file).Base(err)
-	}
-	bs := find(r, []byte(code))
-	if bs == nil {
-		return nil, errors.New("code not found in ", file, ": ", code)
-	}
-	return bs, nil
-}
-
-func loadIP(file, code string) ([]*router.CIDR, error) {
-	bs, err := loadFile(file, code)
-	if err != nil {
-		return nil, err
-	}
-	var geoip router.GeoIP
-	if err := proto.Unmarshal(bs, &geoip); err != nil {
-		return nil, errors.New("error unmarshal IP in ", file, ": ", code).Base(err)
-	}
-	defer runtime.GC() // or debug.FreeOSMemory()
-	return geoip.Cidr, nil
-}
-
-func loadSite(file, code string) ([]*router.Domain, error) {
-	bs, err := loadFile(file, code)
-	if err != nil {
-		return nil, err
-	}
-	var geosite router.GeoSite
-	if err := proto.Unmarshal(bs, &geosite); err != nil {
-		return nil, errors.New("error unmarshal Site in ", file, ": ", code).Base(err)
-	}
-	defer runtime.GC() // or debug.FreeOSMemory()
-	return geosite.Domain, nil
-}
-
-func decodeVarint(r *bufio.Reader) (uint64, error) {
-	var x uint64
-	for shift := uint(0); shift < 64; shift += 7 {
-		b, err := r.ReadByte()
-		if err != nil {
-			return 0, err
-		}
-		x |= (uint64(b) & 0x7F) << shift
-		if (b & 0x80) == 0 {
-			return x, nil
-		}
-	}
-	// The number is too large to represent in a 64-bit value.
-	return 0, errors.New("varint overflow")
-}
-
-func find(r io.Reader, code []byte) []byte {
-	codeL := len(code)
-	if codeL == 0 {
-		return nil
-	}
-
-	br := bufio.NewReaderSize(r, 64*1024)
-	need := 2 + codeL
-	prefixBuf := make([]byte, need)
-
-	for {
-		if _, err := br.ReadByte(); err != nil {
-			return nil
-		}
-
-		x, err := decodeVarint(br)
-		if err != nil {
-			return nil
-		}
-		bodyL := int(x)
-		if bodyL <= 0 {
-			return nil
-		}
-
-		prefixL := bodyL
-		if prefixL > need {
-			prefixL = need
-		}
-		prefix := prefixBuf[:prefixL]
-		if _, err := io.ReadFull(br, prefix); err != nil {
-			return nil
-		}
-
-		match := false
-		if bodyL >= need {
-			if int(prefix[1]) == codeL && bytes.Equal(prefix[2:need], code) {
-				match = true
-			}
-		}
-
-		remain := bodyL - prefixL
-		if match {
-			out := make([]byte, bodyL)
-			copy(out, prefix)
-			if remain > 0 {
-				if _, err := io.ReadFull(br, out[prefixL:]); err != nil {
-					return nil
-				}
-			}
-			return out
-		}
-
-		if remain > 0 {
-			if _, err := br.Discard(remain); err != nil {
-				return nil
-			}
-		}
-	}
-}
-
-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, errors.New("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, errors.New("failed to load geosite: ", country).Base(err)
-		}
-		return domains, nil
-	}
-	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, errors.New("invalid external resource: ", domain)
-		}
-		filename := kv[0]
-		country := kv[1]
-		domains, err := loadGeositeWithAttr(filename, country)
-		if err != nil {
-			return nil, errors.New("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, errors.New("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
-
-	for _, ip := range ips {
-		if strings.HasPrefix(ip, "geoip:") {
-			country := ip[6:]
-			isReverseMatch := false
-			if strings.HasPrefix(ip, "geoip:!") {
-				country = ip[7:]
-				isReverseMatch = true
-			}
-			if len(country) == 0 {
-				return nil, errors.New("empty country name in rule")
-			}
-			geoip, err := loadIP("geoip.dat", strings.ToUpper(country))
-			if err != nil {
-				return nil, errors.New("failed to load GeoIP: ", country).Base(err)
-			}
-
-			geoipList = append(geoipList, &router.GeoIP{
-				CountryCode:  strings.ToUpper(country),
-				Cidr:         geoip,
-				ReverseMatch: isReverseMatch,
-			})
-			continue
-		}
-		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, errors.New("invalid external resource: ", ip)
-			}
-
-			filename := kv[0]
-			country := kv[1]
-			if len(filename) == 0 || len(country) == 0 {
-				return nil, errors.New("empty filename or empty country in rule")
-			}
-
-			isReverseMatch := false
-			if strings.HasPrefix(country, "!") {
-				country = country[1:]
-				isReverseMatch = true
-			}
-			geoip, err := loadIP(filename, strings.ToUpper(country))
-			if err != nil {
-				return nil, errors.New("failed to load IPs: ", country, " from ", filename).Base(err)
-			}
-
-			geoipList = append(geoipList, &router.GeoIP{
-				CountryCode:  strings.ToUpper(filename + "_" + country),
-				Cidr:         geoip,
-				ReverseMatch: isReverseMatch,
-			})
-
-			continue
-		}
-
-		ipRule, err := parseIP(ip)
-		if err != nil {
-			return nil, errors.New("invalid IP: ", ip).Base(err)
-		}
-		customCidrs = append(customCidrs, ipRule)
-	}
-
-	if len(customCidrs) > 0 {
-		geoipList = append(geoipList, &router.GeoIP{
-			Cidr: customCidrs,
-		})
-	}
-
-	return geoipList, nil
-}
-
 type WebhookRuleConfig struct {
 type WebhookRuleConfig struct {
 	URL           string            `json:"url"`
 	URL           string            `json:"url"`
 	Deduplication uint32            `json:"deduplication"`
 	Deduplication uint32            `json:"deduplication"`