rule_set_remote.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. package route
  2. import (
  3. "bytes"
  4. "context"
  5. "io"
  6. "net"
  7. "net/http"
  8. "runtime"
  9. "time"
  10. "github.com/sagernet/sing-box/adapter"
  11. "github.com/sagernet/sing-box/common/srs"
  12. C "github.com/sagernet/sing-box/constant"
  13. "github.com/sagernet/sing-box/option"
  14. E "github.com/sagernet/sing/common/exceptions"
  15. "github.com/sagernet/sing/common/json"
  16. "github.com/sagernet/sing/common/logger"
  17. M "github.com/sagernet/sing/common/metadata"
  18. N "github.com/sagernet/sing/common/network"
  19. "github.com/sagernet/sing/service"
  20. "github.com/sagernet/sing/service/pause"
  21. )
  22. var _ adapter.RuleSet = (*RemoteRuleSet)(nil)
  23. type RemoteRuleSet struct {
  24. ctx context.Context
  25. cancel context.CancelFunc
  26. router adapter.Router
  27. logger logger.ContextLogger
  28. options option.RuleSet
  29. metadata adapter.RuleSetMetadata
  30. updateInterval time.Duration
  31. dialer N.Dialer
  32. rules []adapter.HeadlessRule
  33. lastUpdated time.Time
  34. lastEtag string
  35. updateTicker *time.Ticker
  36. pauseManager pause.Manager
  37. }
  38. func NewRemoteRuleSet(ctx context.Context, router adapter.Router, logger logger.ContextLogger, options option.RuleSet) *RemoteRuleSet {
  39. ctx, cancel := context.WithCancel(ctx)
  40. var updateInterval time.Duration
  41. if options.RemoteOptions.UpdateInterval > 0 {
  42. updateInterval = time.Duration(options.RemoteOptions.UpdateInterval)
  43. } else {
  44. updateInterval = 24 * time.Hour
  45. }
  46. return &RemoteRuleSet{
  47. ctx: ctx,
  48. cancel: cancel,
  49. router: router,
  50. logger: logger,
  51. options: options,
  52. updateInterval: updateInterval,
  53. pauseManager: service.FromContext[pause.Manager](ctx),
  54. }
  55. }
  56. func (s *RemoteRuleSet) Match(metadata *adapter.InboundContext) bool {
  57. for _, rule := range s.rules {
  58. if rule.Match(metadata) {
  59. return true
  60. }
  61. }
  62. return false
  63. }
  64. func (s *RemoteRuleSet) StartContext(ctx context.Context, startContext adapter.RuleSetStartContext) error {
  65. var dialer N.Dialer
  66. if s.options.RemoteOptions.DownloadDetour != "" {
  67. outbound, loaded := s.router.Outbound(s.options.RemoteOptions.DownloadDetour)
  68. if !loaded {
  69. return E.New("download_detour not found: ", s.options.RemoteOptions.DownloadDetour)
  70. }
  71. dialer = outbound
  72. } else {
  73. outbound, err := s.router.DefaultOutbound(N.NetworkTCP)
  74. if err != nil {
  75. return err
  76. }
  77. dialer = outbound
  78. }
  79. s.dialer = dialer
  80. cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
  81. if cacheFile != nil {
  82. if savedSet := cacheFile.LoadRuleSet(s.options.Tag); savedSet != nil {
  83. err := s.loadBytes(savedSet.Content)
  84. if err != nil {
  85. return E.Cause(err, "restore cached rule-set")
  86. }
  87. s.lastUpdated = savedSet.LastUpdated
  88. s.lastEtag = savedSet.LastEtag
  89. }
  90. }
  91. if s.lastUpdated.IsZero() {
  92. err := s.fetchOnce(ctx, startContext)
  93. if err != nil {
  94. return E.Cause(err, "initial rule-set: ", s.options.Tag)
  95. }
  96. }
  97. s.updateTicker = time.NewTicker(s.updateInterval)
  98. go s.loopUpdate()
  99. return nil
  100. }
  101. func (s *RemoteRuleSet) PostStart() error {
  102. if s.lastUpdated.IsZero() {
  103. err := s.fetchOnce(s.ctx, nil)
  104. if err != nil {
  105. s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
  106. }
  107. }
  108. return nil
  109. }
  110. func (s *RemoteRuleSet) Metadata() adapter.RuleSetMetadata {
  111. return s.metadata
  112. }
  113. func (s *RemoteRuleSet) loadBytes(content []byte) error {
  114. var (
  115. plainRuleSet option.PlainRuleSet
  116. err error
  117. )
  118. switch s.options.Format {
  119. case C.RuleSetFormatSource:
  120. var compat option.PlainRuleSetCompat
  121. compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
  122. if err != nil {
  123. return err
  124. }
  125. plainRuleSet = compat.Upgrade()
  126. case C.RuleSetFormatBinary:
  127. plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
  128. if err != nil {
  129. return err
  130. }
  131. default:
  132. return E.New("unknown rule set format: ", s.options.Format)
  133. }
  134. rules := make([]adapter.HeadlessRule, len(plainRuleSet.Rules))
  135. for i, ruleOptions := range plainRuleSet.Rules {
  136. rules[i], err = NewHeadlessRule(s.router, ruleOptions)
  137. if err != nil {
  138. return E.Cause(err, "parse rule_set.rules.[", i, "]")
  139. }
  140. }
  141. s.metadata.ContainsProcessRule = hasHeadlessRule(plainRuleSet.Rules, isProcessHeadlessRule)
  142. s.metadata.ContainsWIFIRule = hasHeadlessRule(plainRuleSet.Rules, isWIFIHeadlessRule)
  143. s.rules = rules
  144. return nil
  145. }
  146. func (s *RemoteRuleSet) loopUpdate() {
  147. if time.Since(s.lastUpdated) > s.updateInterval {
  148. err := s.fetchOnce(s.ctx, nil)
  149. if err != nil {
  150. s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
  151. }
  152. }
  153. for {
  154. runtime.GC()
  155. select {
  156. case <-s.ctx.Done():
  157. return
  158. case <-s.updateTicker.C:
  159. s.pauseManager.WaitActive()
  160. err := s.fetchOnce(s.ctx, nil)
  161. if err != nil {
  162. s.logger.Error("fetch rule-set ", s.options.Tag, ": ", err)
  163. }
  164. }
  165. }
  166. }
  167. func (s *RemoteRuleSet) fetchOnce(ctx context.Context, startContext adapter.RuleSetStartContext) error {
  168. s.logger.Debug("updating rule-set ", s.options.Tag, " from URL: ", s.options.RemoteOptions.URL)
  169. var httpClient *http.Client
  170. if startContext != nil {
  171. httpClient = startContext.HTTPClient(s.options.RemoteOptions.DownloadDetour, s.dialer)
  172. } else {
  173. httpClient = &http.Client{
  174. Transport: &http.Transport{
  175. ForceAttemptHTTP2: true,
  176. TLSHandshakeTimeout: C.TCPTimeout,
  177. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
  178. return s.dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
  179. },
  180. },
  181. }
  182. }
  183. request, err := http.NewRequest("GET", s.options.RemoteOptions.URL, nil)
  184. if err != nil {
  185. return err
  186. }
  187. if s.lastEtag != "" {
  188. request.Header.Set("If-None-Match", s.lastEtag)
  189. }
  190. response, err := httpClient.Do(request.WithContext(ctx))
  191. if err != nil {
  192. return err
  193. }
  194. switch response.StatusCode {
  195. case http.StatusOK:
  196. case http.StatusNotModified:
  197. s.lastUpdated = time.Now()
  198. cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
  199. if cacheFile != nil {
  200. savedRuleSet := cacheFile.LoadRuleSet(s.options.Tag)
  201. if savedRuleSet != nil {
  202. savedRuleSet.LastUpdated = s.lastUpdated
  203. err = cacheFile.SaveRuleSet(s.options.Tag, savedRuleSet)
  204. if err != nil {
  205. s.logger.Error("save rule-set updated time: ", err)
  206. return nil
  207. }
  208. }
  209. }
  210. s.logger.Info("update rule-set ", s.options.Tag, ": not modified")
  211. return nil
  212. default:
  213. return E.New("unexpected status: ", response.Status)
  214. }
  215. content, err := io.ReadAll(response.Body)
  216. if err != nil {
  217. response.Body.Close()
  218. return err
  219. }
  220. err = s.loadBytes(content)
  221. if err != nil {
  222. response.Body.Close()
  223. return err
  224. }
  225. response.Body.Close()
  226. eTagHeader := response.Header.Get("Etag")
  227. if eTagHeader != "" {
  228. s.lastEtag = eTagHeader
  229. }
  230. s.lastUpdated = time.Now()
  231. cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
  232. if cacheFile != nil {
  233. err = cacheFile.SaveRuleSet(s.options.Tag, &adapter.SavedRuleSet{
  234. LastUpdated: s.lastUpdated,
  235. Content: content,
  236. LastEtag: s.lastEtag,
  237. })
  238. if err != nil {
  239. s.logger.Error("save rule-set cache: ", err)
  240. }
  241. }
  242. s.logger.Info("updated rule-set ", s.options.Tag)
  243. return nil
  244. }
  245. func (s *RemoteRuleSet) Close() error {
  246. s.updateTicker.Stop()
  247. s.cancel()
  248. return nil
  249. }