mirror of
https://github.com/octoleo/syncthing.git
synced 2024-12-22 19:08:58 +00:00
125 lines
2.8 KiB
Go
125 lines
2.8 KiB
Go
|
// Copyright (C) 2024 The Syncthing Authors.
|
||
|
//
|
||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||
|
|
||
|
// Package geoip provides an automatically updating MaxMind GeoIP2 database
|
||
|
// provider.
|
||
|
package geoip
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"sync"
|
||
|
"time"
|
||
|
|
||
|
"github.com/maxmind/geoipupdate/v6/pkg/geoipupdate"
|
||
|
"github.com/oschwald/geoip2-golang"
|
||
|
)
|
||
|
|
||
|
type Provider struct {
|
||
|
edition string
|
||
|
accountID int
|
||
|
licenseKey string
|
||
|
refreshInterval time.Duration
|
||
|
directory string
|
||
|
|
||
|
mut sync.Mutex
|
||
|
currentDBDir string
|
||
|
db *geoip2.Reader
|
||
|
}
|
||
|
|
||
|
// NewGeoLite2CityProvider returns a new GeoIP2 database provider for the
|
||
|
// GeoLite2-City database. The database will be stored in the given
|
||
|
// directory (which should exist) and refreshed every 7 days.
|
||
|
func NewGeoLite2CityProvider(ctx context.Context, accountID int, licenseKey string, directory string) (*Provider, error) {
|
||
|
p := &Provider{
|
||
|
edition: "GeoLite2-City",
|
||
|
accountID: accountID,
|
||
|
licenseKey: licenseKey,
|
||
|
refreshInterval: 7 * 24 * time.Hour,
|
||
|
directory: directory,
|
||
|
}
|
||
|
|
||
|
if err := p.download(ctx); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return p, nil
|
||
|
}
|
||
|
|
||
|
func (p *Provider) City(ip net.IP) (*geoip2.City, error) {
|
||
|
p.mut.Lock()
|
||
|
defer p.mut.Unlock()
|
||
|
|
||
|
if p.db == nil {
|
||
|
return nil, errors.New("database not open")
|
||
|
}
|
||
|
|
||
|
return p.db.City(ip)
|
||
|
}
|
||
|
|
||
|
// Serve downloads the GeoIP2 database and keeps it up to date. It will return
|
||
|
// when the context is canceled.
|
||
|
func (p *Provider) Serve(ctx context.Context) error {
|
||
|
for {
|
||
|
select {
|
||
|
case <-ctx.Done():
|
||
|
return ctx.Err()
|
||
|
|
||
|
case <-time.After(p.refreshInterval):
|
||
|
if err := p.download(ctx); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *Provider) download(ctx context.Context) error {
|
||
|
newSubdir, err := os.MkdirTemp(p.directory, "geoipupdate")
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("download: %w", err)
|
||
|
}
|
||
|
|
||
|
cfg := &geoipupdate.Config{
|
||
|
URL: "https://updates.maxmind.com",
|
||
|
DatabaseDirectory: newSubdir,
|
||
|
LockFile: filepath.Join(newSubdir, "geoipupdate.lock"),
|
||
|
RetryFor: 5 * time.Minute,
|
||
|
Parallelism: 1,
|
||
|
AccountID: p.accountID,
|
||
|
LicenseKey: p.licenseKey,
|
||
|
EditionIDs: []string{p.edition},
|
||
|
}
|
||
|
|
||
|
if err := geoipupdate.NewClient(cfg).Run(ctx); err != nil {
|
||
|
return fmt.Errorf("download: %w", err)
|
||
|
}
|
||
|
|
||
|
dbPath := filepath.Join(newSubdir, p.edition+".mmdb")
|
||
|
db, err := geoip2.Open(dbPath)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("open downloaded db: %w", err)
|
||
|
}
|
||
|
|
||
|
p.mut.Lock()
|
||
|
prevDBDir := p.currentDBDir
|
||
|
if p.db != nil {
|
||
|
p.db.Close()
|
||
|
}
|
||
|
p.currentDBDir = newSubdir
|
||
|
p.db = db
|
||
|
p.mut.Unlock()
|
||
|
|
||
|
if prevDBDir != "" {
|
||
|
_ = os.RemoveAll(p.currentDBDir)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|