diff --git a/app/router/condition.go b/app/router/condition.go index 8c5d1de87b1b..7a798f50e9b8 100644 --- a/app/router/condition.go +++ b/app/router/condition.go @@ -41,6 +41,32 @@ func (v *ConditionChan) Len() int { return len(*v) } +type OrConditionChan []Condition + +func NewOrConditionChan() *OrConditionChan { + var condChan OrConditionChan = make([]Condition, 0, 8) + return &condChan +} + +func (v *OrConditionChan) Add(cond Condition) *OrConditionChan { + *v = append(*v, cond) + return v +} + +// Apply applies all conditions registered in this chan. +func (v *OrConditionChan) Apply(ctx routing.Context) bool { + for _, cond := range *v { + if cond.Apply(ctx) { + return true + } + } + return false +} + +func (v *OrConditionChan) Len() int { + return len(*v) +} + var matcherTypeMap = map[Domain_Type]strmatcher.Type{ Domain_Plain: strmatcher.Substr, Domain_Regex: strmatcher.Regex, diff --git a/app/router/condition_test.go b/app/router/condition_test.go index 3f7185e6633c..7671f2db477c 100644 --- a/app/router/condition_test.go +++ b/app/router/condition_test.go @@ -321,7 +321,7 @@ func TestRoutingRule(t *testing.T) { } for _, test := range cases { - cond, err := test.rule.BuildCondition() + cond, err := test.rule.BuildCondition(new(RulesetManager)) common.Must(err) for _, subtest := range test.test { @@ -444,3 +444,67 @@ func BenchmarkMultiGeoIPMatcher(b *testing.B) { _ = matcher.Apply(ctx) } } + +func TestRuleSet(t *testing.T) { + type ruleTest struct { + input routing.Context + output bool + } + + cases := []struct { + rule *RoutingRule + manager *RulesetManager + test []ruleTest + }{ + { + &RoutingRule{RuleSet: "ruleset1", SourcePortList: &net.PortList{Range: []*net.PortRange{{From: 1000, To: 10000}}}}, + func() *RulesetManager { + r := NewRSManager() + r.Add("ruleset1", &RoutingRules{Rules: []*RoutingRule{ + {InboundTag: []string{"in1"}}, + }, Identifier: "ruleset1"}) + return r + }(), + []ruleTest{ + {withInbound(&session.Inbound{Tag: "in1", Source: net.TCPDestination(net.LocalHostIP, 8972)}), true}, + {withInbound(&session.Inbound{Tag: "in1", Source: net.TCPDestination(net.LocalHostIP, 972)}), false}, + {withInbound(&session.Inbound{Tag: "in2", Source: net.TCPDestination(net.LocalHostIP, 8972)}), false}, + {withInbound(&session.Inbound{Tag: "in2", Source: net.TCPDestination(net.LocalHostIP, 972)}), false}, + }, + }, + { + &RoutingRule{RuleSet: "ruleset1", SourcePortList: &net.PortList{Range: []*net.PortRange{{From: 5000, To: 10000}}}}, + func() *RulesetManager { + r := NewRSManager() + r.Add("ruleset1", &RoutingRules{Rules: []*RoutingRule{ + {SourcePortList: &net.PortList{Range: []*net.PortRange{{From: 1000, To: 10000}}}, RuleSet: "ruleset2"}, + }, Identifier: "ruleset1"}) + r.Add("ruleset2", &RoutingRules{Rules: []*RoutingRule{ + {InboundTag: []string{"in1"}}, + }, Identifier: "ruleset2"}) + return r + }(), + []ruleTest{ + {withInbound(&session.Inbound{Tag: "in1", Source: net.TCPDestination(net.LocalHostIP, 8972)}), true}, + {withInbound(&session.Inbound{Tag: "in1", Source: net.TCPDestination(net.LocalHostIP, 972)}), false}, + {withInbound(&session.Inbound{Tag: "in2", Source: net.TCPDestination(net.LocalHostIP, 8972)}), false}, + {withInbound(&session.Inbound{Tag: "in2", Source: net.TCPDestination(net.LocalHostIP, 972)}), false}, + {withInbound(&session.Inbound{Tag: "in1", Source: net.TCPDestination(net.LocalHostIP, 3972)}), false}, + {withInbound(&session.Inbound{Tag: "in2", Source: net.TCPDestination(net.LocalHostIP, 3972)}), false}, + }, + }, + } + + for _, test := range cases { + cond, err := test.rule.BuildCondition(test.manager) + common.Must(err) + + for _, subtest := range test.test { + actual := cond.Apply(subtest.input) + if actual != subtest.output { + t.Error("test case failed: ", subtest.input, " expected ", subtest.output, " but got ", actual) + } + } + } + +} diff --git a/app/router/config.go b/app/router/config.go index 07167a530198..8ff6bb253ef2 100644 --- a/app/router/config.go +++ b/app/router/config.go @@ -63,7 +63,7 @@ func (r *Rule) Apply(ctx routing.Context) bool { return r.Condition.Apply(ctx) } -func (rr *RoutingRule) BuildCondition() (Condition, error) { +func (rr *RoutingRule) BuildCondition(rsm *RulesetManager) (*ConditionChan, error) { conds := NewConditionChan() if len(rr.Domain) > 0 { @@ -138,7 +138,15 @@ func (rr *RoutingRule) BuildCondition() (Condition, error) { conds.Add(cond) } - if conds.Len() == 0 { + if rr.RuleSet != "" { + if rsCond, err := rsm.getRuleSet(rr.RuleSet); err != nil { + return nil, err + } else { + conds.Add(rsCond) + } + } + + if conds.Len() == 0 && rr.RuleSet == "" { return nil, newError("this rule has no effective fields").AtWarning() } @@ -152,3 +160,27 @@ func (br *BalancingRule) Build(ohm outbound.Manager) (*Balancer, error) { ohm: ohm, }, nil } + +func (rrs *RoutingRules) BuildCondition(rsm *RulesetManager) (Condition, error) { + conds := NewOrConditionChan() + + for _, rr := range rrs.Rules { + if rr.GetTag() != "" || rr.GetBalancingTag() != "" { + newError("ignoring tag in rule set").AtWarning().WriteToLog() + } + + if rr.RuleSet == rrs.Identifier { + return nil, newError("import cycle found for tag: " + rrs.Identifier) + } + cond, err := rr.BuildCondition(rsm) + if err != nil { + return nil, err + } + conds.Add(cond) + } + if conds.Len() == 0 { + return nil, newError("this rule set has no effective fields").AtWarning() + } + + return conds, nil +} diff --git a/app/router/config.pb.go b/app/router/config.pb.go index 69abd3706158..5794e6bb8e7c 100644 --- a/app/router/config.pb.go +++ b/app/router/config.pb.go @@ -136,7 +136,7 @@ func (x Config_DomainStrategy) Number() protoreflect.EnumNumber { // Deprecated: Use Config_DomainStrategy.Descriptor instead. func (Config_DomainStrategy) EnumDescriptor() ([]byte, []int) { - return file_app_router_config_proto_rawDescGZIP(), []int{8, 0} + return file_app_router_config_proto_rawDescGZIP(), []int{9, 0} } // Domain for routing decision. @@ -515,6 +515,7 @@ type RoutingRule struct { 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 string `protobuf:"bytes,15,opt,name=attributes,proto3" json:"attributes,omitempty"` + RuleSet string `protobuf:"bytes,17,opt,name=rule_set,json=ruleSet,proto3" json:"rule_set,omitempty"` } func (x *RoutingRule) Reset() { @@ -672,6 +673,13 @@ func (x *RoutingRule) GetAttributes() string { return "" } +func (x *RoutingRule) GetRuleSet() string { + if x != nil { + return x.RuleSet + } + return "" +} + type isRoutingRule_TargetTag interface { isRoutingRule_TargetTag() } @@ -745,6 +753,61 @@ func (x *BalancingRule) GetOutboundSelector() []string { return nil } +type RoutingRules struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + Rules []*RoutingRule `protobuf:"bytes,2,rep,name=rules,proto3" json:"rules,omitempty"` +} + +func (x *RoutingRules) Reset() { + *x = RoutingRules{} + if protoimpl.UnsafeEnabled { + mi := &file_app_router_config_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RoutingRules) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RoutingRules) ProtoMessage() {} + +func (x *RoutingRules) ProtoReflect() protoreflect.Message { + mi := &file_app_router_config_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RoutingRules.ProtoReflect.Descriptor instead. +func (*RoutingRules) Descriptor() ([]byte, []int) { + return file_app_router_config_proto_rawDescGZIP(), []int{8} +} + +func (x *RoutingRules) GetIdentifier() string { + if x != nil { + return x.Identifier + } + return "" +} + +func (x *RoutingRules) GetRules() []*RoutingRule { + if x != nil { + return x.Rules + } + return nil +} + type Config struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -753,12 +816,13 @@ type Config struct { DomainStrategy Config_DomainStrategy `protobuf:"varint,1,opt,name=domain_strategy,json=domainStrategy,proto3,enum=xray.app.router.Config_DomainStrategy" json:"domain_strategy,omitempty"` Rule []*RoutingRule `protobuf:"bytes,2,rep,name=rule,proto3" json:"rule,omitempty"` BalancingRule []*BalancingRule `protobuf:"bytes,3,rep,name=balancing_rule,json=balancingRule,proto3" json:"balancing_rule,omitempty"` + RuleSets []*RoutingRules `protobuf:"bytes,4,rep,name=rule_sets,json=ruleSets,proto3" json:"rule_sets,omitempty"` } func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { - mi := &file_app_router_config_proto_msgTypes[8] + mi := &file_app_router_config_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -771,7 +835,7 @@ func (x *Config) String() string { func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_app_router_config_proto_msgTypes[8] + mi := &file_app_router_config_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -784,7 +848,7 @@ func (x *Config) ProtoReflect() protoreflect.Message { // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { - return file_app_router_config_proto_rawDescGZIP(), []int{8} + return file_app_router_config_proto_rawDescGZIP(), []int{9} } func (x *Config) GetDomainStrategy() Config_DomainStrategy { @@ -808,6 +872,13 @@ func (x *Config) GetBalancingRule() []*BalancingRule { return nil } +func (x *Config) GetRuleSets() []*RoutingRules { + if x != nil { + return x.RuleSets + } + return nil +} + type Domain_Attribute struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -823,7 +894,7 @@ type Domain_Attribute struct { func (x *Domain_Attribute) Reset() { *x = Domain_Attribute{} if protoimpl.UnsafeEnabled { - mi := &file_app_router_config_proto_msgTypes[9] + mi := &file_app_router_config_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -836,7 +907,7 @@ func (x *Domain_Attribute) String() string { func (*Domain_Attribute) ProtoMessage() {} func (x *Domain_Attribute) ProtoReflect() protoreflect.Message { - mi := &file_app_router_config_proto_msgTypes[9] + mi := &file_app_router_config_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -946,7 +1017,7 @@ var file_app_router_config_proto_rawDesc = []byte{ 0x74, 0x12, 0x2e, 0x0a, 0x05, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x52, 0x05, 0x65, 0x6e, 0x74, 0x72, - 0x79, 0x22, 0x8e, 0x06, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, + 0x79, 0x22, 0xa9, 0x06, 0x0a, 0x0b, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x25, 0x0a, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x61, 0x67, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, @@ -994,36 +1065,48 @@ var file_app_router_config_proto_rawDesc = []byte{ 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, - 0x75, 0x74, 0x65, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, - 0x61, 0x67, 0x22, 0x4e, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, - 0x75, 0x6c, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, - 0x64, 0x5f, 0x73, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x10, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, - 0x6f, 0x72, 0x22, 0x9b, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, - 0x0f, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, - 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, - 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, - 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x30, - 0x0a, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, + 0x75, 0x74, 0x65, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x73, 0x65, 0x74, + 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x53, 0x65, 0x74, 0x42, + 0x0c, 0x0a, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x74, 0x61, 0x67, 0x22, 0x4e, 0x0a, + 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x10, + 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, + 0x12, 0x2b, 0x0a, 0x11, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x5f, 0x73, 0x65, 0x6c, + 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x6f, 0x75, 0x74, + 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x62, 0x0a, + 0x0c, 0x52, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x1e, 0x0a, + 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x32, 0x0a, + 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, - 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, - 0x12, 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, - 0x6c, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, - 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, - 0x73, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, 0x10, - 0x0a, 0x0c, 0x49, 0x70, 0x49, 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, - 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, - 0x42, 0x4f, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, - 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, - 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, - 0x02, 0x0f, 0x58, 0x72, 0x61, 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, - 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, + 0x73, 0x22, 0xd7, 0x02, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4f, 0x0a, 0x0f, + 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, + 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x44, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x0e, 0x64, + 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x30, 0x0a, + 0x04, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x78, 0x72, + 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, + 0x75, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x04, 0x72, 0x75, 0x6c, 0x65, 0x12, + 0x45, 0x0a, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6c, + 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, + 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x52, 0x0d, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x69, + 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x78, 0x72, 0x61, 0x79, + 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x2e, 0x52, 0x6f, 0x75, 0x74, + 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x08, 0x72, 0x75, 0x6c, 0x65, 0x53, 0x65, + 0x74, 0x73, 0x22, 0x47, 0x0a, 0x0e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x53, 0x74, 0x72, 0x61, + 0x74, 0x65, 0x67, 0x79, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x73, 0x49, 0x73, 0x10, 0x00, 0x12, 0x09, + 0x0a, 0x05, 0x55, 0x73, 0x65, 0x49, 0x70, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x70, 0x49, + 0x66, 0x4e, 0x6f, 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x10, 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x49, + 0x70, 0x4f, 0x6e, 0x44, 0x65, 0x6d, 0x61, 0x6e, 0x64, 0x10, 0x03, 0x42, 0x4f, 0x0a, 0x13, 0x63, + 0x6f, 0x6d, 0x2e, 0x78, 0x72, 0x61, 0x79, 0x2e, 0x61, 0x70, 0x70, 0x2e, 0x72, 0x6f, 0x75, 0x74, + 0x65, 0x72, 0x50, 0x01, 0x5a, 0x24, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x78, 0x74, 0x6c, 0x73, 0x2f, 0x78, 0x72, 0x61, 0x79, 0x2d, 0x63, 0x6f, 0x72, 0x65, 0x2f, + 0x61, 0x70, 0x70, 0x2f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0xaa, 0x02, 0x0f, 0x58, 0x72, 0x61, + 0x79, 0x2e, 0x41, 0x70, 0x70, 0x2e, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1039,7 +1122,7 @@ func file_app_router_config_proto_rawDescGZIP() []byte { } var file_app_router_config_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_app_router_config_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_app_router_config_proto_goTypes = []interface{}{ (Domain_Type)(0), // 0: xray.app.router.Domain.Type (Config_DomainStrategy)(0), // 1: xray.app.router.Config.DomainStrategy @@ -1051,16 +1134,17 @@ var file_app_router_config_proto_goTypes = []interface{}{ (*GeoSiteList)(nil), // 7: xray.app.router.GeoSiteList (*RoutingRule)(nil), // 8: xray.app.router.RoutingRule (*BalancingRule)(nil), // 9: xray.app.router.BalancingRule - (*Config)(nil), // 10: xray.app.router.Config - (*Domain_Attribute)(nil), // 11: xray.app.router.Domain.Attribute - (*net.PortRange)(nil), // 12: xray.common.net.PortRange - (*net.PortList)(nil), // 13: xray.common.net.PortList - (*net.NetworkList)(nil), // 14: xray.common.net.NetworkList - (net.Network)(0), // 15: xray.common.net.Network + (*RoutingRules)(nil), // 10: xray.app.router.RoutingRules + (*Config)(nil), // 11: xray.app.router.Config + (*Domain_Attribute)(nil), // 12: xray.app.router.Domain.Attribute + (*net.PortRange)(nil), // 13: xray.common.net.PortRange + (*net.PortList)(nil), // 14: xray.common.net.PortList + (*net.NetworkList)(nil), // 15: xray.common.net.NetworkList + (net.Network)(0), // 16: xray.common.net.Network } var file_app_router_config_proto_depIdxs = []int32{ 0, // 0: xray.app.router.Domain.type:type_name -> xray.app.router.Domain.Type - 11, // 1: xray.app.router.Domain.attribute:type_name -> xray.app.router.Domain.Attribute + 12, // 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 @@ -1068,21 +1152,23 @@ var file_app_router_config_proto_depIdxs = []int32{ 2, // 6: xray.app.router.RoutingRule.domain:type_name -> xray.app.router.Domain 3, // 7: xray.app.router.RoutingRule.cidr:type_name -> xray.app.router.CIDR 4, // 8: xray.app.router.RoutingRule.geoip:type_name -> xray.app.router.GeoIP - 12, // 9: xray.app.router.RoutingRule.port_range:type_name -> xray.common.net.PortRange - 13, // 10: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList - 14, // 11: xray.app.router.RoutingRule.network_list:type_name -> xray.common.net.NetworkList - 15, // 12: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network + 13, // 9: xray.app.router.RoutingRule.port_range:type_name -> xray.common.net.PortRange + 14, // 10: xray.app.router.RoutingRule.port_list:type_name -> xray.common.net.PortList + 15, // 11: xray.app.router.RoutingRule.network_list:type_name -> xray.common.net.NetworkList + 16, // 12: xray.app.router.RoutingRule.networks:type_name -> xray.common.net.Network 3, // 13: xray.app.router.RoutingRule.source_cidr:type_name -> xray.app.router.CIDR 4, // 14: xray.app.router.RoutingRule.source_geoip:type_name -> xray.app.router.GeoIP - 13, // 15: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList - 1, // 16: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy - 8, // 17: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule - 9, // 18: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule - 19, // [19:19] is the sub-list for method output_type - 19, // [19:19] is the sub-list for method input_type - 19, // [19:19] is the sub-list for extension type_name - 19, // [19:19] is the sub-list for extension extendee - 0, // [0:19] is the sub-list for field type_name + 14, // 15: xray.app.router.RoutingRule.source_port_list:type_name -> xray.common.net.PortList + 8, // 16: xray.app.router.RoutingRules.rules:type_name -> xray.app.router.RoutingRule + 1, // 17: xray.app.router.Config.domain_strategy:type_name -> xray.app.router.Config.DomainStrategy + 8, // 18: xray.app.router.Config.rule:type_name -> xray.app.router.RoutingRule + 9, // 19: xray.app.router.Config.balancing_rule:type_name -> xray.app.router.BalancingRule + 10, // 20: xray.app.router.Config.rule_sets:type_name -> xray.app.router.RoutingRules + 21, // [21:21] is the sub-list for method output_type + 21, // [21:21] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_app_router_config_proto_init() } @@ -1188,7 +1274,7 @@ func file_app_router_config_proto_init() { } } file_app_router_config_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Config); i { + switch v := v.(*RoutingRules); i { case 0: return &v.state case 1: @@ -1200,6 +1286,18 @@ func file_app_router_config_proto_init() { } } file_app_router_config_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Config); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_app_router_config_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Domain_Attribute); i { case 0: return &v.state @@ -1216,7 +1314,7 @@ func file_app_router_config_proto_init() { (*RoutingRule_Tag)(nil), (*RoutingRule_BalancingTag)(nil), } - file_app_router_config_proto_msgTypes[9].OneofWrappers = []interface{}{ + file_app_router_config_proto_msgTypes[10].OneofWrappers = []interface{}{ (*Domain_Attribute_BoolValue)(nil), (*Domain_Attribute_IntValue)(nil), } @@ -1226,7 +1324,7 @@ func file_app_router_config_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_app_router_config_proto_rawDesc, NumEnums: 2, - NumMessages: 10, + NumMessages: 11, NumExtensions: 0, NumServices: 0, }, diff --git a/app/router/config.proto b/app/router/config.proto index c5e655adeafc..af0f2dd092b0 100644 --- a/app/router/config.proto +++ b/app/router/config.proto @@ -119,6 +119,7 @@ message RoutingRule { repeated string protocol = 9; string attributes = 15; + string rule_set = 17; } message BalancingRule { @@ -126,6 +127,11 @@ message BalancingRule { repeated string outbound_selector = 2; } +message RoutingRules { + string identifier = 1; + repeated RoutingRule rules = 2; +} + message Config { enum DomainStrategy { // Use domain as is. @@ -143,4 +149,5 @@ message Config { DomainStrategy domain_strategy = 1; repeated RoutingRule rule = 2; repeated BalancingRule balancing_rule = 3; + repeated RoutingRules rule_sets = 4; } diff --git a/app/router/router.go b/app/router/router.go index 5d2bdc34adc1..effd9233a73e 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -28,6 +28,34 @@ type Route struct { outboundTag string } +type RulesetManager struct { + ruleSets map[string]Condition + rawRuleSets map[string]*RoutingRules +} + +func NewRSManager() *RulesetManager { + return &RulesetManager{make(map[string]Condition), make(map[string]*RoutingRules)} +} + +func (m *RulesetManager) Add(tag string, rule *RoutingRules) { + m.rawRuleSets[tag] = rule +} + +func (m *RulesetManager) getRuleSet(tag string) (Condition, error) { + if cond, found := m.ruleSets[tag]; found { + return cond, nil + } + if rCond, found := m.rawRuleSets[tag]; found { + cond, err := rCond.BuildCondition(m) + if err != nil { + return nil, err + } + m.ruleSets[tag] = cond + return cond, nil + } + return nil, newError("ruleset not exist: " + tag) +} + // Init initializes the Router. func (r *Router) Init(config *Config, d dns.Client, ohm outbound.Manager) error { r.domainStrategy = config.DomainStrategy @@ -42,12 +70,19 @@ func (r *Router) Init(config *Config, d dns.Client, ohm outbound.Manager) error r.balancers[rule.Tag] = balancer } + var rsm = NewRSManager() + + for _, rs := range config.RuleSets { + rsm.Add(rs.Identifier, rs) + } + r.rules = make([]*Rule, 0, len(config.Rule)) for _, rule := range config.Rule { - cond, err := rule.BuildCondition() + cond, err := rule.BuildCondition(rsm) if err != nil { return err } + rr := &Rule{ Condition: cond, Tag: rule.GetTag(), diff --git a/infra/conf/router.go b/infra/conf/router.go index a93da6b11500..1d1db1a9b347 100644 --- a/infra/conf/router.go +++ b/infra/conf/router.go @@ -37,11 +37,39 @@ func (r *BalancingRule) Build() (*router.BalancingRule, error) { }, nil } +type RuleSet struct { + Identifier string `json:"tag"` + Rules []json.RawMessage `json:"rules"` +} + +func (r *RuleSet) Build() (*router.RoutingRules, error) { + if r.Identifier == "" { + return nil, newError("empty rule set tag") + } + if len(r.Rules) == 0 { + return nil, newError("empty rule set") + } + + ruleSet := new(router.RoutingRules) + ruleSet.Identifier = r.Identifier + + for _, rr := range r.Rules { + if rule, err := ParseRule(rr, true); err != nil { + return nil, err + } else { + ruleSet.Rules = append(ruleSet.Rules, rule) + } + } + + return ruleSet, nil +} + type RouterConfig struct { Settings *RouterRulesConfig `json:"settings"` // Deprecated RuleList []json.RawMessage `json:"rules"` DomainStrategy *string `json:"domainStrategy"` Balancers []*BalancingRule `json:"balancers"` + RuleSets []*RuleSet `json:"ruleSets"` } func (c *RouterConfig) getDomainStrategy() router.Config_DomainStrategy { @@ -78,7 +106,7 @@ func (c *RouterConfig) Build() (*router.Config, error) { } for _, rawRule := range rawRuleList { - rule, err := ParseRule(rawRule) + rule, err := ParseRule(rawRule, false) if err != nil { return nil, err } @@ -91,6 +119,13 @@ func (c *RouterConfig) Build() (*router.Config, error) { } config.BalancingRule = append(config.BalancingRule, balancer) } + for _, rawRuleSet := range c.RuleSets { + rawRuleSet, err := rawRuleSet.Build() + if err != nil { + return nil, err + } + config.RuleSets = append(config.RuleSets, rawRuleSet) + } return config, nil } @@ -456,7 +491,7 @@ func toCidrList(ips StringList) ([]*router.GeoIP, error) { return geoipList, nil } -func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { +func parseFieldRule(msg json.RawMessage, skipTargetCheck bool) (*router.RoutingRule, error) { type RawFieldRule struct { RouterRule Domain *StringList `json:"domain"` @@ -470,6 +505,7 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { InboundTag *StringList `json:"inboundTag"` Protocols *StringList `json:"protocol"` Attributes string `json:"attrs"` + RuleSet string `json:"ruleSet"` } rawFieldRule := new(RawFieldRule) err := json.Unmarshal(msg, rawFieldRule) @@ -478,17 +514,20 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { } rule := new(router.RoutingRule) - switch { - case len(rawFieldRule.OutboundTag) > 0: - rule.TargetTag = &router.RoutingRule_Tag{ - Tag: rawFieldRule.OutboundTag, - } - case len(rawFieldRule.BalancerTag) > 0: - rule.TargetTag = &router.RoutingRule_BalancingTag{ - BalancingTag: rawFieldRule.BalancerTag, + + if !skipTargetCheck { + switch { + case len(rawFieldRule.OutboundTag) > 0: + rule.TargetTag = &router.RoutingRule_Tag{ + Tag: rawFieldRule.OutboundTag, + } + case len(rawFieldRule.BalancerTag) > 0: + rule.TargetTag = &router.RoutingRule_BalancingTag{ + BalancingTag: rawFieldRule.BalancerTag, + } + default: + return nil, newError("neither outboundTag nor balancerTag is specified in routing rule") } - default: - return nil, newError("neither outboundTag nor balancerTag is specified in routing rule") } if rawFieldRule.Domain != nil { @@ -561,17 +600,19 @@ func parseFieldRule(msg json.RawMessage) (*router.RoutingRule, error) { rule.Attributes = rawFieldRule.Attributes } + rule.RuleSet = rawFieldRule.RuleSet + return rule, nil } -func ParseRule(msg json.RawMessage) (*router.RoutingRule, error) { +func ParseRule(msg json.RawMessage, skipTargetCheck bool) (*router.RoutingRule, error) { rawRule := new(RouterRule) err := json.Unmarshal(msg, rawRule) if err != nil { return nil, newError("invalid router rule").Base(err) } if rawRule.Type == "field" { - fieldrule, err := parseFieldRule(msg) + fieldrule, err := parseFieldRule(msg, skipTargetCheck) if err != nil { return nil, newError("invalid field rule").Base(err) } diff --git a/infra/conf/xray.go b/infra/conf/xray.go index e21018a56b5d..64a781bbfc85 100644 --- a/infra/conf/xray.go +++ b/infra/conf/xray.go @@ -400,9 +400,6 @@ func (c *Config) Override(o *Config, fn string) { if o.LogConfig != nil { c.LogConfig = o.LogConfig } - if o.RouterConfig != nil { - c.RouterConfig = o.RouterConfig - } if o.DNSConfig != nil { c.DNSConfig = o.DNSConfig } @@ -471,6 +468,45 @@ func (c *Config) Override(o *Config, fn string) { c.OutboundConfigs = o.OutboundConfigs } } + + if o.RouterConfig != nil { + if c.RouterConfig == nil { + c.RouterConfig = &RouterConfig{} + } + if len(o.RouterConfig.Balancers) > 0 { + c.RouterConfig.Balancers = o.RouterConfig.Balancers + } + if o.RouterConfig.DomainStrategy != nil { + c.RouterConfig.DomainStrategy = o.RouterConfig.DomainStrategy + } + if len(o.RouterConfig.RuleList) > 0 { + c.RouterConfig.RuleList = o.RouterConfig.RuleList + } + if o.RouterConfig.Settings != nil { + c.RouterConfig.Settings = o.RouterConfig.Settings + } + + if len(o.RouterConfig.RuleSets) > 0 { + if len(c.RouterConfig.RuleSets) > 0 { + for _, set := range o.RouterConfig.RuleSets { + found := false + for _, cset := range c.RouterConfig.RuleSets { + if cset.Identifier == set.Identifier { + found = true + } + } + + if found { + ctllog.Println("[", fn, "] found existing rule set: "+set.Identifier+", ignoring") + } else { + c.RouterConfig.RuleSets = append(c.RouterConfig.RuleSets, set) + } + } + } else { + c.RouterConfig.RuleSets = o.RouterConfig.RuleSets + } + } + } } func applyTransportConfig(s *StreamConfig, t *TransportConfig) { diff --git a/infra/conf/xray_test.go b/infra/conf/xray_test.go index d4f0ba6b51fa..455780564a59 100644 --- a/infra/conf/xray_test.go +++ b/infra/conf/xray_test.go @@ -437,6 +437,12 @@ func TestConfig_Override(t *testing.T) { &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos2", Protocol: "kcp"}}}, "config_tail.json", &Config{OutboundConfigs: []OutboundDetourConfig{{Tag: "pos0"}, {Protocol: "vmess", Tag: "pos1"}, {Tag: "pos2", Protocol: "kcp"}}}}, + {"replace/router-rules", + &Config{RouterConfig: &RouterConfig{RuleSets: []*RuleSet{{Identifier: "1"}, {Identifier: "2"}}}}, + &Config{RouterConfig: &RouterConfig{RuleSets: []*RuleSet{{Identifier: "1"}, {Identifier: "3"}}}}, + "config_route_set.json", + &Config{RouterConfig: &RouterConfig{RuleSets: []*RuleSet{{Identifier: "1"}, {Identifier: "2"}, {Identifier: "3"}}}}, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {