geoip2-golang

repository·main·Indexed 25 days ago

https://github.com/oschwald/geoip2-golang

A high-performance GeoIP2 reader for Go that supports MaxMind GeoLite2 and GeoIP2 databases. Built on top of maxminddb-golang, it provides typed access to geolocation data including City, Country, ASN, Anonymous IP, Anonymous Plus, Enterprise, and ISP databases. Version 2.0 utilizes netip.Addr for improved performance and reduced memory usage.

Tokens
9.2K
Snippets
20
Records
35
Agent score
80%

What's inside geoip2-golang

  1. What's new in v2

    main

    Version 2.0 introduced several improvements over v1:

    • Performance: 56% fewer allocations and 34% less memory usage.
    • Modern API: Uses netip.Addr instead of net.IP for improved performance.
    • Network Information: All result structs now include Network and IPAddress fields.
    • Data Validation: A new HasData() method is available on result structs to check if data was found.
    • Structured Names: Replaced map[string]string with a typed Names struct for better performance.
  2. Optimize performance for GeoIP2 lookups

    main

    Database Reuse

    Always reuse database instances across requests. Do not open and close the database for every lookup, as this is expensive.

    // Good: Create once, use many times
    db, err := geoip2.Open("GeoIP2-City.mmdb")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()
    
    // Use db for multiple lookups...

    Concurrent Usage

    The Reader is safe for concurrent use by multiple goroutines.

    Memory Usage

    For applications needing only specific fields, consider using the lower-level maxminddb library with custom result structs to reduce memory allocation.

    JSON Serialization

    All result structs include JSON tags and support marshaling to JSON using the standard encoding/json package.

    // JSON Serialization example
    record, err := db.City(ip)
    if err != nil {
        log.Fatal(err)
    }
    
    jsonData, err := json.Marshal(record)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(jsonData))
  3. Handle errors and missing data in lookups

    main

    When performing lookups, follow these best practices:

    1. Check for errors: All database methods return an error that must be handled.
    2. Check HasData(): Even if no error is returned, the IP might not exist in the database. Always call record.HasData() before accessing fields.
    3. Validate individual fields: Check if specific fields (like City.Names.English or Subdivisions) are empty or if arrays have elements before accessing them to avoid runtime issues.
    package main
    
    import (
    	"fmt"
    	"log"
    	"net/netip"
    
    	"github.com/oschwald/geoip2-golang/v2"
    )
    
    func main() {
    	db, err := geoip2.Open("GeoIP2-City.mmdb")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer db.Close()
    
    	ip, err := netip.ParseAddr("10.0.0.1") // Private IP
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	record, err := db.City(ip)
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	// Always check if data was found
    	if !record.HasData() {
    		fmt.Println("No data found for this IP address")
    		return
    	}
    
    	// Check individual fields before using them
    	if record.City.Names.English != "" {
    		fmt.Printf("City: %v\n", record.City.Names.English)
    	} else {
    		fmt.Println("City name not available")
    	}
    
    	// Check array bounds for subdivisions
    	if len(record.Subdivisions) > 0 {
    		fmt.Printf("Subdivision: %v\n", record.Subdivisions[0].Names.English)
    	} else {
    		fmt.Println("No subdivision data available")
    	}
    
    	fmt.Printf("Country: %v\n", record.Country.Names.English)
    }
  4. Migrate from v1 to v2

    main

    When upgrading to v2, several breaking changes must be addressed:

    • Import Path: Update your imports from github.com/oschwald/geoip2-golang to github.com/oschwald/geoip2-golang/v2.
    • IP Type: Switch from using net.IP to netip.Addr (from the net/netip package).
    • Field Names: The field IsoCode has been renamed to ISOCode.
    • Names Access: Access names via struct fields (e.g., .English) instead of map access (e.g., ["en"]).
    • Data Validation: Use the HasData() method on the record to verify if data was actually found for the IP address.
    // v1
    import "github.com/oschwald/geoip2-golang"
    
    ip := net.ParseIP("81.2.69.142")
    record, err := db.City(ip)
    cityName := record.City.Names["en"]
    
    // v2
    import "github.com/oschwald/geoip2-golang/v2"
    
    ip, err := netip.ParseAddr("81.2.69.142")
    if err != nil {
        // handle error
    }
    record, err := db.City(ip)
    if !record.HasData() {
        // handle no data found
    }
    cityName := record.City.Names.English
  5. How the Reader and database types work together

    main

    The Reader acts as a wrapper around a maxminddb.Reader. When you open a database, the library automatically detects its type (e.g., isCity, isASN, isISP).

    This detection determines which lookup methods are valid. For example, some databases are multi-purpose: a GeoIP2-Enterprise database supports Enterprise, City, and Country lookups, while a GeoLite2-ASN database only supports ASN and ISP lookups. Using the wrong method for the loaded database type will trigger an InvalidMethodError rather than a silent failure or a decoding error.

  6. Troubleshoot common GeoIP2 issues

    main

    If you encounter issues while using the library, check the following:

    • Database not found: Verify that the path to your .mmdb file is correct and that the application has permission to read it.
    • No data returned: If a lookup returns no results, check if record.HasData() returns false. This typically happens if the IP is not in the database or is a private/reserved IP address.
    • Performance issues: Do not open the database file for every single lookup. Instead, open the database once and reuse the same database instance for all lookups.
  7. Use the Domain database for domain lookup

    main

    The Domain database provides the second-level domain associated with an IP address. Use db.Domain(ip) to retrieve this data.

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/netip"
    
    	"github.com/oschwald/geoip2-golang/v2"
    )
    
    func main() {
    	db, err := geoip2.Open("GeoIP2-Domain.mmdb")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer db.Close()
    
    	ip, err := netip.ParseAddr("1.2.0.0")
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	record, err := db.Domain(ip)
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	if !record.HasData() {
    		fmt.Println("No data found for this IP")
    		return
    	}
    
    	fmt.Printf("Domain: %v\n", record.Domain)
    	fmt.Printf("Network: %v\n", record.Network)
    	fmt.Printf("IP Address: %v\n", record.IPAddress)
    }
  8. Use the Connection Type database

    main

    The Connection Type database identifies the connection type of an IP address. Use db.ConnectionType(ip) to retrieve this data.

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/netip"
    
    	"github.com/oschwald/geoip2-golang/v2"
    )
    
    func main() {
    	db, err := geoip2.Open("GeoIP2-Connection-Type.mmdb")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer db.Close()
    
    	ip, err := netip.ParseAddr("1.0.128.0")
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	record, err := db.ConnectionType(ip)
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	if !record.HasData() {
    		fmt.Println("No data found for this IP")
    		return
    	}
    
    	fmt.Printf("Connection Type: %v\n", record.ConnectionType)
    	fmt.Printf("Network: %v\n", record.Network)
    	fmt.Printf("IP Address: %v\n", record.IPAddress)
    }
  9. Use the Country database for country-level data

    main

    The Country database provides country, continent, and EU membership information. Use db.Country(ip) to retrieve this data.

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/netip"
    
    	"github.com/oschwald/geoip2-golang/v2"
    )
    
    func main() {
    	db, err := geoip2.Open("GeoIP2-Country.mmdb")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer db.Close()
    
    	ip, err := netip.ParseAddr("81.2.69.142")
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	record, err := db.Country(ip)
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	if !record.HasData() {
    		fmt.Println("No data found for this IP")
    		return
    	}
    
    	fmt.Printf("Country: %v (%v)\n", record.Country.Names.English, record.Country.ISOCode)
    	fmt.Printf("Continent: %v (%v)\n", record.Continent.Names.English, record.Continent.Code)
    	fmt.Printf("Is in EU: %v\n", record.Country.IsInEuropeanUnion)
    	fmt.Printf("Network: %v\n", record.Traits.Network)
    	fmt.Printf("IP Address: %v\n", record.Traits.IPAddress)
    
    	if record.RegisteredCountry.Names.English != "" {
    		fmt.Printf("Registered Country: %v (%v)\n",
    			record.RegisteredCountry.Names.English, record.RegisteredCountry.ISOCode)
    	}
    }
  10. Use the Anonymous IP database to identify proxies and VPNs

    main

    The Anonymous IP database identifies various types of anonymous and proxy networks, including VPNs, hosting providers, public/residential proxies, and Tor exit nodes. Use db.AnonymousIP(ip) to retrieve this data.

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/netip"
    
    	"github.com/oschwald/geoip2-golang/v2"
    )
    
    func main() {
    	db, err := geoip2.Open("GeoIP2-Anonymous-IP.mmdb")
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer db.Close()
    
    	ip, err := netip.ParseAddr("81.2.69.142")
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	record, err := db.AnonymousIP(ip)
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	if !record.HasData() {
    		fmt.Println("No data found for this IP")
    		return
    	}
    
    	fmt.Printf("Is Anonymous: %v\n", record.IsAnonymous)
    	fmt.Printf("Is Anonymous VPN: %v\n", record.IsAnonymousVPN)
    	fmt.Printf("Is Hosting Provider: %v\n", record.IsHostingProvider)
    	fmt.Printf("Is Public Proxy: %v\n", record.IsPublicProxy)
    	fmt.Printf("Is Residential Proxy: %v\n", record.IsResidentialProxy)
    	fmt.Printf("Is Tor Exit Node: %v\n", record.IsTorExitNode)
    	fmt.Printf("Network: %v\n", record.Network)
    	fmt.Printf("IP Address: %v\n", record.IPAddress)
    }