github.com/v2fly/v2ray-core/v5@v5.16.2-0.20240507031116-8191faa6e095/app/router/condition.go (about)

     1  package router
     2  
     3  import (
     4  	"strings"
     5  
     6  	"go.starlark.net/starlark"
     7  	"go.starlark.net/syntax"
     8  
     9  	"github.com/v2fly/v2ray-core/v5/app/router/routercommon"
    10  	"github.com/v2fly/v2ray-core/v5/common/net"
    11  	"github.com/v2fly/v2ray-core/v5/common/strmatcher"
    12  	"github.com/v2fly/v2ray-core/v5/features/routing"
    13  )
    14  
    15  type Condition interface {
    16  	Apply(ctx routing.Context) bool
    17  }
    18  
    19  type ConditionChan []Condition
    20  
    21  func NewConditionChan() *ConditionChan {
    22  	var condChan ConditionChan = make([]Condition, 0, 8)
    23  	return &condChan
    24  }
    25  
    26  func (v *ConditionChan) Add(cond Condition) *ConditionChan {
    27  	*v = append(*v, cond)
    28  	return v
    29  }
    30  
    31  // Apply applies all conditions registered in this chan.
    32  func (v *ConditionChan) Apply(ctx routing.Context) bool {
    33  	for _, cond := range *v {
    34  		if !cond.Apply(ctx) {
    35  			return false
    36  		}
    37  	}
    38  	return true
    39  }
    40  
    41  func (v *ConditionChan) Len() int {
    42  	return len(*v)
    43  }
    44  
    45  var matcherTypeMap = map[routercommon.Domain_Type]strmatcher.Type{
    46  	routercommon.Domain_Plain:      strmatcher.Substr,
    47  	routercommon.Domain_Regex:      strmatcher.Regex,
    48  	routercommon.Domain_RootDomain: strmatcher.Domain,
    49  	routercommon.Domain_Full:       strmatcher.Full,
    50  }
    51  
    52  func domainToMatcher(domain *routercommon.Domain) (strmatcher.Matcher, error) {
    53  	matcherType, f := matcherTypeMap[domain.Type]
    54  	if !f {
    55  		return nil, newError("unsupported domain type", domain.Type)
    56  	}
    57  
    58  	matcher, err := matcherType.New(domain.Value)
    59  	if err != nil {
    60  		return nil, newError("failed to create domain matcher").Base(err)
    61  	}
    62  
    63  	return matcher, nil
    64  }
    65  
    66  type DomainMatcher struct {
    67  	matcher strmatcher.IndexMatcher
    68  }
    69  
    70  func NewDomainMatcher(matcherType string, domains []*routercommon.Domain) (*DomainMatcher, error) {
    71  	var indexMatcher strmatcher.IndexMatcher
    72  	switch matcherType {
    73  	case "mph", "hybrid":
    74  		indexMatcher = strmatcher.NewMphIndexMatcher()
    75  	case "linear":
    76  		indexMatcher = strmatcher.NewLinearIndexMatcher()
    77  	default:
    78  		indexMatcher = strmatcher.NewLinearIndexMatcher()
    79  	}
    80  	for _, domain := range domains {
    81  		matcher, err := domainToMatcher(domain)
    82  		if err != nil {
    83  			return nil, err
    84  		}
    85  		indexMatcher.Add(matcher)
    86  	}
    87  	if err := indexMatcher.Build(); err != nil {
    88  		return nil, err
    89  	}
    90  	return &DomainMatcher{matcher: indexMatcher}, nil
    91  }
    92  
    93  func (m *DomainMatcher) Match(domain string) bool {
    94  	return m.matcher.MatchAny(domain)
    95  }
    96  
    97  // Apply implements Condition.
    98  func (m *DomainMatcher) Apply(ctx routing.Context) bool {
    99  	domain := ctx.GetTargetDomain()
   100  	if len(domain) == 0 {
   101  		return false
   102  	}
   103  	return m.Match(domain)
   104  }
   105  
   106  type MultiGeoIPMatcher struct {
   107  	matchers []*GeoIPMatcher
   108  	onSource bool
   109  }
   110  
   111  func NewMultiGeoIPMatcher(geoips []*routercommon.GeoIP, onSource bool) (*MultiGeoIPMatcher, error) {
   112  	var matchers []*GeoIPMatcher
   113  	for _, geoip := range geoips {
   114  		matcher, err := globalGeoIPContainer.Add(geoip)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		matchers = append(matchers, matcher)
   119  	}
   120  
   121  	matcher := &MultiGeoIPMatcher{
   122  		matchers: matchers,
   123  		onSource: onSource,
   124  	}
   125  
   126  	return matcher, nil
   127  }
   128  
   129  // Apply implements Condition.
   130  func (m *MultiGeoIPMatcher) Apply(ctx routing.Context) bool {
   131  	var ips []net.IP
   132  	if m.onSource {
   133  		ips = ctx.GetSourceIPs()
   134  	} else {
   135  		ips = ctx.GetTargetIPs()
   136  	}
   137  	for _, ip := range ips {
   138  		for _, matcher := range m.matchers {
   139  			if matcher.Match(ip) {
   140  				return true
   141  			}
   142  		}
   143  	}
   144  	return false
   145  }
   146  
   147  type PortMatcher struct {
   148  	port     net.MemoryPortList
   149  	onSource bool
   150  }
   151  
   152  // NewPortMatcher creates a new port matcher that can match source or destination port
   153  func NewPortMatcher(list *net.PortList, onSource bool) *PortMatcher {
   154  	return &PortMatcher{
   155  		port:     net.PortListFromProto(list),
   156  		onSource: onSource,
   157  	}
   158  }
   159  
   160  // Apply implements Condition.
   161  func (v *PortMatcher) Apply(ctx routing.Context) bool {
   162  	if v.onSource {
   163  		return v.port.Contains(ctx.GetSourcePort())
   164  	}
   165  	return v.port.Contains(ctx.GetTargetPort())
   166  }
   167  
   168  type NetworkMatcher struct {
   169  	list [8]bool
   170  }
   171  
   172  func NewNetworkMatcher(network []net.Network) NetworkMatcher {
   173  	var matcher NetworkMatcher
   174  	for _, n := range network {
   175  		matcher.list[int(n)] = true
   176  	}
   177  	return matcher
   178  }
   179  
   180  // Apply implements Condition.
   181  func (v NetworkMatcher) Apply(ctx routing.Context) bool {
   182  	return v.list[int(ctx.GetNetwork())]
   183  }
   184  
   185  type UserMatcher struct {
   186  	user []string
   187  }
   188  
   189  func NewUserMatcher(users []string) *UserMatcher {
   190  	usersCopy := make([]string, 0, len(users))
   191  	for _, user := range users {
   192  		if len(user) > 0 {
   193  			usersCopy = append(usersCopy, user)
   194  		}
   195  	}
   196  	return &UserMatcher{
   197  		user: usersCopy,
   198  	}
   199  }
   200  
   201  // Apply implements Condition.
   202  func (v *UserMatcher) Apply(ctx routing.Context) bool {
   203  	user := ctx.GetUser()
   204  	if len(user) == 0 {
   205  		return false
   206  	}
   207  	for _, u := range v.user {
   208  		if u == user {
   209  			return true
   210  		}
   211  	}
   212  	return false
   213  }
   214  
   215  type InboundTagMatcher struct {
   216  	tags []string
   217  }
   218  
   219  func NewInboundTagMatcher(tags []string) *InboundTagMatcher {
   220  	tagsCopy := make([]string, 0, len(tags))
   221  	for _, tag := range tags {
   222  		if len(tag) > 0 {
   223  			tagsCopy = append(tagsCopy, tag)
   224  		}
   225  	}
   226  	return &InboundTagMatcher{
   227  		tags: tagsCopy,
   228  	}
   229  }
   230  
   231  // Apply implements Condition.
   232  func (v *InboundTagMatcher) Apply(ctx routing.Context) bool {
   233  	tag := ctx.GetInboundTag()
   234  	if len(tag) == 0 {
   235  		return false
   236  	}
   237  	for _, t := range v.tags {
   238  		if t == tag {
   239  			return true
   240  		}
   241  	}
   242  	return false
   243  }
   244  
   245  type ProtocolMatcher struct {
   246  	protocols []string
   247  }
   248  
   249  func NewProtocolMatcher(protocols []string) *ProtocolMatcher {
   250  	pCopy := make([]string, 0, len(protocols))
   251  
   252  	for _, p := range protocols {
   253  		if len(p) > 0 {
   254  			pCopy = append(pCopy, p)
   255  		}
   256  	}
   257  
   258  	return &ProtocolMatcher{
   259  		protocols: pCopy,
   260  	}
   261  }
   262  
   263  // Apply implements Condition.
   264  func (m *ProtocolMatcher) Apply(ctx routing.Context) bool {
   265  	protocol := ctx.GetProtocol()
   266  	if len(protocol) == 0 {
   267  		return false
   268  	}
   269  	for _, p := range m.protocols {
   270  		if strings.HasPrefix(protocol, p) {
   271  			return true
   272  		}
   273  	}
   274  	return false
   275  }
   276  
   277  type AttributeMatcher struct {
   278  	program *starlark.Program
   279  }
   280  
   281  func NewAttributeMatcher(code string) (*AttributeMatcher, error) {
   282  	starFile, err := syntax.Parse("attr.star", "satisfied=("+code+")", 0)
   283  	if err != nil {
   284  		return nil, newError("attr rule").Base(err)
   285  	}
   286  	p, err := starlark.FileProgram(starFile, func(name string) bool {
   287  		return name == "attrs"
   288  	})
   289  	if err != nil {
   290  		return nil, err
   291  	}
   292  	return &AttributeMatcher{
   293  		program: p,
   294  	}, nil
   295  }
   296  
   297  // Match implements attributes matching.
   298  func (m *AttributeMatcher) Match(attrs map[string]string) bool {
   299  	attrsDict := new(starlark.Dict)
   300  	for key, value := range attrs {
   301  		attrsDict.SetKey(starlark.String(key), starlark.String(value))
   302  	}
   303  
   304  	predefined := make(starlark.StringDict)
   305  	predefined["attrs"] = attrsDict
   306  
   307  	thread := &starlark.Thread{
   308  		Name: "matcher",
   309  	}
   310  	results, err := m.program.Init(thread, predefined)
   311  	if err != nil {
   312  		newError("attr matcher").Base(err).WriteToLog()
   313  	}
   314  	satisfied := results["satisfied"]
   315  	return satisfied != nil && bool(satisfied.Truth())
   316  }
   317  
   318  // Apply implements Condition.
   319  func (m *AttributeMatcher) Apply(ctx routing.Context) bool {
   320  	attributes := ctx.GetAttributes()
   321  	if attributes == nil {
   322  		return false
   323  	}
   324  	return m.Match(attributes)
   325  }