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