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.7KB

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