rule_set_semantics_test.go 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261
  1. package rule
  2. import (
  3. "context"
  4. "net"
  5. "net/netip"
  6. "strings"
  7. "testing"
  8. "github.com/sagernet/sing-box/adapter"
  9. "github.com/sagernet/sing-box/common/convertor/adguard"
  10. C "github.com/sagernet/sing-box/constant"
  11. "github.com/sagernet/sing-box/option"
  12. slogger "github.com/sagernet/sing/common/logger"
  13. M "github.com/sagernet/sing/common/metadata"
  14. N "github.com/sagernet/sing/common/network"
  15. mDNS "github.com/miekg/dns"
  16. "github.com/stretchr/testify/require"
  17. )
  18. func TestRouteRuleSetMergeDestinationAddressGroup(t *testing.T) {
  19. t.Parallel()
  20. testCases := []struct {
  21. name string
  22. metadata adapter.InboundContext
  23. inner adapter.HeadlessRule
  24. }{
  25. {
  26. name: "domain",
  27. metadata: testMetadata("www.example.com"),
  28. inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationAddressItem(t, rule, []string{"www.example.com"}, nil) }),
  29. },
  30. {
  31. name: "domain_suffix",
  32. metadata: testMetadata("www.example.com"),
  33. inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationAddressItem(t, rule, nil, []string{"example.com"}) }),
  34. },
  35. {
  36. name: "domain_keyword",
  37. metadata: testMetadata("www.example.com"),
  38. inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationKeywordItem(rule, []string{"example"}) }),
  39. },
  40. {
  41. name: "domain_regex",
  42. metadata: testMetadata("www.example.com"),
  43. inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) { addDestinationRegexItem(t, rule, []string{`^www\.example\.com$`}) }),
  44. },
  45. {
  46. name: "ip_cidr",
  47. metadata: func() adapter.InboundContext {
  48. metadata := testMetadata("lookup.example")
  49. metadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("8.8.8.8")}
  50. return metadata
  51. }(),
  52. inner: headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  53. addDestinationIPCIDRItem(t, rule, []string{"8.8.8.0/24"})
  54. }),
  55. },
  56. }
  57. for _, testCase := range testCases {
  58. testCase := testCase
  59. t.Run(testCase.name, func(t *testing.T) {
  60. t.Parallel()
  61. ruleSet := newLocalRuleSetForTest("merge-destination", testCase.inner)
  62. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  63. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  64. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  65. })
  66. require.True(t, rule.Match(&testCase.metadata))
  67. })
  68. }
  69. }
  70. func TestRouteRuleSetMergeSourceAndPortGroups(t *testing.T) {
  71. t.Parallel()
  72. t.Run("source address", func(t *testing.T) {
  73. t.Parallel()
  74. metadata := testMetadata("www.example.com")
  75. ruleSet := newLocalRuleSetForTest("merge-source-address", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  76. addSourceAddressItem(t, rule, []string{"10.0.0.0/8"})
  77. }))
  78. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  79. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  80. addSourceAddressItem(t, rule, []string{"198.51.100.0/24"})
  81. })
  82. require.True(t, rule.Match(&metadata))
  83. })
  84. t.Run("source address via ruleset ipcidr match source", func(t *testing.T) {
  85. t.Parallel()
  86. metadata := testMetadata("www.example.com")
  87. ruleSet := newLocalRuleSetForTest("merge-source-address-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  88. addDestinationIPCIDRItem(t, rule, []string{"10.0.0.0/8"})
  89. }))
  90. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  91. addRuleSetItem(rule, &RuleSetItem{
  92. setList: []adapter.RuleSet{ruleSet},
  93. ipCidrMatchSource: true,
  94. })
  95. addSourceAddressItem(t, rule, []string{"198.51.100.0/24"})
  96. })
  97. require.True(t, rule.Match(&metadata))
  98. })
  99. t.Run("destination port", func(t *testing.T) {
  100. t.Parallel()
  101. metadata := testMetadata("www.example.com")
  102. ruleSet := newLocalRuleSetForTest("merge-destination-port", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  103. addDestinationPortItem(rule, []uint16{443})
  104. }))
  105. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  106. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  107. addDestinationPortItem(rule, []uint16{8443})
  108. })
  109. require.True(t, rule.Match(&metadata))
  110. })
  111. t.Run("destination port range", func(t *testing.T) {
  112. t.Parallel()
  113. metadata := testMetadata("www.example.com")
  114. ruleSet := newLocalRuleSetForTest("merge-destination-port-range", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  115. addDestinationPortRangeItem(t, rule, []string{"400:500"})
  116. }))
  117. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  118. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  119. addDestinationPortItem(rule, []uint16{8443})
  120. })
  121. require.True(t, rule.Match(&metadata))
  122. })
  123. t.Run("source port", func(t *testing.T) {
  124. t.Parallel()
  125. metadata := testMetadata("www.example.com")
  126. ruleSet := newLocalRuleSetForTest("merge-source-port", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  127. addSourcePortItem(rule, []uint16{1000})
  128. }))
  129. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  130. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  131. addSourcePortItem(rule, []uint16{2000})
  132. })
  133. require.True(t, rule.Match(&metadata))
  134. })
  135. t.Run("source port range", func(t *testing.T) {
  136. t.Parallel()
  137. metadata := testMetadata("www.example.com")
  138. ruleSet := newLocalRuleSetForTest("merge-source-port-range", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  139. addSourcePortRangeItem(t, rule, []string{"900:1100"})
  140. }))
  141. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  142. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  143. addSourcePortItem(rule, []uint16{2000})
  144. })
  145. require.True(t, rule.Match(&metadata))
  146. })
  147. }
  148. func TestRouteRuleSetOuterGroupedStateMergesIntoSameGroup(t *testing.T) {
  149. t.Parallel()
  150. testCases := []struct {
  151. name string
  152. metadata adapter.InboundContext
  153. buildOuter func(*testing.T, *abstractDefaultRule)
  154. buildInner func(*testing.T, *abstractDefaultRule)
  155. }{
  156. {
  157. name: "destination address",
  158. metadata: testMetadata("www.example.com"),
  159. buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
  160. t.Helper()
  161. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  162. },
  163. buildInner: func(t *testing.T, rule *abstractDefaultRule) {
  164. t.Helper()
  165. addDestinationAddressItem(t, rule, nil, []string{"google.com"})
  166. },
  167. },
  168. {
  169. name: "source address",
  170. metadata: testMetadata("www.example.com"),
  171. buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
  172. t.Helper()
  173. addSourceAddressItem(t, rule, []string{"10.0.0.0/8"})
  174. },
  175. buildInner: func(t *testing.T, rule *abstractDefaultRule) {
  176. t.Helper()
  177. addSourceAddressItem(t, rule, []string{"198.51.100.0/24"})
  178. },
  179. },
  180. {
  181. name: "source port",
  182. metadata: testMetadata("www.example.com"),
  183. buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
  184. t.Helper()
  185. addSourcePortItem(rule, []uint16{1000})
  186. },
  187. buildInner: func(t *testing.T, rule *abstractDefaultRule) {
  188. t.Helper()
  189. addSourcePortItem(rule, []uint16{2000})
  190. },
  191. },
  192. {
  193. name: "destination port",
  194. metadata: testMetadata("www.example.com"),
  195. buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
  196. t.Helper()
  197. addDestinationPortItem(rule, []uint16{443})
  198. },
  199. buildInner: func(t *testing.T, rule *abstractDefaultRule) {
  200. t.Helper()
  201. addDestinationPortItem(rule, []uint16{8443})
  202. },
  203. },
  204. {
  205. name: "destination ip cidr",
  206. metadata: func() adapter.InboundContext {
  207. metadata := testMetadata("lookup.example")
  208. metadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("203.0.113.1")}
  209. return metadata
  210. }(),
  211. buildOuter: func(t *testing.T, rule *abstractDefaultRule) {
  212. t.Helper()
  213. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  214. },
  215. buildInner: func(t *testing.T, rule *abstractDefaultRule) {
  216. t.Helper()
  217. addDestinationIPCIDRItem(t, rule, []string{"198.51.100.0/24"})
  218. },
  219. },
  220. }
  221. for _, testCase := range testCases {
  222. testCase := testCase
  223. t.Run(testCase.name, func(t *testing.T) {
  224. t.Parallel()
  225. ruleSet := newLocalRuleSetForTest("outer-merge-"+testCase.name, headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  226. testCase.buildInner(t, rule)
  227. }))
  228. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  229. testCase.buildOuter(t, rule)
  230. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  231. })
  232. require.True(t, rule.Match(&testCase.metadata))
  233. })
  234. }
  235. }
  236. func TestRouteRuleSetOtherFieldsStayAnd(t *testing.T) {
  237. t.Parallel()
  238. metadata := testMetadata("www.example.com")
  239. ruleSet := newLocalRuleSetForTest("other-fields-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  240. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  241. }))
  242. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  243. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  244. addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
  245. })
  246. require.False(t, rule.Match(&metadata))
  247. }
  248. func TestRouteRuleSetMergedBranchKeepsAndConstraints(t *testing.T) {
  249. t.Parallel()
  250. t.Run("outer group does not bypass inner non grouped condition", func(t *testing.T) {
  251. t.Parallel()
  252. metadata := testMetadata("www.example.com")
  253. ruleSet := newLocalRuleSetForTest("network-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  254. addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
  255. }))
  256. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  257. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  258. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  259. })
  260. require.False(t, rule.Match(&metadata))
  261. })
  262. t.Run("outer group does not satisfy different grouped branch", func(t *testing.T) {
  263. t.Parallel()
  264. metadata := testMetadata("www.example.com")
  265. ruleSet := newLocalRuleSetForTest("different-group", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  266. addDestinationAddressItem(t, rule, nil, []string{"google.com"})
  267. }))
  268. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  269. addSourcePortItem(rule, []uint16{1000})
  270. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  271. })
  272. require.False(t, rule.Match(&metadata))
  273. })
  274. }
  275. func TestRouteRuleSetOrSemantics(t *testing.T) {
  276. t.Parallel()
  277. t.Run("later ruleset can satisfy outer group", func(t *testing.T) {
  278. t.Parallel()
  279. metadata := testMetadata("www.example.com")
  280. emptyStateSet := newLocalRuleSetForTest("network-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  281. addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
  282. }))
  283. destinationStateSet := newLocalRuleSetForTest("domain-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  284. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  285. }))
  286. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  287. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{emptyStateSet, destinationStateSet}})
  288. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  289. })
  290. require.True(t, rule.Match(&metadata))
  291. })
  292. t.Run("later rule in same set can satisfy outer group", func(t *testing.T) {
  293. t.Parallel()
  294. metadata := testMetadata("www.example.com")
  295. ruleSet := newLocalRuleSetForTest(
  296. "rule-set-or",
  297. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  298. addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
  299. }),
  300. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  301. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  302. }),
  303. )
  304. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  305. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  306. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  307. })
  308. require.True(t, rule.Match(&metadata))
  309. })
  310. t.Run("cross ruleset union is not allowed", func(t *testing.T) {
  311. t.Parallel()
  312. metadata := testMetadata("www.example.com")
  313. sourceStateSet := newLocalRuleSetForTest("source-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  314. addSourcePortItem(rule, []uint16{1000})
  315. }))
  316. destinationStateSet := newLocalRuleSetForTest("destination-only", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  317. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  318. }))
  319. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  320. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{sourceStateSet, destinationStateSet}})
  321. addSourcePortItem(rule, []uint16{2000})
  322. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  323. })
  324. require.False(t, rule.Match(&metadata))
  325. })
  326. }
  327. func TestRouteRuleSetLogicalSemantics(t *testing.T) {
  328. t.Parallel()
  329. t.Run("logical or keeps all successful branch states", func(t *testing.T) {
  330. t.Parallel()
  331. metadata := testMetadata("www.example.com")
  332. ruleSet := newLocalRuleSetForTest("logical-or", headlessLogicalRule(
  333. C.LogicalTypeOr,
  334. false,
  335. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  336. addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
  337. }),
  338. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  339. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  340. }),
  341. ))
  342. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  343. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  344. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  345. })
  346. require.True(t, rule.Match(&metadata))
  347. })
  348. t.Run("logical and unions child states", func(t *testing.T) {
  349. t.Parallel()
  350. metadata := testMetadata("www.example.com")
  351. ruleSet := newLocalRuleSetForTest("logical-and", headlessLogicalRule(
  352. C.LogicalTypeAnd,
  353. false,
  354. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  355. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  356. }),
  357. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  358. addSourcePortItem(rule, []uint16{1000})
  359. }),
  360. ))
  361. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  362. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  363. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  364. addSourcePortItem(rule, []uint16{2000})
  365. })
  366. require.True(t, rule.Match(&metadata))
  367. })
  368. t.Run("invert success does not contribute positive state", func(t *testing.T) {
  369. t.Parallel()
  370. metadata := testMetadata("www.example.com")
  371. ruleSet := newLocalRuleSetForTest("invert", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  372. rule.invert = true
  373. addDestinationAddressItem(t, rule, nil, []string{"cn"})
  374. }))
  375. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  376. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  377. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  378. })
  379. require.False(t, rule.Match(&metadata))
  380. })
  381. }
  382. func TestRouteRuleSetInvertMergedBranchSemantics(t *testing.T) {
  383. t.Parallel()
  384. t.Run("default invert keeps inherited group outside grouped predicate", func(t *testing.T) {
  385. t.Parallel()
  386. metadata := testMetadata("www.example.com")
  387. ruleSet := newLocalRuleSetForTest("invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  388. rule.invert = true
  389. addDestinationAddressItem(t, rule, nil, []string{"google.com"})
  390. }))
  391. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  392. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  393. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  394. })
  395. require.True(t, rule.Match(&metadata))
  396. })
  397. t.Run("default invert keeps inherited group after negation succeeds", func(t *testing.T) {
  398. t.Parallel()
  399. metadata := testMetadata("www.example.com")
  400. ruleSet := newLocalRuleSetForTest("invert-network", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  401. rule.invert = true
  402. addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
  403. }))
  404. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  405. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  406. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  407. })
  408. require.True(t, rule.Match(&metadata))
  409. })
  410. t.Run("logical invert keeps inherited group outside grouped predicate", func(t *testing.T) {
  411. t.Parallel()
  412. metadata := testMetadata("www.example.com")
  413. ruleSet := newLocalRuleSetForTest("logical-invert-grouped", headlessLogicalRule(
  414. C.LogicalTypeOr,
  415. true,
  416. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  417. addDestinationAddressItem(t, rule, nil, []string{"google.com"})
  418. }),
  419. ))
  420. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  421. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  422. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  423. })
  424. require.True(t, rule.Match(&metadata))
  425. })
  426. t.Run("logical invert keeps inherited group after negation succeeds", func(t *testing.T) {
  427. t.Parallel()
  428. metadata := testMetadata("www.example.com")
  429. ruleSet := newLocalRuleSetForTest("logical-invert-network", headlessLogicalRule(
  430. C.LogicalTypeOr,
  431. true,
  432. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  433. addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
  434. }),
  435. ))
  436. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  437. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  438. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  439. })
  440. require.True(t, rule.Match(&metadata))
  441. })
  442. }
  443. func TestRouteRuleSetNoLeakageRegressions(t *testing.T) {
  444. t.Parallel()
  445. t.Run("same ruleset failed branch does not leak", func(t *testing.T) {
  446. t.Parallel()
  447. metadata := testMetadata("www.example.com")
  448. ruleSet := newLocalRuleSetForTest(
  449. "same-set",
  450. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  451. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  452. addSourcePortItem(rule, []uint16{1})
  453. }),
  454. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  455. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  456. addSourcePortItem(rule, []uint16{1000})
  457. }),
  458. )
  459. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  460. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  461. })
  462. require.False(t, rule.Match(&metadata))
  463. })
  464. t.Run("adguard exclusion remains isolated across rulesets", func(t *testing.T) {
  465. t.Parallel()
  466. metadata := testMetadata("im.qq.com")
  467. excludeSet := newLocalRuleSetForTest("adguard", mustAdGuardRule(t, "@@||im.qq.com^\n||whatever1.com^\n"))
  468. otherSet := newLocalRuleSetForTest("other", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  469. addDestinationAddressItem(t, rule, nil, []string{"whatever2.com"})
  470. }))
  471. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  472. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{excludeSet, otherSet}})
  473. })
  474. require.False(t, rule.Match(&metadata))
  475. })
  476. }
  477. func TestDefaultRuleDoesNotReuseGroupedMatchCacheAcrossEvaluations(t *testing.T) {
  478. t.Parallel()
  479. metadata := testMetadata("www.example.com")
  480. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  481. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  482. })
  483. require.True(t, rule.Match(&metadata))
  484. metadata.Destination.Fqdn = "www.example.org"
  485. require.False(t, rule.Match(&metadata))
  486. }
  487. func TestRouteRuleSetRemoteUsesSameSemantics(t *testing.T) {
  488. t.Parallel()
  489. metadata := testMetadata("www.example.com")
  490. ruleSet := newRemoteRuleSetForTest(
  491. "remote",
  492. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  493. addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
  494. }),
  495. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  496. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  497. }),
  498. )
  499. rule := routeRuleForTest(func(rule *abstractDefaultRule) {
  500. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  501. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  502. })
  503. require.True(t, rule.Match(&metadata))
  504. }
  505. func TestDNSRuleSetSemantics(t *testing.T) {
  506. t.Parallel()
  507. t.Run("outer destination group merges into matching ruleset branch", func(t *testing.T) {
  508. t.Parallel()
  509. metadata := testMetadata("www.baidu.com")
  510. ruleSet := newLocalRuleSetForTest("dns-merged-branch", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  511. addDestinationAddressItem(t, rule, nil, []string{"google.com"})
  512. }))
  513. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  514. addDestinationAddressItem(t, rule, nil, []string{"baidu.com"})
  515. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  516. })
  517. require.True(t, rule.Match(&metadata))
  518. })
  519. t.Run("outer destination group does not bypass ruleset non grouped condition", func(t *testing.T) {
  520. t.Parallel()
  521. metadata := testMetadata("www.example.com")
  522. ruleSet := newLocalRuleSetForTest("dns-network-and", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  523. addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
  524. }))
  525. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  526. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  527. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  528. })
  529. require.False(t, rule.Match(&metadata))
  530. })
  531. t.Run("outer destination group stays outside inverted grouped branch", func(t *testing.T) {
  532. t.Parallel()
  533. metadata := testMetadata("www.baidu.com")
  534. ruleSet := newLocalRuleSetForTest("dns-invert-grouped", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  535. rule.invert = true
  536. addDestinationAddressItem(t, rule, nil, []string{"google.com"})
  537. }))
  538. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  539. addDestinationAddressItem(t, rule, nil, []string{"baidu.com"})
  540. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  541. })
  542. require.True(t, rule.Match(&metadata))
  543. })
  544. t.Run("outer destination group stays outside inverted logical branch", func(t *testing.T) {
  545. t.Parallel()
  546. metadata := testMetadata("www.example.com")
  547. ruleSet := newLocalRuleSetForTest("dns-logical-invert-network", headlessLogicalRule(
  548. C.LogicalTypeOr,
  549. true,
  550. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  551. addOtherItem(rule, NewNetworkItem([]string{N.NetworkUDP}))
  552. }),
  553. ))
  554. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  555. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  556. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  557. })
  558. require.True(t, rule.Match(&metadata))
  559. })
  560. t.Run("match address limit merges destination group", func(t *testing.T) {
  561. t.Parallel()
  562. metadata := testMetadata("www.example.com")
  563. ruleSet := newLocalRuleSetForTest("dns-merge", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  564. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  565. }))
  566. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  567. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  568. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  569. })
  570. require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1"))))
  571. })
  572. t.Run("dns keeps ruleset or semantics", func(t *testing.T) {
  573. t.Parallel()
  574. metadata := testMetadata("www.example.com")
  575. emptyStateSet := newLocalRuleSetForTest("dns-empty", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  576. addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
  577. }))
  578. destinationStateSet := newLocalRuleSetForTest("dns-destination", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  579. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  580. }))
  581. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  582. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{emptyStateSet, destinationStateSet}})
  583. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  584. })
  585. require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1"))))
  586. })
  587. t.Run("ruleset ip cidr flags stay scoped", func(t *testing.T) {
  588. t.Parallel()
  589. metadata := testMetadata("www.example.com")
  590. ruleSet := newLocalRuleSetForTest("dns-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  591. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  592. }))
  593. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  594. addRuleSetItem(rule, &RuleSetItem{
  595. setList: []adapter.RuleSet{ruleSet},
  596. ipCidrAcceptEmpty: true,
  597. })
  598. })
  599. require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1"))))
  600. require.False(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8"))))
  601. require.True(t, rule.MatchAddressLimit(&metadata, dnsResponseForTest()))
  602. require.False(t, metadata.IPCIDRMatchSource)
  603. require.False(t, metadata.IPCIDRAcceptEmpty)
  604. })
  605. t.Run("pre lookup ruleset only deferred fields fail closed", func(t *testing.T) {
  606. t.Parallel()
  607. metadata := testMetadata("lookup.example")
  608. ruleSet := newLocalRuleSetForTest("dns-prelookup-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  609. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  610. }))
  611. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  612. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  613. })
  614. // This is accepted without match_response so mixed rule_set deployments keep
  615. // working; the destination-IP-only branch simply cannot match before a DNS
  616. // response is available.
  617. require.False(t, rule.Match(&metadata))
  618. })
  619. t.Run("pre lookup ruleset destination cidr does not fall back to other predicates", func(t *testing.T) {
  620. t.Parallel()
  621. metadata := testMetadata("lookup.example")
  622. ruleSet := newLocalRuleSetForTest("dns-prelookup-network-and-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  623. addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
  624. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  625. }))
  626. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  627. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  628. })
  629. require.False(t, rule.Match(&metadata))
  630. })
  631. t.Run("pre lookup mixed ruleset still matches non response branch", func(t *testing.T) {
  632. t.Parallel()
  633. metadata := testMetadata("www.example.com")
  634. ruleSet := newLocalRuleSetForTest(
  635. "dns-prelookup-mixed",
  636. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  637. addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
  638. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  639. }),
  640. headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  641. addDestinationAddressItem(t, rule, nil, []string{"example.com"})
  642. }),
  643. )
  644. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  645. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  646. })
  647. // Destination-IP predicates inside rule_set fail closed before the DNS response,
  648. // but they must not force validation errors or suppress sibling non-response
  649. // branches.
  650. require.True(t, rule.Match(&metadata))
  651. })
  652. }
  653. func TestDNSMatchResponseRuleSetDestinationCIDRUsesDNSResponse(t *testing.T) {
  654. t.Parallel()
  655. ruleSet := newLocalRuleSetForTest("dns-response-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  656. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  657. }))
  658. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  659. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  660. })
  661. rule.matchResponse = true
  662. matchedMetadata := testMetadata("lookup.example")
  663. matchedMetadata.DNSResponse = dnsResponseForTest(netip.MustParseAddr("203.0.113.1"))
  664. require.True(t, rule.Match(&matchedMetadata))
  665. require.Empty(t, matchedMetadata.DestinationAddresses)
  666. unmatchedMetadata := testMetadata("lookup.example")
  667. unmatchedMetadata.DNSResponse = dnsResponseForTest(netip.MustParseAddr("8.8.8.8"))
  668. require.False(t, rule.Match(&unmatchedMetadata))
  669. }
  670. func TestDNSMatchResponseMissingResponseUsesBooleanSemantics(t *testing.T) {
  671. t.Parallel()
  672. t.Run("plain rule remains false", func(t *testing.T) {
  673. t.Parallel()
  674. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {})
  675. rule.matchResponse = true
  676. metadata := testMetadata("lookup.example")
  677. require.False(t, rule.Match(&metadata))
  678. })
  679. t.Run("invert rule becomes true", func(t *testing.T) {
  680. t.Parallel()
  681. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  682. rule.invert = true
  683. })
  684. rule.matchResponse = true
  685. metadata := testMetadata("lookup.example")
  686. require.True(t, rule.Match(&metadata))
  687. })
  688. t.Run("logical wrapper respects inverted child", func(t *testing.T) {
  689. t.Parallel()
  690. nestedRule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  691. rule.invert = true
  692. })
  693. nestedRule.matchResponse = true
  694. logicalRule := &LogicalDNSRule{
  695. abstractLogicalRule: abstractLogicalRule{
  696. rules: []adapter.HeadlessRule{nestedRule},
  697. mode: C.LogicalTypeAnd,
  698. },
  699. }
  700. metadata := testMetadata("lookup.example")
  701. require.True(t, logicalRule.Match(&metadata))
  702. })
  703. }
  704. func TestDNSAddressLimitIgnoresDestinationAddresses(t *testing.T) {
  705. t.Parallel()
  706. testCases := []struct {
  707. name string
  708. build func(*testing.T, *abstractDefaultRule)
  709. matchedResponse *mDNS.Msg
  710. unmatchedResponse *mDNS.Msg
  711. }{
  712. {
  713. name: "ip_cidr",
  714. build: func(t *testing.T, rule *abstractDefaultRule) {
  715. t.Helper()
  716. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  717. },
  718. matchedResponse: dnsResponseForTest(netip.MustParseAddr("203.0.113.1")),
  719. unmatchedResponse: dnsResponseForTest(netip.MustParseAddr("8.8.8.8")),
  720. },
  721. {
  722. name: "ip_is_private",
  723. build: func(t *testing.T, rule *abstractDefaultRule) {
  724. t.Helper()
  725. addDestinationIPIsPrivateItem(rule)
  726. },
  727. matchedResponse: dnsResponseForTest(netip.MustParseAddr("10.0.0.1")),
  728. unmatchedResponse: dnsResponseForTest(netip.MustParseAddr("8.8.8.8")),
  729. },
  730. {
  731. name: "ip_accept_any",
  732. build: func(t *testing.T, rule *abstractDefaultRule) {
  733. t.Helper()
  734. addDestinationIPAcceptAnyItem(rule)
  735. },
  736. matchedResponse: dnsResponseForTest(netip.MustParseAddr("203.0.113.1")),
  737. unmatchedResponse: dnsResponseForTest(),
  738. },
  739. }
  740. for _, testCase := range testCases {
  741. testCase := testCase
  742. t.Run(testCase.name, func(t *testing.T) {
  743. t.Parallel()
  744. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  745. testCase.build(t, rule)
  746. })
  747. mismatchMetadata := testMetadata("lookup.example")
  748. mismatchMetadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("203.0.113.1")}
  749. require.False(t, rule.MatchAddressLimit(&mismatchMetadata, testCase.unmatchedResponse))
  750. matchMetadata := testMetadata("lookup.example")
  751. matchMetadata.DestinationAddresses = []netip.Addr{netip.MustParseAddr("8.8.8.8")}
  752. require.True(t, rule.MatchAddressLimit(&matchMetadata, testCase.matchedResponse))
  753. })
  754. }
  755. }
  756. func TestDNSLegacyAddressLimitPreLookupDefersDirectRules(t *testing.T) {
  757. t.Parallel()
  758. testCases := []struct {
  759. name string
  760. build func(*testing.T, *abstractDefaultRule)
  761. matchedResponse *mDNS.Msg
  762. unmatchedResponse *mDNS.Msg
  763. }{
  764. {
  765. name: "ip_cidr",
  766. build: func(t *testing.T, rule *abstractDefaultRule) {
  767. t.Helper()
  768. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  769. },
  770. matchedResponse: dnsResponseForTest(netip.MustParseAddr("203.0.113.1")),
  771. unmatchedResponse: dnsResponseForTest(netip.MustParseAddr("8.8.8.8")),
  772. },
  773. {
  774. name: "ip_is_private",
  775. build: func(t *testing.T, rule *abstractDefaultRule) {
  776. t.Helper()
  777. addDestinationIPIsPrivateItem(rule)
  778. },
  779. matchedResponse: dnsResponseForTest(netip.MustParseAddr("10.0.0.1")),
  780. unmatchedResponse: dnsResponseForTest(netip.MustParseAddr("8.8.8.8")),
  781. },
  782. {
  783. name: "ip_accept_any",
  784. build: func(t *testing.T, rule *abstractDefaultRule) {
  785. t.Helper()
  786. addDestinationIPAcceptAnyItem(rule)
  787. },
  788. matchedResponse: dnsResponseForTest(netip.MustParseAddr("203.0.113.1")),
  789. unmatchedResponse: dnsResponseForTest(),
  790. },
  791. }
  792. for _, testCase := range testCases {
  793. testCase := testCase
  794. t.Run(testCase.name, func(t *testing.T) {
  795. t.Parallel()
  796. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  797. testCase.build(t, rule)
  798. })
  799. preLookupMetadata := testMetadata("lookup.example")
  800. require.True(t, rule.LegacyPreMatch(&preLookupMetadata))
  801. matchedMetadata := testMetadata("lookup.example")
  802. require.True(t, rule.MatchAddressLimit(&matchedMetadata, testCase.matchedResponse))
  803. unmatchedMetadata := testMetadata("lookup.example")
  804. require.False(t, rule.MatchAddressLimit(&unmatchedMetadata, testCase.unmatchedResponse))
  805. })
  806. }
  807. }
  808. func TestDNSLegacyAddressLimitPreLookupDefersRuleSetDestinationCIDR(t *testing.T) {
  809. t.Parallel()
  810. ruleSet := newLocalRuleSetForTest("dns-legacy-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  811. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  812. }))
  813. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  814. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  815. })
  816. preLookupMetadata := testMetadata("lookup.example")
  817. require.True(t, rule.LegacyPreMatch(&preLookupMetadata))
  818. matchedMetadata := testMetadata("lookup.example")
  819. require.True(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1"))))
  820. unmatchedMetadata := testMetadata("lookup.example")
  821. require.False(t, rule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8"))))
  822. }
  823. func TestDNSLegacyLogicalAddressLimitPreLookupDefersNestedRules(t *testing.T) {
  824. t.Parallel()
  825. nestedRule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  826. addDestinationIPIsPrivateItem(rule)
  827. })
  828. logicalRule := &LogicalDNSRule{
  829. abstractLogicalRule: abstractLogicalRule{
  830. rules: []adapter.HeadlessRule{nestedRule},
  831. mode: C.LogicalTypeAnd,
  832. },
  833. }
  834. preLookupMetadata := testMetadata("lookup.example")
  835. require.True(t, logicalRule.LegacyPreMatch(&preLookupMetadata))
  836. matchedMetadata := testMetadata("lookup.example")
  837. require.True(t, logicalRule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(netip.MustParseAddr("10.0.0.1"))))
  838. unmatchedMetadata := testMetadata("lookup.example")
  839. require.False(t, logicalRule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8"))))
  840. }
  841. func TestDNSLegacyInvertAddressLimitPreLookupRegression(t *testing.T) {
  842. t.Parallel()
  843. testCases := []struct {
  844. name string
  845. build func(*testing.T, *abstractDefaultRule)
  846. matchedAddrs []netip.Addr
  847. unmatchedAddrs []netip.Addr
  848. }{
  849. {
  850. name: "ip_cidr",
  851. build: func(t *testing.T, rule *abstractDefaultRule) {
  852. t.Helper()
  853. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  854. },
  855. matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")},
  856. unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")},
  857. },
  858. {
  859. name: "ip_is_private",
  860. build: func(t *testing.T, rule *abstractDefaultRule) {
  861. t.Helper()
  862. addDestinationIPIsPrivateItem(rule)
  863. },
  864. matchedAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.1")},
  865. unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")},
  866. },
  867. {
  868. name: "ip_accept_any",
  869. build: func(t *testing.T, rule *abstractDefaultRule) {
  870. t.Helper()
  871. addDestinationIPAcceptAnyItem(rule)
  872. },
  873. matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")},
  874. },
  875. }
  876. for _, testCase := range testCases {
  877. testCase := testCase
  878. t.Run(testCase.name, func(t *testing.T) {
  879. t.Parallel()
  880. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  881. rule.invert = true
  882. testCase.build(t, rule)
  883. })
  884. preLookupMetadata := testMetadata("lookup.example")
  885. require.True(t, rule.LegacyPreMatch(&preLookupMetadata))
  886. matchedMetadata := testMetadata("lookup.example")
  887. require.False(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(testCase.matchedAddrs...)))
  888. unmatchedMetadata := testMetadata("lookup.example")
  889. require.True(t, rule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(testCase.unmatchedAddrs...)))
  890. })
  891. }
  892. }
  893. func TestDNSLegacyInvertLogicalAddressLimitPreLookupRegression(t *testing.T) {
  894. t.Parallel()
  895. t.Run("inverted deferred child does not suppress branch", func(t *testing.T) {
  896. t.Parallel()
  897. logicalRule := &LogicalDNSRule{
  898. abstractLogicalRule: abstractLogicalRule{
  899. rules: []adapter.HeadlessRule{
  900. dnsRuleForTest(func(rule *abstractDefaultRule) {
  901. rule.invert = true
  902. addDestinationIPIsPrivateItem(rule)
  903. }),
  904. },
  905. mode: C.LogicalTypeAnd,
  906. },
  907. }
  908. preLookupMetadata := testMetadata("lookup.example")
  909. require.True(t, logicalRule.LegacyPreMatch(&preLookupMetadata))
  910. })
  911. }
  912. func TestDNSLegacyInvertRuleSetAddressLimitPreLookupRegression(t *testing.T) {
  913. t.Parallel()
  914. ruleSet := newLocalRuleSetForTest("dns-legacy-invert-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  915. rule.invert = true
  916. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  917. }))
  918. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  919. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  920. })
  921. preLookupMetadata := testMetadata("lookup.example")
  922. require.True(t, rule.LegacyPreMatch(&preLookupMetadata))
  923. matchedMetadata := testMetadata("lookup.example")
  924. require.False(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(netip.MustParseAddr("203.0.113.1"))))
  925. unmatchedMetadata := testMetadata("lookup.example")
  926. require.True(t, rule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(netip.MustParseAddr("8.8.8.8"))))
  927. }
  928. func TestDNSInvertAddressLimitPreLookupRegression(t *testing.T) {
  929. t.Parallel()
  930. testCases := []struct {
  931. name string
  932. build func(*testing.T, *abstractDefaultRule)
  933. matchedAddrs []netip.Addr
  934. unmatchedAddrs []netip.Addr
  935. }{
  936. {
  937. name: "ip_cidr",
  938. build: func(t *testing.T, rule *abstractDefaultRule) {
  939. t.Helper()
  940. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  941. },
  942. matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")},
  943. unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")},
  944. },
  945. {
  946. name: "ip_is_private",
  947. build: func(t *testing.T, rule *abstractDefaultRule) {
  948. t.Helper()
  949. addDestinationIPIsPrivateItem(rule)
  950. },
  951. matchedAddrs: []netip.Addr{netip.MustParseAddr("10.0.0.1")},
  952. unmatchedAddrs: []netip.Addr{netip.MustParseAddr("8.8.8.8")},
  953. },
  954. {
  955. name: "ip_accept_any",
  956. build: func(t *testing.T, rule *abstractDefaultRule) {
  957. t.Helper()
  958. addDestinationIPAcceptAnyItem(rule)
  959. },
  960. matchedAddrs: []netip.Addr{netip.MustParseAddr("203.0.113.1")},
  961. },
  962. }
  963. for _, testCase := range testCases {
  964. testCase := testCase
  965. t.Run(testCase.name, func(t *testing.T) {
  966. t.Parallel()
  967. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  968. rule.invert = true
  969. testCase.build(t, rule)
  970. })
  971. preLookupMetadata := testMetadata("lookup.example")
  972. require.True(t, rule.Match(&preLookupMetadata))
  973. matchedMetadata := testMetadata("lookup.example")
  974. matchedMetadata.DestinationAddresses = testCase.matchedAddrs
  975. require.False(t, rule.MatchAddressLimit(&matchedMetadata, dnsResponseForTest(testCase.matchedAddrs...)))
  976. unmatchedMetadata := testMetadata("lookup.example")
  977. unmatchedMetadata.DestinationAddresses = testCase.unmatchedAddrs
  978. require.True(t, rule.MatchAddressLimit(&unmatchedMetadata, dnsResponseForTest(testCase.unmatchedAddrs...)))
  979. })
  980. }
  981. t.Run("mixed resolved and deferred fields invert matches pre lookup", func(t *testing.T) {
  982. t.Parallel()
  983. metadata := testMetadata("lookup.example")
  984. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  985. rule.invert = true
  986. addOtherItem(rule, NewNetworkItem([]string{N.NetworkTCP}))
  987. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  988. })
  989. require.True(t, rule.Match(&metadata))
  990. })
  991. t.Run("ruleset only deferred fields invert matches pre lookup", func(t *testing.T) {
  992. t.Parallel()
  993. metadata := testMetadata("lookup.example")
  994. ruleSet := newLocalRuleSetForTest("dns-ruleset-ipcidr", headlessDefaultRule(t, func(rule *abstractDefaultRule) {
  995. addDestinationIPCIDRItem(t, rule, []string{"203.0.113.0/24"})
  996. }))
  997. rule := dnsRuleForTest(func(rule *abstractDefaultRule) {
  998. rule.invert = true
  999. addRuleSetItem(rule, &RuleSetItem{setList: []adapter.RuleSet{ruleSet}})
  1000. })
  1001. require.True(t, rule.Match(&metadata))
  1002. })
  1003. }
  1004. func routeRuleForTest(build func(*abstractDefaultRule)) *DefaultRule {
  1005. rule := &DefaultRule{}
  1006. build(&rule.abstractDefaultRule)
  1007. return rule
  1008. }
  1009. func dnsRuleForTest(build func(*abstractDefaultRule)) *DefaultDNSRule {
  1010. rule := &DefaultDNSRule{}
  1011. build(&rule.abstractDefaultRule)
  1012. return rule
  1013. }
  1014. func headlessDefaultRule(t *testing.T, build func(*abstractDefaultRule)) *DefaultHeadlessRule {
  1015. t.Helper()
  1016. rule := &DefaultHeadlessRule{}
  1017. build(&rule.abstractDefaultRule)
  1018. return rule
  1019. }
  1020. func headlessLogicalRule(mode string, invert bool, rules ...adapter.HeadlessRule) *LogicalHeadlessRule {
  1021. return &LogicalHeadlessRule{
  1022. abstractLogicalRule: abstractLogicalRule{
  1023. rules: rules,
  1024. mode: mode,
  1025. invert: invert,
  1026. },
  1027. }
  1028. }
  1029. func newLocalRuleSetForTest(tag string, rules ...adapter.HeadlessRule) *LocalRuleSet {
  1030. return &LocalRuleSet{
  1031. tag: tag,
  1032. rules: rules,
  1033. }
  1034. }
  1035. func newRemoteRuleSetForTest(tag string, rules ...adapter.HeadlessRule) *RemoteRuleSet {
  1036. return &RemoteRuleSet{
  1037. options: option.RuleSet{Tag: tag},
  1038. rules: rules,
  1039. }
  1040. }
  1041. func mustAdGuardRule(t *testing.T, content string) adapter.HeadlessRule {
  1042. t.Helper()
  1043. rules, err := adguard.ToOptions(strings.NewReader(content), slogger.NOP())
  1044. require.NoError(t, err)
  1045. require.Len(t, rules, 1)
  1046. rule, err := NewHeadlessRule(context.Background(), rules[0])
  1047. require.NoError(t, err)
  1048. return rule
  1049. }
  1050. func testMetadata(domain string) adapter.InboundContext {
  1051. return adapter.InboundContext{
  1052. Network: N.NetworkTCP,
  1053. Source: M.Socksaddr{
  1054. Addr: netip.MustParseAddr("10.0.0.1"),
  1055. Port: 1000,
  1056. },
  1057. Destination: M.Socksaddr{
  1058. Fqdn: domain,
  1059. Port: 443,
  1060. },
  1061. }
  1062. }
  1063. func dnsResponseForTest(addresses ...netip.Addr) *mDNS.Msg {
  1064. response := &mDNS.Msg{
  1065. MsgHdr: mDNS.MsgHdr{
  1066. Response: true,
  1067. Rcode: mDNS.RcodeSuccess,
  1068. },
  1069. }
  1070. for _, address := range addresses {
  1071. if address.Is4() {
  1072. response.Answer = append(response.Answer, &mDNS.A{
  1073. Hdr: mDNS.RR_Header{
  1074. Name: mDNS.Fqdn("lookup.example"),
  1075. Rrtype: mDNS.TypeA,
  1076. Class: mDNS.ClassINET,
  1077. Ttl: 60,
  1078. },
  1079. A: net.IP(append([]byte(nil), address.AsSlice()...)),
  1080. })
  1081. } else {
  1082. response.Answer = append(response.Answer, &mDNS.AAAA{
  1083. Hdr: mDNS.RR_Header{
  1084. Name: mDNS.Fqdn("lookup.example"),
  1085. Rrtype: mDNS.TypeAAAA,
  1086. Class: mDNS.ClassINET,
  1087. Ttl: 60,
  1088. },
  1089. AAAA: net.IP(append([]byte(nil), address.AsSlice()...)),
  1090. })
  1091. }
  1092. }
  1093. return response
  1094. }
  1095. func addRuleSetItem(rule *abstractDefaultRule, item *RuleSetItem) {
  1096. rule.ruleSetItem = item
  1097. rule.allItems = append(rule.allItems, item)
  1098. }
  1099. func addOtherItem(rule *abstractDefaultRule, item RuleItem) {
  1100. rule.items = append(rule.items, item)
  1101. rule.allItems = append(rule.allItems, item)
  1102. }
  1103. func addSourceAddressItem(t *testing.T, rule *abstractDefaultRule, cidrs []string) {
  1104. t.Helper()
  1105. item, err := NewIPCIDRItem(true, cidrs)
  1106. require.NoError(t, err)
  1107. rule.sourceAddressItems = append(rule.sourceAddressItems, item)
  1108. rule.allItems = append(rule.allItems, item)
  1109. }
  1110. func addDestinationAddressItem(t *testing.T, rule *abstractDefaultRule, domains []string, suffixes []string) {
  1111. t.Helper()
  1112. item, err := NewDomainItem(domains, suffixes)
  1113. require.NoError(t, err)
  1114. rule.destinationAddressItems = append(rule.destinationAddressItems, item)
  1115. rule.allItems = append(rule.allItems, item)
  1116. }
  1117. func addDestinationKeywordItem(rule *abstractDefaultRule, keywords []string) {
  1118. item := NewDomainKeywordItem(keywords)
  1119. rule.destinationAddressItems = append(rule.destinationAddressItems, item)
  1120. rule.allItems = append(rule.allItems, item)
  1121. }
  1122. func addDestinationRegexItem(t *testing.T, rule *abstractDefaultRule, regexes []string) {
  1123. t.Helper()
  1124. item, err := NewDomainRegexItem(regexes)
  1125. require.NoError(t, err)
  1126. rule.destinationAddressItems = append(rule.destinationAddressItems, item)
  1127. rule.allItems = append(rule.allItems, item)
  1128. }
  1129. func addDestinationIPCIDRItem(t *testing.T, rule *abstractDefaultRule, cidrs []string) {
  1130. t.Helper()
  1131. item, err := NewIPCIDRItem(false, cidrs)
  1132. require.NoError(t, err)
  1133. rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
  1134. rule.allItems = append(rule.allItems, item)
  1135. }
  1136. func addDestinationIPIsPrivateItem(rule *abstractDefaultRule) {
  1137. item := NewIPIsPrivateItem(false)
  1138. rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
  1139. rule.allItems = append(rule.allItems, item)
  1140. }
  1141. func addDestinationIPAcceptAnyItem(rule *abstractDefaultRule) {
  1142. item := NewIPAcceptAnyItem()
  1143. rule.destinationIPCIDRItems = append(rule.destinationIPCIDRItems, item)
  1144. rule.allItems = append(rule.allItems, item)
  1145. }
  1146. func addSourcePortItem(rule *abstractDefaultRule, ports []uint16) {
  1147. item := NewPortItem(true, ports)
  1148. rule.sourcePortItems = append(rule.sourcePortItems, item)
  1149. rule.allItems = append(rule.allItems, item)
  1150. }
  1151. func addSourcePortRangeItem(t *testing.T, rule *abstractDefaultRule, ranges []string) {
  1152. t.Helper()
  1153. item, err := NewPortRangeItem(true, ranges)
  1154. require.NoError(t, err)
  1155. rule.sourcePortItems = append(rule.sourcePortItems, item)
  1156. rule.allItems = append(rule.allItems, item)
  1157. }
  1158. func addDestinationPortItem(rule *abstractDefaultRule, ports []uint16) {
  1159. item := NewPortItem(false, ports)
  1160. rule.destinationPortItems = append(rule.destinationPortItems, item)
  1161. rule.allItems = append(rule.allItems, item)
  1162. }
  1163. func addDestinationPortRangeItem(t *testing.T, rule *abstractDefaultRule, ranges []string) {
  1164. t.Helper()
  1165. item, err := NewPortRangeItem(false, ranges)
  1166. require.NoError(t, err)
  1167. rule.destinationPortItems = append(rule.destinationPortItems, item)
  1168. rule.allItems = append(rule.allItems, item)
  1169. }