Highly-opinionated (ex-bullshit-free) MTPROTO proxy for Telegram. If you use v1.0 or upgrade broke you proxy, please read the chapter Version 2
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

firehol.go 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. package ipblocklist
  2. import (
  3. "bufio"
  4. "context"
  5. "fmt"
  6. "net"
  7. "regexp"
  8. "strings"
  9. "sync"
  10. "time"
  11. "github.com/9seconds/mtg/v2/ipblocklist/files"
  12. "github.com/9seconds/mtg/v2/mtglib"
  13. "github.com/panjf2000/ants/v2"
  14. "github.com/yl2chen/cidranger"
  15. )
  16. var (
  17. fireholRegexpComment = regexp.MustCompile(`\s*#.*?$`)
  18. fireholIPv4DefaultCIDR = net.CIDRMask(32, 32) // nolint: gomnd
  19. fireholIPv6DefaultCIDR = net.CIDRMask(128, 128) // nolint: gomnd
  20. )
  21. // Firehol is IPBlocklist which uses lists from FireHOL:
  22. // https://iplists.firehol.org/
  23. //
  24. // It can use both local files and remote URLs. This is not necessary
  25. // that blocklists should be taken from this website, we expect only
  26. // compatible formats here.
  27. //
  28. // Example of the format:
  29. //
  30. // # this is a comment
  31. // # to ignore
  32. // 127.0.0.1 # you can specify an IP
  33. // 10.0.0.0/8 # or cidr
  34. type Firehol struct {
  35. ctx context.Context
  36. ctxCancel context.CancelFunc
  37. logger mtglib.Logger
  38. updateMutex sync.RWMutex
  39. ranger cidranger.Ranger
  40. blocklists []files.File
  41. workerPool *ants.Pool
  42. }
  43. // Shutdown stop a background update process.
  44. func (f *Firehol) Shutdown() {
  45. f.ctxCancel()
  46. }
  47. // Contains is given IP list can be found in FireHOL blocklists.
  48. func (f *Firehol) Contains(ip net.IP) bool {
  49. if ip == nil {
  50. return true
  51. }
  52. f.updateMutex.RLock()
  53. defer f.updateMutex.RUnlock()
  54. ok, err := f.ranger.Contains(ip)
  55. if err != nil {
  56. f.logger.BindStr("ip", ip.String()).DebugError("Cannot check if ip is present", err)
  57. }
  58. return ok && err == nil
  59. }
  60. // Run starts a background update process.
  61. //
  62. // This is a blocking method so you probably want to run it in a
  63. // goroutine.
  64. func (f *Firehol) Run(updateEach time.Duration) {
  65. if updateEach == 0 {
  66. updateEach = DefaultFireholUpdateEach
  67. }
  68. ticker := time.NewTicker(updateEach)
  69. defer func() {
  70. ticker.Stop()
  71. select {
  72. case <-ticker.C:
  73. default:
  74. }
  75. }()
  76. f.update()
  77. for {
  78. select {
  79. case <-f.ctx.Done():
  80. return
  81. case <-ticker.C:
  82. f.update()
  83. }
  84. }
  85. }
  86. func (f *Firehol) update() {
  87. ctx, cancel := context.WithCancel(f.ctx)
  88. defer cancel()
  89. wg := &sync.WaitGroup{}
  90. wg.Add(len(f.blocklists))
  91. treeMutex := &sync.Mutex{}
  92. ranger := cidranger.NewPCTrieRanger()
  93. for _, v := range f.blocklists {
  94. go func(file files.File) {
  95. defer wg.Done()
  96. logger := f.logger.BindStr("filename", file.String())
  97. fileContent, err := file.Open(ctx)
  98. if err != nil {
  99. logger.WarningError("update has failed", err)
  100. return
  101. }
  102. defer fileContent.Close()
  103. if err := f.updateFromFile(treeMutex, ranger, bufio.NewScanner(fileContent)); err != nil {
  104. logger.WarningError("update has failed", err)
  105. }
  106. }(v)
  107. }
  108. wg.Wait()
  109. f.updateMutex.Lock()
  110. defer f.updateMutex.Unlock()
  111. f.ranger = ranger
  112. f.logger.Info("ip list was updated")
  113. }
  114. func (f *Firehol) updateFromFile(mutex sync.Locker,
  115. ranger cidranger.Ranger,
  116. scanner *bufio.Scanner) error {
  117. for scanner.Scan() {
  118. text := scanner.Text()
  119. text = fireholRegexpComment.ReplaceAllLiteralString(text, "")
  120. text = strings.TrimSpace(text)
  121. if text == "" {
  122. continue
  123. }
  124. ipnet, err := f.updateParseLine(text)
  125. if err != nil {
  126. return fmt.Errorf("cannot parse a line: %w", err)
  127. }
  128. mutex.Lock()
  129. err = ranger.Insert(cidranger.NewBasicRangerEntry(*ipnet))
  130. mutex.Unlock()
  131. if err != nil {
  132. return fmt.Errorf("cannot insert %v into ranger: %w", ipnet, err)
  133. }
  134. }
  135. if scanner.Err() != nil {
  136. return fmt.Errorf("cannot parse a file: %w", scanner.Err())
  137. }
  138. return nil
  139. }
  140. func (f *Firehol) updateParseLine(text string) (*net.IPNet, error) {
  141. if _, ipnet, err := net.ParseCIDR(text); err == nil {
  142. return ipnet, nil
  143. }
  144. ipaddr := net.ParseIP(text)
  145. if ipaddr == nil {
  146. return nil, fmt.Errorf("incorrect ip address %s", text)
  147. }
  148. mask := fireholIPv4DefaultCIDR
  149. if ipaddr.To4() == nil {
  150. mask = fireholIPv6DefaultCIDR
  151. }
  152. return &net.IPNet{
  153. IP: ipaddr,
  154. Mask: mask,
  155. }, nil
  156. }
  157. // NewFirehol creates a new instance of FireHOL IP blocklist.
  158. //
  159. // This method does not start an update process so please execute Run
  160. // when it is necessary.
  161. func NewFirehol(logger mtglib.Logger, network mtglib.Network,
  162. downloadConcurrency uint,
  163. urls []string,
  164. localFiles []string) (*Firehol, error) {
  165. blocklists := []files.File{}
  166. for _, v := range localFiles {
  167. file, err := files.NewLocal(v)
  168. if err != nil {
  169. return nil, fmt.Errorf("cannot create a local file %s: %w", v, err)
  170. }
  171. blocklists = append(blocklists, file)
  172. }
  173. httpClient := network.MakeHTTPClient(nil)
  174. for _, v := range urls {
  175. file, err := files.NewHTTP(httpClient, v)
  176. if err != nil {
  177. return nil, fmt.Errorf("cannot create a HTTP file %s: %w", v, err)
  178. }
  179. blocklists = append(blocklists, file)
  180. }
  181. return NewFireholFromFiles(logger, downloadConcurrency, blocklists)
  182. }
  183. func NewFireholFromFiles(logger mtglib.Logger,
  184. downloadConcurrency uint,
  185. blocklists []files.File) (*Firehol, error) {
  186. if downloadConcurrency == 0 {
  187. downloadConcurrency = DefaultFireholDownloadConcurrency
  188. }
  189. workerPool, _ := ants.NewPool(int(downloadConcurrency))
  190. ctx, cancel := context.WithCancel(context.Background())
  191. return &Firehol{
  192. ctx: ctx,
  193. ctxCancel: cancel,
  194. logger: logger.Named("firehol"),
  195. ranger: cidranger.NewPCTrieRanger(),
  196. workerPool: workerPool,
  197. blocklists: blocklists,
  198. }, nil
  199. }