geoip.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. // Copyright (C) 2024 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. // Package geoip provides an automatically updating MaxMind GeoIP2 database
  7. // provider.
  8. package geoip
  9. import (
  10. "context"
  11. "errors"
  12. "fmt"
  13. "net"
  14. "os"
  15. "path/filepath"
  16. "sync"
  17. "time"
  18. "github.com/maxmind/geoipupdate/v6/pkg/geoipupdate"
  19. "github.com/oschwald/geoip2-golang"
  20. )
  21. type Provider struct {
  22. edition string
  23. accountID int
  24. licenseKey string
  25. refreshInterval time.Duration
  26. directory string
  27. mut sync.Mutex
  28. currentDBDir string
  29. db *geoip2.Reader
  30. }
  31. // NewGeoLite2CityProvider returns a new GeoIP2 database provider for the
  32. // GeoLite2-City database. The database will be stored in the given
  33. // directory (which should exist) and refreshed every 7 days.
  34. func NewGeoLite2CityProvider(ctx context.Context, accountID int, licenseKey string, directory string) (*Provider, error) {
  35. p := &Provider{
  36. edition: "GeoLite2-City",
  37. accountID: accountID,
  38. licenseKey: licenseKey,
  39. refreshInterval: 7 * 24 * time.Hour,
  40. directory: directory,
  41. }
  42. if err := p.download(ctx); err != nil {
  43. return nil, err
  44. }
  45. return p, nil
  46. }
  47. func (p *Provider) City(ip net.IP) (*geoip2.City, error) {
  48. p.mut.Lock()
  49. defer p.mut.Unlock()
  50. if p.db == nil {
  51. return nil, errors.New("database not open")
  52. }
  53. return p.db.City(ip)
  54. }
  55. // Serve downloads the GeoIP2 database and keeps it up to date. It will return
  56. // when the context is canceled.
  57. func (p *Provider) Serve(ctx context.Context) error {
  58. for {
  59. select {
  60. case <-ctx.Done():
  61. return ctx.Err()
  62. case <-time.After(p.refreshInterval):
  63. if err := p.download(ctx); err != nil {
  64. return err
  65. }
  66. }
  67. }
  68. }
  69. func (p *Provider) download(ctx context.Context) error {
  70. newSubdir, err := os.MkdirTemp(p.directory, "geoipupdate")
  71. if err != nil {
  72. return fmt.Errorf("download: %w", err)
  73. }
  74. cfg := &geoipupdate.Config{
  75. URL: "https://updates.maxmind.com",
  76. DatabaseDirectory: newSubdir,
  77. LockFile: filepath.Join(newSubdir, "geoipupdate.lock"),
  78. RetryFor: 5 * time.Minute,
  79. Parallelism: 1,
  80. AccountID: p.accountID,
  81. LicenseKey: p.licenseKey,
  82. EditionIDs: []string{p.edition},
  83. }
  84. if err := geoipupdate.NewClient(cfg).Run(ctx); err != nil {
  85. return fmt.Errorf("download: %w", err)
  86. }
  87. dbPath := filepath.Join(newSubdir, p.edition+".mmdb")
  88. db, err := geoip2.Open(dbPath)
  89. if err != nil {
  90. return fmt.Errorf("open downloaded db: %w", err)
  91. }
  92. p.mut.Lock()
  93. prevDBDir := p.currentDBDir
  94. if p.db != nil {
  95. p.db.Close()
  96. }
  97. p.currentDBDir = newSubdir
  98. p.db = db
  99. p.mut.Unlock()
  100. if prevDBDir != "" {
  101. _ = os.RemoveAll(p.currentDBDir)
  102. }
  103. return nil
  104. }