convertor.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. package adguard
  2. import (
  3. "bufio"
  4. "bytes"
  5. "io"
  6. "net/netip"
  7. "os"
  8. "strconv"
  9. "strings"
  10. C "github.com/sagernet/sing-box/constant"
  11. "github.com/sagernet/sing-box/option"
  12. "github.com/sagernet/sing/common"
  13. E "github.com/sagernet/sing/common/exceptions"
  14. "github.com/sagernet/sing/common/logger"
  15. M "github.com/sagernet/sing/common/metadata"
  16. )
  17. type agdguardRuleLine struct {
  18. ruleLine string
  19. isRawDomain bool
  20. isExclude bool
  21. isSuffix bool
  22. hasStart bool
  23. hasEnd bool
  24. isRegexp bool
  25. isImportant bool
  26. }
  27. func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
  28. scanner := bufio.NewScanner(reader)
  29. var (
  30. ruleLines []agdguardRuleLine
  31. ignoredLines int
  32. )
  33. parseLine:
  34. for scanner.Scan() {
  35. ruleLine := scanner.Text()
  36. if ruleLine == "" {
  37. continue
  38. }
  39. if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
  40. continue
  41. }
  42. originRuleLine := ruleLine
  43. if M.IsDomainName(ruleLine) {
  44. ruleLines = append(ruleLines, agdguardRuleLine{
  45. ruleLine: ruleLine,
  46. isRawDomain: true,
  47. })
  48. continue
  49. }
  50. hostLine, err := parseAdGuardHostLine(ruleLine)
  51. if err == nil {
  52. if hostLine != "" {
  53. ruleLines = append(ruleLines, agdguardRuleLine{
  54. ruleLine: hostLine,
  55. isRawDomain: true,
  56. hasStart: true,
  57. hasEnd: true,
  58. })
  59. }
  60. continue
  61. }
  62. if strings.HasSuffix(ruleLine, "|") {
  63. ruleLine = ruleLine[:len(ruleLine)-1]
  64. }
  65. var (
  66. isExclude bool
  67. isSuffix bool
  68. hasStart bool
  69. hasEnd bool
  70. isRegexp bool
  71. isImportant bool
  72. )
  73. if !strings.HasPrefix(ruleLine, "/") && strings.Contains(ruleLine, "$") {
  74. params := common.SubstringAfter(ruleLine, "$")
  75. for _, param := range strings.Split(params, ",") {
  76. paramParts := strings.Split(param, "=")
  77. var ignored bool
  78. if len(paramParts) > 0 && len(paramParts) <= 2 {
  79. switch paramParts[0] {
  80. case "app", "network":
  81. // maybe support by package_name/process_name
  82. case "dnstype":
  83. // maybe support by query_type
  84. case "important":
  85. ignored = true
  86. isImportant = true
  87. case "dnsrewrite":
  88. if len(paramParts) == 2 && M.ParseAddr(paramParts[1]).IsUnspecified() {
  89. ignored = true
  90. }
  91. }
  92. }
  93. if !ignored {
  94. ignoredLines++
  95. logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
  96. continue parseLine
  97. }
  98. }
  99. ruleLine = common.SubstringBefore(ruleLine, "$")
  100. }
  101. if strings.HasPrefix(ruleLine, "@@") {
  102. ruleLine = ruleLine[2:]
  103. isExclude = true
  104. }
  105. if strings.HasSuffix(ruleLine, "|") {
  106. ruleLine = ruleLine[:len(ruleLine)-1]
  107. }
  108. if strings.HasPrefix(ruleLine, "||") {
  109. ruleLine = ruleLine[2:]
  110. isSuffix = true
  111. } else if strings.HasPrefix(ruleLine, "|") {
  112. ruleLine = ruleLine[1:]
  113. hasStart = true
  114. }
  115. if strings.HasSuffix(ruleLine, "^") {
  116. ruleLine = ruleLine[:len(ruleLine)-1]
  117. hasEnd = true
  118. }
  119. if strings.HasPrefix(ruleLine, "/") && strings.HasSuffix(ruleLine, "/") {
  120. ruleLine = ruleLine[1 : len(ruleLine)-1]
  121. if ignoreIPCIDRRegexp(ruleLine) {
  122. ignoredLines++
  123. logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
  124. continue
  125. }
  126. isRegexp = true
  127. } else {
  128. if strings.Contains(ruleLine, "://") {
  129. ruleLine = common.SubstringAfter(ruleLine, "://")
  130. isSuffix = true
  131. }
  132. if strings.Contains(ruleLine, "/") {
  133. ignoredLines++
  134. logger.Debug("ignored unsupported rule with path: ", originRuleLine)
  135. continue
  136. }
  137. if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
  138. ignoredLines++
  139. logger.Debug("ignored unsupported rule with query: ", originRuleLine)
  140. continue
  141. }
  142. if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
  143. strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
  144. strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
  145. ignoredLines++
  146. logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
  147. continue
  148. }
  149. if strings.Contains(ruleLine, "~") {
  150. ignoredLines++
  151. logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
  152. continue
  153. }
  154. var domainCheck string
  155. if strings.HasPrefix(ruleLine, ".") || strings.HasPrefix(ruleLine, "-") {
  156. domainCheck = "r" + ruleLine
  157. } else {
  158. domainCheck = ruleLine
  159. }
  160. if ruleLine == "" {
  161. ignoredLines++
  162. logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
  163. continue
  164. } else {
  165. domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
  166. if !M.IsDomainName(domainCheck) {
  167. _, ipErr := parseADGuardIPCIDRLine(ruleLine)
  168. if ipErr == nil {
  169. ignoredLines++
  170. logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
  171. continue
  172. }
  173. if M.ParseSocksaddr(domainCheck).Port != 0 {
  174. logger.Debug("ignored unsupported rule with port: ", originRuleLine)
  175. } else {
  176. logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
  177. }
  178. ignoredLines++
  179. continue
  180. }
  181. }
  182. }
  183. ruleLines = append(ruleLines, agdguardRuleLine{
  184. ruleLine: ruleLine,
  185. isExclude: isExclude,
  186. isSuffix: isSuffix,
  187. hasStart: hasStart,
  188. hasEnd: hasEnd,
  189. isRegexp: isRegexp,
  190. isImportant: isImportant,
  191. })
  192. }
  193. if len(ruleLines) == 0 {
  194. return nil, E.New("AdGuard rule-set is empty or all rules are unsupported")
  195. }
  196. if common.All(ruleLines, func(it agdguardRuleLine) bool {
  197. return it.isRawDomain
  198. }) {
  199. return []option.HeadlessRule{
  200. {
  201. Type: C.RuleTypeDefault,
  202. DefaultOptions: option.DefaultHeadlessRule{
  203. Domain: common.Map(ruleLines, func(it agdguardRuleLine) string {
  204. return it.ruleLine
  205. }),
  206. },
  207. },
  208. }, nil
  209. }
  210. mapDomain := func(it agdguardRuleLine) string {
  211. ruleLine := it.ruleLine
  212. if it.isSuffix {
  213. ruleLine = "||" + ruleLine
  214. } else if it.hasStart {
  215. ruleLine = "|" + ruleLine
  216. }
  217. if it.hasEnd {
  218. ruleLine += "^"
  219. }
  220. return ruleLine
  221. }
  222. importantDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && !it.isRegexp && !it.isExclude }), mapDomain)
  223. importantDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && it.isRegexp && !it.isExclude }), mapDomain)
  224. importantExcludeDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && !it.isRegexp && it.isExclude }), mapDomain)
  225. importantExcludeDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return it.isImportant && it.isRegexp && it.isExclude }), mapDomain)
  226. domain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && !it.isRegexp && !it.isExclude }), mapDomain)
  227. domainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && it.isRegexp && !it.isExclude }), mapDomain)
  228. excludeDomain := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && !it.isRegexp && it.isExclude }), mapDomain)
  229. excludeDomainRegex := common.Map(common.Filter(ruleLines, func(it agdguardRuleLine) bool { return !it.isImportant && it.isRegexp && it.isExclude }), mapDomain)
  230. currentRule := option.HeadlessRule{
  231. Type: C.RuleTypeDefault,
  232. DefaultOptions: option.DefaultHeadlessRule{
  233. AdGuardDomain: domain,
  234. DomainRegex: domainRegex,
  235. },
  236. }
  237. if len(excludeDomain) > 0 || len(excludeDomainRegex) > 0 {
  238. currentRule = option.HeadlessRule{
  239. Type: C.RuleTypeLogical,
  240. LogicalOptions: option.LogicalHeadlessRule{
  241. Mode: C.LogicalTypeAnd,
  242. Rules: []option.HeadlessRule{
  243. {
  244. Type: C.RuleTypeDefault,
  245. DefaultOptions: option.DefaultHeadlessRule{
  246. AdGuardDomain: excludeDomain,
  247. DomainRegex: excludeDomainRegex,
  248. Invert: true,
  249. },
  250. },
  251. currentRule,
  252. },
  253. },
  254. }
  255. }
  256. if len(importantDomain) > 0 || len(importantDomainRegex) > 0 {
  257. currentRule = option.HeadlessRule{
  258. Type: C.RuleTypeLogical,
  259. LogicalOptions: option.LogicalHeadlessRule{
  260. Mode: C.LogicalTypeOr,
  261. Rules: []option.HeadlessRule{
  262. {
  263. Type: C.RuleTypeDefault,
  264. DefaultOptions: option.DefaultHeadlessRule{
  265. AdGuardDomain: importantDomain,
  266. DomainRegex: importantDomainRegex,
  267. },
  268. },
  269. currentRule,
  270. },
  271. },
  272. }
  273. }
  274. if len(importantExcludeDomain) > 0 || len(importantExcludeDomainRegex) > 0 {
  275. currentRule = option.HeadlessRule{
  276. Type: C.RuleTypeLogical,
  277. LogicalOptions: option.LogicalHeadlessRule{
  278. Mode: C.LogicalTypeAnd,
  279. Rules: []option.HeadlessRule{
  280. {
  281. Type: C.RuleTypeDefault,
  282. DefaultOptions: option.DefaultHeadlessRule{
  283. AdGuardDomain: importantExcludeDomain,
  284. DomainRegex: importantExcludeDomainRegex,
  285. Invert: true,
  286. },
  287. },
  288. currentRule,
  289. },
  290. },
  291. }
  292. }
  293. if ignoredLines > 0 {
  294. logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
  295. }
  296. return []option.HeadlessRule{currentRule}, nil
  297. }
  298. var ErrInvalid = E.New("invalid binary AdGuard rule-set")
  299. func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
  300. if len(rules) != 1 {
  301. return nil, ErrInvalid
  302. }
  303. rule := rules[0]
  304. var (
  305. importantDomain []string
  306. importantDomainRegex []string
  307. importantExcludeDomain []string
  308. importantExcludeDomainRegex []string
  309. domain []string
  310. domainRegex []string
  311. excludeDomain []string
  312. excludeDomainRegex []string
  313. )
  314. parse:
  315. for {
  316. switch rule.Type {
  317. case C.RuleTypeLogical:
  318. if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
  319. return nil, ErrInvalid
  320. }
  321. if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
  322. if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
  323. importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
  324. importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
  325. if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
  326. return nil, ErrInvalid
  327. }
  328. } else {
  329. excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
  330. excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
  331. if len(excludeDomain)+len(excludeDomainRegex) == 0 {
  332. return nil, ErrInvalid
  333. }
  334. }
  335. } else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
  336. importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
  337. importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
  338. if len(importantDomain)+len(importantDomainRegex) == 0 {
  339. return nil, ErrInvalid
  340. }
  341. } else {
  342. return nil, ErrInvalid
  343. }
  344. rule = rule.LogicalOptions.Rules[1]
  345. case C.RuleTypeDefault:
  346. domain = rule.DefaultOptions.AdGuardDomain
  347. domainRegex = rule.DefaultOptions.DomainRegex
  348. if len(domain)+len(domainRegex) == 0 {
  349. return nil, ErrInvalid
  350. }
  351. break parse
  352. }
  353. }
  354. var output bytes.Buffer
  355. for _, ruleLine := range importantDomain {
  356. output.WriteString(ruleLine)
  357. output.WriteString("$important\n")
  358. }
  359. for _, ruleLine := range importantDomainRegex {
  360. output.WriteString("/")
  361. output.WriteString(ruleLine)
  362. output.WriteString("/$important\n")
  363. }
  364. for _, ruleLine := range importantExcludeDomain {
  365. output.WriteString("@@")
  366. output.WriteString(ruleLine)
  367. output.WriteString("$important\n")
  368. }
  369. for _, ruleLine := range importantExcludeDomainRegex {
  370. output.WriteString("@@/")
  371. output.WriteString(ruleLine)
  372. output.WriteString("/$important\n")
  373. }
  374. for _, ruleLine := range domain {
  375. output.WriteString(ruleLine)
  376. output.WriteString("\n")
  377. }
  378. for _, ruleLine := range domainRegex {
  379. output.WriteString("/")
  380. output.WriteString(ruleLine)
  381. output.WriteString("/\n")
  382. }
  383. for _, ruleLine := range excludeDomain {
  384. output.WriteString("@@")
  385. output.WriteString(ruleLine)
  386. output.WriteString("\n")
  387. }
  388. for _, ruleLine := range excludeDomainRegex {
  389. output.WriteString("@@/")
  390. output.WriteString(ruleLine)
  391. output.WriteString("/\n")
  392. }
  393. return output.Bytes(), nil
  394. }
  395. func ignoreIPCIDRRegexp(ruleLine string) bool {
  396. if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
  397. ruleLine = ruleLine[12:]
  398. } else if strings.HasPrefix(ruleLine, "(https?:\\/\\/)") {
  399. ruleLine = ruleLine[13:]
  400. } else if strings.HasPrefix(ruleLine, "^") {
  401. ruleLine = ruleLine[1:]
  402. }
  403. return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
  404. common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
  405. }
  406. func parseAdGuardHostLine(ruleLine string) (string, error) {
  407. idx := strings.Index(ruleLine, " ")
  408. if idx == -1 {
  409. return "", os.ErrInvalid
  410. }
  411. address, err := netip.ParseAddr(ruleLine[:idx])
  412. if err != nil {
  413. return "", err
  414. }
  415. if !address.IsUnspecified() {
  416. return "", nil
  417. }
  418. domain := ruleLine[idx+1:]
  419. if !M.IsDomainName(domain) {
  420. return "", E.New("invalid domain name: ", domain)
  421. }
  422. return domain, nil
  423. }
  424. func parseADGuardIPCIDRLine(ruleLine string) (netip.Prefix, error) {
  425. var isPrefix bool
  426. if strings.HasSuffix(ruleLine, ".") {
  427. isPrefix = true
  428. ruleLine = ruleLine[:len(ruleLine)-1]
  429. }
  430. ruleStringParts := strings.Split(ruleLine, ".")
  431. if len(ruleStringParts) > 4 || len(ruleStringParts) < 4 && !isPrefix {
  432. return netip.Prefix{}, os.ErrInvalid
  433. }
  434. ruleParts := make([]uint8, 0, len(ruleStringParts))
  435. for _, part := range ruleStringParts {
  436. rulePart, err := strconv.ParseUint(part, 10, 8)
  437. if err != nil {
  438. return netip.Prefix{}, err
  439. }
  440. ruleParts = append(ruleParts, uint8(rulePart))
  441. }
  442. bitLen := len(ruleParts) * 8
  443. for len(ruleParts) < 4 {
  444. ruleParts = append(ruleParts, 0)
  445. }
  446. return netip.PrefixFrom(netip.AddrFrom4(*(*[4]byte)(ruleParts)), bitLen), nil
  447. }