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