condition_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. package router_test
  2. import (
  3. "errors"
  4. "io/fs"
  5. "os"
  6. "path/filepath"
  7. "strconv"
  8. "testing"
  9. "github.com/v2fly/v2ray-core/v4/app/router"
  10. "github.com/v2fly/v2ray-core/v4/common"
  11. "github.com/v2fly/v2ray-core/v4/common/geodata"
  12. "github.com/v2fly/v2ray-core/v4/common/net"
  13. "github.com/v2fly/v2ray-core/v4/common/platform"
  14. "github.com/v2fly/v2ray-core/v4/common/platform/filesystem"
  15. "github.com/v2fly/v2ray-core/v4/common/protocol"
  16. "github.com/v2fly/v2ray-core/v4/common/protocol/http"
  17. "github.com/v2fly/v2ray-core/v4/common/session"
  18. "github.com/v2fly/v2ray-core/v4/features/routing"
  19. routing_session "github.com/v2fly/v2ray-core/v4/features/routing/session"
  20. )
  21. func init() {
  22. wd, err := os.Getwd()
  23. common.Must(err)
  24. tempPath := filepath.Join(wd, "..", "..", "testing", "temp")
  25. os.Setenv("v2ray.location.asset", tempPath)
  26. geoipPath := platform.GetAssetLocation("geoip.dat")
  27. geositePath := platform.GetAssetLocation("geosite.dat")
  28. if _, err := os.Stat(geoipPath); err != nil && errors.Is(err, fs.ErrNotExist) {
  29. common.Must(os.MkdirAll(tempPath, 0755))
  30. geoipBytes, err := common.FetchHTTPContent(geoipURL)
  31. common.Must(err)
  32. common.Must(filesystem.WriteFile(geoipPath, geoipBytes))
  33. }
  34. if _, err := os.Stat(geositePath); err != nil && errors.Is(err, fs.ErrNotExist) {
  35. common.Must(os.MkdirAll(tempPath, 0755))
  36. geositeBytes, err := common.FetchHTTPContent(geositeURL)
  37. common.Must(err)
  38. common.Must(filesystem.WriteFile(geositePath, geositeBytes))
  39. }
  40. }
  41. func withBackground() routing.Context {
  42. return &routing_session.Context{}
  43. }
  44. func withOutbound(outbound *session.Outbound) routing.Context {
  45. return &routing_session.Context{Outbound: outbound}
  46. }
  47. func withInbound(inbound *session.Inbound) routing.Context {
  48. return &routing_session.Context{Inbound: inbound}
  49. }
  50. func withContent(content *session.Content) routing.Context {
  51. return &routing_session.Context{Content: content}
  52. }
  53. func TestRoutingRule(t *testing.T) {
  54. type ruleTest struct {
  55. input routing.Context
  56. output bool
  57. }
  58. cases := []struct {
  59. rule *router.RoutingRule
  60. test []ruleTest
  61. }{
  62. {
  63. rule: &router.RoutingRule{
  64. Domain: []*router.Domain{
  65. {
  66. Value: "v2fly.org",
  67. Type: router.Domain_Plain,
  68. },
  69. {
  70. Value: "google.com",
  71. Type: router.Domain_Domain,
  72. },
  73. {
  74. Value: "^facebook\\.com$",
  75. Type: router.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: &router.RoutingRule{
  112. Cidr: []*router.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: &router.RoutingRule{
  148. Geoip: []*router.GeoIP{
  149. {
  150. Cidr: []*router.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: &router.RoutingRule{
  188. SourceCidr: []*router.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: &router.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: &router.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: &router.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: &router.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: &router.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: &router.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. for _, test := range cases {
  323. cond, err := test.rule.BuildCondition()
  324. common.Must(err)
  325. for _, subtest := range test.test {
  326. actual := cond.Apply(subtest.input)
  327. if actual != subtest.output {
  328. t.Error("test case failed: ", subtest.input, " expected ", subtest.output, " but got ", actual)
  329. }
  330. }
  331. }
  332. }
  333. func TestChinaSites(t *testing.T) {
  334. domains, err := geodata.LoadSite("geosite.dat", "CN")
  335. common.Must(err)
  336. matcher, err := router.NewDomainMatcher(domains)
  337. common.Must(err)
  338. acMatcher, err := router.NewMphMatcherGroup(domains)
  339. common.Must(err)
  340. type TestCase struct {
  341. Domain string
  342. Output bool
  343. }
  344. testCases := []TestCase{
  345. {
  346. Domain: "163.com",
  347. Output: true,
  348. },
  349. {
  350. Domain: "163.com",
  351. Output: true,
  352. },
  353. {
  354. Domain: "164.com",
  355. Output: false,
  356. },
  357. {
  358. Domain: "164.com",
  359. Output: false,
  360. },
  361. }
  362. for i := 0; i < 1024; i++ {
  363. testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
  364. }
  365. for _, testCase := range testCases {
  366. r1 := matcher.ApplyDomain(testCase.Domain)
  367. r2 := acMatcher.ApplyDomain(testCase.Domain)
  368. if r1 != testCase.Output {
  369. t.Error("DomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r1)
  370. } else if r2 != testCase.Output {
  371. t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r2)
  372. }
  373. }
  374. }
  375. func BenchmarkMphDomainMatcher(b *testing.B) {
  376. domains, err := geodata.LoadSite("geosite.dat", "CN")
  377. common.Must(err)
  378. matcher, err := router.NewMphMatcherGroup(domains)
  379. common.Must(err)
  380. type TestCase struct {
  381. Domain string
  382. Output bool
  383. }
  384. testCases := []TestCase{
  385. {
  386. Domain: "163.com",
  387. Output: true,
  388. },
  389. {
  390. Domain: "163.com",
  391. Output: true,
  392. },
  393. {
  394. Domain: "164.com",
  395. Output: false,
  396. },
  397. {
  398. Domain: "164.com",
  399. Output: false,
  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. b.ResetTimer()
  406. for i := 0; i < b.N; i++ {
  407. for _, testCase := range testCases {
  408. _ = matcher.ApplyDomain(testCase.Domain)
  409. }
  410. }
  411. }
  412. func BenchmarkDomainMatcher(b *testing.B) {
  413. domains, err := geodata.LoadSite("geosite.dat", "CN")
  414. common.Must(err)
  415. matcher, err := router.NewDomainMatcher(domains)
  416. common.Must(err)
  417. type TestCase struct {
  418. Domain string
  419. Output bool
  420. }
  421. testCases := []TestCase{
  422. {
  423. Domain: "163.com",
  424. Output: true,
  425. },
  426. {
  427. Domain: "163.com",
  428. Output: true,
  429. },
  430. {
  431. Domain: "164.com",
  432. Output: false,
  433. },
  434. {
  435. Domain: "164.com",
  436. Output: false,
  437. },
  438. }
  439. for i := 0; i < 1024; i++ {
  440. testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
  441. }
  442. b.ResetTimer()
  443. for i := 0; i < b.N; i++ {
  444. for _, testCase := range testCases {
  445. _ = matcher.ApplyDomain(testCase.Domain)
  446. }
  447. }
  448. }
  449. func BenchmarkMultiGeoIPMatcher(b *testing.B) {
  450. var geoips []*router.GeoIP
  451. {
  452. ips, err := geodata.LoadIP("geoip.dat", "CN")
  453. common.Must(err)
  454. geoips = append(geoips, &router.GeoIP{
  455. CountryCode: "CN",
  456. Cidr: ips,
  457. })
  458. }
  459. {
  460. ips, err := geodata.LoadIP("geoip.dat", "JP")
  461. common.Must(err)
  462. geoips = append(geoips, &router.GeoIP{
  463. CountryCode: "JP",
  464. Cidr: ips,
  465. })
  466. }
  467. {
  468. ips, err := geodata.LoadIP("geoip.dat", "CA")
  469. common.Must(err)
  470. geoips = append(geoips, &router.GeoIP{
  471. CountryCode: "CA",
  472. Cidr: ips,
  473. })
  474. }
  475. {
  476. ips, err := geodata.LoadIP("geoip.dat", "US")
  477. common.Must(err)
  478. geoips = append(geoips, &router.GeoIP{
  479. CountryCode: "US",
  480. Cidr: ips,
  481. })
  482. }
  483. matcher, err := router.NewMultiGeoIPMatcher(geoips, false)
  484. common.Must(err)
  485. ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})
  486. b.ResetTimer()
  487. for i := 0; i < b.N; i++ {
  488. _ = matcher.Apply(ctx)
  489. }
  490. }