condition_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. package router_test
  2. import (
  3. "os"
  4. "path/filepath"
  5. "runtime"
  6. "strconv"
  7. "testing"
  8. "github.com/xtls/xray-core/app/router"
  9. . "github.com/xtls/xray-core/app/router"
  10. "github.com/xtls/xray-core/common"
  11. "github.com/xtls/xray-core/common/net"
  12. "github.com/xtls/xray-core/common/protocol"
  13. "github.com/xtls/xray-core/common/protocol/http"
  14. "github.com/xtls/xray-core/common/session"
  15. "github.com/xtls/xray-core/features/routing"
  16. routing_session "github.com/xtls/xray-core/features/routing/session"
  17. "github.com/xtls/xray-core/infra/conf"
  18. )
  19. func withBackground() routing.Context {
  20. return &routing_session.Context{}
  21. }
  22. func withOutbound(outbound *session.Outbound) routing.Context {
  23. return &routing_session.Context{Outbound: outbound}
  24. }
  25. func withInbound(inbound *session.Inbound) routing.Context {
  26. return &routing_session.Context{Inbound: inbound}
  27. }
  28. func withContent(content *session.Content) routing.Context {
  29. return &routing_session.Context{Content: content}
  30. }
  31. func TestRoutingRule(t *testing.T) {
  32. type ruleTest struct {
  33. input routing.Context
  34. output bool
  35. }
  36. cases := []struct {
  37. rule *RoutingRule
  38. test []ruleTest
  39. }{
  40. {
  41. rule: &RoutingRule{
  42. Domain: []*Domain{
  43. {
  44. Value: "example.com",
  45. Type: Domain_Plain,
  46. },
  47. {
  48. Value: "google.com",
  49. Type: Domain_Domain,
  50. },
  51. {
  52. Value: "^facebook\\.com$",
  53. Type: Domain_Regex,
  54. },
  55. },
  56. },
  57. test: []ruleTest{
  58. {
  59. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.com"), 80)}),
  60. output: true,
  61. },
  62. {
  63. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.example.com.www"), 80)}),
  64. output: true,
  65. },
  66. {
  67. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("example.co"), 80)}),
  68. output: false,
  69. },
  70. {
  71. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.google.com"), 80)}),
  72. output: true,
  73. },
  74. {
  75. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("facebook.com"), 80)}),
  76. output: true,
  77. },
  78. {
  79. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.DomainAddress("www.facebook.com"), 80)}),
  80. output: false,
  81. },
  82. {
  83. input: withBackground(),
  84. output: false,
  85. },
  86. },
  87. },
  88. {
  89. rule: &RoutingRule{
  90. Geoip: []*GeoIP{
  91. {
  92. Cidr: []*CIDR{
  93. {
  94. Ip: []byte{8, 8, 8, 8},
  95. Prefix: 32,
  96. },
  97. {
  98. Ip: []byte{8, 8, 8, 8},
  99. Prefix: 32,
  100. },
  101. {
  102. Ip: net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334").IP(),
  103. Prefix: 128,
  104. },
  105. },
  106. },
  107. },
  108. },
  109. test: []ruleTest{
  110. {
  111. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)}),
  112. output: true,
  113. },
  114. {
  115. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.4.4"), 80)}),
  116. output: false,
  117. },
  118. {
  119. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), 80)}),
  120. output: true,
  121. },
  122. {
  123. input: withBackground(),
  124. output: false,
  125. },
  126. },
  127. },
  128. {
  129. rule: &RoutingRule{
  130. SourceGeoip: []*GeoIP{
  131. {
  132. Cidr: []*CIDR{
  133. {
  134. Ip: []byte{192, 168, 0, 0},
  135. Prefix: 16,
  136. },
  137. },
  138. },
  139. },
  140. },
  141. test: []ruleTest{
  142. {
  143. input: withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress("192.168.0.1"), 80)}),
  144. output: true,
  145. },
  146. {
  147. input: withInbound(&session.Inbound{Source: net.TCPDestination(net.ParseAddress("10.0.0.1"), 80)}),
  148. output: false,
  149. },
  150. },
  151. },
  152. {
  153. rule: &RoutingRule{
  154. UserEmail: []string{
  155. "[email protected]",
  156. },
  157. },
  158. test: []ruleTest{
  159. {
  160. input: withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "[email protected]"}}),
  161. output: true,
  162. },
  163. {
  164. input: withInbound(&session.Inbound{User: &protocol.MemoryUser{Email: "[email protected]"}}),
  165. output: false,
  166. },
  167. {
  168. input: withBackground(),
  169. output: false,
  170. },
  171. },
  172. },
  173. {
  174. rule: &RoutingRule{
  175. Protocol: []string{"http"},
  176. },
  177. test: []ruleTest{
  178. {
  179. input: withContent(&session.Content{Protocol: (&http.SniffHeader{}).Protocol()}),
  180. output: true,
  181. },
  182. },
  183. },
  184. {
  185. rule: &RoutingRule{
  186. InboundTag: []string{"test", "test1"},
  187. },
  188. test: []ruleTest{
  189. {
  190. input: withInbound(&session.Inbound{Tag: "test"}),
  191. output: true,
  192. },
  193. {
  194. input: withInbound(&session.Inbound{Tag: "test2"}),
  195. output: false,
  196. },
  197. },
  198. },
  199. {
  200. rule: &RoutingRule{
  201. PortList: &net.PortList{
  202. Range: []*net.PortRange{
  203. {From: 443, To: 443},
  204. {From: 1000, To: 1100},
  205. },
  206. },
  207. },
  208. test: []ruleTest{
  209. {
  210. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 443)}),
  211. output: true,
  212. },
  213. {
  214. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1100)}),
  215. output: true,
  216. },
  217. {
  218. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 1005)}),
  219. output: true,
  220. },
  221. {
  222. input: withOutbound(&session.Outbound{Target: net.TCPDestination(net.LocalHostIP, 53)}),
  223. output: false,
  224. },
  225. },
  226. },
  227. {
  228. rule: &RoutingRule{
  229. SourcePortList: &net.PortList{
  230. Range: []*net.PortRange{
  231. {From: 123, To: 123},
  232. {From: 9993, To: 9999},
  233. },
  234. },
  235. },
  236. test: []ruleTest{
  237. {
  238. input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 123)}),
  239. output: true,
  240. },
  241. {
  242. input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9999)}),
  243. output: true,
  244. },
  245. {
  246. input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 9994)}),
  247. output: true,
  248. },
  249. {
  250. input: withInbound(&session.Inbound{Source: net.UDPDestination(net.LocalHostIP, 53)}),
  251. output: false,
  252. },
  253. },
  254. },
  255. {
  256. rule: &RoutingRule{
  257. Protocol: []string{"http"},
  258. Attributes: map[string]string{
  259. ":path": "/test",
  260. },
  261. },
  262. test: []ruleTest{
  263. {
  264. input: withContent(&session.Content{Protocol: "http/1.1", Attributes: map[string]string{":path": "/test/1"}}),
  265. output: true,
  266. },
  267. },
  268. },
  269. {
  270. rule: &RoutingRule{
  271. Attributes: map[string]string{
  272. "Custom": "p([a-z]+)ch",
  273. },
  274. },
  275. test: []ruleTest{
  276. {
  277. input: withContent(&session.Content{Attributes: map[string]string{"custom": "peach"}}),
  278. output: true,
  279. },
  280. },
  281. },
  282. }
  283. for _, test := range cases {
  284. cond, err := test.rule.BuildCondition()
  285. common.Must(err)
  286. for _, subtest := range test.test {
  287. actual := cond.Apply(subtest.input)
  288. if actual != subtest.output {
  289. t.Error("test case failed: ", subtest.input, " expected ", subtest.output, " but got ", actual)
  290. }
  291. }
  292. }
  293. }
  294. func loadGeoSiteDomains(geo string) ([]*Domain, error) {
  295. os.Setenv("XRAY_LOCATION_ASSET", filepath.Join("..", "..", "resources"))
  296. domains, err := conf.ParseDomainRule(geo)
  297. if err != nil {
  298. return nil, err
  299. }
  300. if runtime.GOOS != "windows" && runtime.GOOS != "wasm" {
  301. domains, err = router.GetDomainList(domains)
  302. if err != nil {
  303. return nil, err
  304. }
  305. }
  306. return domains, nil
  307. }
  308. func TestChinaSites(t *testing.T) {
  309. domains, err := loadGeoSiteDomains("geosite:cn")
  310. common.Must(err)
  311. acMatcher, err := NewMphMatcherGroup(domains)
  312. common.Must(err)
  313. type TestCase struct {
  314. Domain string
  315. Output bool
  316. }
  317. testCases := []TestCase{
  318. {
  319. Domain: "163.com",
  320. Output: true,
  321. },
  322. {
  323. Domain: "163.com",
  324. Output: true,
  325. },
  326. {
  327. Domain: "164.com",
  328. Output: false,
  329. },
  330. {
  331. Domain: "164.com",
  332. Output: false,
  333. },
  334. }
  335. for i := 0; i < 1024; i++ {
  336. testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
  337. }
  338. for _, testCase := range testCases {
  339. r := acMatcher.ApplyDomain(testCase.Domain)
  340. if r != testCase.Output {
  341. t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
  342. }
  343. }
  344. }
  345. func TestChinaSitesWithAttrs(t *testing.T) {
  346. domains, err := loadGeoSiteDomains("geosite:google@cn")
  347. common.Must(err)
  348. acMatcher, err := NewMphMatcherGroup(domains)
  349. common.Must(err)
  350. type TestCase struct {
  351. Domain string
  352. Output bool
  353. }
  354. testCases := []TestCase{
  355. {
  356. Domain: "google.cn",
  357. Output: true,
  358. },
  359. {
  360. Domain: "recaptcha.net",
  361. Output: true,
  362. },
  363. {
  364. Domain: "164.com",
  365. Output: false,
  366. },
  367. {
  368. Domain: "164.com",
  369. Output: false,
  370. },
  371. }
  372. for i := 0; i < 1024; i++ {
  373. testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
  374. }
  375. for _, testCase := range testCases {
  376. r := acMatcher.ApplyDomain(testCase.Domain)
  377. if r != testCase.Output {
  378. t.Error("ACDomainMatcher expected output ", testCase.Output, " for domain ", testCase.Domain, " but got ", r)
  379. }
  380. }
  381. }
  382. func BenchmarkMphDomainMatcher(b *testing.B) {
  383. domains, err := loadGeoSiteDomains("geosite:cn")
  384. common.Must(err)
  385. matcher, err := NewMphMatcherGroup(domains)
  386. common.Must(err)
  387. type TestCase struct {
  388. Domain string
  389. Output bool
  390. }
  391. testCases := []TestCase{
  392. {
  393. Domain: "163.com",
  394. Output: true,
  395. },
  396. {
  397. Domain: "163.com",
  398. Output: true,
  399. },
  400. {
  401. Domain: "164.com",
  402. Output: false,
  403. },
  404. {
  405. Domain: "164.com",
  406. Output: false,
  407. },
  408. }
  409. for i := 0; i < 1024; i++ {
  410. testCases = append(testCases, TestCase{Domain: strconv.Itoa(i) + ".not-exists.com", Output: false})
  411. }
  412. b.ResetTimer()
  413. for i := 0; i < b.N; i++ {
  414. for _, testCase := range testCases {
  415. _ = matcher.ApplyDomain(testCase.Domain)
  416. }
  417. }
  418. }
  419. func BenchmarkMultiGeoIPMatcher(b *testing.B) {
  420. var geoips []*GeoIP
  421. {
  422. ips, err := loadGeoIP("geoip:cn")
  423. common.Must(err)
  424. geoips = append(geoips, &GeoIP{
  425. CountryCode: "CN",
  426. Cidr: ips.Cidr,
  427. })
  428. }
  429. {
  430. ips, err := loadGeoIP("JP")
  431. common.Must(err)
  432. geoips = append(geoips, &GeoIP{
  433. CountryCode: "JP",
  434. Cidr: ips.Cidr,
  435. })
  436. }
  437. {
  438. ips, err := loadGeoIP("geoip:ca")
  439. common.Must(err)
  440. geoips = append(geoips, &GeoIP{
  441. CountryCode: "CA",
  442. Cidr: ips.Cidr,
  443. })
  444. }
  445. {
  446. ips, err := loadGeoIP("geoip:us")
  447. common.Must(err)
  448. geoips = append(geoips, &GeoIP{
  449. CountryCode: "US",
  450. Cidr: ips.Cidr,
  451. })
  452. }
  453. matcher, err := NewIPMatcher(geoips, MatcherAsType_Target)
  454. common.Must(err)
  455. ctx := withOutbound(&session.Outbound{Target: net.TCPDestination(net.ParseAddress("8.8.8.8"), 80)})
  456. b.ResetTimer()
  457. for i := 0; i < b.N; i++ {
  458. _ = matcher.Apply(ctx)
  459. }
  460. }