github.com/xmplusdev/xmcore@v1.8.11-0.20240412132628-5518b55526af/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/xmcore/app/router" 10 "github.com/xmplusdev/xmcore/common" 11 "github.com/xmplusdev/xmcore/common/errors" 12 "github.com/xmplusdev/xmcore/common/net" 13 "github.com/xmplusdev/xmcore/common/platform" 14 "github.com/xmplusdev/xmcore/common/platform/filesystem" 15 "github.com/xmplusdev/xmcore/common/protocol" 16 "github.com/xmplusdev/xmcore/common/protocol/http" 17 "github.com/xmplusdev/xmcore/common/session" 18 "github.com/xmplusdev/xmcore/features/routing" 19 routing_session "github.com/xmplusdev/xmcore/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 }