mirror of
https://github.com/XTLS/Xray-core.git
synced 2024-12-01 19:06:42 +00:00
bf4b1fab3c
In the past, we use Starlark script, it is over engineered and barely used. By switching to simple key value string contains logic we can reduce core size about 0.7MB
311 lines
6.2 KiB
Go
311 lines
6.2 KiB
Go
package router
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/xtls/xray-core/common/net"
|
|
"github.com/xtls/xray-core/common/strmatcher"
|
|
"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[Domain_Type]strmatcher.Type{
|
|
Domain_Plain: strmatcher.Substr,
|
|
Domain_Regex: strmatcher.Regex,
|
|
Domain_Domain: strmatcher.Domain,
|
|
Domain_Full: strmatcher.Full,
|
|
}
|
|
|
|
func domainToMatcher(domain *Domain) (strmatcher.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 strmatcher.IndexMatcher
|
|
}
|
|
|
|
func NewMphMatcherGroup(domains []*Domain) (*DomainMatcher, error) {
|
|
g := strmatcher.NewMphMatcherGroup()
|
|
for _, d := range domains {
|
|
matcherType, f := matcherTypeMap[d.Type]
|
|
if !f {
|
|
return nil, newError("unsupported domain type", d.Type)
|
|
}
|
|
_, err := g.AddPattern(d.Value, matcherType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
g.Build()
|
|
return &DomainMatcher{
|
|
matchers: g,
|
|
}, nil
|
|
}
|
|
|
|
func NewDomainMatcher(domains []*Domain) (*DomainMatcher, error) {
|
|
g := new(strmatcher.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(strings.ToLower(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(domain)
|
|
}
|
|
|
|
type MultiGeoIPMatcher struct {
|
|
matchers []*GeoIPMatcher
|
|
onSource bool
|
|
}
|
|
|
|
func NewMultiGeoIPMatcher(geoips []*GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
|
|
var matchers []*GeoIPMatcher
|
|
for _, geoip := range geoips {
|
|
matcher, err := globalGeoIPContainer.Add(geoip)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
matchers = append(matchers, matcher)
|
|
}
|
|
|
|
matcher := &MultiGeoIPMatcher{
|
|
matchers: matchers,
|
|
onSource: onSource,
|
|
}
|
|
|
|
return matcher, nil
|
|
}
|
|
|
|
// Apply implements Condition.
|
|
func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
|
|
var ips []net.IP
|
|
if m.onSource {
|
|
ips = ctx.GetSourceIPs()
|
|
} else {
|
|
ips = ctx.GetTargetIPs()
|
|
}
|
|
for _, ip := range ips {
|
|
for _, matcher := range m.matchers {
|
|
if matcher.Match(ip) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
type PortMatcher struct {
|
|
port net.MemoryPortList
|
|
onSource bool
|
|
}
|
|
|
|
// 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 {
|
|
configuredKeys map[string]string
|
|
}
|
|
|
|
// Match implements attributes matching.
|
|
func (m *AttributeMatcher) Match(attrs map[string]string) bool {
|
|
// headers are insensitive most likely. So we do a convert
|
|
httpHeaders := make(map[string]string)
|
|
for key, value := range attrs {
|
|
httpHeaders[strings.ToLower(key)] = strings.ToLower(value)
|
|
}
|
|
for key, value := range m.configuredKeys {
|
|
if a, ok := httpHeaders[key]; !ok || !strings.Contains(a, value) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Apply implements Condition.
|
|
func (m *AttributeMatcher) Apply(ctx routing.Context) bool {
|
|
attributes := ctx.GetAttributes()
|
|
if attributes == nil {
|
|
return false
|
|
}
|
|
return m.Match(attributes)
|
|
}
|