rule_set.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package option
  2. import (
  3. "net/url"
  4. "path/filepath"
  5. "reflect"
  6. C "github.com/sagernet/sing-box/constant"
  7. "github.com/sagernet/sing/common"
  8. "github.com/sagernet/sing/common/domain"
  9. E "github.com/sagernet/sing/common/exceptions"
  10. F "github.com/sagernet/sing/common/format"
  11. "github.com/sagernet/sing/common/json"
  12. "github.com/sagernet/sing/common/json/badjson"
  13. "github.com/sagernet/sing/common/json/badoption"
  14. "go4.org/netipx"
  15. )
  16. type _RuleSet struct {
  17. Type string `json:"type,omitempty"`
  18. Tag string `json:"tag"`
  19. Format string `json:"format,omitempty"`
  20. InlineOptions PlainRuleSet `json:"-"`
  21. LocalOptions LocalRuleSet `json:"-"`
  22. RemoteOptions RemoteRuleSet `json:"-"`
  23. }
  24. type RuleSet _RuleSet
  25. func (r RuleSet) MarshalJSON() ([]byte, error) {
  26. if r.Type != C.RuleSetTypeInline {
  27. var defaultFormat string
  28. switch r.Type {
  29. case C.RuleSetTypeLocal:
  30. defaultFormat = ruleSetDefaultFormat(r.LocalOptions.Path)
  31. case C.RuleSetTypeRemote:
  32. defaultFormat = ruleSetDefaultFormat(r.RemoteOptions.URL)
  33. }
  34. if r.Format == defaultFormat {
  35. r.Format = ""
  36. }
  37. }
  38. var v any
  39. switch r.Type {
  40. case "", C.RuleSetTypeInline:
  41. r.Type = ""
  42. v = r.InlineOptions
  43. case C.RuleSetTypeLocal:
  44. v = r.LocalOptions
  45. case C.RuleSetTypeRemote:
  46. v = r.RemoteOptions
  47. default:
  48. return nil, E.New("unknown rule-set type: " + r.Type)
  49. }
  50. return badjson.MarshallObjects((_RuleSet)(r), v)
  51. }
  52. func (r *RuleSet) UnmarshalJSON(bytes []byte) error {
  53. err := json.Unmarshal(bytes, (*_RuleSet)(r))
  54. if err != nil {
  55. return err
  56. }
  57. if r.Tag == "" {
  58. return E.New("missing tag")
  59. }
  60. var v any
  61. switch r.Type {
  62. case "", C.RuleSetTypeInline:
  63. r.Type = C.RuleSetTypeInline
  64. v = &r.InlineOptions
  65. case C.RuleSetTypeLocal:
  66. v = &r.LocalOptions
  67. case C.RuleSetTypeRemote:
  68. v = &r.RemoteOptions
  69. default:
  70. return E.New("unknown rule-set type: " + r.Type)
  71. }
  72. err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v)
  73. if err != nil {
  74. return err
  75. }
  76. if r.Type != C.RuleSetTypeInline {
  77. if r.Format == "" {
  78. switch r.Type {
  79. case C.RuleSetTypeLocal:
  80. r.Format = ruleSetDefaultFormat(r.LocalOptions.Path)
  81. case C.RuleSetTypeRemote:
  82. r.Format = ruleSetDefaultFormat(r.RemoteOptions.URL)
  83. }
  84. }
  85. switch r.Format {
  86. case "":
  87. return E.New("missing format")
  88. case C.RuleSetFormatSource, C.RuleSetFormatBinary:
  89. default:
  90. return E.New("unknown rule-set format: " + r.Format)
  91. }
  92. } else {
  93. r.Format = ""
  94. }
  95. return nil
  96. }
  97. func ruleSetDefaultFormat(path string) string {
  98. if pathURL, err := url.Parse(path); err == nil {
  99. path = pathURL.Path
  100. }
  101. switch filepath.Ext(path) {
  102. case ".json":
  103. return C.RuleSetFormatSource
  104. case ".srs":
  105. return C.RuleSetFormatBinary
  106. default:
  107. return ""
  108. }
  109. }
  110. type LocalRuleSet struct {
  111. Path string `json:"path,omitempty"`
  112. }
  113. type RemoteRuleSet struct {
  114. URL string `json:"url"`
  115. DownloadDetour string `json:"download_detour,omitempty"`
  116. UpdateInterval badoption.Duration `json:"update_interval,omitempty"`
  117. }
  118. type _HeadlessRule struct {
  119. Type string `json:"type,omitempty"`
  120. DefaultOptions DefaultHeadlessRule `json:"-"`
  121. LogicalOptions LogicalHeadlessRule `json:"-"`
  122. }
  123. type HeadlessRule _HeadlessRule
  124. func (r HeadlessRule) MarshalJSON() ([]byte, error) {
  125. var v any
  126. switch r.Type {
  127. case C.RuleTypeDefault:
  128. r.Type = ""
  129. v = r.DefaultOptions
  130. case C.RuleTypeLogical:
  131. v = r.LogicalOptions
  132. default:
  133. return nil, E.New("unknown rule type: " + r.Type)
  134. }
  135. return badjson.MarshallObjects((_HeadlessRule)(r), v)
  136. }
  137. func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error {
  138. err := json.Unmarshal(bytes, (*_HeadlessRule)(r))
  139. if err != nil {
  140. return err
  141. }
  142. var v any
  143. switch r.Type {
  144. case "", C.RuleTypeDefault:
  145. r.Type = C.RuleTypeDefault
  146. v = &r.DefaultOptions
  147. case C.RuleTypeLogical:
  148. v = &r.LogicalOptions
  149. default:
  150. return E.New("unknown rule type: " + r.Type)
  151. }
  152. err = badjson.UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v)
  153. if err != nil {
  154. return err
  155. }
  156. return nil
  157. }
  158. func (r HeadlessRule) IsValid() bool {
  159. switch r.Type {
  160. case C.RuleTypeDefault, "":
  161. return r.DefaultOptions.IsValid()
  162. case C.RuleTypeLogical:
  163. return r.LogicalOptions.IsValid()
  164. default:
  165. panic("unknown rule type: " + r.Type)
  166. }
  167. }
  168. type DefaultHeadlessRule struct {
  169. QueryType badoption.Listable[DNSQueryType] `json:"query_type,omitempty"`
  170. Network badoption.Listable[string] `json:"network,omitempty"`
  171. Domain badoption.Listable[string] `json:"domain,omitempty"`
  172. DomainSuffix badoption.Listable[string] `json:"domain_suffix,omitempty"`
  173. DomainKeyword badoption.Listable[string] `json:"domain_keyword,omitempty"`
  174. DomainRegex badoption.Listable[string] `json:"domain_regex,omitempty"`
  175. SourceIPCIDR badoption.Listable[string] `json:"source_ip_cidr,omitempty"`
  176. IPCIDR badoption.Listable[string] `json:"ip_cidr,omitempty"`
  177. SourcePort badoption.Listable[uint16] `json:"source_port,omitempty"`
  178. SourcePortRange badoption.Listable[string] `json:"source_port_range,omitempty"`
  179. Port badoption.Listable[uint16] `json:"port,omitempty"`
  180. PortRange badoption.Listable[string] `json:"port_range,omitempty"`
  181. ProcessName badoption.Listable[string] `json:"process_name,omitempty"`
  182. ProcessPath badoption.Listable[string] `json:"process_path,omitempty"`
  183. ProcessPathRegex badoption.Listable[string] `json:"process_path_regex,omitempty"`
  184. PackageName badoption.Listable[string] `json:"package_name,omitempty"`
  185. NetworkType badoption.Listable[InterfaceType] `json:"network_type,omitempty"`
  186. NetworkIsExpensive bool `json:"network_is_expensive,omitempty"`
  187. NetworkIsConstrained bool `json:"network_is_constrained,omitempty"`
  188. WIFISSID badoption.Listable[string] `json:"wifi_ssid,omitempty"`
  189. WIFIBSSID badoption.Listable[string] `json:"wifi_bssid,omitempty"`
  190. NetworkInterfaceAddress *badjson.TypedMap[InterfaceType, badoption.Listable[*badoption.Prefixable]] `json:"network_interface_address,omitempty"`
  191. DefaultInterfaceAddress badoption.Listable[*badoption.Prefixable] `json:"default_interface_address,omitempty"`
  192. Invert bool `json:"invert,omitempty"`
  193. DomainMatcher *domain.Matcher `json:"-"`
  194. SourceIPSet *netipx.IPSet `json:"-"`
  195. IPSet *netipx.IPSet `json:"-"`
  196. AdGuardDomain badoption.Listable[string] `json:"-"`
  197. AdGuardDomainMatcher *domain.AdGuardMatcher `json:"-"`
  198. }
  199. func (r DefaultHeadlessRule) IsValid() bool {
  200. var defaultValue DefaultHeadlessRule
  201. defaultValue.Invert = r.Invert
  202. return !reflect.DeepEqual(r, defaultValue)
  203. }
  204. type LogicalHeadlessRule struct {
  205. Mode string `json:"mode"`
  206. Rules []HeadlessRule `json:"rules,omitempty"`
  207. Invert bool `json:"invert,omitempty"`
  208. }
  209. func (r LogicalHeadlessRule) IsValid() bool {
  210. return len(r.Rules) > 0 && common.All(r.Rules, HeadlessRule.IsValid)
  211. }
  212. type _PlainRuleSetCompat struct {
  213. Version uint8 `json:"version"`
  214. Options PlainRuleSet `json:"-"`
  215. RawMessage json.RawMessage `json:"-"`
  216. }
  217. type PlainRuleSetCompat _PlainRuleSetCompat
  218. func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
  219. var v any
  220. switch r.Version {
  221. case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
  222. v = r.Options
  223. default:
  224. return nil, E.New("unknown rule-set version: ", r.Version)
  225. }
  226. return badjson.MarshallObjects((_PlainRuleSetCompat)(r), v)
  227. }
  228. func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
  229. err := json.Unmarshal(bytes, (*_PlainRuleSetCompat)(r))
  230. if err != nil {
  231. return err
  232. }
  233. var v any
  234. switch r.Version {
  235. case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
  236. v = &r.Options
  237. case 0:
  238. return E.New("missing rule-set version")
  239. default:
  240. return E.New("unknown rule-set version: ", r.Version)
  241. }
  242. err = badjson.UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v)
  243. if err != nil {
  244. return err
  245. }
  246. r.RawMessage = bytes
  247. return nil
  248. }
  249. func (r PlainRuleSetCompat) Upgrade() (PlainRuleSet, error) {
  250. switch r.Version {
  251. case C.RuleSetVersion1, C.RuleSetVersion2, C.RuleSetVersion3, C.RuleSetVersion4:
  252. default:
  253. return PlainRuleSet{}, E.New("unknown rule-set version: " + F.ToString(r.Version))
  254. }
  255. return r.Options, nil
  256. }
  257. type PlainRuleSet struct {
  258. Rules []HeadlessRule `json:"rules,omitempty"`
  259. }