package router import ( "strings" "go.starlark.net/starlark" "go.starlark.net/syntax" dm "github.com/xtls/xray-core/common/matcher/domain" "github.com/xtls/xray-core/common/matcher/str" "github.com/xtls/xray-core/common/net" "github.com/xtls/xray-core/features/routing" ) type Condition interface { Apply(ctx routing.Context) bool } type ConditionChan []Condition func NewConditionChan() *ConditionChan { var condChan ConditionChan = make([]Condition, 0, 8) return &condChan } func (v *ConditionChan) Add(cond Condition) *ConditionChan { *v = append(*v, cond) return v } // Apply applies all conditions registered in this chan. func (v *ConditionChan) Apply(ctx routing.Context) bool { for _, cond := range *v { if !cond.Apply(ctx) { return false } } return true } func (v *ConditionChan) Len() int { return len(*v) } var matcherTypeMap = map[dm.MatchingType]str.Type{ dm.MatchingType_Keyword: str.Substr, dm.MatchingType_Regex: str.Regex, dm.MatchingType_Subdomain: str.Domain, dm.MatchingType_Full: str.Full, } func domainToMatcher(domain *dm.Domain) (str.Matcher, error) { matcherType, f := matcherTypeMap[domain.Type] if !f { return nil, newError("unsupported domain type", domain.Type) } matcher, err := matcherType.New(domain.Value) if err != nil { return nil, newError("failed to create domain matcher").Base(err) } return matcher, nil } type DomainMatcher struct { matchers str.IndexMatcher } func NewDomainMatcher(domains []*dm.Domain) (*DomainMatcher, error) { g := new(str.MatcherGroup) for _, d := range domains { m, err := domainToMatcher(d) if err != nil { return nil, err } g.Add(m) } return &DomainMatcher{ matchers: g, }, nil } func (m *DomainMatcher) ApplyDomain(domain string) bool { return len(m.matchers.Match(domain)) > 0 } // Apply implements Condition. func (m *DomainMatcher) Apply(ctx routing.Context) bool { domain := ctx.GetTargetDomain() if len(domain) == 0 { return false } return m.ApplyDomain(strings.ToLower(domain)) } type PortMatcher struct { port net.MemoryPortList onSource bool } // NewPortMatcher create a new port matcher that can match source or destination port func NewPortMatcher(list *net.PortList, onSource bool) *PortMatcher { return &PortMatcher{ port: net.PortListFromProto(list), onSource: onSource, } } // Apply implements Condition. func (v *PortMatcher) Apply(ctx routing.Context) bool { if v.onSource { return v.port.Contains(ctx.GetSourcePort()) } else { return v.port.Contains(ctx.GetTargetPort()) } } type NetworkMatcher struct { list [8]bool } func NewNetworkMatcher(network []net.Network) NetworkMatcher { var matcher NetworkMatcher for _, n := range network { matcher.list[int(n)] = true } return matcher } // Apply implements Condition. func (v NetworkMatcher) Apply(ctx routing.Context) bool { return v.list[int(ctx.GetNetwork())] } type UserMatcher struct { user []string } func NewUserMatcher(users []string) *UserMatcher { usersCopy := make([]string, 0, len(users)) for _, user := range users { if len(user) > 0 { usersCopy = append(usersCopy, user) } } return &UserMatcher{ user: usersCopy, } } // Apply implements Condition. func (v *UserMatcher) Apply(ctx routing.Context) bool { user := ctx.GetUser() if len(user) == 0 { return false } for _, u := range v.user { if u == user { return true } } return false } type InboundTagMatcher struct { tags []string } func NewInboundTagMatcher(tags []string) *InboundTagMatcher { tagsCopy := make([]string, 0, len(tags)) for _, tag := range tags { if len(tag) > 0 { tagsCopy = append(tagsCopy, tag) } } return &InboundTagMatcher{ tags: tagsCopy, } } // Apply implements Condition. func (v *InboundTagMatcher) Apply(ctx routing.Context) bool { tag := ctx.GetInboundTag() if len(tag) == 0 { return false } for _, t := range v.tags { if t == tag { return true } } return false } type ProtocolMatcher struct { protocols []string } func NewProtocolMatcher(protocols []string) *ProtocolMatcher { pCopy := make([]string, 0, len(protocols)) for _, p := range protocols { if len(p) > 0 { pCopy = append(pCopy, p) } } return &ProtocolMatcher{ protocols: pCopy, } } // Apply implements Condition. func (m *ProtocolMatcher) Apply(ctx routing.Context) bool { protocol := ctx.GetProtocol() if len(protocol) == 0 { return false } for _, p := range m.protocols { if strings.HasPrefix(protocol, p) { return true } } return false } type AttributeMatcher struct { program *starlark.Program } func NewAttributeMatcher(code string) (*AttributeMatcher, error) { starFile, err := syntax.Parse("attr.star", "satisfied=("+code+")", 0) if err != nil { return nil, newError("attr rule").Base(err) } p, err := starlark.FileProgram(starFile, func(name string) bool { return name == "attrs" }) if err != nil { return nil, err } return &AttributeMatcher{ program: p, }, nil } // Match implements attributes matching. func (m *AttributeMatcher) Match(attrs map[string]string) bool { attrsDict := new(starlark.Dict) for key, value := range attrs { attrsDict.SetKey(starlark.String(key), starlark.String(value)) } predefined := make(starlark.StringDict) predefined["attrs"] = attrsDict thread := &starlark.Thread{ Name: "matcher", } results, err := m.program.Init(thread, predefined) if err != nil { newError("attr matcher").Base(err).WriteToLog() } satisfied := results["satisfied"] return satisfied != nil && bool(satisfied.Truth()) } // Apply implements Condition. func (m *AttributeMatcher) Apply(ctx routing.Context) bool { attributes := ctx.GetAttributes() if attributes == nil { return false } return m.Match(attributes) }