mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-05-08 14:13:22 +00:00
Xray-core: Refactor geodata (#5814)
https://github.com/XTLS/Xray-core/issues/4422#issuecomment-3533007890 Breaking changes https://github.com/XTLS/Xray-core/pull/5569 Reverts https://github.com/XTLS/Xray-core/pull/5505 Closes https://github.com/XTLS/Xray-core/pull/643
This commit is contained in:
@@ -2,48 +2,24 @@ package dns
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"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,
|
||||
}
|
||||
|
||||
// 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 localTLDsAndDotlessDomainsRule = &NameServer_OriginalRule{
|
||||
Rule: "geosite:private",
|
||||
Size: uint32(len(localTLDsAndDotlessDomains)),
|
||||
}
|
||||
|
||||
func toStrMatcher(t DomainMatchingType, domain string) (strmatcher.Matcher, error) {
|
||||
strMType, f := typeMap[t]
|
||||
if !f {
|
||||
return nil, errors.New("unknown mapping type", t).AtWarning()
|
||||
}
|
||||
matcher, err := strMType.New(domain)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create str matcher").Base(err)
|
||||
}
|
||||
return matcher, nil
|
||||
var localTLDsAndDotlessDomainsRules = []*geodata.DomainRule{
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^[^.]+$"}}}, // This will only match domains without any dot
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "local"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "localdomain"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "localhost"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "lan"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "home.arpa"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "example"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "invalid"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "test"}}},
|
||||
}
|
||||
|
||||
func toNetIP(addrs []net.Address) ([]net.IP, error) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
router "github.com/xtls/xray-core/app/router"
|
||||
geodata "github.com/xtls/xray-core/common/geodata"
|
||||
net "github.com/xtls/xray-core/common/net"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
@@ -23,58 +23,6 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
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 (
|
||||
@@ -111,11 +59,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 {
|
||||
@@ -124,30 +72,29 @@ 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 {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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"`
|
||||
SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
|
||||
PrioritizedDomain []*NameServer_PriorityDomain `protobuf:"bytes,2,rep,name=prioritized_domain,json=prioritizedDomain,proto3" json:"prioritized_domain,omitempty"`
|
||||
ExpectedGeoip []*router.GeoIP `protobuf:"bytes,3,rep,name=expected_geoip,json=expectedGeoip,proto3" json:"expected_geoip,omitempty"`
|
||||
OriginalRules []*NameServer_OriginalRule `protobuf:"bytes,4,rep,name=original_rules,json=originalRules,proto3" json:"original_rules,omitempty"`
|
||||
QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||
ActPrior bool `protobuf:"varint,8,opt,name=actPrior,proto3" json:"actPrior,omitempty"`
|
||||
Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"`
|
||||
DisableCache *bool `protobuf:"varint,11,opt,name=disableCache,proto3,oneof" json:"disableCache,omitempty"`
|
||||
ServeStale *bool `protobuf:"varint,15,opt,name=serveStale,proto3,oneof" json:"serveStale,omitempty"`
|
||||
ServeExpiredTTL *uint32 `protobuf:"varint,16,opt,name=serveExpiredTTL,proto3,oneof" json:"serveExpiredTTL,omitempty"`
|
||||
FinalQuery bool `protobuf:"varint,12,opt,name=finalQuery,proto3" json:"finalQuery,omitempty"`
|
||||
UnexpectedGeoip []*router.GeoIP `protobuf:"bytes,13,rep,name=unexpected_geoip,json=unexpectedGeoip,proto3" json:"unexpected_geoip,omitempty"`
|
||||
ActUnprior bool `protobuf:"varint,14,opt,name=actUnprior,proto3" json:"actUnprior,omitempty"`
|
||||
PolicyID uint32 `protobuf:"varint,17,opt,name=policyID,proto3" json:"policyID,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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"`
|
||||
SkipFallback bool `protobuf:"varint,6,opt,name=skipFallback,proto3" json:"skipFallback,omitempty"`
|
||||
Domain []*geodata.DomainRule `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||
ExpectedIp []*geodata.IPRule `protobuf:"bytes,3,rep,name=expected_ip,json=expectedIp,proto3" json:"expected_ip,omitempty"`
|
||||
QueryStrategy QueryStrategy `protobuf:"varint,7,opt,name=query_strategy,json=queryStrategy,proto3,enum=xray.app.dns.QueryStrategy" json:"query_strategy,omitempty"`
|
||||
ActPrior bool `protobuf:"varint,8,opt,name=actPrior,proto3" json:"actPrior,omitempty"`
|
||||
Tag string `protobuf:"bytes,9,opt,name=tag,proto3" json:"tag,omitempty"`
|
||||
TimeoutMs uint64 `protobuf:"varint,10,opt,name=timeoutMs,proto3" json:"timeoutMs,omitempty"`
|
||||
DisableCache *bool `protobuf:"varint,11,opt,name=disableCache,proto3,oneof" json:"disableCache,omitempty"`
|
||||
ServeStale *bool `protobuf:"varint,15,opt,name=serveStale,proto3,oneof" json:"serveStale,omitempty"`
|
||||
ServeExpiredTTL *uint32 `protobuf:"varint,16,opt,name=serveExpiredTTL,proto3,oneof" json:"serveExpiredTTL,omitempty"`
|
||||
FinalQuery bool `protobuf:"varint,12,opt,name=finalQuery,proto3" json:"finalQuery,omitempty"`
|
||||
UnexpectedIp []*geodata.IPRule `protobuf:"bytes,13,rep,name=unexpected_ip,json=unexpectedIp,proto3" json:"unexpected_ip,omitempty"`
|
||||
ActUnprior bool `protobuf:"varint,14,opt,name=actUnprior,proto3" json:"actUnprior,omitempty"`
|
||||
PolicyID uint32 `protobuf:"varint,17,opt,name=policyID,proto3" json:"policyID,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NameServer) Reset() {
|
||||
@@ -201,23 +148,16 @@ func (x *NameServer) GetSkipFallback() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetPrioritizedDomain() []*NameServer_PriorityDomain {
|
||||
func (x *NameServer) GetDomain() []*geodata.DomainRule {
|
||||
if x != nil {
|
||||
return x.PrioritizedDomain
|
||||
return x.Domain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NameServer) GetExpectedGeoip() []*router.GeoIP {
|
||||
func (x *NameServer) GetExpectedIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.ExpectedGeoip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *NameServer) GetOriginalRules() []*NameServer_OriginalRule {
|
||||
if x != nil {
|
||||
return x.OriginalRules
|
||||
return x.ExpectedIp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -278,9 +218,9 @@ func (x *NameServer) GetFinalQuery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *NameServer) GetUnexpectedGeoip() []*router.GeoIP {
|
||||
func (x *NameServer) GetUnexpectedIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.UnexpectedGeoip
|
||||
return x.UnexpectedIp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -429,114 +369,9 @@ func (x *Config) GetEnableParallelQuery() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type NameServer_PriorityDomain struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NameServer_PriorityDomain) Reset() {
|
||||
*x = NameServer_PriorityDomain{}
|
||||
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 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 `protogen:"open.v1"`
|
||||
Rule string `protobuf:"bytes,1,opt,name=rule,proto3" json:"rule,omitempty"`
|
||||
Size uint32 `protobuf:"varint,2,opt,name=size,proto3" json:"size,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *NameServer_OriginalRule) Reset() {
|
||||
*x = NameServer_OriginalRule{}
|
||||
mi := &file_app_dns_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *NameServer_OriginalRule) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*NameServer_OriginalRule) ProtoMessage() {}
|
||||
|
||||
func (x *NameServer_OriginalRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_dns_config_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 NameServer_OriginalRule.ProtoReflect.Descriptor instead.
|
||||
func (*NameServer_OriginalRule) Descriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{0, 1}
|
||||
}
|
||||
|
||||
func (x *NameServer_OriginalRule) GetRule() string {
|
||||
if x != nil {
|
||||
return x.Rule
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *NameServer_OriginalRule) GetSize() uint32 {
|
||||
if x != nil {
|
||||
return x.Size
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type Config_HostMapping struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
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"`
|
||||
Domain *geodata.DomainRule `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.
|
||||
@@ -547,7 +382,7 @@ type Config_HostMapping struct {
|
||||
|
||||
func (x *Config_HostMapping) Reset() {
|
||||
*x = Config_HostMapping{}
|
||||
mi := &file_app_dns_config_proto_msgTypes[4]
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -559,7 +394,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[4]
|
||||
mi := &file_app_dns_config_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -575,18 +410,11 @@ func (*Config_HostMapping) Descriptor() ([]byte, []int) {
|
||||
return file_app_dns_config_proto_rawDescGZIP(), []int{1, 0}
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetType() DomainMatchingType {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return DomainMatchingType_Full
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetDomain() string {
|
||||
func (x *Config_HostMapping) GetDomain() *geodata.DomainRule {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Config_HostMapping) GetIp() [][]byte {
|
||||
@@ -607,15 +435,15 @@ var File_app_dns_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_app_dns_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x14app/dns/config.proto\x12\fxray.app.dns\x1a\x1ccommon/net/destination.proto\x1a\x17app/router/config.proto\"\xdf\a\n" +
|
||||
"\x14app/dns/config.proto\x12\fxray.app.dns\x1a\x1ccommon/net/destination.proto\x1a\x1bcommon/geodata/geodat.proto\"\xde\x05\n" +
|
||||
"\n" +
|
||||
"NameServer\x123\n" +
|
||||
"\aaddress\x18\x01 \x01(\v2\x19.xray.common.net.EndpointR\aaddress\x12\x1b\n" +
|
||||
"\tclient_ip\x18\x05 \x01(\fR\bclientIp\x12\"\n" +
|
||||
"\fskipFallback\x18\x06 \x01(\bR\fskipFallback\x12V\n" +
|
||||
"\x12prioritized_domain\x18\x02 \x03(\v2'.xray.app.dns.NameServer.PriorityDomainR\x11prioritizedDomain\x12=\n" +
|
||||
"\x0eexpected_geoip\x18\x03 \x03(\v2\x16.xray.app.router.GeoIPR\rexpectedGeoip\x12L\n" +
|
||||
"\x0eoriginal_rules\x18\x04 \x03(\v2%.xray.app.dns.NameServer.OriginalRuleR\roriginalRules\x12B\n" +
|
||||
"\fskipFallback\x18\x06 \x01(\bR\fskipFallback\x127\n" +
|
||||
"\x06domain\x18\x02 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\x12<\n" +
|
||||
"\vexpected_ip\x18\x03 \x03(\v2\x1b.xray.common.geodata.IPRuleR\n" +
|
||||
"expectedIp\x12B\n" +
|
||||
"\x0equery_strategy\x18\a \x01(\x0e2\x1b.xray.app.dns.QueryStrategyR\rqueryStrategy\x12\x1a\n" +
|
||||
"\bactPrior\x18\b \x01(\bR\bactPrior\x12\x10\n" +
|
||||
"\x03tag\x18\t \x01(\tR\x03tag\x12\x1c\n" +
|
||||
@@ -628,21 +456,15 @@ const file_app_dns_config_proto_rawDesc = "" +
|
||||
"\x0fserveExpiredTTL\x18\x10 \x01(\rH\x02R\x0fserveExpiredTTL\x88\x01\x01\x12\x1e\n" +
|
||||
"\n" +
|
||||
"finalQuery\x18\f \x01(\bR\n" +
|
||||
"finalQuery\x12A\n" +
|
||||
"\x10unexpected_geoip\x18\r \x03(\v2\x16.xray.app.router.GeoIPR\x0funexpectedGeoip\x12\x1e\n" +
|
||||
"finalQuery\x12@\n" +
|
||||
"\runexpected_ip\x18\r \x03(\v2\x1b.xray.common.geodata.IPRuleR\funexpectedIp\x12\x1e\n" +
|
||||
"\n" +
|
||||
"actUnprior\x18\x0e \x01(\bR\n" +
|
||||
"actUnprior\x12\x1a\n" +
|
||||
"\bpolicyID\x18\x11 \x01(\rR\bpolicyID\x1a^\n" +
|
||||
"\x0ePriorityDomain\x124\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2 .xray.app.dns.DomainMatchingTypeR\x04type\x12\x16\n" +
|
||||
"\x06domain\x18\x02 \x01(\tR\x06domain\x1a6\n" +
|
||||
"\fOriginalRule\x12\x12\n" +
|
||||
"\x04rule\x18\x01 \x01(\tR\x04rule\x12\x12\n" +
|
||||
"\x04size\x18\x02 \x01(\rR\x04sizeB\x0f\n" +
|
||||
"\bpolicyID\x18\x11 \x01(\rR\bpolicyIDB\x0f\n" +
|
||||
"\r_disableCacheB\r\n" +
|
||||
"\v_serveStaleB\x12\n" +
|
||||
"\x10_serveExpiredTTL\"\x98\x05\n" +
|
||||
"\x10_serveExpiredTTLJ\x04\b\x04\x10\x05\"\x82\x05\n" +
|
||||
"\x06Config\x129\n" +
|
||||
"\vname_server\x18\x05 \x03(\v2\x18.xray.app.dns.NameServerR\n" +
|
||||
"nameServer\x12\x1b\n" +
|
||||
@@ -658,17 +480,11 @@ const file_app_dns_config_proto_rawDesc = "" +
|
||||
"\x0fdisableFallback\x18\n" +
|
||||
" \x01(\bR\x0fdisableFallback\x126\n" +
|
||||
"\x16disableFallbackIfMatch\x18\v \x01(\bR\x16disableFallbackIfMatch\x120\n" +
|
||||
"\x13enableParallelQuery\x18\x0e \x01(\bR\x13enableParallelQuery\x1a\x92\x01\n" +
|
||||
"\vHostMapping\x124\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2 .xray.app.dns.DomainMatchingTypeR\x04type\x12\x16\n" +
|
||||
"\x06domain\x18\x02 \x01(\tR\x06domain\x12\x0e\n" +
|
||||
"\x13enableParallelQuery\x18\x0e \x01(\bR\x13enableParallelQuery\x1a}\n" +
|
||||
"\vHostMapping\x127\n" +
|
||||
"\x06domain\x18\x02 \x01(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\x12\x0e\n" +
|
||||
"\x02ip\x18\x03 \x03(\fR\x02ip\x12%\n" +
|
||||
"\x0eproxied_domain\x18\x04 \x01(\tR\rproxiedDomainJ\x04\b\a\x10\b*E\n" +
|
||||
"\x12DomainMatchingType\x12\b\n" +
|
||||
"\x04Full\x10\x00\x12\r\n" +
|
||||
"\tSubdomain\x10\x01\x12\v\n" +
|
||||
"\aKeyword\x10\x02\x12\t\n" +
|
||||
"\x05Regex\x10\x03*B\n" +
|
||||
"\x0eproxied_domain\x18\x04 \x01(\tR\rproxiedDomainJ\x04\b\a\x10\b*B\n" +
|
||||
"\rQueryStrategy\x12\n" +
|
||||
"\n" +
|
||||
"\x06USE_IP\x10\x00\x12\v\n" +
|
||||
@@ -689,36 +505,32 @@ 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, 5)
|
||||
var file_app_dns_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_app_dns_config_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
|
||||
var file_app_dns_config_proto_goTypes = []any{
|
||||
(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
|
||||
(*Config_HostMapping)(nil), // 6: xray.app.dns.Config.HostMapping
|
||||
(*net.Endpoint)(nil), // 7: xray.common.net.Endpoint
|
||||
(*router.GeoIP)(nil), // 8: xray.app.router.GeoIP
|
||||
(QueryStrategy)(0), // 0: xray.app.dns.QueryStrategy
|
||||
(*NameServer)(nil), // 1: xray.app.dns.NameServer
|
||||
(*Config)(nil), // 2: xray.app.dns.Config
|
||||
(*Config_HostMapping)(nil), // 3: xray.app.dns.Config.HostMapping
|
||||
(*net.Endpoint)(nil), // 4: xray.common.net.Endpoint
|
||||
(*geodata.DomainRule)(nil), // 5: xray.common.geodata.DomainRule
|
||||
(*geodata.IPRule)(nil), // 6: xray.common.geodata.IPRule
|
||||
}
|
||||
var file_app_dns_config_proto_depIdxs = []int32{
|
||||
7, // 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
|
||||
8, // 2: xray.app.dns.NameServer.expected_geoip:type_name -> xray.app.router.GeoIP
|
||||
5, // 3: xray.app.dns.NameServer.original_rules:type_name -> xray.app.dns.NameServer.OriginalRule
|
||||
1, // 4: xray.app.dns.NameServer.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
8, // 5: xray.app.dns.NameServer.unexpected_geoip:type_name -> xray.app.router.GeoIP
|
||||
2, // 6: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
|
||||
6, // 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
|
||||
0, // 10: xray.app.dns.Config.HostMapping.type:type_name -> xray.app.dns.DomainMatchingType
|
||||
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
|
||||
4, // 0: xray.app.dns.NameServer.address:type_name -> xray.common.net.Endpoint
|
||||
5, // 1: xray.app.dns.NameServer.domain:type_name -> xray.common.geodata.DomainRule
|
||||
6, // 2: xray.app.dns.NameServer.expected_ip:type_name -> xray.common.geodata.IPRule
|
||||
0, // 3: xray.app.dns.NameServer.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
6, // 4: xray.app.dns.NameServer.unexpected_ip:type_name -> xray.common.geodata.IPRule
|
||||
1, // 5: xray.app.dns.Config.name_server:type_name -> xray.app.dns.NameServer
|
||||
3, // 6: xray.app.dns.Config.static_hosts:type_name -> xray.app.dns.Config.HostMapping
|
||||
0, // 7: xray.app.dns.Config.query_strategy:type_name -> xray.app.dns.QueryStrategy
|
||||
5, // 8: xray.app.dns.Config.HostMapping.domain:type_name -> xray.common.geodata.DomainRule
|
||||
9, // [9:9] is the sub-list for method output_type
|
||||
9, // [9:9] is the sub-list for method input_type
|
||||
9, // [9:9] is the sub-list for extension type_name
|
||||
9, // [9:9] is the sub-list for extension extendee
|
||||
0, // [0:9] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_app_dns_config_proto_init() }
|
||||
@@ -732,8 +544,8 @@ func file_app_dns_config_proto_init() {
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_dns_config_proto_rawDesc), len(file_app_dns_config_proto_rawDesc)),
|
||||
NumEnums: 2,
|
||||
NumMessages: 5,
|
||||
NumEnums: 1,
|
||||
NumMessages: 3,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -7,26 +7,15 @@ option java_package = "com.xray.app.dns";
|
||||
option java_multiple_files = true;
|
||||
|
||||
import "common/net/destination.proto";
|
||||
import "app/router/config.proto";
|
||||
import "common/geodata/geodat.proto";
|
||||
|
||||
message NameServer {
|
||||
xray.common.net.Endpoint address = 1;
|
||||
bytes client_ip = 5;
|
||||
bool skipFallback = 6;
|
||||
|
||||
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 expected_geoip = 3;
|
||||
repeated OriginalRule original_rules = 4;
|
||||
repeated xray.common.geodata.DomainRule domain = 2;
|
||||
repeated xray.common.geodata.IPRule expected_ip = 3;
|
||||
reserved 4;
|
||||
QueryStrategy query_strategy = 7;
|
||||
bool actPrior = 8;
|
||||
string tag = 9;
|
||||
@@ -35,18 +24,11 @@ message NameServer {
|
||||
optional bool serveStale = 15;
|
||||
optional uint32 serveExpiredTTL = 16;
|
||||
bool finalQuery = 12;
|
||||
repeated xray.app.router.GeoIP unexpected_geoip = 13;
|
||||
repeated xray.common.geodata.IPRule unexpected_ip = 13;
|
||||
bool actUnprior = 14;
|
||||
uint32 policyID = 17;
|
||||
}
|
||||
|
||||
enum DomainMatchingType {
|
||||
Full = 0;
|
||||
Subdomain = 1;
|
||||
Keyword = 2;
|
||||
Regex = 3;
|
||||
}
|
||||
|
||||
enum QueryStrategy {
|
||||
USE_IP = 0;
|
||||
USE_IP4 = 1;
|
||||
@@ -64,8 +46,7 @@ message Config {
|
||||
bytes client_ip = 3;
|
||||
|
||||
message HostMapping {
|
||||
DomainMatchingType type = 1;
|
||||
string domain = 2;
|
||||
xray.common.geodata.DomainRule domain = 2;
|
||||
|
||||
repeated bytes ip = 3;
|
||||
|
||||
|
||||
@@ -12,13 +12,11 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
@@ -32,15 +30,15 @@ type DNS struct {
|
||||
hosts *StaticHosts
|
||||
clients []*Client
|
||||
ctx context.Context
|
||||
domainMatcher strmatcher.IndexMatcher
|
||||
domainMatcher geodata.DomainMatcher
|
||||
matcherInfos []*DomainMatcherInfo
|
||||
checkSystem bool
|
||||
}
|
||||
|
||||
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher
|
||||
// DomainMatcherInfo contains information attached to index returned by Server.domainMatcher.
|
||||
type DomainMatcherInfo struct {
|
||||
clientIdx uint16
|
||||
domainRuleIdx uint16
|
||||
clientIdx uint16
|
||||
domainRule string
|
||||
}
|
||||
|
||||
// New creates a new DNS server with given configuration.
|
||||
@@ -85,56 +83,40 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
return nil, errors.New("unexpected query strategy ", config.QueryStrategy)
|
||||
}
|
||||
|
||||
var hosts *StaticHosts
|
||||
mphLoaded := false
|
||||
domainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return "" })
|
||||
if domainMatcherPath != "" {
|
||||
if f, err := os.Open(domainMatcherPath); err == nil {
|
||||
defer f.Close()
|
||||
if m, err := router.LoadGeoSiteMatcher(f, "HOSTS"); err == nil {
|
||||
f.Seek(0, 0)
|
||||
if hostIPs, err := router.LoadGeoSiteHosts(f); err == nil {
|
||||
if sh, err := NewStaticHostsFromCache(m, hostIPs); err == nil {
|
||||
hosts = sh
|
||||
mphLoaded = true
|
||||
errors.LogDebug(ctx, "MphDomainMatcher loaded from cache for DNS hosts, size: ", sh.matchers.Size())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
hosts, err := NewStaticHosts(config.StaticHosts)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create hosts").Base(err)
|
||||
}
|
||||
|
||||
if !mphLoaded {
|
||||
sh, err := NewStaticHosts(config.StaticHosts)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create hosts").Base(err)
|
||||
}
|
||||
hosts = sh
|
||||
}
|
||||
|
||||
var clients []*Client
|
||||
domainRuleCount := 0
|
||||
|
||||
var defaultTag = config.Tag
|
||||
if len(config.Tag) == 0 {
|
||||
defaultTag = generateRandomTag()
|
||||
}
|
||||
|
||||
for _, ns := range config.NameServer {
|
||||
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 := &strmatcher.MatcherGroup{}
|
||||
clients := make([]*Client, 0, len(config.NameServer))
|
||||
matcherInfos := make([]*DomainMatcherInfo, 0)
|
||||
effectiveRules := make([]*geodata.DomainRule, 0)
|
||||
|
||||
for _, ns := range config.NameServer {
|
||||
clientIdx := len(clients)
|
||||
updateDomain := func(domainRule strmatcher.Matcher, originalRuleIdx int, matcherInfos []*DomainMatcherInfo) {
|
||||
midx := domainMatcher.Add(domainRule)
|
||||
matcherInfos[midx] = &DomainMatcherInfo{
|
||||
clientIdx: uint16(clientIdx),
|
||||
domainRuleIdx: uint16(originalRuleIdx),
|
||||
updateRules := func(isLocalNameServer bool) {
|
||||
// Prioritize local domains with specific TLDs or those without any dot for the local DNS
|
||||
if isLocalNameServer {
|
||||
effectiveRules = append(effectiveRules, localTLDsAndDotlessDomainsRules...)
|
||||
for _, rule := range localTLDsAndDotlessDomainsRules {
|
||||
matcherInfos = append(matcherInfos, &DomainMatcherInfo{
|
||||
clientIdx: uint16(clientIdx),
|
||||
domainRule: rule.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
effectiveRules = append(effectiveRules, ns.Domain...)
|
||||
for _, rule := range ns.Domain {
|
||||
matcherInfos = append(matcherInfos, &DomainMatcherInfo{
|
||||
clientIdx: uint16(clientIdx),
|
||||
domainRule: rule.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,18 +145,24 @@ func New(ctx context.Context, config *Config) (*DNS, error) {
|
||||
if len(ns.Tag) > 0 {
|
||||
tag = ns.Tag
|
||||
}
|
||||
|
||||
clientIPOption := ResolveIpOptionOverride(ns.QueryStrategy, ipOption)
|
||||
if !clientIPOption.IPv4Enable && !clientIPOption.IPv6Enable {
|
||||
return nil, errors.New("no QueryStrategy available for ", ns.Address)
|
||||
}
|
||||
|
||||
client, err := NewClient(ctx, ns, myClientIP, disableCache, serveStale, serveExpiredTTL, tag, clientIPOption, &matcherInfos, updateDomain)
|
||||
client, err := NewClient(ctx, ns, myClientIP, disableCache, serveStale, serveExpiredTTL, tag, clientIPOption, updateRules)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create client").Base(err)
|
||||
}
|
||||
clients = append(clients, client)
|
||||
}
|
||||
|
||||
domainMatcher, err := geodata.DomainReg.BuildDomainMatcher(effectiveRules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If there is no DNS client in config, add a `localhost` DNS client
|
||||
if len(clients) == 0 {
|
||||
clients = append(clients, NewLocalDNSClient(ipOption))
|
||||
@@ -283,14 +271,14 @@ func (s *DNS) sortClients(domain string) []*Client {
|
||||
|
||||
// Priority domain matching
|
||||
hasMatch := false
|
||||
MatchSlice := s.domainMatcher.Match(domain)
|
||||
MatchSlice := s.domainMatcher.Match(strings.ToLower(domain))
|
||||
sort.Slice(MatchSlice, func(i, j int) bool {
|
||||
return MatchSlice[i] < MatchSlice[j]
|
||||
})
|
||||
for _, match := range MatchSlice {
|
||||
info := s.matcherInfos[match]
|
||||
client := s.clients[info.clientIdx]
|
||||
domainRule := client.domains[info.domainRuleIdx]
|
||||
domainRule := info.domainRule
|
||||
domainRules = append(domainRules, fmt.Sprintf("%s(DNS idx:%d)", domainRule, info.clientIdx))
|
||||
if clientUsed[info.clientIdx] {
|
||||
continue
|
||||
|
||||
@@ -11,9 +11,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/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/serial"
|
||||
"github.com/xtls/xray-core/core"
|
||||
@@ -331,10 +331,9 @@ func TestPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "google.com"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -471,8 +470,7 @@ func TestStaticHostDomain(t *testing.T) {
|
||||
},
|
||||
StaticHosts: []*Config_HostMapping{
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "example.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "example.com"}}},
|
||||
ProxiedDomain: "google.com",
|
||||
},
|
||||
},
|
||||
@@ -539,11 +537,10 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
{
|
||||
CountryCode: "local",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
// inner ip, will not match
|
||||
Ip: []byte{192, 168, 11, 1},
|
||||
Prefix: 32,
|
||||
@@ -563,20 +560,18 @@ func TestIPMatch(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
{
|
||||
CountryCode: "test",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "test",
|
||||
Cidr: []*router.CIDR{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 4},
|
||||
Prefix: 32,
|
||||
},
|
||||
@@ -663,19 +658,15 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
// Equivalent of dotless:localhost
|
||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]*localhost[^.]*$"},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^[^.]*localhost[^.]*$"}}},
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will match localhost, localhost-a and localhost-b,
|
||||
CountryCode: "local",
|
||||
Cidr: []*router.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},
|
||||
},
|
||||
},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will match localhost, localhost-a and localhost-b,
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 2}, Prefix: 32}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 3}, Prefix: 32}}},
|
||||
{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 4}, Prefix: 32}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -688,23 +679,21 @@ func TestLocalDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
// Equivalent of dotless: and domain:local
|
||||
{Type: DomainMatchingType_Regex, Domain: "^[^.]*$"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "local"},
|
||||
{Type: DomainMatchingType_Subdomain, Domain: "localdomain"},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^[^.]*$"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "local"}}},
|
||||
{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "localdomain"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
StaticHosts: []*Config_HostMapping{
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "hostnamestatic",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "hostnamestatic"}}},
|
||||
Ip: [][]byte{{127, 0, 0, 53}},
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "hostnamealias",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "hostnamealias"}}},
|
||||
ProxiedDomain: "hostname.localdomain",
|
||||
},
|
||||
},
|
||||
@@ -891,17 +880,27 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "google.com"}},
|
||||
},
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.8.8 and 8.8.4.4
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
{Ip: []byte{8, 8, 4, 4}, Prefix: 32},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will only match 8.8.8.8 and 8.8.4.4
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 4, 4},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -916,16 +915,19 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "google.com"}},
|
||||
},
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will match 8.8.8.8 and 8.8.8.7, etc
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 8, 7}, Prefix: 24},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will match 8.8.8.8 and 8.8.8.7, etc
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 7},
|
||||
Prefix: 24,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -940,16 +942,19 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "api.google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "api.google.com"}},
|
||||
},
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.7.7 (api.google.com)
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 7, 7}, Prefix: 32},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will only match 8.8.7.7 (api.google.com)
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 7, 7},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -964,16 +969,19 @@ func TestMultiMatchPrioritizedDomain(t *testing.T) {
|
||||
},
|
||||
Port: uint32(port),
|
||||
},
|
||||
PrioritizedDomain: []*NameServer_PriorityDomain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "v2.api.google.com",
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "v2.api.google.com"}},
|
||||
},
|
||||
},
|
||||
ExpectedGeoip: []*router.GeoIP{
|
||||
{ // Will only match 8.8.7.8 (v2.api.google.com)
|
||||
Cidr: []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 7, 8}, Prefix: 32},
|
||||
ExpectedIp: []*geodata.IPRule{
|
||||
// Will only match 8.8.7.8 (v2.api.google.com)
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 7, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
115
app/dns/hosts.go
115
app/dns/hosts.go
@@ -2,39 +2,28 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
|
||||
// StaticHosts represents static domain-ip mapping in DNS server.
|
||||
type StaticHosts struct {
|
||||
ips [][]net.Address
|
||||
matchers strmatcher.IndexMatcher
|
||||
reps [][]net.Address
|
||||
matcher geodata.DomainMatcher
|
||||
}
|
||||
|
||||
// NewStaticHosts creates a new StaticHosts instance.
|
||||
func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||
g := new(strmatcher.MatcherGroup)
|
||||
sh := &StaticHosts{
|
||||
ips: make([][]net.Address, len(hosts)+16),
|
||||
matchers: g,
|
||||
}
|
||||
reps := make([][]net.Address, 0, len(hosts))
|
||||
rules := make([]*geodata.DomainRule, 0, len(hosts))
|
||||
|
||||
defer runtime.GC()
|
||||
for i, mapping := range hosts {
|
||||
hosts[i] = nil
|
||||
matcher, err := toStrMatcher(mapping.Type, mapping.Domain)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "failed to create domain matcher, ignore domain rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||
continue
|
||||
}
|
||||
id := g.Add(matcher)
|
||||
ips := make([]net.Address, 0, len(mapping.Ip)+1)
|
||||
for _, mapping := range hosts {
|
||||
rep := make([]net.Address, 0, len(mapping.Ip))
|
||||
switch {
|
||||
case len(mapping.ProxiedDomain) > 0:
|
||||
if mapping.ProxiedDomain[0] == '#' {
|
||||
@@ -42,28 +31,36 @@ func NewStaticHosts(hosts []*Config_HostMapping) (*StaticHosts, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ips = append(ips, dns.RCodeError(rcode))
|
||||
rep = append(rep, dns.RCodeError(rcode))
|
||||
} else {
|
||||
ips = append(ips, net.DomainAddress(mapping.ProxiedDomain))
|
||||
rep = append(rep, net.DomainAddress(mapping.ProxiedDomain))
|
||||
}
|
||||
case len(mapping.Ip) > 0:
|
||||
for _, ip := range mapping.Ip {
|
||||
addr := net.IPAddress(ip)
|
||||
if addr == nil {
|
||||
errors.LogError(context.Background(), "invalid IP address in static hosts: ", ip, ", ignore this ip for rule [type: ", mapping.Type, ", domain: ", mapping.Domain, "]")
|
||||
errors.LogError(context.Background(), "invalid IP address in static hosts: ", ip, ", ignore this ip for rule: ", mapping.Domain)
|
||||
continue
|
||||
}
|
||||
ips = append(ips, addr)
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
continue
|
||||
rep = append(rep, addr)
|
||||
}
|
||||
}
|
||||
|
||||
sh.ips[id] = ips
|
||||
// if len(rep) == 0 {
|
||||
// errors.LogError(context.Background(), "empty value in static hosts, ignore this rule: ", mapping.Domain)
|
||||
// continue
|
||||
// }
|
||||
reps = append(reps, rep)
|
||||
rules = append(rules, mapping.Domain)
|
||||
}
|
||||
|
||||
return sh, nil
|
||||
matcher, err := geodata.DomainReg.BuildDomainMatcher(rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &StaticHosts{
|
||||
reps: reps,
|
||||
matcher: matcher,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
||||
@@ -79,16 +76,16 @@ func filterIP(ips []net.Address, option dns.IPOption) []net.Address {
|
||||
func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
||||
ips := make([]net.Address, 0)
|
||||
found := false
|
||||
for _, id := range h.matchers.Match(domain) {
|
||||
for _, v := range h.ips[id] {
|
||||
if err, ok := v.(dns.RCodeError); ok {
|
||||
for _, ruleIdx := range h.matcher.Match(domain) {
|
||||
for _, rep := range h.reps[ruleIdx] {
|
||||
if err, ok := rep.(dns.RCodeError); ok {
|
||||
if uint16(err) == 0 {
|
||||
return nil, dns.ErrEmptyResponse
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
ips = append(ips, h.ips[id]...)
|
||||
ips = append(ips, h.reps[ruleIdx]...)
|
||||
found = true
|
||||
}
|
||||
if !found {
|
||||
@@ -98,10 +95,13 @@ func (h *StaticHosts) lookupInternal(domain string) ([]net.Address, error) {
|
||||
}
|
||||
|
||||
func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) ([]net.Address, error) {
|
||||
domain = strings.ToLower(domain)
|
||||
switch addrs, err := h.lookupInternal(domain); {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case len(addrs) == 0: // Not recorded in static hosts, return nil
|
||||
case addrs == nil: // Not recorded in static hosts, return nil
|
||||
return nil, nil
|
||||
case len(addrs) == 0: // Domain recorded, but no valid IP returned
|
||||
return addrs, nil
|
||||
case len(addrs) == 1 && addrs[0].Family().IsDomain(): // Try to unwrap domain
|
||||
errors.LogDebug(context.Background(), "found replaced domain: ", domain, " -> ", addrs[0].Domain(), ". Try to unwrap it")
|
||||
@@ -124,50 +124,3 @@ func (h *StaticHosts) lookup(domain string, option dns.IPOption, maxDepth int) (
|
||||
func (h *StaticHosts) Lookup(domain string, option dns.IPOption) ([]net.Address, error) {
|
||||
return h.lookup(domain, option, 5)
|
||||
}
|
||||
func NewStaticHostsFromCache(matcher strmatcher.IndexMatcher, hostIPs map[string][]string) (*StaticHosts, error) {
|
||||
sh := &StaticHosts{
|
||||
ips: make([][]net.Address, matcher.Size()+1),
|
||||
matchers: matcher,
|
||||
}
|
||||
|
||||
order := hostIPs["_ORDER"]
|
||||
var offset uint32
|
||||
|
||||
img, ok := matcher.(*strmatcher.IndexMatcherGroup)
|
||||
if !ok {
|
||||
// Single matcher (e.g. only manual or only one geosite)
|
||||
if len(order) > 0 {
|
||||
pattern := order[0]
|
||||
ips := parseIPs(hostIPs[pattern])
|
||||
for i := uint32(1); i <= matcher.Size(); i++ {
|
||||
sh.ips[i] = ips
|
||||
}
|
||||
}
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
for i, m := range img.Matchers {
|
||||
if i < len(order) {
|
||||
pattern := order[i]
|
||||
ips := parseIPs(hostIPs[pattern])
|
||||
for j := uint32(1); j <= m.Size(); j++ {
|
||||
sh.ips[offset+j] = ips
|
||||
}
|
||||
offset += m.Size()
|
||||
}
|
||||
}
|
||||
return sh, nil
|
||||
}
|
||||
|
||||
func parseIPs(raw []string) []net.Address {
|
||||
addrs := make([]net.Address, 0, len(raw))
|
||||
for _, s := range raw {
|
||||
if len(s) > 1 && s[0] == '#' {
|
||||
rcode, _ := strconv.Atoi(s[1:])
|
||||
addrs = append(addrs, dns.RCodeError(rcode))
|
||||
} else {
|
||||
addrs = append(addrs, net.ParseAddress(s))
|
||||
}
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package dns_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
. "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/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
)
|
||||
@@ -15,20 +14,17 @@ import (
|
||||
func TestStaticHosts(t *testing.T) {
|
||||
pb := []*Config_HostMapping{
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "lan",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "lan"}}},
|
||||
ProxiedDomain: "#3",
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "example.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "example.com"}}},
|
||||
Ip: [][]byte{
|
||||
{1, 1, 1, 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "proxy.xray.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "proxy.xray.com"}}},
|
||||
Ip: [][]byte{
|
||||
{1, 2, 3, 4},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
@@ -36,20 +32,17 @@ func TestStaticHosts(t *testing.T) {
|
||||
ProxiedDomain: "another-proxy.xray.com",
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Full,
|
||||
Domain: "proxy2.xray.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Full, Value: "proxy2.xray.com"}}},
|
||||
ProxiedDomain: "proxy.xray.com",
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "example.cn",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "example.cn"}}},
|
||||
Ip: [][]byte{
|
||||
{2, 2, 2, 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: DomainMatchingType_Subdomain,
|
||||
Domain: "baidu.com",
|
||||
Domain: &geodata.DomainRule{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "baidu.com"}}},
|
||||
Ip: [][]byte{
|
||||
{127, 0, 0, 1},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
@@ -132,57 +125,3 @@ func TestStaticHosts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
func TestStaticHostsFromCache(t *testing.T) {
|
||||
sites := []*router.GeoSite{
|
||||
{
|
||||
CountryCode: "cloudflare-dns.com",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Full, Value: "example.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "geosite:cn",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "baidu.cn"},
|
||||
},
|
||||
},
|
||||
}
|
||||
deps := map[string][]string{
|
||||
"HOSTS": {"cloudflare-dns.com", "geosite:cn"},
|
||||
}
|
||||
hostIPs := map[string][]string{
|
||||
"cloudflare-dns.com": {"1.1.1.1"},
|
||||
"geosite:cn": {"2.2.2.2"},
|
||||
"_ORDER": {"cloudflare-dns.com", "geosite:cn"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := router.SerializeGeoSiteList(sites, deps, hostIPs, &buf)
|
||||
common.Must(err)
|
||||
|
||||
// Load matcher
|
||||
m, err := router.LoadGeoSiteMatcher(bytes.NewReader(buf.Bytes()), "HOSTS")
|
||||
common.Must(err)
|
||||
|
||||
// Load hostIPs
|
||||
f := bytes.NewReader(buf.Bytes())
|
||||
hips, err := router.LoadGeoSiteHosts(f)
|
||||
common.Must(err)
|
||||
|
||||
hosts, err := NewStaticHostsFromCache(m, hips)
|
||||
common.Must(err)
|
||||
|
||||
{
|
||||
ips, _ := hosts.Lookup("example.com", dns.IPOption{IPv4Enable: true})
|
||||
if len(ips) != 1 || ips[0].String() != "1.1.1.1" {
|
||||
t.Error("failed to lookup example.com from cache")
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ips, _ := hosts.Lookup("baidu.cn", dns.IPOption{IPv4Enable: true})
|
||||
if len(ips) != 1 || ips[0].String() != "2.2.2.2" {
|
||||
t.Error("failed to lookup baidu.cn from cache deps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,34 +3,18 @@ package dns
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/core"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
|
||||
type mphMatcherWrapper struct {
|
||||
m strmatcher.IndexMatcher
|
||||
}
|
||||
|
||||
func (w *mphMatcherWrapper) Match(s string) bool {
|
||||
return w.m.Match(s) != nil
|
||||
}
|
||||
|
||||
func (w *mphMatcherWrapper) String() string {
|
||||
return "mph-matcher"
|
||||
}
|
||||
|
||||
// Server is the interface for Name Server.
|
||||
type Server interface {
|
||||
// Name of the Client.
|
||||
@@ -46,9 +30,8 @@ type Server interface {
|
||||
type Client struct {
|
||||
server Server
|
||||
skipFallback bool
|
||||
domains []string
|
||||
expectedIPs router.GeoIPMatcher
|
||||
unexpectedIPs router.GeoIPMatcher
|
||||
expectedIPs geodata.IPMatcher
|
||||
unexpectedIPs geodata.IPMatcher
|
||||
actPrior bool
|
||||
actUnprior bool
|
||||
tag string
|
||||
@@ -111,11 +94,9 @@ func NewClient(
|
||||
disableCache bool, serveStale bool, serveExpiredTTL uint32,
|
||||
tag string,
|
||||
ipOption dns.IPOption,
|
||||
matcherInfos *[]*DomainMatcherInfo,
|
||||
updateDomainRule func(strmatcher.Matcher, int, []*DomainMatcherInfo),
|
||||
updateRules func(bool),
|
||||
) (*Client, error) {
|
||||
client := &Client{}
|
||||
|
||||
err := core.RequireFeatures(ctx, func(dispatcher routing.Dispatcher) error {
|
||||
// Create a new server for each client for now
|
||||
server, err := NewServer(ctx, ns.Address.AsDestination(), dispatcher, disableCache, serveStale, serveExpiredTTL, clientIP)
|
||||
@@ -123,97 +104,25 @@ func NewClient(
|
||||
return errors.New("failed to create nameserver").Base(err).AtWarning()
|
||||
}
|
||||
|
||||
// Prioritize local domains with specific TLDs or those without any dot for the local DNS
|
||||
if _, isLocalDNS := server.(*LocalNameServer); isLocalDNS {
|
||||
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 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.
|
||||
// Related issues:
|
||||
// https://github.com/v2fly/v2ray-core/issues/529
|
||||
// https://github.com/v2fly/v2ray-core/issues/719
|
||||
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
|
||||
|
||||
// Check if domain matcher cache is provided via environment
|
||||
domainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return "" })
|
||||
var mphLoaded bool
|
||||
|
||||
if domainMatcherPath != "" && ns.Tag != "" {
|
||||
f, err := filesystem.NewFileReader(domainMatcherPath)
|
||||
if err == nil {
|
||||
defer f.Close()
|
||||
g, err := router.LoadGeoSiteMatcher(f, ns.Tag)
|
||||
if err == nil {
|
||||
errors.LogDebug(ctx, "MphDomainMatcher loaded from cache for ", ns.Tag, " dns tag)")
|
||||
updateDomainRule(&mphMatcherWrapper{m: g}, 0, *matcherInfos)
|
||||
rules = append(rules, "[MPH Cache]")
|
||||
mphLoaded = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !mphLoaded {
|
||||
for i, domain := range ns.PrioritizedDomain {
|
||||
ns.PrioritizedDomain[i] = nil
|
||||
domainRule, err := toStrMatcher(domain.Type, domain.Domain)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(ctx, err, "failed to create domain matcher, ignore domain rule [type: ", domain.Type, ", domain: ", domain.Domain, "]")
|
||||
domainRule, _ = toStrMatcher(DomainMatchingType_Full, "hack.fix.index.for.illegal.domain.rule")
|
||||
}
|
||||
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++
|
||||
}
|
||||
updateDomainRule(domainRule, originalRuleIdx, *matcherInfos)
|
||||
}
|
||||
}
|
||||
ns.PrioritizedDomain = nil
|
||||
runtime.GC()
|
||||
_, isLocalDNS := server.(*LocalNameServer)
|
||||
updateRules(isLocalDNS)
|
||||
|
||||
// Establish expected IPs
|
||||
var expectedMatcher router.GeoIPMatcher
|
||||
if len(ns.ExpectedGeoip) > 0 {
|
||||
expectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.ExpectedGeoip...)
|
||||
var expectedMatcher geodata.IPMatcher
|
||||
if len(ns.ExpectedIp) > 0 {
|
||||
expectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.ExpectedIp)
|
||||
if err != nil {
|
||||
return errors.New("failed to create expected ip matcher").Base(err).AtWarning()
|
||||
}
|
||||
ns.ExpectedGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
// Establish unexpected IPs
|
||||
var unexpectedMatcher router.GeoIPMatcher
|
||||
if len(ns.UnexpectedGeoip) > 0 {
|
||||
unexpectedMatcher, err = router.BuildOptimizedGeoIPMatcher(ns.UnexpectedGeoip...)
|
||||
var unexpectedMatcher geodata.IPMatcher
|
||||
if len(ns.UnexpectedIp) > 0 {
|
||||
unexpectedMatcher, err = geodata.IPReg.BuildIPMatcher(ns.UnexpectedIp)
|
||||
if err != nil {
|
||||
return errors.New("failed to create unexpected ip matcher").Base(err).AtWarning()
|
||||
}
|
||||
ns.UnexpectedGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(clientIP) > 0 {
|
||||
@@ -234,7 +143,6 @@ func NewClient(
|
||||
|
||||
client.server = server
|
||||
client.skipFallback = ns.SkipFallback
|
||||
client.domains = rules
|
||||
client.expectedIPs = expectedMatcher
|
||||
client.unexpectedIPs = unexpectedMatcher
|
||||
client.actPrior = ns.ActPrior
|
||||
|
||||
@@ -12,6 +12,7 @@ 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/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
"github.com/xtls/xray-core/testing/mocks"
|
||||
@@ -303,12 +304,12 @@ func TestServiceTestRoute(t *testing.T) {
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
},
|
||||
{
|
||||
Domain: []*router.Domain{{Type: router.Domain_Domain, Value: "com"}},
|
||||
Domain: []*geodata.DomainRule{{Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "com"}}}},
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
},
|
||||
{
|
||||
SourceGeoip: []*router.GeoIP{{CountryCode: "private", Cidr: []*router.CIDR{{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
SourceIp: []*geodata.IPRule{{Value: &geodata.IPRule_Custom{Custom: &geodata.CIDR{Ip: []byte{127, 0, 0, 0}, Prefix: 8}}}},
|
||||
TargetTag: &router.RoutingRule_Tag{Tag: "out"},
|
||||
},
|
||||
{
|
||||
UserEmail: []string{"example@example.com"},
|
||||
|
||||
@@ -2,7 +2,6 @@ package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -10,8 +9,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
|
||||
@@ -45,67 +44,18 @@ 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,
|
||||
}
|
||||
type DomainMatcher struct{ geodata.DomainMatcher }
|
||||
|
||||
type DomainMatcher struct {
|
||||
Matchers strmatcher.IndexMatcher
|
||||
}
|
||||
|
||||
func SerializeDomainMatcher(domains []*Domain, w io.Writer) error {
|
||||
|
||||
g := strmatcher.NewMphMatcherGroup()
|
||||
for _, d := range domains {
|
||||
matcherType, f := matcherTypeMap[d.Type]
|
||||
if !f {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := g.AddPattern(d.Value, matcherType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
g.Build()
|
||||
// serialize
|
||||
return g.Serialize(w)
|
||||
}
|
||||
|
||||
func NewDomainMatcherFromBuffer(data []byte) (*strmatcher.MphMatcherGroup, error) {
|
||||
matcher, err := strmatcher.NewMphMatcherGroupFromBuffer(data)
|
||||
func NewDomainMatcher(rules []*geodata.DomainRule) (*DomainMatcher, error) {
|
||||
m, err := geodata.DomainReg.BuildDomainMatcher(rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
|
||||
g := strmatcher.NewMphMatcherGroup()
|
||||
for i, d := range domains {
|
||||
domains[i] = nil
|
||||
matcherType, f := matcherTypeMap[d.Type]
|
||||
if !f {
|
||||
errors.LogError(context.Background(), "ignore unsupported domain type ", d.Type, " of rule ", d.Value)
|
||||
continue
|
||||
}
|
||||
_, err := g.AddPattern(d.Value, matcherType)
|
||||
if err != nil {
|
||||
errors.LogErrorInner(context.Background(), err, "ignore domain rule ", d.Type, " ", d.Value)
|
||||
continue
|
||||
}
|
||||
}
|
||||
g.Build()
|
||||
return &DomainMatcher{
|
||||
Matchers: g,
|
||||
}, nil
|
||||
return &DomainMatcher{DomainMatcher: m}, nil
|
||||
}
|
||||
|
||||
func (m *DomainMatcher) ApplyDomain(domain string) bool {
|
||||
return len(m.Matchers.Match(strings.ToLower(domain))) > 0
|
||||
return m.DomainMatcher.MatchAny(strings.ToLower(domain))
|
||||
}
|
||||
|
||||
// Apply implements Condition.
|
||||
@@ -114,7 +64,7 @@ func (m *DomainMatcher) Apply(ctx routing.Context) bool {
|
||||
if len(domain) == 0 {
|
||||
return false
|
||||
}
|
||||
return m.ApplyDomain(domain)
|
||||
return m.DomainMatcher.MatchAny(strings.ToLower(domain))
|
||||
}
|
||||
|
||||
type MatcherAsType byte
|
||||
@@ -127,16 +77,16 @@ const (
|
||||
)
|
||||
|
||||
type IPMatcher struct {
|
||||
matcher GeoIPMatcher
|
||||
matcher geodata.IPMatcher
|
||||
asType MatcherAsType
|
||||
}
|
||||
|
||||
func NewIPMatcher(geoips []*GeoIP, asType MatcherAsType) (*IPMatcher, error) {
|
||||
matcher, err := BuildOptimizedGeoIPMatcher(geoips...)
|
||||
func NewIPMatcher(rules []*geodata.IPRule, asType MatcherAsType) (*IPMatcher, error) {
|
||||
m, err := geodata.IPReg.BuildIPMatcher(rules)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IPMatcher{matcher: matcher, asType: asType}, nil
|
||||
return &IPMatcher{matcher: m, asType: asType}, nil
|
||||
}
|
||||
|
||||
// Apply implements Condition.
|
||||
|
||||
@@ -1,962 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
type GeoIPMatcher interface {
|
||||
// TODO: (PERF) all net.IP -> netipx.Addr
|
||||
|
||||
// Invalid IP always return false.
|
||||
Match(ip net.IP) bool
|
||||
|
||||
// Returns true if *any* IP is valid and match.
|
||||
AnyMatch(ips []net.IP) bool
|
||||
|
||||
// Returns true only if *all* IPs are valid and match. Any invalid IP, or non-matching valid IP, causes false.
|
||||
Matches(ips []net.IP) bool
|
||||
|
||||
// Filters IPs. Invalid IPs are silently dropped and not included in either result.
|
||||
FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP)
|
||||
|
||||
ToggleReverse()
|
||||
|
||||
SetReverse(reverse bool)
|
||||
}
|
||||
|
||||
type GeoIPSet struct {
|
||||
ipv4, ipv6 *netipx.IPSet
|
||||
max4, max6 uint8
|
||||
}
|
||||
|
||||
type HeuristicGeoIPMatcher struct {
|
||||
ipset *GeoIPSet
|
||||
reverse bool
|
||||
}
|
||||
|
||||
type ipBucket struct {
|
||||
rep netip.Addr
|
||||
ips []net.IP
|
||||
}
|
||||
|
||||
// Match implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) Match(ip net.IP) bool {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return m.matchAddr(ipx)
|
||||
}
|
||||
|
||||
func (m *HeuristicGeoIPMatcher) matchAddr(ipx netip.Addr) bool {
|
||||
if ipx.Is4() {
|
||||
return m.ipset.ipv4.Contains(ipx) != m.reverse
|
||||
}
|
||||
if ipx.Is6() {
|
||||
return m.ipset.ipv6.Contains(ipx) != m.reverse
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyMatch implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return m.Match(ips[0])
|
||||
}
|
||||
|
||||
heur4 := m.ipset.max4 <= 24
|
||||
heur6 := m.ipset.max6 <= 64
|
||||
if !heur4 && !heur6 {
|
||||
for _, ip := range ips {
|
||||
if ipx, ok := netipx.FromStdIP(ip); ok {
|
||||
if m.matchAddr(ipx) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
buckets := make(map[[9]byte]struct{}, n)
|
||||
for _, ip := range ips {
|
||||
key, ok := prefixKeyFromIP(ip)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
heur := (key[0] == 4 && heur4) || (key[0] == 6 && heur6)
|
||||
if heur {
|
||||
if _, exists := buckets[key]; exists {
|
||||
continue
|
||||
}
|
||||
}
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if m.matchAddr(ipx) {
|
||||
return true
|
||||
}
|
||||
if heur {
|
||||
buckets[key] = struct{}{}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Matches implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return m.Match(ips[0])
|
||||
}
|
||||
|
||||
heur4 := m.ipset.max4 <= 24
|
||||
heur6 := m.ipset.max6 <= 64
|
||||
if !heur4 && !heur6 {
|
||||
for _, ip := range ips {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if !m.matchAddr(ipx) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
buckets := make(map[[9]byte]netip.Addr, n)
|
||||
precise := make([]netip.Addr, 0, n)
|
||||
|
||||
for _, ip := range ips {
|
||||
key, ok := prefixKeyFromIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if (key[0] == 4 && heur4) || (key[0] == 6 && heur6) {
|
||||
if _, exists := buckets[key]; !exists {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
buckets[key] = ipx
|
||||
}
|
||||
} else {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
precise = append(precise, ipx)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ipx := range buckets {
|
||||
if !m.matchAddr(ipx) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, ipx := range precise {
|
||||
if !m.matchAddr(ipx) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func prefixKeyFromIP(ip net.IP) (key [9]byte, ok bool) {
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
key[0] = 4
|
||||
key[1] = ip4[0]
|
||||
key[2] = ip4[1]
|
||||
key[3] = ip4[2] // /24
|
||||
return key, true
|
||||
}
|
||||
if ip16 := ip.To16(); ip16 != nil {
|
||||
key[0] = 6
|
||||
key[1] = ip16[0]
|
||||
key[2] = ip16[1]
|
||||
key[3] = ip16[2]
|
||||
key[4] = ip16[3]
|
||||
key[5] = ip16[4]
|
||||
key[6] = ip16[5]
|
||||
key[7] = ip16[6]
|
||||
key[8] = ip16[7] // /64
|
||||
return key, true
|
||||
}
|
||||
return key, false // illegal
|
||||
}
|
||||
|
||||
// FilterIPs implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return []net.IP{}, []net.IP{}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
ipx, ok := netipx.FromStdIP(ips[0])
|
||||
if !ok {
|
||||
return []net.IP{}, []net.IP{}
|
||||
}
|
||||
if m.matchAddr(ipx) {
|
||||
return ips, []net.IP{}
|
||||
}
|
||||
return []net.IP{}, ips
|
||||
}
|
||||
|
||||
heur4 := m.ipset.max4 <= 24
|
||||
heur6 := m.ipset.max6 <= 64
|
||||
if !heur4 && !heur6 {
|
||||
matched = make([]net.IP, 0, n)
|
||||
unmatched = make([]net.IP, 0, n)
|
||||
for _, ip := range ips {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
continue // illegal ip, ignore
|
||||
}
|
||||
if m.matchAddr(ipx) {
|
||||
matched = append(matched, ip)
|
||||
} else {
|
||||
unmatched = append(unmatched, ip)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
buckets := make(map[[9]byte]*ipBucket, n)
|
||||
precise := make([]net.IP, 0, n)
|
||||
|
||||
for _, ip := range ips {
|
||||
key, ok := prefixKeyFromIP(ip)
|
||||
if !ok {
|
||||
continue // illegal ip, ignore
|
||||
}
|
||||
|
||||
if (key[0] == 4 && !heur4) || (key[0] == 6 && !heur6) {
|
||||
precise = append(precise, ip)
|
||||
continue
|
||||
}
|
||||
|
||||
b, exists := buckets[key]
|
||||
if !exists {
|
||||
// build bucket
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
continue // illegal ip, ignore
|
||||
}
|
||||
b = &ipBucket{
|
||||
rep: ipx,
|
||||
ips: make([]net.IP, 0, 4), // for dns answer
|
||||
}
|
||||
buckets[key] = b
|
||||
}
|
||||
b.ips = append(b.ips, ip)
|
||||
}
|
||||
|
||||
matched = make([]net.IP, 0, n)
|
||||
unmatched = make([]net.IP, 0, n)
|
||||
for _, b := range buckets {
|
||||
if m.matchAddr(b.rep) {
|
||||
matched = append(matched, b.ips...)
|
||||
} else {
|
||||
unmatched = append(unmatched, b.ips...)
|
||||
}
|
||||
}
|
||||
for _, ip := range precise {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
continue // illegal ip, ignore
|
||||
}
|
||||
if m.matchAddr(ipx) {
|
||||
matched = append(matched, ip)
|
||||
} else {
|
||||
unmatched = append(unmatched, ip)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ToggleReverse implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) ToggleReverse() {
|
||||
m.reverse = !m.reverse
|
||||
}
|
||||
|
||||
// SetReverse implements GeoIPMatcher.
|
||||
func (m *HeuristicGeoIPMatcher) SetReverse(reverse bool) {
|
||||
m.reverse = reverse
|
||||
}
|
||||
|
||||
type GeneralMultiGeoIPMatcher struct {
|
||||
matchers []GeoIPMatcher
|
||||
}
|
||||
|
||||
// Match implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) Match(ip net.IP) bool {
|
||||
for _, m := range mm.matchers {
|
||||
if m.Match(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyMatch implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
for _, m := range mm.matchers {
|
||||
if m.AnyMatch(ips) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Matches implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
for _, m := range mm.matchers {
|
||||
if m.Matches(ips) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FilterIPs implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
matched = make([]net.IP, 0, len(ips))
|
||||
unmatched = ips
|
||||
for _, m := range mm.matchers {
|
||||
if len(unmatched) == 0 {
|
||||
break
|
||||
}
|
||||
var mtch []net.IP
|
||||
mtch, unmatched = m.FilterIPs(unmatched)
|
||||
if len(mtch) > 0 {
|
||||
matched = append(matched, mtch...)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ToggleReverse implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) ToggleReverse() {
|
||||
for _, m := range mm.matchers {
|
||||
m.ToggleReverse()
|
||||
}
|
||||
}
|
||||
|
||||
// SetReverse implements GeoIPMatcher.
|
||||
func (mm *GeneralMultiGeoIPMatcher) SetReverse(reverse bool) {
|
||||
for _, m := range mm.matchers {
|
||||
m.SetReverse(reverse)
|
||||
}
|
||||
}
|
||||
|
||||
type HeuristicMultiGeoIPMatcher struct {
|
||||
matchers []*HeuristicGeoIPMatcher
|
||||
}
|
||||
|
||||
// Match implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) Match(ip net.IP) bool {
|
||||
ipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, m := range mm.matchers {
|
||||
if m.matchAddr(ipx) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AnyMatch implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) AnyMatch(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return mm.Match(ips[0])
|
||||
}
|
||||
|
||||
buckets := make(map[[9]byte]struct{}, n)
|
||||
for _, ip := range ips {
|
||||
var ipx netip.Addr
|
||||
state := uint8(0) // 0 = Not initialized, 1 = Initialized, 4 = IPv4 can be skipped, 6 = IPv6 can be skipped
|
||||
for _, m := range mm.matchers {
|
||||
heur4 := m.ipset.max4 <= 24
|
||||
heur6 := m.ipset.max6 <= 64
|
||||
|
||||
if state == 0 && (heur4 || heur6) {
|
||||
key, ok := prefixKeyFromIP(ip)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
if _, exists := buckets[key]; exists {
|
||||
state = key[0]
|
||||
} else {
|
||||
buckets[key] = struct{}{}
|
||||
state = 1
|
||||
}
|
||||
}
|
||||
if (heur4 && state == 4) || (heur6 && state == 6) {
|
||||
continue
|
||||
}
|
||||
|
||||
if !ipx.IsValid() {
|
||||
nipx, ok := netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
ipx = nipx
|
||||
}
|
||||
if m.matchAddr(ipx) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Matches implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) Matches(ips []net.IP) bool {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
return mm.Match(ips[0])
|
||||
}
|
||||
|
||||
var views ipViews
|
||||
for _, m := range mm.matchers {
|
||||
if !views.ensureForMatcher(m, ips) {
|
||||
return false
|
||||
}
|
||||
|
||||
matched := true
|
||||
if m.ipset.max4 <= 24 {
|
||||
for _, ipx := range views.buckets4 {
|
||||
if !m.matchAddr(ipx) {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, ipx := range views.precise4 {
|
||||
if !m.matchAddr(ipx) {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
if m.ipset.max6 <= 64 {
|
||||
for _, ipx := range views.buckets6 {
|
||||
if !m.matchAddr(ipx) {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, ipx := range views.precise6 {
|
||||
if !m.matchAddr(ipx) {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ipViews struct {
|
||||
buckets4, buckets6 map[[9]byte]netip.Addr
|
||||
precise4, precise6 []netip.Addr
|
||||
}
|
||||
|
||||
func (v *ipViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP) bool {
|
||||
needHeur4 := m.ipset.max4 <= 24 && v.buckets4 == nil
|
||||
needHeur6 := m.ipset.max6 <= 64 && v.buckets6 == nil
|
||||
needPrec4 := m.ipset.max4 > 24 && v.precise4 == nil
|
||||
needPrec6 := m.ipset.max6 > 64 && v.precise6 == nil
|
||||
|
||||
if !needHeur4 && !needHeur6 && !needPrec4 && !needPrec6 {
|
||||
return true
|
||||
}
|
||||
|
||||
if needHeur4 {
|
||||
v.buckets4 = make(map[[9]byte]netip.Addr, len(ips))
|
||||
}
|
||||
if needHeur6 {
|
||||
v.buckets6 = make(map[[9]byte]netip.Addr, len(ips))
|
||||
}
|
||||
if needPrec4 {
|
||||
v.precise4 = make([]netip.Addr, 0, len(ips))
|
||||
}
|
||||
if needPrec6 {
|
||||
v.precise6 = make([]netip.Addr, 0, len(ips))
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
key, ok := prefixKeyFromIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
switch key[0] {
|
||||
case 4:
|
||||
var ipx netip.Addr
|
||||
if needHeur4 {
|
||||
if _, exists := v.buckets4[key]; !exists {
|
||||
ipx, ok = netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
v.buckets4[key] = ipx
|
||||
}
|
||||
}
|
||||
if needPrec4 {
|
||||
if !ipx.IsValid() {
|
||||
ipx, ok = netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
v.precise4 = append(v.precise4, ipx)
|
||||
}
|
||||
case 6:
|
||||
var ipx netip.Addr
|
||||
if needHeur6 {
|
||||
if _, exists := v.buckets6[key]; !exists {
|
||||
ipx, ok = netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
v.buckets6[key] = ipx
|
||||
}
|
||||
}
|
||||
if needPrec6 {
|
||||
if !ipx.IsValid() {
|
||||
ipx, ok = netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
v.precise6 = append(v.precise6, ipx)
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// FilterIPs implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) FilterIPs(ips []net.IP) (matched []net.IP, unmatched []net.IP) {
|
||||
n := len(ips)
|
||||
if n == 0 {
|
||||
return []net.IP{}, []net.IP{}
|
||||
}
|
||||
|
||||
if n == 1 {
|
||||
ipx, ok := netipx.FromStdIP(ips[0])
|
||||
if !ok {
|
||||
return []net.IP{}, []net.IP{}
|
||||
}
|
||||
for _, m := range mm.matchers {
|
||||
if m.matchAddr(ipx) {
|
||||
return ips, []net.IP{}
|
||||
}
|
||||
}
|
||||
return []net.IP{}, ips
|
||||
}
|
||||
|
||||
var views ipBucketViews
|
||||
|
||||
matched = make([]net.IP, 0, n)
|
||||
for _, m := range mm.matchers {
|
||||
views.ensureForMatcher(m, ips)
|
||||
|
||||
if m.ipset.max4 <= 24 {
|
||||
for key, b := range views.buckets4 {
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
if m.matchAddr(b.rep) {
|
||||
views.buckets4[key] = nil
|
||||
matched = append(matched, b.ips...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ipx, ip := range views.precise4 {
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
if m.matchAddr(ipx) {
|
||||
views.precise4[ipx] = nil
|
||||
matched = append(matched, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if m.ipset.max6 <= 64 {
|
||||
for key, b := range views.buckets6 {
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
if m.matchAddr(b.rep) {
|
||||
views.buckets6[key] = nil
|
||||
matched = append(matched, b.ips...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for ipx, ip := range views.precise6 {
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
if m.matchAddr(ipx) {
|
||||
views.precise6[ipx] = nil
|
||||
matched = append(matched, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unmatched = make([]net.IP, 0, n-len(matched))
|
||||
if views.buckets4 != nil {
|
||||
for _, b := range views.buckets4 {
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
unmatched = append(unmatched, b.ips...)
|
||||
}
|
||||
}
|
||||
if views.precise4 != nil {
|
||||
for _, ip := range views.precise4 {
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
unmatched = append(unmatched, ip)
|
||||
}
|
||||
}
|
||||
if views.buckets6 != nil {
|
||||
for _, b := range views.buckets6 {
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
unmatched = append(unmatched, b.ips...)
|
||||
}
|
||||
}
|
||||
if views.precise6 != nil {
|
||||
for _, ip := range views.precise6 {
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
unmatched = append(unmatched, ip)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type ipBucketViews struct {
|
||||
buckets4, buckets6 map[[9]byte]*ipBucket
|
||||
precise4, precise6 map[netip.Addr]net.IP
|
||||
}
|
||||
|
||||
func (v *ipBucketViews) ensureForMatcher(m *HeuristicGeoIPMatcher, ips []net.IP) {
|
||||
needHeur4 := m.ipset.max4 <= 24 && v.buckets4 == nil
|
||||
needHeur6 := m.ipset.max6 <= 64 && v.buckets6 == nil
|
||||
needPrec4 := m.ipset.max4 > 24 && v.precise4 == nil
|
||||
needPrec6 := m.ipset.max6 > 64 && v.precise6 == nil
|
||||
|
||||
if !needHeur4 && !needHeur6 && !needPrec4 && !needPrec6 {
|
||||
return
|
||||
}
|
||||
|
||||
if needHeur4 {
|
||||
v.buckets4 = make(map[[9]byte]*ipBucket, len(ips))
|
||||
}
|
||||
if needHeur6 {
|
||||
v.buckets6 = make(map[[9]byte]*ipBucket, len(ips))
|
||||
}
|
||||
if needPrec4 {
|
||||
v.precise4 = make(map[netip.Addr]net.IP, len(ips))
|
||||
}
|
||||
if needPrec6 {
|
||||
v.precise6 = make(map[netip.Addr]net.IP, len(ips))
|
||||
}
|
||||
|
||||
for _, ip := range ips {
|
||||
key, ok := prefixKeyFromIP(ip)
|
||||
if !ok {
|
||||
continue // illegal ip, ignore
|
||||
}
|
||||
|
||||
switch key[0] {
|
||||
case 4:
|
||||
var ipx netip.Addr
|
||||
if needHeur4 {
|
||||
b, exists := v.buckets4[key]
|
||||
if !exists {
|
||||
// build bucket
|
||||
ipx, ok = netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
continue // illegal ip, ignore
|
||||
}
|
||||
b = &ipBucket{
|
||||
rep: ipx,
|
||||
ips: make([]net.IP, 0, 4), // for dns answer
|
||||
}
|
||||
v.buckets4[key] = b
|
||||
}
|
||||
b.ips = append(b.ips, ip)
|
||||
}
|
||||
if needPrec4 {
|
||||
if !ipx.IsValid() {
|
||||
ipx, ok = netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
continue // illegal ip, ignore
|
||||
}
|
||||
}
|
||||
v.precise4[ipx] = ip
|
||||
}
|
||||
case 6:
|
||||
var ipx netip.Addr
|
||||
if needHeur6 {
|
||||
b, exists := v.buckets6[key]
|
||||
if !exists {
|
||||
// build bucket
|
||||
ipx, ok = netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
continue // illegal ip, ignore
|
||||
}
|
||||
b = &ipBucket{
|
||||
rep: ipx,
|
||||
ips: make([]net.IP, 0, 4), // for dns answer
|
||||
}
|
||||
v.buckets6[key] = b
|
||||
}
|
||||
b.ips = append(b.ips, ip)
|
||||
}
|
||||
if needPrec6 {
|
||||
if !ipx.IsValid() {
|
||||
ipx, ok = netipx.FromStdIP(ip)
|
||||
if !ok {
|
||||
continue // illegal ip, ignore
|
||||
}
|
||||
}
|
||||
v.precise6[ipx] = ip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToggleReverse implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) ToggleReverse() {
|
||||
for _, m := range mm.matchers {
|
||||
m.ToggleReverse()
|
||||
}
|
||||
}
|
||||
|
||||
// SetReverse implements GeoIPMatcher.
|
||||
func (mm *HeuristicMultiGeoIPMatcher) SetReverse(reverse bool) {
|
||||
for _, m := range mm.matchers {
|
||||
m.SetReverse(reverse)
|
||||
}
|
||||
}
|
||||
|
||||
type GeoIPSetFactory struct {
|
||||
sync.Mutex
|
||||
shared map[string]*GeoIPSet // TODO: cleanup
|
||||
}
|
||||
|
||||
var ipsetFactory = GeoIPSetFactory{shared: make(map[string]*GeoIPSet)}
|
||||
|
||||
func (f *GeoIPSetFactory) GetOrCreate(key string, cidrGroups [][]*CIDR) (*GeoIPSet, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if ipset := f.shared[key]; ipset != nil {
|
||||
return ipset, nil
|
||||
}
|
||||
|
||||
ipset, err := f.Create(cidrGroups...)
|
||||
if err == nil {
|
||||
f.shared[key] = ipset
|
||||
}
|
||||
return ipset, err
|
||||
}
|
||||
|
||||
func (f *GeoIPSetFactory) Create(cidrGroups ...[]*CIDR) (*GeoIPSet, error) {
|
||||
var ipv4Builder, ipv6Builder netipx.IPSetBuilder
|
||||
|
||||
for _, cidrGroup := range cidrGroups {
|
||||
for i, cidrEntry := range cidrGroup {
|
||||
cidrGroup[i] = nil
|
||||
ipBytes := cidrEntry.GetIp()
|
||||
prefixLen := int(cidrEntry.GetPrefix())
|
||||
|
||||
addr, ok := netip.AddrFromSlice(ipBytes)
|
||||
if !ok {
|
||||
errors.LogError(context.Background(), "ignore invalid IP byte slice: ", ipBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
prefix := netip.PrefixFrom(addr, prefixLen)
|
||||
if !prefix.IsValid() {
|
||||
errors.LogError(context.Background(), "ignore created invalid prefix from addr ", addr, " and length ", prefixLen)
|
||||
continue
|
||||
}
|
||||
|
||||
if addr.Is4() {
|
||||
ipv4Builder.AddPrefix(prefix)
|
||||
} else if addr.Is6() {
|
||||
ipv6Builder.AddPrefix(prefix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipv4, err := ipv4Builder.IPSet()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build IPv4 set").Base(err)
|
||||
}
|
||||
ipv6, err := ipv6Builder.IPSet()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build IPv6 set").Base(err)
|
||||
}
|
||||
|
||||
var max4, max6 int
|
||||
|
||||
for _, p := range ipv4.Prefixes() {
|
||||
if b := p.Bits(); b > max4 {
|
||||
max4 = b
|
||||
}
|
||||
}
|
||||
for _, p := range ipv6.Prefixes() {
|
||||
if b := p.Bits(); b > max6 {
|
||||
max6 = b
|
||||
}
|
||||
}
|
||||
|
||||
if max4 == 0 {
|
||||
max4 = 0xff
|
||||
}
|
||||
if max6 == 0 {
|
||||
max6 = 0xff
|
||||
}
|
||||
|
||||
return &GeoIPSet{ipv4: ipv4, ipv6: ipv6, max4: uint8(max4), max6: uint8(max6)}, nil
|
||||
}
|
||||
|
||||
func 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)
|
||||
|
||||
for _, geoip := range geoips {
|
||||
if geoip == nil {
|
||||
return nil, errors.New("geoip entry is nil")
|
||||
}
|
||||
if geoip.CountryCode == "" {
|
||||
ipset, err := ipsetFactory.Create(geoip.Cidr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: geoip.ReverseMatch})
|
||||
continue
|
||||
}
|
||||
if !geoip.ReverseMatch {
|
||||
pos = append(pos, geoip)
|
||||
} else {
|
||||
neg = append(neg, geoip)
|
||||
}
|
||||
}
|
||||
|
||||
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.CountryCode < gj.CountryCode
|
||||
})
|
||||
|
||||
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.CountryCode != last.CountryCode) {
|
||||
last = geoip
|
||||
sb.WriteString(geoip.CountryCode)
|
||||
sb.WriteString(",")
|
||||
cidrGroups = append(cidrGroups, geoip.Cidr)
|
||||
}
|
||||
}
|
||||
|
||||
return ipsetFactory.GetOrCreate(sb.String(), cidrGroups)
|
||||
}
|
||||
|
||||
ipset, err := buildIPSet(pos)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ipset != nil {
|
||||
subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: false})
|
||||
}
|
||||
|
||||
ipset, err = buildIPSet(neg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ipset != nil {
|
||||
subs = append(subs, &HeuristicGeoIPMatcher{ipset: ipset, reverse: true})
|
||||
}
|
||||
|
||||
switch len(subs) {
|
||||
case 0:
|
||||
return nil, errors.New("no valid geoip matcher")
|
||||
case 1:
|
||||
return subs[0], nil
|
||||
default:
|
||||
return &HeuristicMultiGeoIPMatcher{matchers: subs}, nil
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"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 getAssetPath(file string) (string, error) {
|
||||
path := platform.GetAssetLocation(file)
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
path := filepath.Join("..", "..", "resources", file)
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("can't find %s in standard asset locations or {project_root}/resources", file)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't stat %s: %v", path, err)
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't stat %s: %v", path, err)
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher(t *testing.T) {
|
||||
cidrList := []*router.CIDR{
|
||||
{Ip: []byte{0, 0, 0, 0}, Prefix: 8},
|
||||
{Ip: []byte{10, 0, 0, 0}, Prefix: 8},
|
||||
{Ip: []byte{100, 64, 0, 0}, Prefix: 10},
|
||||
{Ip: []byte{127, 0, 0, 0}, Prefix: 8},
|
||||
{Ip: []byte{169, 254, 0, 0}, Prefix: 16},
|
||||
{Ip: []byte{172, 16, 0, 0}, Prefix: 12},
|
||||
{Ip: []byte{192, 0, 0, 0}, Prefix: 24},
|
||||
{Ip: []byte{192, 0, 2, 0}, Prefix: 24},
|
||||
{Ip: []byte{192, 168, 0, 0}, Prefix: 16},
|
||||
{Ip: []byte{192, 18, 0, 0}, Prefix: 15},
|
||||
{Ip: []byte{198, 51, 100, 0}, Prefix: 24},
|
||||
{Ip: []byte{203, 0, 113, 0}, Prefix: 24},
|
||||
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
|
||||
}
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: cidrList,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "192.168.1.1",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "192.0.0.0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "192.0.1.0",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "0.1.0.0",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "1.0.0.1",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "8.8.8.7",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "91.108.255.254",
|
||||
Output: true,
|
||||
},
|
||||
}
|
||||
|
||||
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 TestGeoIPMatcherRegression(t *testing.T) {
|
||||
cidrList := []*router.CIDR{
|
||||
{Ip: []byte{98, 108, 20, 0}, Prefix: 22},
|
||||
{Ip: []byte{98, 108, 20, 0}, Prefix: 23},
|
||||
}
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: cidrList,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "98.108.22.11",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "98.108.25.0",
|
||||
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 TestGeoIPReverseMatcher(t *testing.T) {
|
||||
cidrList := []*router.CIDR{
|
||||
{Ip: []byte{8, 8, 8, 8}, Prefix: 32},
|
||||
{Ip: []byte{91, 108, 4, 0}, Prefix: 16},
|
||||
}
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: cidrList,
|
||||
})
|
||||
common.Must(err)
|
||||
matcher.SetReverse(true) // Reverse match
|
||||
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Input: "8.8.8.8",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Input: "2001:cdba::3257:9652",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Input: "91.108.255.254",
|
||||
Output: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
ip := net.ParseAddress(testCase.Input).IP()
|
||||
actual := matcher.Match(ip)
|
||||
if actual != testCase.Output {
|
||||
t.Error("expect input", testCase.Input, "to be", testCase.Output, ", but actually", actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoIPMatcher4CN(t *testing.T) {
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
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)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
if !matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP()) {
|
||||
t.Error("expect US geoip contain 2001:4860:4860::8888, but actually not")
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoIP(country string) ([]*router.CIDR, error) {
|
||||
path, err := getAssetPath("geoip.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
geoipBytes, err := filesystem.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var geoipList router.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 BenchmarkGeoIPMatcher4CN(b *testing.B) {
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = matcher.Match([]byte{8, 8, 8, 8})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGeoIPMatcher6US(b *testing.B) {
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := router.BuildOptimizedGeoIPMatcher(&router.GeoIP{
|
||||
Cidr: ips,
|
||||
})
|
||||
common.Must(err)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = matcher.Match(net.ParseAddress("2001:4860:4860::8888").IP())
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/xtls/xray-core/app/router"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
)
|
||||
|
||||
func TestDomainMatcherSerialization(t *testing.T) {
|
||||
|
||||
domains := []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "google.com"},
|
||||
{Type: router.Domain_Domain, Value: "v2ray.com"},
|
||||
{Type: router.Domain_Full, Value: "full.example.com"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := router.SerializeDomainMatcher(domains, &buf); err != nil {
|
||||
t.Fatalf("Serialize failed: %v", err)
|
||||
}
|
||||
|
||||
matcher, err := router.NewDomainMatcherFromBuffer(buf.Bytes())
|
||||
if err != nil {
|
||||
t.Fatalf("Deserialize failed: %v", err)
|
||||
}
|
||||
|
||||
dMatcher := &router.DomainMatcher{
|
||||
Matchers: matcher,
|
||||
}
|
||||
testCases := []struct {
|
||||
Input string
|
||||
Match bool
|
||||
}{
|
||||
{"google.com", true},
|
||||
{"maps.google.com", true},
|
||||
{"v2ray.com", true},
|
||||
{"full.example.com", true},
|
||||
|
||||
{"example.com", false},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
if res := dMatcher.ApplyDomain(tc.Input); res != tc.Match {
|
||||
t.Errorf("Match(%s) = %v, want %v", tc.Input, res, tc.Match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoSiteSerialization(t *testing.T) {
|
||||
sites := []*router.GeoSite{
|
||||
{
|
||||
CountryCode: "CN",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "baidu.cn"},
|
||||
{Type: router.Domain_Domain, Value: "qq.com"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "US",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "google.com"},
|
||||
{Type: router.Domain_Domain, Value: "facebook.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := router.SerializeGeoSiteList(sites, nil, nil, &buf); err != nil {
|
||||
t.Fatalf("SerializeGeoSiteList failed: %v", err)
|
||||
}
|
||||
|
||||
tmp := t.TempDir()
|
||||
path := filepath.Join(tmp, "matcher.cache")
|
||||
|
||||
f, err := os.Create(path)
|
||||
require.NoError(t, err)
|
||||
_, err = f.Write(buf.Bytes())
|
||||
require.NoError(t, err)
|
||||
f.Close()
|
||||
|
||||
f, err = os.Open(path)
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
require.NoError(t, err)
|
||||
data, _ := filesystem.ReadFile(path)
|
||||
|
||||
// cn
|
||||
gp, err := router.LoadGeoSiteMatcher(bytes.NewReader(data), "CN")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadGeoSiteMatcher(CN) failed: %v", err)
|
||||
}
|
||||
|
||||
cnMatcher := &router.DomainMatcher{
|
||||
Matchers: gp,
|
||||
}
|
||||
|
||||
if !cnMatcher.ApplyDomain("baidu.cn") {
|
||||
t.Error("CN matcher should match baidu.cn")
|
||||
}
|
||||
if cnMatcher.ApplyDomain("google.com") {
|
||||
t.Error("CN matcher should NOT match google.com")
|
||||
}
|
||||
|
||||
// us
|
||||
gp, err = router.LoadGeoSiteMatcher(bytes.NewReader(data), "US")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadGeoSiteMatcher(US) failed: %v", err)
|
||||
}
|
||||
|
||||
usMatcher := &router.DomainMatcher{
|
||||
Matchers: gp,
|
||||
}
|
||||
if !usMatcher.ApplyDomain("google.com") {
|
||||
t.Error("US matcher should match google.com")
|
||||
}
|
||||
if usMatcher.ApplyDomain("baidu.cn") {
|
||||
t.Error("US matcher should NOT match baidu.cn")
|
||||
}
|
||||
|
||||
// unknown
|
||||
_, err = router.LoadGeoSiteMatcher(bytes.NewReader(data), "unknown")
|
||||
if err == nil {
|
||||
t.Error("LoadGeoSiteMatcher(unknown) should fail")
|
||||
}
|
||||
}
|
||||
func TestGeoSiteSerializationWithDeps(t *testing.T) {
|
||||
sites := []*router.GeoSite{
|
||||
{
|
||||
CountryCode: "geosite:cn",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "baidu.cn"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "geosite:google@cn",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "google.cn"},
|
||||
},
|
||||
},
|
||||
{
|
||||
CountryCode: "rule-1",
|
||||
Domain: []*router.Domain{
|
||||
{Type: router.Domain_Domain, Value: "google.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
deps := map[string][]string{
|
||||
"rule-1": {"geosite:cn", "geosite:google@cn"},
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := router.SerializeGeoSiteList(sites, deps, nil, &buf)
|
||||
require.NoError(t, err)
|
||||
|
||||
matcher, err := router.LoadGeoSiteMatcher(bytes.NewReader(buf.Bytes()), "rule-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, matcher.Match("google.com") != nil)
|
||||
require.True(t, matcher.Match("baidu.cn") != nil)
|
||||
require.True(t, matcher.Match("google.cn") != nil)
|
||||
}
|
||||
@@ -1,20 +1,19 @@
|
||||
package router_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
. "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/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/common/protocol/http"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
routing_session "github.com/xtls/xray-core/features/routing/session"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func withBackground() routing.Context {
|
||||
@@ -45,18 +44,15 @@ func TestRoutingRule(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
rule: &RoutingRule{
|
||||
Domain: []*Domain{
|
||||
Domain: []*geodata.DomainRule{
|
||||
{
|
||||
Value: "example.com",
|
||||
Type: Domain_Plain,
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Substr, Value: "example.com"}},
|
||||
},
|
||||
{
|
||||
Value: "google.com",
|
||||
Type: Domain_Domain,
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Domain, Value: "google.com"}},
|
||||
},
|
||||
{
|
||||
Value: "^facebook\\.com$",
|
||||
Type: Domain_Regex,
|
||||
Value: &geodata.DomainRule_Custom{Custom: &geodata.Domain{Type: geodata.Domain_Regex, Value: "^facebook\\.com$"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -93,18 +89,26 @@ func TestRoutingRule(t *testing.T) {
|
||||
},
|
||||
{
|
||||
rule: &RoutingRule{
|
||||
Geoip: []*GeoIP{
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
{
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{8, 8, 8, 8},
|
||||
Prefix: 32,
|
||||
},
|
||||
{
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(),
|
||||
Prefix: 128,
|
||||
},
|
||||
@@ -133,10 +137,10 @@ func TestRoutingRule(t *testing.T) {
|
||||
},
|
||||
{
|
||||
rule: &RoutingRule{
|
||||
SourceGeoip: []*GeoIP{
|
||||
SourceIp: []*geodata.IPRule{
|
||||
{
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
},
|
||||
@@ -300,35 +304,12 @@ func TestRoutingRule(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func loadGeoSite(country string) ([]*Domain, error) {
|
||||
path, err := getAssetPath("geosite.dat")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
geositeBytes, err := filesystem.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var geositeList 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 nil, errors.New("country not found: " + country)
|
||||
}
|
||||
|
||||
func TestChinaSites(t *testing.T) {
|
||||
domains, err := loadGeoSite("CN")
|
||||
t.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
||||
common.Must(err)
|
||||
|
||||
acMatcher, err := NewMphMatcherGroup(domains)
|
||||
matcher, err := NewDomainMatcher(rules)
|
||||
common.Must(err)
|
||||
|
||||
type TestCase struct {
|
||||
@@ -359,18 +340,19 @@ func TestChinaSites(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
r := acMatcher.ApplyDomain(testCase.Domain)
|
||||
r := matcher.ApplyDomain(testCase.Domain)
|
||||
if r != testCase.Output {
|
||||
t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
|
||||
t.Error("DomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||
domains, err := loadGeoSite("CN")
|
||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
rules, err := geodata.ParseDomainRules([]string{"geosite:cn"}, geodata.Domain_Substr)
|
||||
common.Must(err)
|
||||
|
||||
matcher, err := NewMphMatcherGroup(domains)
|
||||
matcher, err := NewDomainMatcher(rules)
|
||||
common.Must(err)
|
||||
|
||||
type TestCase struct {
|
||||
@@ -409,45 +391,11 @@ func BenchmarkMphDomainMatcher(b *testing.B) {
|
||||
}
|
||||
|
||||
func BenchmarkMultiGeoIPMatcher(b *testing.B) {
|
||||
var geoips []*GeoIP
|
||||
b.Setenv("xray.location.asset", filepath.Join("..", "..", "resources"))
|
||||
rules, err := geodata.ParseIPRules([]string{"geoip:cn", "geoip:jp", "geoip:ca", "geoip:us"})
|
||||
common.Must(err)
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("CN")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "CN",
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("JP")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "JP",
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("CA")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "CA",
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
ips, err := loadGeoIP("US")
|
||||
common.Must(err)
|
||||
geoips = append(geoips, &GeoIP{
|
||||
CountryCode: "US",
|
||||
Cidr: ips,
|
||||
})
|
||||
}
|
||||
|
||||
matcher, err := NewIPMatcher(geoips, MatcherAsType_Target)
|
||||
matcher, err := NewIPMatcher(rules, MatcherAsType_Target)
|
||||
common.Must(err)
|
||||
|
||||
ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})
|
||||
|
||||
@@ -3,12 +3,9 @@ package router
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/platform"
|
||||
"github.com/xtls/xray-core/common/platform/filesystem"
|
||||
"github.com/xtls/xray-core/features/outbound"
|
||||
"github.com/xtls/xray-core/features/routing"
|
||||
)
|
||||
@@ -76,60 +73,37 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) {
|
||||
conds.Add(&AttributeMatcher{configuredKeys})
|
||||
}
|
||||
|
||||
if len(rr.Geoip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.Geoip, MatcherAsType_Target)
|
||||
if len(rr.Ip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.Ip, MatcherAsType_Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
rr.Geoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.SourceGeoip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.SourceGeoip, MatcherAsType_Source)
|
||||
if len(rr.SourceIp) > 0 {
|
||||
cond, err := NewIPMatcher(rr.SourceIp, MatcherAsType_Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
rr.SourceGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.LocalGeoip) > 0 {
|
||||
cond, err := NewIPMatcher(rr.LocalGeoip, MatcherAsType_Local)
|
||||
if len(rr.LocalIp) > 0 {
|
||||
cond, err := NewIPMatcher(rr.LocalIp, MatcherAsType_Local)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(cond)
|
||||
errors.LogWarning(context.Background(), "Due to some limitations, in UDP connections, localIP is always equal to listen interface IP, so \"localIP\" rule condition does not work properly on UDP inbound connections that listen on all interfaces")
|
||||
rr.LocalGeoip = nil
|
||||
runtime.GC()
|
||||
}
|
||||
|
||||
if len(rr.Domain) > 0 {
|
||||
var matcher *DomainMatcher
|
||||
var err error
|
||||
// Check if domain matcher cache is provided via environment
|
||||
domainMatcherPath := platform.NewEnvFlag(platform.MphCachePath).GetValue(func() string { return "" })
|
||||
|
||||
if domainMatcherPath != "" {
|
||||
matcher, err = GetDomainMatcherWithRuleTag(domainMatcherPath, rr.RuleTag)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build domain condition from cached MphDomainMatcher").Base(err)
|
||||
}
|
||||
errors.LogDebug(context.Background(), "MphDomainMatcher loaded from cache for ", rr.RuleTag, " rule tag)")
|
||||
|
||||
} else {
|
||||
matcher, err = NewMphMatcherGroup(rr.Domain)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to build domain condition with MphDomainMatcher").Base(err)
|
||||
}
|
||||
errors.LogDebug(context.Background(), "MphDomainMatcher is enabled for ", len(rr.Domain), " domain rule(s)")
|
||||
cond, err := NewDomainMatcher(rr.Domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conds.Add(matcher)
|
||||
rr.Domain = nil
|
||||
runtime.GC()
|
||||
conds.Add(cond)
|
||||
}
|
||||
|
||||
if len(rr.Process) > 0 {
|
||||
@@ -189,20 +163,3 @@ func (br *BalancingRule) Build(ohm outbound.Manager, dispatcher routing.Dispatch
|
||||
return nil, errors.New("unrecognized balancer type")
|
||||
}
|
||||
}
|
||||
|
||||
func GetDomainMatcherWithRuleTag(domainMatcherPath string, ruleTag string) (*DomainMatcher, error) {
|
||||
f, err := filesystem.NewFileReader(domainMatcherPath)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load file: ", domainMatcherPath).Base(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
g, err := LoadGeoSiteMatcher(f, ruleTag)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to load file:", domainMatcherPath).Base(err)
|
||||
}
|
||||
return &DomainMatcher{
|
||||
Matchers: g,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
geodata "github.com/xtls/xray-core/common/geodata"
|
||||
net "github.com/xtls/xray-core/common/net"
|
||||
serial "github.com/xtls/xray-core/common/serial"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
@@ -23,63 +24,6 @@ const (
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// 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 (
|
||||
@@ -116,11 +60,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 {
|
||||
@@ -129,326 +73,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{11, 0}
|
||||
}
|
||||
|
||||
// Domain for routing decision.
|
||||
type Domain struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// 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"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Domain) Reset() {
|
||||
*x = Domain{}
|
||||
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 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 `protogen:"open.v1"`
|
||||
// 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"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *CIDR) Reset() {
|
||||
*x = CIDR{}
|
||||
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 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 `protogen:"open.v1"`
|
||||
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"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoIP) Reset() {
|
||||
*x = GeoIP{}
|
||||
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 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
|
||||
}
|
||||
|
||||
func (x *GeoIP) GetReverseMatch() bool {
|
||||
if x != nil {
|
||||
return x.ReverseMatch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type GeoIPList struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Entry []*GeoIP `protobuf:"bytes,1,rep,name=entry,proto3" json:"entry,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoIPList) Reset() {
|
||||
*x = GeoIPList{}
|
||||
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 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 `protogen:"open.v1"`
|
||||
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"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeoSite) Reset() {
|
||||
*x = GeoSite{}
|
||||
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 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 `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_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 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{5, 0}
|
||||
}
|
||||
|
||||
type RoutingRule struct {
|
||||
@@ -460,37 +85,35 @@ type RoutingRule struct {
|
||||
TargetTag isRoutingRule_TargetTag `protobuf_oneof:"target_tag"`
|
||||
RuleTag string `protobuf:"bytes,19,opt,name=rule_tag,json=ruleTag,proto3" json:"rule_tag,omitempty"`
|
||||
// List of domains for target domain matching.
|
||||
Domain []*Domain `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,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"`
|
||||
// List of ports.
|
||||
Domain []*geodata.DomainRule `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||
// List of IPs for target IP address matching.
|
||||
Ip []*geodata.IPRule `protobuf:"bytes,10,rep,name=ip,proto3" json:"ip,omitempty"`
|
||||
// List of ports for target port matching.
|
||||
PortList *net.PortList `protobuf:"bytes,14,opt,name=port_list,json=portList,proto3" json:"port_list,omitempty"`
|
||||
// List of networks for matching.
|
||||
Networks []net.Network `protobuf:"varint,13,rep,packed,name=networks,proto3,enum=xray.common.net.Network" json:"networks,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"`
|
||||
// List of IPs for source IP address matching.
|
||||
SourceIp []*geodata.IPRule `protobuf:"bytes,11,rep,name=source_ip,json=sourceIp,proto3" json:"source_ip,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"`
|
||||
InboundTag []string `protobuf:"bytes,8,rep,name=inbound_tag,json=inboundTag,proto3" json:"inbound_tag,omitempty"`
|
||||
Protocol []string `protobuf:"bytes,9,rep,name=protocol,proto3" json:"protocol,omitempty"`
|
||||
Attributes map[string]string `protobuf:"bytes,15,rep,name=attributes,proto3" json:"attributes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
LocalGeoip []*GeoIP `protobuf:"bytes,17,rep,name=local_geoip,json=localGeoip,proto3" json:"local_geoip,omitempty"`
|
||||
LocalPortList *net.PortList `protobuf:"bytes,18,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_list,omitempty"`
|
||||
VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
|
||||
Process []string `protobuf:"bytes,21,rep,name=process,proto3" json:"process,omitempty"`
|
||||
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
|
||||
// List of IPs for local IP address matching.
|
||||
LocalIp []*geodata.IPRule `protobuf:"bytes,17,rep,name=local_ip,json=localIp,proto3" json:"local_ip,omitempty"`
|
||||
// List of ports for local port matching.
|
||||
LocalPortList *net.PortList `protobuf:"bytes,18,opt,name=local_port_list,json=localPortList,proto3" json:"local_port_list,omitempty"`
|
||||
VlessRouteList *net.PortList `protobuf:"bytes,20,opt,name=vless_route_list,json=vlessRouteList,proto3" json:"vless_route_list,omitempty"`
|
||||
Process []string `protobuf:"bytes,21,rep,name=process,proto3" json:"process,omitempty"`
|
||||
Webhook *WebhookConfig `protobuf:"bytes,22,opt,name=webhook,proto3" json:"webhook,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *RoutingRule) Reset() {
|
||||
*x = RoutingRule{}
|
||||
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)
|
||||
}
|
||||
@@ -502,7 +125,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 x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -515,7 +138,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 (x *RoutingRule) GetTargetTag() isRoutingRule_TargetTag {
|
||||
@@ -550,16 +173,16 @@ func (x *RoutingRule) GetRuleTag() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetDomain() []*Domain {
|
||||
func (x *RoutingRule) GetDomain() []*geodata.DomainRule {
|
||||
if x != nil {
|
||||
return x.Domain
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetGeoip() []*GeoIP {
|
||||
func (x *RoutingRule) GetIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.Geoip
|
||||
return x.Ip
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -578,9 +201,9 @@ func (x *RoutingRule) GetNetworks() []net.Network {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetSourceGeoip() []*GeoIP {
|
||||
func (x *RoutingRule) GetSourceIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.SourceGeoip
|
||||
return x.SourceIp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -620,9 +243,9 @@ func (x *RoutingRule) GetAttributes() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *RoutingRule) GetLocalGeoip() []*GeoIP {
|
||||
func (x *RoutingRule) GetLocalIp() []*geodata.IPRule {
|
||||
if x != nil {
|
||||
return x.LocalGeoip
|
||||
return x.LocalIp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -684,7 +307,7 @@ type WebhookConfig struct {
|
||||
|
||||
func (x *WebhookConfig) Reset() {
|
||||
*x = WebhookConfig{}
|
||||
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)
|
||||
}
|
||||
@@ -696,7 +319,7 @@ func (x *WebhookConfig) String() string {
|
||||
func (*WebhookConfig) ProtoMessage() {}
|
||||
|
||||
func (x *WebhookConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[7]
|
||||
mi := &file_app_router_config_proto_msgTypes[1]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -709,7 +332,7 @@ func (x *WebhookConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use WebhookConfig.ProtoReflect.Descriptor instead.
|
||||
func (*WebhookConfig) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{7}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *WebhookConfig) GetUrl() string {
|
||||
@@ -746,7 +369,7 @@ type BalancingRule struct {
|
||||
|
||||
func (x *BalancingRule) Reset() {
|
||||
*x = BalancingRule{}
|
||||
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)
|
||||
}
|
||||
@@ -758,7 +381,7 @@ func (x *BalancingRule) String() string {
|
||||
func (*BalancingRule) ProtoMessage() {}
|
||||
|
||||
func (x *BalancingRule) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[8]
|
||||
mi := &file_app_router_config_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -771,7 +394,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{8}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *BalancingRule) GetTag() string {
|
||||
@@ -820,7 +443,7 @@ type StrategyWeight struct {
|
||||
|
||||
func (x *StrategyWeight) Reset() {
|
||||
*x = StrategyWeight{}
|
||||
mi := &file_app_router_config_proto_msgTypes[9]
|
||||
mi := &file_app_router_config_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -832,7 +455,7 @@ func (x *StrategyWeight) String() string {
|
||||
func (*StrategyWeight) ProtoMessage() {}
|
||||
|
||||
func (x *StrategyWeight) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[9]
|
||||
mi := &file_app_router_config_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -845,7 +468,7 @@ func (x *StrategyWeight) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StrategyWeight.ProtoReflect.Descriptor instead.
|
||||
func (*StrategyWeight) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{9}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *StrategyWeight) GetRegexp() bool {
|
||||
@@ -887,7 +510,7 @@ type StrategyLeastLoadConfig struct {
|
||||
|
||||
func (x *StrategyLeastLoadConfig) Reset() {
|
||||
*x = StrategyLeastLoadConfig{}
|
||||
mi := &file_app_router_config_proto_msgTypes[10]
|
||||
mi := &file_app_router_config_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -899,7 +522,7 @@ func (x *StrategyLeastLoadConfig) String() string {
|
||||
func (*StrategyLeastLoadConfig) ProtoMessage() {}
|
||||
|
||||
func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[10]
|
||||
mi := &file_app_router_config_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -912,7 +535,7 @@ func (x *StrategyLeastLoadConfig) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use StrategyLeastLoadConfig.ProtoReflect.Descriptor instead.
|
||||
func (*StrategyLeastLoadConfig) Descriptor() ([]byte, []int) {
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{10}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *StrategyLeastLoadConfig) GetCosts() []*StrategyWeight {
|
||||
@@ -961,7 +584,7 @@ type Config struct {
|
||||
|
||||
func (x *Config) Reset() {
|
||||
*x = Config{}
|
||||
mi := &file_app_router_config_proto_msgTypes[11]
|
||||
mi := &file_app_router_config_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -973,7 +596,7 @@ func (x *Config) String() string {
|
||||
func (*Config) ProtoMessage() {}
|
||||
|
||||
func (x *Config) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_app_router_config_proto_msgTypes[11]
|
||||
mi := &file_app_router_config_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -986,7 +609,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{11}
|
||||
return file_app_router_config_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *Config) GetDomainStrategy() Config_DomainStrategy {
|
||||
@@ -1010,141 +633,21 @@ func (x *Config) GetBalancingRule() []*BalancingRule {
|
||||
return nil
|
||||
}
|
||||
|
||||
type Domain_Attribute struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
// Types that are valid to be assigned to TypedValue:
|
||||
//
|
||||
// *Domain_Attribute_BoolValue
|
||||
// *Domain_Attribute_IntValue
|
||||
TypedValue isDomain_Attribute_TypedValue `protobuf_oneof:"typed_value"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) Reset() {
|
||||
*x = Domain_Attribute{}
|
||||
mi := &file_app_router_config_proto_msgTypes[12]
|
||||
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[12]
|
||||
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 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 (x *Domain_Attribute) GetTypedValue() isDomain_Attribute_TypedValue {
|
||||
if x != nil {
|
||||
return x.TypedValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetBoolValue() bool {
|
||||
if x != nil {
|
||||
if x, ok := x.TypedValue.(*Domain_Attribute_BoolValue); ok {
|
||||
return x.BoolValue
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *Domain_Attribute) GetIntValue() int64 {
|
||||
if x != nil {
|
||||
if x, ok := x.TypedValue.(*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
|
||||
|
||||
const file_app_router_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x17app/router/config.proto\x12\x0fxray.app.router\x1a!common/serial/typed_message.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\"\xb3\x02\n" +
|
||||
"\x06Domain\x120\n" +
|
||||
"\x04type\x18\x01 \x01(\x0e2\x1c.xray.app.router.Domain.TypeR\x04type\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value\x12?\n" +
|
||||
"\tattribute\x18\x03 \x03(\v2!.xray.app.router.Domain.AttributeR\tattribute\x1al\n" +
|
||||
"\tAttribute\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x1f\n" +
|
||||
"\n" +
|
||||
"bool_value\x18\x02 \x01(\bH\x00R\tboolValue\x12\x1d\n" +
|
||||
"\tint_value\x18\x03 \x01(\x03H\x00R\bintValueB\r\n" +
|
||||
"\vtyped_value\"2\n" +
|
||||
"\x04Type\x12\t\n" +
|
||||
"\x05Plain\x10\x00\x12\t\n" +
|
||||
"\x05Regex\x10\x01\x12\n" +
|
||||
"\n" +
|
||||
"\x06Domain\x10\x02\x12\b\n" +
|
||||
"\x04Full\x10\x03\".\n" +
|
||||
"\x04CIDR\x12\x0e\n" +
|
||||
"\x02ip\x18\x01 \x01(\fR\x02ip\x12\x16\n" +
|
||||
"\x06prefix\x18\x02 \x01(\rR\x06prefix\"z\n" +
|
||||
"\x05GeoIP\x12!\n" +
|
||||
"\fcountry_code\x18\x01 \x01(\tR\vcountryCode\x12)\n" +
|
||||
"\x04cidr\x18\x02 \x03(\v2\x15.xray.app.router.CIDRR\x04cidr\x12#\n" +
|
||||
"\rreverse_match\x18\x03 \x01(\bR\freverseMatch\"9\n" +
|
||||
"\tGeoIPList\x12,\n" +
|
||||
"\x05entry\x18\x01 \x03(\v2\x16.xray.app.router.GeoIPR\x05entry\"]\n" +
|
||||
"\aGeoSite\x12!\n" +
|
||||
"\fcountry_code\x18\x01 \x01(\tR\vcountryCode\x12/\n" +
|
||||
"\x06domain\x18\x02 \x03(\v2\x17.xray.app.router.DomainR\x06domain\"=\n" +
|
||||
"\vGeoSiteList\x12.\n" +
|
||||
"\x05entry\x18\x01 \x03(\v2\x18.xray.app.router.GeoSiteR\x05entry\"\xbc\a\n" +
|
||||
"\x17app/router/config.proto\x12\x0fxray.app.router\x1a!common/serial/typed_message.proto\x1a\x15common/net/port.proto\x1a\x18common/net/network.proto\x1a\x1bcommon/geodata/geodat.proto\"\xc1\a\n" +
|
||||
"\vRoutingRule\x12\x12\n" +
|
||||
"\x03tag\x18\x01 \x01(\tH\x00R\x03tag\x12%\n" +
|
||||
"\rbalancing_tag\x18\f \x01(\tH\x00R\fbalancingTag\x12\x19\n" +
|
||||
"\brule_tag\x18\x13 \x01(\tR\aruleTag\x12/\n" +
|
||||
"\x06domain\x18\x02 \x03(\v2\x17.xray.app.router.DomainR\x06domain\x12,\n" +
|
||||
"\x05geoip\x18\n" +
|
||||
" \x03(\v2\x16.xray.app.router.GeoIPR\x05geoip\x126\n" +
|
||||
"\brule_tag\x18\x13 \x01(\tR\aruleTag\x127\n" +
|
||||
"\x06domain\x18\x02 \x03(\v2\x1f.xray.common.geodata.DomainRuleR\x06domain\x12+\n" +
|
||||
"\x02ip\x18\n" +
|
||||
" \x03(\v2\x1b.xray.common.geodata.IPRuleR\x02ip\x126\n" +
|
||||
"\tport_list\x18\x0e \x01(\v2\x19.xray.common.net.PortListR\bportList\x124\n" +
|
||||
"\bnetworks\x18\r \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x129\n" +
|
||||
"\fsource_geoip\x18\v \x03(\v2\x16.xray.app.router.GeoIPR\vsourceGeoip\x12C\n" +
|
||||
"\bnetworks\x18\r \x03(\x0e2\x18.xray.common.net.NetworkR\bnetworks\x128\n" +
|
||||
"\tsource_ip\x18\v \x03(\v2\x1b.xray.common.geodata.IPRuleR\bsourceIp\x12C\n" +
|
||||
"\x10source_port_list\x18\x10 \x01(\v2\x19.xray.common.net.PortListR\x0esourcePortList\x12\x1d\n" +
|
||||
"\n" +
|
||||
"user_email\x18\a \x03(\tR\tuserEmail\x12\x1f\n" +
|
||||
@@ -1153,9 +656,8 @@ const file_app_router_config_proto_rawDesc = "" +
|
||||
"\bprotocol\x18\t \x03(\tR\bprotocol\x12L\n" +
|
||||
"\n" +
|
||||
"attributes\x18\x0f \x03(\v2,.xray.app.router.RoutingRule.AttributesEntryR\n" +
|
||||
"attributes\x127\n" +
|
||||
"\vlocal_geoip\x18\x11 \x03(\v2\x16.xray.app.router.GeoIPR\n" +
|
||||
"localGeoip\x12A\n" +
|
||||
"attributes\x126\n" +
|
||||
"\blocal_ip\x18\x11 \x03(\v2\x1b.xray.common.geodata.IPRuleR\alocalIp\x12A\n" +
|
||||
"\x0flocal_port_list\x18\x12 \x01(\v2\x19.xray.common.net.PortListR\rlocalPortList\x12C\n" +
|
||||
"\x10vless_route_list\x18\x14 \x01(\v2\x19.xray.common.net.PortListR\x0evlessRouteList\x12\x18\n" +
|
||||
"\aprocess\x18\x15 \x03(\tR\aprocess\x128\n" +
|
||||
@@ -1187,16 +689,16 @@ const file_app_router_config_proto_rawDesc = "" +
|
||||
"\tbaselines\x18\x03 \x03(\x03R\tbaselines\x12\x1a\n" +
|
||||
"\bexpected\x18\x04 \x01(\x05R\bexpected\x12\x16\n" +
|
||||
"\x06maxRTT\x18\x05 \x01(\x03R\x06maxRTT\x12\x1c\n" +
|
||||
"\ttolerance\x18\x06 \x01(\x02R\ttolerance\"\x90\x02\n" +
|
||||
"\ttolerance\x18\x06 \x01(\x02R\ttolerance\"\x96\x02\n" +
|
||||
"\x06Config\x12O\n" +
|
||||
"\x0fdomain_strategy\x18\x01 \x01(\x0e2&.xray.app.router.Config.DomainStrategyR\x0edomainStrategy\x120\n" +
|
||||
"\x04rule\x18\x02 \x03(\v2\x1c.xray.app.router.RoutingRuleR\x04rule\x12E\n" +
|
||||
"\x0ebalancing_rule\x18\x03 \x03(\v2\x1e.xray.app.router.BalancingRuleR\rbalancingRule\"<\n" +
|
||||
"\x0ebalancing_rule\x18\x03 \x03(\v2\x1e.xray.app.router.BalancingRuleR\rbalancingRule\"B\n" +
|
||||
"\x0eDomainStrategy\x12\b\n" +
|
||||
"\x04AsIs\x10\x00\x12\x10\n" +
|
||||
"\fIpIfNonMatch\x10\x02\x12\x0e\n" +
|
||||
"\n" +
|
||||
"IpOnDemand\x10\x03BO\n" +
|
||||
"IpOnDemand\x10\x03\"\x04\b\x01\x10\x01BO\n" +
|
||||
"\x13com.xray.app.routerP\x01Z$github.com/xtls/xray-core/app/router\xaa\x02\x0fXray.App.Routerb\x06proto3"
|
||||
|
||||
var (
|
||||
@@ -1211,59 +713,47 @@ 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, 15)
|
||||
var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
|
||||
var file_app_router_config_proto_goTypes = []any{
|
||||
(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
|
||||
(*WebhookConfig)(nil), // 9: xray.app.router.WebhookConfig
|
||||
(*BalancingRule)(nil), // 10: xray.app.router.BalancingRule
|
||||
(*StrategyWeight)(nil), // 11: xray.app.router.StrategyWeight
|
||||
(*StrategyLeastLoadConfig)(nil), // 12: xray.app.router.StrategyLeastLoadConfig
|
||||
(*Config)(nil), // 13: xray.app.router.Config
|
||||
(*Domain_Attribute)(nil), // 14: xray.app.router.Domain.Attribute
|
||||
nil, // 15: xray.app.router.RoutingRule.AttributesEntry
|
||||
nil, // 16: xray.app.router.WebhookConfig.HeadersEntry
|
||||
(*net.PortList)(nil), // 17: xray.common.net.PortList
|
||||
(net.Network)(0), // 18: xray.common.net.Network
|
||||
(*serial.TypedMessage)(nil), // 19: xray.common.serial.TypedMessage
|
||||
(Config_DomainStrategy)(0), // 0: xray.app.router.Config.DomainStrategy
|
||||
(*RoutingRule)(nil), // 1: xray.app.router.RoutingRule
|
||||
(*WebhookConfig)(nil), // 2: xray.app.router.WebhookConfig
|
||||
(*BalancingRule)(nil), // 3: xray.app.router.BalancingRule
|
||||
(*StrategyWeight)(nil), // 4: xray.app.router.StrategyWeight
|
||||
(*StrategyLeastLoadConfig)(nil), // 5: xray.app.router.StrategyLeastLoadConfig
|
||||
(*Config)(nil), // 6: xray.app.router.Config
|
||||
nil, // 7: xray.app.router.RoutingRule.AttributesEntry
|
||||
nil, // 8: xray.app.router.WebhookConfig.HeadersEntry
|
||||
(*geodata.DomainRule)(nil), // 9: xray.common.geodata.DomainRule
|
||||
(*geodata.IPRule)(nil), // 10: xray.common.geodata.IPRule
|
||||
(*net.PortList)(nil), // 11: xray.common.net.PortList
|
||||
(net.Network)(0), // 12: xray.common.net.Network
|
||||
(*serial.TypedMessage)(nil), // 13: xray.common.serial.TypedMessage
|
||||
}
|
||||
var file_app_router_config_proto_depIdxs = []int32{
|
||||
0, // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type
|
||||
14, // 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
|
||||
4, // 7: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP
|
||||
17, // 8: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList
|
||||
18, // 9: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network
|
||||
4, // 10: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP
|
||||
17, // 11: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList
|
||||
15, // 12: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry
|
||||
4, // 13: xray.app.router.RoutingRule.local_geoip:type_name -> xray.app.router.GeoIP
|
||||
17, // 14: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList
|
||||
17, // 15: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList
|
||||
9, // 16: xray.app.router.RoutingRule.webhook:type_name -> xray.app.router.WebhookConfig
|
||||
16, // 17: xray.app.router.WebhookConfig.headers:type_name -> xray.app.router.WebhookConfig.HeadersEntry
|
||||
19, // 18: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage
|
||||
11, // 19: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight
|
||||
1, // 20: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
|
||||
8, // 21: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
|
||||
10, // 22: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
|
||||
23, // [23:23] is the sub-list for method output_type
|
||||
23, // [23:23] is the sub-list for method input_type
|
||||
23, // [23:23] is the sub-list for extension type_name
|
||||
23, // [23:23] is the sub-list for extension extendee
|
||||
0, // [0:23] is the sub-list for field type_name
|
||||
9, // 0: xray.app.router.RoutingRule.domain:type_name -> xray.common.geodata.DomainRule
|
||||
10, // 1: xray.app.router.RoutingRule.ip:type_name -> xray.common.geodata.IPRule
|
||||
11, // 2: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList
|
||||
12, // 3: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network
|
||||
10, // 4: xray.app.router.RoutingRule.source_ip:type_name -> xray.common.geodata.IPRule
|
||||
11, // 5: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList
|
||||
7, // 6: xray.app.router.RoutingRule.attributes:type_name -> xray.app.router.RoutingRule.AttributesEntry
|
||||
10, // 7: xray.app.router.RoutingRule.local_ip:type_name -> xray.common.geodata.IPRule
|
||||
11, // 8: xray.app.router.RoutingRule.local_port_list:type_name -> xray.common.net.PortList
|
||||
11, // 9: xray.app.router.RoutingRule.vless_route_list:type_name -> xray.common.net.PortList
|
||||
2, // 10: xray.app.router.RoutingRule.webhook:type_name -> xray.app.router.WebhookConfig
|
||||
8, // 11: xray.app.router.WebhookConfig.headers:type_name -> xray.app.router.WebhookConfig.HeadersEntry
|
||||
13, // 12: xray.app.router.BalancingRule.strategy_settings:type_name -> xray.common.serial.TypedMessage
|
||||
4, // 13: xray.app.router.StrategyLeastLoadConfig.costs:type_name -> xray.app.router.StrategyWeight
|
||||
0, // 14: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy
|
||||
1, // 15: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule
|
||||
3, // 16: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule
|
||||
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_router_config_proto_init() }
|
||||
@@ -1271,21 +761,17 @@ func file_app_router_config_proto_init() {
|
||||
if File_app_router_config_proto != nil {
|
||||
return
|
||||
}
|
||||
file_app_router_config_proto_msgTypes[6].OneofWrappers = []any{
|
||||
file_app_router_config_proto_msgTypes[0].OneofWrappers = []any{
|
||||
(*RoutingRule_Tag)(nil),
|
||||
(*RoutingRule_BalancingTag)(nil),
|
||||
}
|
||||
file_app_router_config_proto_msgTypes[12].OneofWrappers = []any{
|
||||
(*Domain_Attribute_BoolValue)(nil),
|
||||
(*Domain_Attribute_IntValue)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_app_router_config_proto_rawDesc), len(file_app_router_config_proto_rawDesc)),
|
||||
NumEnums: 2,
|
||||
NumMessages: 15,
|
||||
NumEnums: 1,
|
||||
NumMessages: 8,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
@@ -9,67 +9,7 @@ option java_multiple_files = true;
|
||||
import "common/serial/typed_message.proto";
|
||||
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;
|
||||
bool reverse_match = 3;
|
||||
}
|
||||
|
||||
message GeoIPList {
|
||||
repeated GeoIP entry = 1;
|
||||
}
|
||||
|
||||
message GeoSite {
|
||||
string country_code = 1;
|
||||
repeated Domain domain = 2;
|
||||
}
|
||||
|
||||
message GeoSiteList {
|
||||
repeated GeoSite entry = 1;
|
||||
}
|
||||
import "common/geodata/geodat.proto";
|
||||
|
||||
message RoutingRule {
|
||||
oneof target_tag {
|
||||
@@ -79,26 +19,23 @@ message RoutingRule {
|
||||
// Tag of routing balancer.
|
||||
string balancing_tag = 12;
|
||||
}
|
||||
string rule_tag = 19;
|
||||
|
||||
string rule_tag = 19;
|
||||
|
||||
// List of domains for target domain matching.
|
||||
repeated Domain domain = 2;
|
||||
repeated xray.common.geodata.DomainRule domain = 2;
|
||||
|
||||
// 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;
|
||||
// List of IPs for target IP address matching.
|
||||
repeated xray.common.geodata.IPRule ip = 10;
|
||||
|
||||
// List of ports.
|
||||
// List of ports for target port matching.
|
||||
xray.common.net.PortList port_list = 14;
|
||||
|
||||
// List of networks for matching.
|
||||
repeated xray.common.net.Network networks = 13;
|
||||
|
||||
// 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;
|
||||
// List of IPs for source IP address matching.
|
||||
repeated xray.common.geodata.IPRule source_ip = 11;
|
||||
|
||||
// List of ports for source port matching.
|
||||
xray.common.net.PortList source_port_list = 16;
|
||||
@@ -109,10 +46,14 @@ message RoutingRule {
|
||||
|
||||
map<string, string> attributes = 15;
|
||||
|
||||
repeated GeoIP local_geoip = 17;
|
||||
// List of IPs for local IP address matching.
|
||||
repeated xray.common.geodata.IPRule local_ip = 17;
|
||||
|
||||
// List of ports for local port matching.
|
||||
xray.common.net.PortList local_port_list = 18;
|
||||
|
||||
xray.common.net.PortList vless_route_list = 20;
|
||||
|
||||
repeated string process = 21;
|
||||
WebhookConfig webhook = 22;
|
||||
}
|
||||
@@ -155,8 +96,7 @@ message Config {
|
||||
// Use domain as is.
|
||||
AsIs = 0;
|
||||
|
||||
// [Deprecated] Always resolve IP for domains.
|
||||
// UseIp = 1;
|
||||
reserved 1;
|
||||
|
||||
// Resolve to IP if the domain doesn't match any rules.
|
||||
IpIfNonMatch = 2;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
)
|
||||
|
||||
type geoSiteListGob struct {
|
||||
Sites map[string][]byte
|
||||
Deps map[string][]string
|
||||
Hosts map[string][]string
|
||||
}
|
||||
|
||||
func SerializeGeoSiteList(sites []*GeoSite, deps map[string][]string, hosts map[string][]string, w io.Writer) error {
|
||||
data := geoSiteListGob{
|
||||
Sites: make(map[string][]byte),
|
||||
Deps: deps,
|
||||
Hosts: hosts,
|
||||
}
|
||||
|
||||
for _, site := range sites {
|
||||
if site == nil {
|
||||
continue
|
||||
}
|
||||
var buf bytesWriter
|
||||
if err := SerializeDomainMatcher(site.Domain, &buf); err != nil {
|
||||
return err
|
||||
}
|
||||
data.Sites[site.CountryCode] = buf.Bytes()
|
||||
}
|
||||
|
||||
return gob.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
type bytesWriter struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (w *bytesWriter) Write(p []byte) (n int, err error) {
|
||||
w.data = append(w.data, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *bytesWriter) Bytes() []byte {
|
||||
return w.data
|
||||
}
|
||||
|
||||
func LoadGeoSiteMatcher(r io.Reader, countryCode string) (strmatcher.IndexMatcher, error) {
|
||||
var data geoSiteListGob
|
||||
if err := gob.NewDecoder(r).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return loadWithDeps(&data, countryCode, make(map[string]bool))
|
||||
}
|
||||
|
||||
func loadWithDeps(data *geoSiteListGob, code string, visited map[string]bool) (strmatcher.IndexMatcher, error) {
|
||||
if visited[code] {
|
||||
return nil, errors.New("cyclic dependency")
|
||||
}
|
||||
visited[code] = true
|
||||
|
||||
var matchers []strmatcher.IndexMatcher
|
||||
|
||||
if siteData, ok := data.Sites[code]; ok {
|
||||
m, err := NewDomainMatcherFromBuffer(siteData)
|
||||
if err == nil {
|
||||
matchers = append(matchers, m)
|
||||
}
|
||||
}
|
||||
|
||||
if deps, ok := data.Deps[code]; ok {
|
||||
for _, dep := range deps {
|
||||
m, err := loadWithDeps(data, dep, visited)
|
||||
if err == nil {
|
||||
matchers = append(matchers, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(matchers) == 0 {
|
||||
return nil, errors.New("matcher not found for: " + code)
|
||||
}
|
||||
if len(matchers) == 1 {
|
||||
return matchers[0], nil
|
||||
}
|
||||
runtime.GC()
|
||||
return &strmatcher.IndexMatcherGroup{Matchers: matchers}, nil
|
||||
}
|
||||
func LoadGeoSiteHosts(r io.Reader) (map[string][]string, error) {
|
||||
var data geoSiteListGob
|
||||
if err := gob.NewDecoder(r).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data.Hosts, nil
|
||||
}
|
||||
@@ -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/geodata"
|
||||
"github.com/xtls/xray-core/common/net"
|
||||
"github.com/xtls/xray-core/common/session"
|
||||
"github.com/xtls/xray-core/features/dns"
|
||||
@@ -155,10 +156,10 @@ func TestIPOnDemand(t *testing.T) {
|
||||
TargetTag: &RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
Geoip: []*GeoIP{
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
},
|
||||
@@ -200,10 +201,10 @@ func TestIPIfNonMatchDomain(t *testing.T) {
|
||||
TargetTag: &RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
Geoip: []*GeoIP{
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{192, 168, 0, 0},
|
||||
Prefix: 16,
|
||||
},
|
||||
@@ -245,10 +246,10 @@ func TestIPIfNonMatchIP(t *testing.T) {
|
||||
TargetTag: &RoutingRule_Tag{
|
||||
Tag: "test",
|
||||
},
|
||||
Geoip: []*GeoIP{
|
||||
Ip: []*geodata.IPRule{
|
||||
{
|
||||
Cidr: []*CIDR{
|
||||
{
|
||||
Value: &geodata.IPRule_Custom{
|
||||
Custom: &geodata.CIDR{
|
||||
Ip: []byte{127, 0, 0, 0},
|
||||
Prefix: 8,
|
||||
},
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/strmatcher"
|
||||
"github.com/xtls/xray-core/core"
|
||||
feature_stats "github.com/xtls/xray-core/features/stats"
|
||||
grpc "google.golang.org/grpc"
|
||||
@@ -163,15 +162,10 @@ func (s *statsServer) GetUsersStats(ctx context.Context, request *GetUsersStatsR
|
||||
}
|
||||
|
||||
func (s *statsServer) QueryStats(ctx context.Context, request *QueryStatsRequest) (*QueryStatsResponse, error) {
|
||||
matcher, err := strmatcher.Substr.New(request.Pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &QueryStatsResponse{}
|
||||
|
||||
s.stats.VisitCounters(func(name string, c feature_stats.Counter) bool {
|
||||
if matcher.Match(name) {
|
||||
if strings.Contains(name, request.Pattern) {
|
||||
var value int64
|
||||
if request.Reset_ {
|
||||
value = c.Set(0)
|
||||
|
||||
Reference in New Issue
Block a user