github.com/xraypb/Xray-core@v1.8.1/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/golang/protobuf/proto" 10 . "github.com/xraypb/Xray-core/app/router" 11 "github.com/xraypb/Xray-core/common" 12 "github.com/xraypb/Xray-core/common/errors" 13 "github.com/xraypb/Xray-core/common/net" 14 "github.com/xraypb/Xray-core/common/platform" 15 "github.com/xraypb/Xray-core/common/platform/filesystem" 16 "github.com/xraypb/Xray-core/common/protocol" 17 "github.com/xraypb/Xray-core/common/protocol/http" 18 "github.com/xraypb/Xray-core/common/session" 19 "github.com/xraypb/Xray-core/features/routing" 20 routing_session "github.com/xraypb/Xray-core/features/routing/session" 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: "attrs[':path'].startswith('/test')", 312 }, 313 test: []ruleTest{ 314 { 315 input: withContent(&session.Content{Protocol: "http/1.1", Attributes: map[string]string{":path": "/test/1"}}), 316 output: true, 317 }, 318 }, 319 }, 320 } 321 322 for _, test := range cases { 323 cond, err := test.rule.BuildCondition() 324 common.Must(err) 325 326 for _, subtest := range test.test { 327 actual := cond.Apply(subtest.input) 328 if actual != subtest.output { 329 t.Error("test case failed: ", subtest.input, " expected ", subtest.output, " but got ", actual) 330 } 331 } 332 } 333 } 334 335 func loadGeoSite(country string) ([]*Domain, error) { 336 geositeBytes, err := filesystem.ReadAsset("geosite.dat") 337 if err != nil { 338 return nil, err 339 } 340 var geositeList GeoSiteList 341 if err := proto.Unmarshal(geositeBytes, &geositeList); err != nil { 342 return nil, err 343 } 344 345 for _, site := range geositeList.Entry { 346 if site.CountryCode == country { 347 return site.Domain, nil 348 } 349 } 350 351 return nil, errors.New("country not found: " + country) 352 } 353 354 func TestChinaSites(t *testing.T) { 355 domains, err := loadGeoSite("CN") 356 common.Must(err) 357 358 matcher, err := NewDomainMatcher(domains) 359 common.Must(err) 360 361 acMatcher, err := NewMphMatcherGroup(domains) 362 common.Must(err) 363 364 type TestCase struct { 365 Domain string 366 Output bool 367 } 368 testCases := []TestCase{ 369 { 370 Domain: "163.com", 371 Output: true, 372 }, 373 { 374 Domain: "163.com", 375 Output: true, 376 }, 377 { 378 Domain: "164.com", 379 Output: false, 380 }, 381 { 382 Domain: "164.com", 383 Output: false, 384 }, 385 } 386 387 for i := 0; i < 1024; i++ { 388 testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false}) 389 } 390 391 for _, testCase := range testCases { 392 r1 := matcher.ApplyDomain(testCase.Domain) 393 r2 := acMatcher.ApplyDomain(testCase.Domain) 394 if r1 != testCase.Output { 395 t.Error("DomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r1) 396 } else if r2 != testCase.Output { 397 t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r2) 398 } 399 } 400 } 401 402 func BenchmarkMphDomainMatcher(b *testing.B) { 403 domains, err := loadGeoSite("CN") 404 common.Must(err) 405 406 matcher, err := NewMphMatcherGroup(domains) 407 common.Must(err) 408 409 type TestCase struct { 410 Domain string 411 Output bool 412 } 413 testCases := []TestCase{ 414 { 415 Domain: "163.com", 416 Output: true, 417 }, 418 { 419 Domain: "163.com", 420 Output: true, 421 }, 422 { 423 Domain: "164.com", 424 Output: false, 425 }, 426 { 427 Domain: "164.com", 428 Output: false, 429 }, 430 } 431 432 for i := 0; i < 1024; i++ { 433 testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false}) 434 } 435 436 b.ResetTimer() 437 for i := 0; i < b.N; i++ { 438 for _, testCase := range testCases { 439 _ = matcher.ApplyDomain(testCase.Domain) 440 } 441 } 442 } 443 444 func BenchmarkDomainMatcher(b *testing.B) { 445 domains, err := loadGeoSite("CN") 446 common.Must(err) 447 448 matcher, err := NewDomainMatcher(domains) 449 common.Must(err) 450 451 type TestCase struct { 452 Domain string 453 Output bool 454 } 455 testCases := []TestCase{ 456 { 457 Domain: "163.com", 458 Output: true, 459 }, 460 { 461 Domain: "163.com", 462 Output: true, 463 }, 464 { 465 Domain: "164.com", 466 Output: false, 467 }, 468 { 469 Domain: "164.com", 470 Output: false, 471 }, 472 } 473 474 for i := 0; i < 1024; i++ { 475 testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false}) 476 } 477 478 b.ResetTimer() 479 for i := 0; i < b.N; i++ { 480 for _, testCase := range testCases { 481 _ = matcher.ApplyDomain(testCase.Domain) 482 } 483 } 484 } 485 486 func BenchmarkMultiGeoIPMatcher(b *testing.B) { 487 var geoips []*GeoIP 488 489 { 490 ips, err := loadGeoIP("CN") 491 common.Must(err) 492 geoips = append(geoips, &GeoIP{ 493 CountryCode: "CN", 494 Cidr: ips, 495 }) 496 } 497 498 { 499 ips, err := loadGeoIP("JP") 500 common.Must(err) 501 geoips = append(geoips, &GeoIP{ 502 CountryCode: "JP", 503 Cidr: ips, 504 }) 505 } 506 507 { 508 ips, err := loadGeoIP("CA") 509 common.Must(err) 510 geoips = append(geoips, &GeoIP{ 511 CountryCode: "CA", 512 Cidr: ips, 513 }) 514 } 515 516 { 517 ips, err := loadGeoIP("US") 518 common.Must(err) 519 geoips = append(geoips, &GeoIP{ 520 CountryCode: "US", 521 Cidr: ips, 522 }) 523 } 524 525 matcher, err := NewMultiGeoIPMatcher(geoips, false) 526 common.Must(err) 527 528 ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}) 529 530 b.ResetTimer() 531 532 for i := 0; i < b.N; i++ { 533 _ = matcher.Apply(ctx) 534 } 535 }