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