github.com/xmplusdev/xray-core@v1.8.10/app/router/condition_test.go (about)

     1  package router_test
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"strconv"
     7  	"testing"
     8  
     9  	. "github.com/xmplusdev/xray-core/app/router"
    10  	"github.com/xmplusdev/xray-core/common"
    11  	"github.com/xmplusdev/xray-core/common/errors"
    12  	"github.com/xmplusdev/xray-core/common/net"
    13  	"github.com/xmplusdev/xray-core/common/platform"
    14  	"github.com/xmplusdev/xray-core/common/platform/filesystem"
    15  	"github.com/xmplusdev/xray-core/common/protocol"
    16  	"github.com/xmplusdev/xray-core/common/protocol/http"
    17  	"github.com/xmplusdev/xray-core/common/session"
    18  	"github.com/xmplusdev/xray-core/features/routing"
    19  	routing_session "github.com/xmplusdev/xray-core/features/routing/session"
    20  	"google.golang.org/protobuf/proto"
    21  )
    22  
    23  func init() {
    24  	wd, err := os.Getwd()
    25  	common.Must(err)
    26  
    27  	if _, err := os.Stat(platform.GetAssetLocation("geoip.dat")); err != nil && os.IsNotExist(err) {
    28  		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geoip.dat"), filepath.Join(wd, "..", "..", "release", "config", "geoip.dat")))
    29  	}
    30  	if _, err := os.Stat(platform.GetAssetLocation("geosite.dat")); err != nil && os.IsNotExist(err) {
    31  		common.Must(filesystem.CopyFile(platform.GetAssetLocation("geosite.dat"), filepath.Join(wd, "..", "..", "release", "config", "geosite.dat")))
    32  	}
    33  }
    34  
    35  func withBackground() routing.Context {
    36  	return &routing_session.Context{}
    37  }
    38  
    39  func withOutbound(outbound *session.Outbound) routing.Context {
    40  	return &routing_session.Context{Outbound: outbound}
    41  }
    42  
    43  func withInbound(inbound *session.Inbound) routing.Context {
    44  	return &routing_session.Context{Inbound: inbound}
    45  }
    46  
    47  func withContent(content *session.Content) routing.Context {
    48  	return &routing_session.Context{Content: content}
    49  }
    50  
    51  func TestRoutingRule(t *testing.T) {
    52  	type ruleTest struct {
    53  		input  routing.Context
    54  		output bool
    55  	}
    56  
    57  	cases := []struct {
    58  		rule *RoutingRule
    59  		test []ruleTest
    60  	}{
    61  		{
    62  			rule: &RoutingRule{
    63  				Domain: []*Domain{
    64  					{
    65  						Value: "example.com",
    66  						Type:  Domain_Plain,
    67  					},
    68  					{
    69  						Value: "google.com",
    70  						Type:  Domain_Domain,
    71  					},
    72  					{
    73  						Value: "^facebook\\.com$",
    74  						Type:  Domain_Regex,
    75  					},
    76  				},
    77  			},
    78  			test: []ruleTest{
    79  				{
    80  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}),
    81  					output: true,
    82  				},
    83  				{
    84  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.example.com.www"), 80)}),
    85  					output: true,
    86  				},
    87  				{
    88  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.co"), 80)}),
    89  					output: false,
    90  				},
    91  				{
    92  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.google.com"), 80)}),
    93  					output: true,
    94  				},
    95  				{
    96  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("facebook.com"), 80)}),
    97  					output: true,
    98  				},
    99  				{
   100  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.facebook.com"), 80)}),
   101  					output: false,
   102  				},
   103  				{
   104  					input:  withBackground(),
   105  					output: false,
   106  				},
   107  			},
   108  		},
   109  		{
   110  			rule: &RoutingRule{
   111  				Cidr: []*CIDR{
   112  					{
   113  						Ip:     []byte{8, 8, 8, 8},
   114  						Prefix: 32,
   115  					},
   116  					{
   117  						Ip:     []byte{8, 8, 8, 8},
   118  						Prefix: 32,
   119  					},
   120  					{
   121  						Ip:     net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(),
   122  						Prefix: 128,
   123  					},
   124  				},
   125  			},
   126  			test: []ruleTest{
   127  				{
   128  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}),
   129  					output: true,
   130  				},
   131  				{
   132  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.4.4"), 80)}),
   133  					output: false,
   134  				},
   135  				{
   136  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 80)}),
   137  					output: true,
   138  				},
   139  				{
   140  					input:  withBackground(),
   141  					output: false,
   142  				},
   143  			},
   144  		},
   145  		{
   146  			rule: &RoutingRule{
   147  				Geoip: []*GeoIP{
   148  					{
   149  						Cidr: []*CIDR{
   150  							{
   151  								Ip:     []byte{8, 8, 8, 8},
   152  								Prefix: 32,
   153  							},
   154  							{
   155  								Ip:     []byte{8, 8, 8, 8},
   156  								Prefix: 32,
   157  							},
   158  							{
   159  								Ip:     net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(),
   160  								Prefix: 128,
   161  							},
   162  						},
   163  					},
   164  				},
   165  			},
   166  			test: []ruleTest{
   167  				{
   168  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}),
   169  					output: true,
   170  				},
   171  				{
   172  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.4.4"), 80)}),
   173  					output: false,
   174  				},
   175  				{
   176  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 80)}),
   177  					output: true,
   178  				},
   179  				{
   180  					input:  withBackground(),
   181  					output: false,
   182  				},
   183  			},
   184  		},
   185  		{
   186  			rule: &RoutingRule{
   187  				SourceCidr: []*CIDR{
   188  					{
   189  						Ip:     []byte{192, 168, 0, 0},
   190  						Prefix: 16,
   191  					},
   192  				},
   193  			},
   194  			test: []ruleTest{
   195  				{
   196  					input:  withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress("192.168.0.1"), 80)}),
   197  					output: true,
   198  				},
   199  				{
   200  					input:  withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress("10.0.0.1"), 80)}),
   201  					output: false,
   202  				},
   203  			},
   204  		},
   205  		{
   206  			rule: &RoutingRule{
   207  				UserEmail: []string{
   208  					"admin@example.com",
   209  				},
   210  			},
   211  			test: []ruleTest{
   212  				{
   213  					input:  withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "admin@example.com"}}),
   214  					output: true,
   215  				},
   216  				{
   217  					input:  withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "love@example.com"}}),
   218  					output: false,
   219  				},
   220  				{
   221  					input:  withBackground(),
   222  					output: false,
   223  				},
   224  			},
   225  		},
   226  		{
   227  			rule: &RoutingRule{
   228  				Protocol: []string{"http"},
   229  			},
   230  			test: []ruleTest{
   231  				{
   232  					input:  withContent(&session.Content{Protocol: (&http.SniffHeader{}).Protocol()}),
   233  					output: true,
   234  				},
   235  			},
   236  		},
   237  		{
   238  			rule: &RoutingRule{
   239  				InboundTag: []string{"test", "test1"},
   240  			},
   241  			test: []ruleTest{
   242  				{
   243  					input:  withInbound(&session.Inbound{Tag: "test"}),
   244  					output: true,
   245  				},
   246  				{
   247  					input:  withInbound(&session.Inbound{Tag: "test2"}),
   248  					output: false,
   249  				},
   250  			},
   251  		},
   252  		{
   253  			rule: &RoutingRule{
   254  				PortList: &net.PortList{
   255  					Range: []*net.PortRange{
   256  						{From: 443, To: 443},
   257  						{From: 1000, To: 1100},
   258  					},
   259  				},
   260  			},
   261  			test: []ruleTest{
   262  				{
   263  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 443)}),
   264  					output: true,
   265  				},
   266  				{
   267  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1100)}),
   268  					output: true,
   269  				},
   270  				{
   271  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1005)}),
   272  					output: true,
   273  				},
   274  				{
   275  					input:  withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 53)}),
   276  					output: false,
   277  				},
   278  			},
   279  		},
   280  		{
   281  			rule: &RoutingRule{
   282  				SourcePortList: &net.PortList{
   283  					Range: []*net.PortRange{
   284  						{From: 123, To: 123},
   285  						{From: 9993, To: 9999},
   286  					},
   287  				},
   288  			},
   289  			test: []ruleTest{
   290  				{
   291  					input:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 123)}),
   292  					output: true,
   293  				},
   294  				{
   295  					input:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9999)}),
   296  					output: true,
   297  				},
   298  				{
   299  					input:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9994)}),
   300  					output: true,
   301  				},
   302  				{
   303  					input:  withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 53)}),
   304  					output: false,
   305  				},
   306  			},
   307  		},
   308  		{
   309  			rule: &RoutingRule{
   310  				Protocol: []string{"http"},
   311  				Attributes: map[string]string{
   312  					":path": "/test",
   313  				},
   314  			},
   315  			test: []ruleTest{
   316  				{
   317  					input:  withContent(&session.Content{Protocol: "http/1.1", Attributes: map[string]string{":path": "/test/1"}}),
   318  					output: true,
   319  				},
   320  			},
   321  		},
   322  		{
   323  			rule: &RoutingRule{
   324  				Attributes: map[string]string{
   325  					"Custom": "p([a-z]+)ch",
   326  				},
   327  			},
   328  			test: []ruleTest{
   329  				{
   330  					input:  withContent(&session.Content{Attributes: map[string]string{"custom": "peach"}}),
   331  					output: true,
   332  				},
   333  			},
   334  		},
   335  	}
   336  
   337  	for _, test := range cases {
   338  		cond, err := test.rule.BuildCondition()
   339  		common.Must(err)
   340  
   341  		for _, subtest := range test.test {
   342  			actual := cond.Apply(subtest.input)
   343  			if actual != subtest.output {
   344  				t.Error("test case failed: ", subtest.input, " expected ", subtest.output, " but got ", actual)
   345  			}
   346  		}
   347  	}
   348  }
   349  
   350  func loadGeoSite(country string) ([]*Domain, error) {
   351  	geositeBytes, err := filesystem.ReadAsset("geosite.dat")
   352  	if err != nil {
   353  		return nil, err
   354  	}
   355  	var geositeList GeoSiteList
   356  	if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil {
   357  		return nil, err
   358  	}
   359  
   360  	for _, site := range geositeList.Entry {
   361  		if site.CountryCode == country {
   362  			return site.Domain, nil
   363  		}
   364  	}
   365  
   366  	return nil, errors.New("country not found: " + country)
   367  }
   368  
   369  func TestChinaSites(t *testing.T) {
   370  	domains, err := loadGeoSite("CN")
   371  	common.Must(err)
   372  
   373  	matcher, err := NewDomainMatcher(domains)
   374  	common.Must(err)
   375  
   376  	acMatcher, err := NewMphMatcherGroup(domains)
   377  	common.Must(err)
   378  
   379  	type TestCase struct {
   380  		Domain string
   381  		Output bool
   382  	}
   383  	testCases := []TestCase{
   384  		{
   385  			Domain: "163.com",
   386  			Output: true,
   387  		},
   388  		{
   389  			Domain: "163.com",
   390  			Output: true,
   391  		},
   392  		{
   393  			Domain: "164.com",
   394  			Output: false,
   395  		},
   396  		{
   397  			Domain: "164.com",
   398  			Output: false,
   399  		},
   400  	}
   401  
   402  	for i := 0; i < 1024; i++ {
   403  		testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
   404  	}
   405  
   406  	for _, testCase := range testCases {
   407  		r1 := matcher.ApplyDomain(testCase.Domain)
   408  		r2 := acMatcher.ApplyDomain(testCase.Domain)
   409  		if r1 != testCase.Output {
   410  			t.Error("DomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r1)
   411  		} else if r2 != testCase.Output {
   412  			t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r2)
   413  		}
   414  	}
   415  }
   416  
   417  func BenchmarkMphDomainMatcher(b *testing.B) {
   418  	domains, err := loadGeoSite("CN")
   419  	common.Must(err)
   420  
   421  	matcher, err := NewMphMatcherGroup(domains)
   422  	common.Must(err)
   423  
   424  	type TestCase struct {
   425  		Domain string
   426  		Output bool
   427  	}
   428  	testCases := []TestCase{
   429  		{
   430  			Domain: "163.com",
   431  			Output: true,
   432  		},
   433  		{
   434  			Domain: "163.com",
   435  			Output: true,
   436  		},
   437  		{
   438  			Domain: "164.com",
   439  			Output: false,
   440  		},
   441  		{
   442  			Domain: "164.com",
   443  			Output: false,
   444  		},
   445  	}
   446  
   447  	for i := 0; i < 1024; i++ {
   448  		testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
   449  	}
   450  
   451  	b.ResetTimer()
   452  	for i := 0; i < b.N; i++ {
   453  		for _, testCase := range testCases {
   454  			_ = matcher.ApplyDomain(testCase.Domain)
   455  		}
   456  	}
   457  }
   458  
   459  func BenchmarkDomainMatcher(b *testing.B) {
   460  	domains, err := loadGeoSite("CN")
   461  	common.Must(err)
   462  
   463  	matcher, err := NewDomainMatcher(domains)
   464  	common.Must(err)
   465  
   466  	type TestCase struct {
   467  		Domain string
   468  		Output bool
   469  	}
   470  	testCases := []TestCase{
   471  		{
   472  			Domain: "163.com",
   473  			Output: true,
   474  		},
   475  		{
   476  			Domain: "163.com",
   477  			Output: true,
   478  		},
   479  		{
   480  			Domain: "164.com",
   481  			Output: false,
   482  		},
   483  		{
   484  			Domain: "164.com",
   485  			Output: false,
   486  		},
   487  	}
   488  
   489  	for i := 0; i < 1024; i++ {
   490  		testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
   491  	}
   492  
   493  	b.ResetTimer()
   494  	for i := 0; i < b.N; i++ {
   495  		for _, testCase := range testCases {
   496  			_ = matcher.ApplyDomain(testCase.Domain)
   497  		}
   498  	}
   499  }
   500  
   501  func BenchmarkMultiGeoIPMatcher(b *testing.B) {
   502  	var geoips []*GeoIP
   503  
   504  	{
   505  		ips, err := loadGeoIP("CN")
   506  		common.Must(err)
   507  		geoips = append(geoips, &GeoIP{
   508  			CountryCode: "CN",
   509  			Cidr:        ips,
   510  		})
   511  	}
   512  
   513  	{
   514  		ips, err := loadGeoIP("JP")
   515  		common.Must(err)
   516  		geoips = append(geoips, &GeoIP{
   517  			CountryCode: "JP",
   518  			Cidr:        ips,
   519  		})
   520  	}
   521  
   522  	{
   523  		ips, err := loadGeoIP("CA")
   524  		common.Must(err)
   525  		geoips = append(geoips, &GeoIP{
   526  			CountryCode: "CA",
   527  			Cidr:        ips,
   528  		})
   529  	}
   530  
   531  	{
   532  		ips, err := loadGeoIP("US")
   533  		common.Must(err)
   534  		geoips = append(geoips, &GeoIP{
   535  			CountryCode: "US",
   536  			Cidr:        ips,
   537  		})
   538  	}
   539  
   540  	matcher, err := NewMultiGeoIPMatcher(geoips, false)
   541  	common.Must(err)
   542  
   543  	ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})
   544  
   545  	b.ResetTimer()
   546  
   547  	for i := 0; i < b.N; i++ {
   548  		_ = matcher.Apply(ctx)
   549  	}
   550  }