ipmeta.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. package ipmeta
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "net"
  7. "os"
  8. "sync"
  9. "time"
  10. "github.com/ip2location/ip2location-go"
  11. "github.com/minio/minio-go/v7"
  12. "github.com/minio/minio-go/v7/pkg/credentials"
  13. "github.com/scraperwall/asndb/v2"
  14. log "github.com/sirupsen/logrus"
  15. )
  16. type IPs []string
  17. type Meta struct {
  18. IP string
  19. IP2Loc ip2location.IP2Locationrecord
  20. ASN *asndb.ASN
  21. ASNError error
  22. IP2LocError error
  23. }
  24. type IPMeta struct {
  25. asndb *asndb.DB
  26. ip2locdb *ip2location.DB
  27. mutex sync.RWMutex
  28. options IPMetaOpts
  29. }
  30. type IPMetaOpts struct {
  31. S3Endpoint string
  32. S3Bucket string
  33. S3Key string
  34. S33Secret string
  35. S3Object string
  36. IP2LocFile string
  37. AsnDBURL string
  38. UpdateInterval time.Duration
  39. }
  40. func (i *IPMeta) load(ctx context.Context, force bool) error {
  41. var i2ldb *ip2location.DB
  42. var asnDB *asndb.DB
  43. var err error
  44. // Load IP2Loc database
  45. //
  46. if i.options.S33Secret != "" &&
  47. i.options.S3Bucket != "" &&
  48. i.options.S3Endpoint != "" &&
  49. i.options.S3Key != "" &&
  50. i.options.S3Object != "" &&
  51. i.options.IP2LocFile != "" {
  52. stat, err := os.Stat(i.options.IP2LocFile)
  53. if force || err != nil || stat.ModTime().Before(time.Now().Add(-1*i.options.UpdateInterval)) {
  54. mc, err := minio.New(i.options.S3Endpoint, &minio.Options{
  55. Creds: credentials.NewStaticV4(i.options.S3Key, i.options.S33Secret, ""),
  56. Secure: true,
  57. })
  58. if err != nil {
  59. return err
  60. }
  61. obj, err := mc.GetObject(ctx, i.options.S3Bucket, i.options.S3Object, minio.GetObjectOptions{})
  62. if err != nil {
  63. return err
  64. }
  65. fh, err := os.OpenFile(i.options.IP2LocFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0666)
  66. if err != nil {
  67. return fmt.Errorf("%s: %s", i.options.IP2LocFile, err)
  68. }
  69. _, err = io.Copy(fh, obj)
  70. if err != nil {
  71. return fmt.Errorf("writing %s failed: %s", i.options.IP2LocFile, err)
  72. }
  73. err = obj.Close()
  74. if err != nil {
  75. return err
  76. }
  77. err = fh.Close()
  78. if err != nil {
  79. return err
  80. }
  81. }
  82. i2ldb, err = ip2location.OpenDB(i.options.IP2LocFile)
  83. if err != nil {
  84. return fmt.Errorf("failed to open IP2Location DB: %s", err)
  85. }
  86. }
  87. if i.options.AsnDBURL != "" {
  88. // Load the ASN database
  89. //
  90. asnDB, err = asndb.New(i.options.AsnDBURL)
  91. if err != nil {
  92. return fmt.Errorf("failed to load ASNDB from %s: %s", i.options.AsnDBURL, err)
  93. }
  94. }
  95. i.mutex.Lock()
  96. i.ip2locdb = i2ldb
  97. i.asndb = asnDB
  98. i.mutex.Unlock()
  99. return nil
  100. }
  101. func (i *IPMeta) reload(ctx context.Context) {
  102. ticker := time.NewTicker(i.options.UpdateInterval)
  103. for {
  104. select {
  105. case <-ctx.Done():
  106. ticker.Stop()
  107. break
  108. case <-ticker.C:
  109. err := i.load(ctx, true)
  110. if err != nil {
  111. log.Println(err)
  112. }
  113. }
  114. }
  115. }
  116. func (i *IPMeta) Lookup(ip net.IP) *Meta {
  117. meta := Meta{IP: ip.String()}
  118. i.mutex.RLock()
  119. defer i.mutex.RUnlock()
  120. if i.asndb != nil {
  121. meta.ASN = i.asndb.Lookup(ip)
  122. if meta.ASN == nil {
  123. meta.ASNError = fmt.Errorf("no ASN record found for %s", ip)
  124. }
  125. }
  126. if i.ip2locdb != nil {
  127. rec, err := i.ip2locdb.Get_all(ip.String())
  128. meta.IP2LocError = err
  129. meta.IP2Loc = rec
  130. }
  131. return &meta
  132. }
  133. func New(ctx context.Context, opts IPMetaOpts) (*IPMeta, error) {
  134. i2l := IPMeta{
  135. mutex: sync.RWMutex{},
  136. options: opts,
  137. }
  138. err := i2l.load(ctx, false)
  139. if err != nil {
  140. return nil, err
  141. }
  142. if i2l.options.UpdateInterval > time.Hour {
  143. go i2l.reload(ctx)
  144. }
  145. return &i2l, nil
  146. }