1package router_test 2 3import ( 4 "os" 5 "path/filepath" 6 "strconv" 7 "testing" 8 9 "github.com/golang/protobuf/proto" 10 11 . "github.com/v2fly/v2ray-core/v4/app/router" 12 "github.com/v2fly/v2ray-core/v4/common" 13 "github.com/v2fly/v2ray-core/v4/common/errors" 14 "github.com/v2fly/v2ray-core/v4/common/net" 15 "github.com/v2fly/v2ray-core/v4/common/platform" 16 "github.com/v2fly/v2ray-core/v4/common/platform/filesystem" 17 "github.com/v2fly/v2ray-core/v4/common/protocol" 18 "github.com/v2fly/v2ray-core/v4/common/protocol/http" 19 "github.com/v2fly/v2ray-core/v4/common/session" 20 "github.com/v2fly/v2ray-core/v4/features/routing" 21 routing_session "github.com/v2fly/v2ray-core/v4/features/routing/session" 22) 23 24func 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 36func withBackground() routing.Context { 37 return &routing_session.Context{} 38} 39 40func withOutbound(outbound *session.Outbound) routing.Context { 41 return &routing_session.Context{Outbound: outbound} 42} 43 44func withInbound(inbound *session.Inbound) routing.Context { 45 return &routing_session.Context{Inbound: inbound} 46} 47 48func withContent(content *session.Content) routing.Context { 49 return &routing_session.Context{Content: content} 50} 51 52func 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: "v2fly.org", 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("v2fly.org"), 80)}), 82 output: true, 83 }, 84 { 85 input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.v2fly.org.www"), 80)}), 86 output: true, 87 }, 88 { 89 input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("v2ray.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@v2fly.org", 210 }, 211 }, 212 test: []ruleTest{ 213 { 214 input: withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "admin@v2fly.org"}}), 215 output: true, 216 }, 217 { 218 input: withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "love@v2fly.org"}}), 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 336func 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 355func TestChinaSites(t *testing.T) { 356 domains, err := loadGeoSite("CN") 357 common.Must(err) 358 359 matcher, err := NewDomainMatcher(domains) 360 common.Must(err) 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 402func 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 444func 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 486func 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} 536