Files

MaxMind

The maxmind package provides MaxMind GeoIP database integration for IP geolocation, including automatic database downloading and updates.

Overview

The maxmind package implements MaxMind GeoIP database management, providing IP geolocation lookups for country and city information. It supports automatic database downloading, scheduled updates, and thread-safe access.

Key Features

  • MaxMind GeoIP database loading
  • Automatic database downloading from MaxMind
  • Scheduled updates every 24 hours
  • City lookup with cache support
  • IP geolocation (country, city, timezone)
  • Thread-safe access

Architecture

graph TD
    A[MaxMind Config] --> B[Load Database]
    B --> C{Exists?}
    C -->|No| D[Download]
    C -->|Yes| E[Load]
    D --> F[Extract from TarGz]
    E --> G[Open Reader]

    H[IP Lookup] --> I[City Lookup]
    I --> J{Cache Hit?}
    J -->|Yes| K[Return Cached]
    J -->|No| L[Database Query]
    L --> M[Cache Result]
    M --> K

    N[Update Scheduler] --> O[Check Daily]
    O --> P{Different?}
    P -->|Yes| Q[Download Update]
    P -->|No| O

Core Components

MaxMind Structure

type MaxMind struct {
    *Config
    lastUpdate time.Time
    db         struct {
        *maxminddb.Reader
        sync.RWMutex
    }
}

Configuration

type Config struct {
    Database   string  // Database type (GeoLite2 or GeoIP2)
    AccountID  int
    LicenseKey Secret
}

IP Information

type IPInfo struct {
    IP          net.IP
    Str         string
    Country     *Country
    City        *City
    Location    *Location
}

type Country struct {
    IsoCode string
    Name    string
}

type City struct {
    Country   *Country
    Name      string
    Location  *Location
}

type Location struct {
    TimeZone string
    Latitude float64
    Longitude float64
}

Public API

Initialization

// LoadMaxMindDB loads or downloads the MaxMind database.
func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error

Lookup

// LookupCity looks up city information for an IP.
func LookupCity(info *IPInfo) (city *City, ok bool)

Usage

Basic Setup

maxmindCfg := &maxmind.Config{
    Database:   maxmind.MaxMindGeoLite,
    AccountID:  123456,
    LicenseKey: "your-license-key",
}

err := maxmindCfg.LoadMaxMindDB(parent)
if err != nil {
    log.Fatal(err)
}

IP Lookup

// Create IP info
ipInfo := &maxmind.IPInfo{
    IP:  net.ParseIP("8.8.8.8"),
    Str: "8.8.8.8",
}

// Lookup city
city, ok := maxmind.LookupCity(ipInfo)
if ok {
    fmt.Printf("Country: %s\n", city.Country.IsoCode)
    fmt.Printf("City: %s\n", city.Name)
    fmt.Printf("Timezone: %s\n", city.Location.TimeZone)
}

Database Types

const (
    MaxMindGeoLite = "GeoLite2-Country"
    MaxMindGeoIP2  = "GeoIP2-Country"
)

Data Flow

sequenceDiagram
    participant Config
    participant MaxMind
    participant Database
    participant Cache
    participant UpdateScheduler

    Config->>MaxMind: LoadMaxMindDB()
    MaxMind->>Database: Open()
    alt Database Missing
        MaxMind->>MaxMind: Download()
        MaxMind->>Database: Extract & Create
    end
    Database-->>MaxMind: Reader

    Note over MaxMind: Start Update Scheduler

    loop Every 24 Hours
        UpdateScheduler->>MaxMind: Check Update
        MaxMind->>MaxMind: Check Last-Modified
        alt Update Available
            MaxMind->>MaxMind: Download
            MaxMind->>Database: Replace
        end
    end

    participant Lookup
    Lookup->>MaxMind: LookupCity(ip)
    MaxMind->>Cache: Check
    alt Cache Hit
        Cache-->>Lookup: City Info
    else Cache Miss
        MaxMind->>Database: Query
        Database-->>MaxMind: City Info
        MaxMind->>Cache: Store
        MaxMind-->>Lookup: City Info
    end

Database Download

Download Process

func (cfg *MaxMind) download() error {
    resp, err := cfg.doReq(http.MethodGet)
    if err != nil {
        return err
    }

    // Read response
    databaseGZ, err := io.ReadAll(resp.Body)
    if err != nil {
        return err
    }

    // Extract from tar.gz
    err = extractFileFromTarGz(databaseGZ, cfg.dbFilename(), tmpDBPath)
    if err != nil {
        return err
    }

    // Validate
    db, err := maxmindDBOpen(tmpDBPath)
    if err != nil {
        os.Remove(tmpDBPath)
        return err
    }
    db.Close()

    // Rename to final location
    os.Rename(tmpDBPath, dbFile)
    return nil
}

Security Checks

The download process includes tar bomb protection:

sumSize := int64(0)
for {
    hdr, err := tr.Next()
    if err == io.EOF {
        break
    }
    sumSize += hdr.Size
    if sumSize > 30*1024*1024 {
        return errors.New("file size exceeds 30MB")
    }
}

Update Scheduling

func (cfg *MaxMind) scheduleUpdate(parent task.Parent) {
    task := parent.Subtask("maxmind_schedule_update", true)
    ticker := time.NewTicker(updateInterval) // 24 hours

    cfg.loadLastUpdate()
    cfg.update()

    for {
        select {
        case <-task.Context().Done():
            return
        case <-ticker.C:
            cfg.update()
        }
    }
}

Thread Safety

The database uses a read-write mutex:

type MaxMind struct {
    *Config
    db struct {
        *maxminddb.Reader
        sync.RWMutex
    }
}

// Lookups use RLock
func (cfg *MaxMind) lookup(ip net.IP) (*maxminddb.City, error) {
    cfg.db.RLock()
    defer cfg.db.RUnlock()
    return cfg.db.Lookup(ip)
}

Configuration

Environment Variables

Variable Description
MAXMIND_ACCOUNT_ID MaxMind account ID
MAXMIND_LICENSE_KEY MaxMind license key

YAML Configuration

providers:
  maxmind:
    database: geolite2
    account_id: 123456
    license_key: your-license-key

Integration Points

The maxmind package integrates with:

  • ACL: IP-based access control (country/timezone matching)
  • Config: Configuration management
  • Logging: Update notifications
  • City Cache: IP geolocation caching

Error Handling

var (
    ErrResponseNotOK   = gperr.New("response not OK")
    ErrDownloadFailure = gperr.New("download failure")
)

Performance Considerations

  • 24-hour update interval reduces unnecessary downloads
  • Database size ~10-30MB
  • City lookup cache reduces database queries
  • RLock for concurrent reads