maxminddb

package module
v2.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: ISC Imports: 16 Imported by: 40

README

MaxMind DB Reader for Go

Go Reference

This is a Go reader for the MaxMind DB format. Although this can be used to read GeoLite2 and GeoIP2 databases, geoip2 provides a higher-level API for doing so.

This is not an official MaxMind API.

Installation

go get github.com/oschwald/maxminddb-golang/v2

Version 2 Features

Version 2 includes significant improvements:

  • Modern API: Uses netip.Addr instead of net.IP for better performance
  • Custom Unmarshaling: Implement CursorUnmarshaler for reflection-free custom decoding
  • Network Iteration: Iterate over all networks in a database with Networks() and NetworksWithin()
  • Enhanced Performance: Optimized data structures and decoding paths
  • Better Error Handling: More detailed error types and improved debugging
  • Integrity Checks: Validate databases with Reader.Verify() and access metadata helpers such as Metadata.BuildTime()

See MIGRATION.md for guidance on updating existing v1 code.

Quick Start

package main

import (
	"fmt"
	"log"
	"net/netip"

	"github.com/oschwald/maxminddb-golang/v2"
)

func main() {
	db, err := maxminddb.Open("GeoLite2-City.mmdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	ip, err := netip.ParseAddr("81.2.69.142")
	if err != nil {
		log.Fatal(err)
	}

	var record struct {
		Country struct {
			ISOCode string            `maxminddb:"iso_code"`
			Names   map[string]string `maxminddb:"names"`
		} `maxminddb:"country"`
		City struct {
			Names map[string]string `maxminddb:"names"`
		} `maxminddb:"city"`
	}

	err = db.Lookup(ip).Decode(&record)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Country: %s (%s)\n", record.Country.Names["en"], record.Country.ISOCode)
	fmt.Printf("City: %s\n", record.City.Names["en"])
}

Usage Patterns

Basic Lookup
db, err := maxminddb.Open("GeoLite2-City.mmdb")
if err != nil {
	log.Fatal(err)
}
defer db.Close()

var record any
ip := netip.MustParseAddr("1.2.3.4")
err = db.Lookup(ip).Decode(&record)
Untrusted Database Files

Call Reader.Verify once immediately after opening an untrusted database and before performing lookups or decoding records:

if err := db.Verify(); err != nil {
	log.Fatal(err)
}

Verification applies to the database contents at the time of the call. Keep those contents immutable for the Reader's lifetime: do not modify a slice passed to OpenBytes or rewrite or truncate a memory-mapped file in place. Open and verify a new Reader when publishing an updated database.

Reflection decoding limits each operation to 32,768 declared container child slots; map keys and values each consume one slot. Maps and slices reserve their children before allocation or traversal. Materialized string and byte payloads also have an operation-wide bound: every delivered payload byte draws from a shared 2 MiB allowance. Materialized map keys and keys inspected by DecodePath draw their full size from the same payload allowance.

Decoding into any activates these limits even when the root is a scalar. A standalone scalar decoded into a directly typed destination or a named empty-interface type remains unbudgeted because it cannot amplify. A non-empty DecodePath shares one set of limits between path navigation and the selected value. Skipping an unknown field still charges any inline containers, but does not follow pointer targets or charge payload that is not materialized. Custom unmarshalers and low-level cursor traversal control and must bound their own work; Reader.Verify validates the complete data section and the original metadata graph, including unknown metadata fields, before those APIs are used with untrusted input.

Custom Struct Decoding
type City struct {
	Country struct {
		ISOCode string `maxminddb:"iso_code"`
		Names   struct {
			English string `maxminddb:"en"`
			German  string `maxminddb:"de"`
		} `maxminddb:"names"`
	} `maxminddb:"country"`
	Subdivisions []struct {
		ISOCode string `maxminddb:"iso_code"`
	} `maxminddb:"subdivisions,maxsize:32"`
}

var city City
err = db.Lookup(ip).Decode(&city)

The maxsize:N tag option rejects a matching MMDB map or array with more than N entries, or a matching string or byte value with more than N bytes. An MMDB array decoded into []byte is covered as well. The check happens before the matching field is allocated or mutated and is supported by both reflection decoding and maxminddb-gen. Tag options use the encoding/json/v2 comma and colon grammar, for example maxminddb:"subdivisions,maxsize:32". Because a comma delimits options, quote a literal field name containing a comma with the same grammar, for example maxminddb:"'city,name'". For a supported custom field type, maxsize checks every size-bearing MMDB kind (map, array, string, and bytes) before invoking the unmarshaler because the encodings accepted by a callback cannot be inferred from its Go type.

High-Performance Custom Unmarshaling

For application-owned structs, maxminddb-gen can generate an UnmarshalMaxMindDBCursor method that avoids reflection. The generator is versioned with this module and remains optional; types with neither generated nor handwritten custom unmarshaling methods continue to use reflection.

Add the tool to the consuming module's go.mod and add a generation directive in the package that owns the target types:

tool github.com/oschwald/maxminddb-golang/v2/maxminddb-gen
//go:generate go tool maxminddb-gen $GOFILE

This discovers the exported structs declared in the directive's source file. For models.go, it writes models_maxminddb.go; recognized build suffixes and source build constraints are preserved. Constrained inputs must match the generation environment; multiple inputs share the intersection of their constraints. Use -output to override the default. Run go generate ./... and check the generated file into source control. See maxminddb-gen/README.md for supported types, diagnostics, and reproducible CI usage.

For new handwritten decoders, implement mmdbdata.CursorUnmarshaler. Cursor reads return an opaque successor positioned after the decoded value, allowing nested custom decoding to continue without rescanning it.

The older UnmarshalMaxMindDB(*mmdbdata.Decoder) error callback is deprecated. It remains supported throughout v2 but is planned for removal in v3; see GitHub #224. When a type implements both callbacks, UnmarshalMaxMindDBCursor takes precedence.

Custom unmarshalers control their own traversal and allocation. If a database is not trusted, an implementation should use one aggregate per-record work budget across nested calls. The budget should cover recursion, collection entries, repeated pointer targets, and produced string or byte payloads; the reflection decoder's expansion guard is not applied inside custom callbacks.

type Label string

func (label *Label) UnmarshalMaxMindDBCursor(
	cursor mmdbdata.Cursor,
) (mmdbdata.Cursor, error) {
	value, next, err := cursor.ReadString()
	if err != nil {
		return mmdbdata.Cursor{}, mmdbdata.NormalizeUnmarshalError[Label](err)
	}
	*label = Label(value)
	return next, nil
}
Network Iteration
// Iterate over all networks in the database
for result := range db.Networks() {
	var record struct {
		Country struct {
			ISOCode string `maxminddb:"iso_code"`
		} `maxminddb:"country"`
	}
	err := result.Decode(&record)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s: %s\n", result.Prefix(), record.Country.ISOCode)
}

// Iterate over networks within a specific prefix
prefix := netip.MustParsePrefix("192.168.0.0/16")
for result := range db.NetworksWithin(prefix) {
	// Process networks within 192.168.0.0/16
}
Path-Based Decoding
var countryCode string
err = db.Lookup(ip).DecodePath(&countryCode, "country", "iso_code")

var cityName string
err = db.Lookup(ip).DecodePath(&cityName, "city", "names", "en")

Supported Database Types

This library supports all MaxMind DB (.mmdb) format databases, including:

MaxMind Official Databases:

  • GeoLite/GeoIP City: Comprehensive location data including city, country, subdivisions
  • GeoLite/GeoIP Country: Country-level geolocation data
  • GeoLite ASN: Autonomous System Number and organization data
  • GeoIP Anonymous IP: Anonymous network and proxy detection
  • GeoIP Enterprise: Enhanced City data with additional business fields
  • GeoIP ISP: Internet service provider information
  • GeoIP Domain: Second-level domain data
  • GeoIP Connection Type: Connection type identification

Third-Party Databases:

  • DB-IP databases: Compatible with DB-IP's .mmdb format databases
  • IPinfo databases: Works with IPinfo's MaxMind DB format files
  • Custom databases: Any database following the MaxMind DB file format specification

The library is format-agnostic and will work with any valid .mmdb file regardless of the data provider.

Performance Tips

  1. Reuse Reader instances: Lookups, decoding, and iteration are safe to run concurrently. Close invalidates outstanding results, Reader-backed cursors, and their derived traversal handles; it must not run concurrently with their use and should run only after readers are done.
  2. Use specific structs: Only decode the fields you need rather than using any
  3. Generate a decoder: For high-throughput applications, use maxminddb-gen, or implement CursorUnmarshaler for custom decoding
  4. Consider caching: Use Result.Offset() as a cache key for database records

Standalone mmdbdata.NewDecoder calls do not cache strings by default. For a decoder that repeatedly reads the same strings, pass mmdbdata.WithStringCache(). The decoder and its cursors share the cache. Use decoder.CursorAt(offset) to read different records from the same buffer without constructing a new decoder or changing its position. Independent cursors can be read concurrently. Keep the input buffer unchanged while a cached decoder or any of its cursors are in use. Separate NewDecoder calls do not share caches.

On 64-bit systems, the table allocates 72 KiB when the first string of 2 through 100 bytes is read, plus memory for retained strings. Reader-supplied decoders and cursors use the Reader's caching policy.

Getting Database Files

Free GeoLite2 Databases

Download from MaxMind's GeoLite page.

Documentation

Requirements

  • Go 1.26 or later
  • MaxMind DB file in .mmdb format

Contributing

Contributions welcome! Please fork the repository and open a pull request with your changes.

Cache Benchmarks

The real-City benchmarks use GeoLite2-City.mmdb in the current directory or the path in MAXMIND_REAL_CITY_DB. They need a full City database and skip when the default file is absent. An invalid explicit path fails the benchmark.

Measure lookup time and allocations without instrumentation:

MAXMIND_REAL_CITY_DB=/path/to/GeoIP2-City.mmdb go test -run '^$' -bench '^BenchmarkRealCityCache$' -benchmem

Measure cache hit rates separately:

MAXMIND_REAL_CITY_DB=/path/to/GeoIP2-City.mmdb go test -tags cachemetrics -run '^$' -bench '^BenchmarkRealCityCacheHitRate$'

The cachemetrics build adds shared counters and suppresses benchmark timing. Use its hit-rate results separately from uninstrumented timings. BenchmarkStringCacheHotHome exercises the home-slot fast path; BenchmarkStringCacheHotDisplaced exercises bucket scanning. Neither microbenchmark represents a full database lookup.

License

This is free software, licensed under the ISC License.

Documentation

Overview

Package maxminddb provides a reader for the MaxMind DB file format.

This package provides an API for reading MaxMind GeoIP2 and GeoLite2 databases in the MaxMind DB file format (.mmdb files). The API is designed to be simple to use while providing high performance for IP geolocation lookups and related data.

Basic Usage

The most common use case is looking up geolocation data for an IP address:

db, err := maxminddb.Open("GeoLite2-City.mmdb")
if err != nil {
	log.Fatal(err)
}
defer db.Close()

ip, err := netip.ParseAddr("81.2.69.142")
if err != nil {
	log.Fatal(err)
}

var record struct {
	Country struct {
		ISOCode string `maxminddb:"iso_code"`
		Names   map[string]string `maxminddb:"names"`
	} `maxminddb:"country"`
	City struct {
		Names map[string]string `maxminddb:"names"`
	} `maxminddb:"city"`
}

err = db.Lookup(ip).Decode(&record)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("Country: %s\n", record.Country.Names["en"])
fmt.Printf("City: %s\n", record.City.Names["en"])

Database Types

This library supports all MaxMind database types:

  • GeoLite2/GeoIP2 City: Comprehensive location data including city, country, subdivisions
  • GeoLite2/GeoIP2 Country: Country-level geolocation data
  • GeoLite2 ASN: Autonomous System Number and organization data
  • GeoIP2 Anonymous IP: Anonymous network and proxy detection
  • GeoIP2 Enterprise: Enhanced City data with additional business fields
  • GeoIP2 ISP: Internet service provider information
  • GeoIP2 Domain: Second-level domain data
  • GeoIP2 Connection Type: Connection type identification

Performance

For maximum performance in high-throughput applications, consider:

  1. Using custom struct types that only include the fields you need
  2. Generating decoders with maxminddb-gen or implementing CursorUnmarshaler
  3. Reusing the Reader instance across multiple goroutines (it's thread-safe)

Custom Unmarshaling

For new custom decoding logic, implement mmdbdata.CursorUnmarshaler. Types implementing this interface automatically use custom decoding logic when decoded by Reader:

type Label string

func (label *Label) UnmarshalMaxMindDBCursor(
	cursor mmdbdata.Cursor,
) (mmdbdata.Cursor, error) {
	value, next, err := cursor.ReadString()
	if err != nil {
		return mmdbdata.Cursor{}, mmdbdata.NormalizeUnmarshalError[Label](err)
	}
	*label = Label(value)
	return next, nil
}

The older mmdbdata.Unmarshaler interface remains supported throughout v2 but is deprecated and planned for removal in v3. When a type implements both interfaces, mmdbdata.CursorUnmarshaler takes precedence.

Network Iteration

You can iterate over all networks in a database:

for result := range db.Networks() {
	var record struct {
		Country struct {
			ISOCode string `maxminddb:"iso_code"`
		} `maxminddb:"country"`
	}
	err := result.Decode(&record)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s: %s\n", result.Prefix(), record.Country.ISOCode)
}

Database Files

MaxMind provides both free (GeoLite2) and commercial (GeoIP2) databases:

Thread Safety

Reader lookup, decode, and iteration methods are safe to call concurrently. Close must not be called concurrently with other Reader or Result methods, or with use of Reader-backed cursors or cursor-derived traversal handles.

Example (CursorUnmarshaler)

This example demonstrates how to use CursorUnmarshaler for custom decoding. Types implementing CursorUnmarshaler automatically use custom decoding logic instead of reflection, similar to how json.Unmarshaler works with encoding/json.

package main

import (
	"fmt"
	"log"
	"net/netip"

	"github.com/oschwald/maxminddb-golang/v2"
	"github.com/oschwald/maxminddb-golang/v2/mmdbdata"
)

// CustomCity represents a simplified city record with custom unmarshaling.
// This demonstrates the CursorUnmarshaler interface for custom decoding.
type CustomCity struct {
	Names     map[string]string
	GeoNameID uint
}

// UnmarshalMaxMindDBCursor implements the mmdbdata.CursorUnmarshaler interface.
// This provides custom decoding logic, similar to how json.Unmarshaler works
// with encoding/json, allowing fine-grained control over data processing.
func (c *CustomCity) UnmarshalMaxMindDBCursor(
	cursor mmdbdata.Cursor,
) (mmdbdata.Cursor, error) {
	entries, err := cursor.Map()
	if err != nil {
		return mmdbdata.Cursor{}, mmdbdata.NormalizeUnmarshalError[CustomCity](err)
	}
	var next mmdbdata.Cursor
	for {
		key, valueCursor, ok := entries.Next(next)
		if !ok {
			break
		}

		switch string(key) {
		case "city":
			next, err = c.unmarshalCity(valueCursor)
		default:
			next, err = valueCursor.Skip()
		}
		if err != nil {
			return mmdbdata.Cursor{}, err
		}
	}
	return entries.End()
}

func (c *CustomCity) unmarshalCity(cursor mmdbdata.Cursor) (mmdbdata.Cursor, error) {
	entries, err := cursor.Map()
	if err != nil {
		return mmdbdata.Cursor{}, mmdbdata.NormalizeUnmarshalError[CustomCity](err)
	}
	var next mmdbdata.Cursor
	for {
		key, valueCursor, ok := entries.Next(next)
		if !ok {
			break
		}

		switch string(key) {
		case "names":
			next, err = c.unmarshalNames(valueCursor)
		case "geoname_id":
			var geoID uint64
			geoID, next, err = valueCursor.ReadUint()
			if err != nil {
				err = mmdbdata.NormalizeUnmarshalError[uint](err)
			} else {
				converted := uint(geoID)
				if uint64(converted) != geoID {
					err = mmdbdata.NewUnmarshalTypeError[uint](geoID)
				} else {
					c.GeoNameID = converted
				}
			}
		default:
			next, err = valueCursor.Skip()
		}
		if err != nil {
			return mmdbdata.Cursor{}, err
		}
	}
	return entries.End()
}

func (c *CustomCity) unmarshalNames(cursor mmdbdata.Cursor) (mmdbdata.Cursor, error) {
	entries, err := cursor.Map()
	if err != nil {
		return mmdbdata.Cursor{}, mmdbdata.NormalizeUnmarshalError[map[string]string](err)
	}
	names := make(map[string]string, entries.Size())
	var next mmdbdata.Cursor
	for {
		key, valueCursor, ok := entries.Next(next)
		if !ok {
			break
		}
		var value string
		value, next, err = valueCursor.ReadString()
		if err != nil {
			return mmdbdata.Cursor{}, mmdbdata.NormalizeUnmarshalError[string](err)
		}
		names[string(key)] = value
	}
	next, err = entries.End()
	if err == nil {
		c.Names = names
	}
	return next, err
}

func main() {
	db, err := maxminddb.Open("testdata/test-data/GeoIP2-City-Test.mmdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	addr := netip.MustParseAddr("81.2.69.142")

	// CustomCity implements CursorUnmarshaler, so it will automatically use
	// the custom UnmarshalMaxMindDBCursor method instead of reflection.
	var city CustomCity
	err = db.Lookup(addr).Decode(&city)
	if err != nil {
		log.Panic(err)
	}

	fmt.Printf("City ID: %d\n", city.GeoNameID)
	fmt.Printf("English name: %s\n", city.Names["en"])
	fmt.Printf("German name: %s\n", city.Names["de"])

}
Output:
City ID: 2643743
English name: London
German name: London

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type InvalidDatabaseError

type InvalidDatabaseError = mmdberrors.InvalidDatabaseError

InvalidDatabaseError is returned when database data is malformed or is rejected by decoder structural, resource, or schema validation.

type Metadata

type Metadata struct {
	// Description contains localized database descriptions.
	// Keys are language codes (e.g., "en", "zh-CN"), values are UTF-8 descriptions.
	Description map[string]string `maxminddb:"description"`

	// DatabaseType indicates the structure of data records associated with IP addresses.
	// Names starting with "GeoIP" are reserved for MaxMind databases.
	DatabaseType string `maxminddb:"database_type"`

	// Languages lists locale codes for which this database may contain localized data.
	// Records should not contain localized data for locales not in this array.
	Languages []string `maxminddb:"languages"`

	// BinaryFormatMajorVersion is the major version of the MaxMind DB binary format.
	// Current supported version is 2.
	BinaryFormatMajorVersion uint `maxminddb:"binary_format_major_version"`

	// BinaryFormatMinorVersion is the minor version of the MaxMind DB binary format.
	// Current supported version is 0.
	BinaryFormatMinorVersion uint `maxminddb:"binary_format_minor_version"`

	// BuildEpoch contains the database build timestamp as Unix epoch seconds.
	// Use BuildTime() method for a time.Time representation.
	BuildEpoch uint `maxminddb:"build_epoch"`

	// IPVersion indicates the IP version support:
	//   4: IPv4 addresses only
	//   6: Both IPv4 and IPv6 addresses
	IPVersion uint `maxminddb:"ip_version"`

	// NodeCount is the number of nodes in the search tree.
	NodeCount uint `maxminddb:"node_count"`

	// RecordSize is the size in bits of each record in the search tree.
	// Valid values are 24, 28, or 32.
	RecordSize uint `maxminddb:"record_size"`
}

Metadata holds the metadata decoded from the MaxMind DB file.

Key fields include:

  • DatabaseType: indicates the structure of data records (e.g., "GeoIP2-City")
  • Description: localized descriptions in various languages
  • Languages: locale codes for which the database may contain localized data
  • BuildEpoch: database build timestamp as Unix epoch seconds
  • IPVersion: supported IP version (4 for IPv4-only, 6 for IPv4/IPv6)
  • NodeCount: number of nodes in the search tree
  • RecordSize: size in bits of each record in the search tree (24, 28, or 32)

For detailed field descriptions, see the MaxMind DB specification: https://maxmind.github.io/MaxMind-DB/

func (Metadata) BuildTime

func (m Metadata) BuildTime() time.Time

BuildTime returns the database build time as a time.Time. This is a convenience method that converts the BuildEpoch field from Unix epoch seconds to a time.Time value.

type NetworksOption

type NetworksOption func(*networkOptions)

NetworksOption are options for Networks and NetworksWithin.

func IncludeAliasedNetworks

func IncludeAliasedNetworks() NetworksOption

IncludeAliasedNetworks is an option for Networks and NetworksWithin that makes them iterate over aliases of the IPv4 subtree in an IPv6 database, e.g., ::ffff:0:0/96, 2001::/32, and 2002::/16.

func IncludeNetworksWithoutData

func IncludeNetworksWithoutData() NetworksOption

IncludeNetworksWithoutData is an option for Networks and NetworksWithin that makes them include networks without any data in the iteration.

func SkipEmptyValues

func SkipEmptyValues() NetworksOption

SkipEmptyValues is an option for Networks and NetworksWithin that makes them skip networks whose data is an empty map or empty array. This is useful for databases that store empty maps or arrays for records without meaningful data, allowing iteration over only records with actual content.

Example

This example demonstrates how to use SkipEmptyValues to iterate only over networks that have actual data, skipping those with empty maps or arrays.

package main

import (
	"fmt"
	"log"

	"github.com/oschwald/maxminddb-golang/v2"
)

func main() {
	db, err := maxminddb.Open("testdata/test-data/GeoIP2-Anonymous-IP-Test.mmdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Without SkipEmptyValues, you get all networks including empty ones
	fmt.Println("All networks:")
	count := 0
	for result := range db.Networks() {
		if result.Err() != nil {
			log.Panic(result.Err())
		}
		count++
		if count > 10 {
			fmt.Printf("... (%d more networks, many with empty data)\n", 529-count)
			break
		}

		var record map[string]any
		err := result.Decode(&record)
		if err != nil {
			log.Panic(err)
		}

		if len(record) == 0 {
			fmt.Printf("%s: (empty)\n", result.Prefix())
		} else {
			fmt.Printf("%s: %v\n", result.Prefix(), record)
		}
	}

	fmt.Println("\nOnly networks with data:")
	// With SkipEmptyValues, you only get networks with actual data
	for result := range db.Networks(maxminddb.SkipEmptyValues()) {
		if result.Err() != nil {
			log.Panic(result.Err())
		}

		var record map[string]any
		err := result.Decode(&record)
		if err != nil {
			log.Panic(result.Err())
		}
		fmt.Printf("%s: %v\n", result.Prefix(), record)
	}

}
Output:
All networks:
1.0.0.0/15: (empty)
1.2.0.0/16: map[is_anonymous:true is_anonymous_vpn:true]
1.3.0.0/16: (empty)
1.4.0.0/14: (empty)
1.8.0.0/13: (empty)
1.16.0.0/12: (empty)
1.32.0.0/11: (empty)
1.64.0.0/11: (empty)
1.96.0.0/12: (empty)
1.112.0.0/13: (empty)
... (518 more networks, many with empty data)

Only networks with data:
1.2.0.0/16: map[is_anonymous:true is_anonymous_vpn:true]
1.124.213.1/32: map[is_anonymous:true is_anonymous_vpn:true is_tor_exit_node:true]
6.1.0.0/31: map[is_anonymous:true is_anonymous_vpn:true]
6.1.0.2/32: map[is_anonymous:true is_hosting_provider:true]
6.1.0.3/32: map[is_anonymous:true is_public_proxy:true]
6.1.0.4/32: map[is_anonymous:true is_residential_proxy:true]
65.0.0.0/13: map[is_anonymous:true is_tor_exit_node:true]
71.160.223.0/24: map[is_anonymous:true is_hosting_provider:true]
81.2.69.0/24: map[is_anonymous:true is_anonymous_vpn:true is_hosting_provider:true is_public_proxy:true is_residential_proxy:true is_tor_exit_node:true]
186.30.236.0/24: map[is_anonymous:true is_public_proxy:true]
2001:480:3a::/64: map[is_anonymous:true is_public_proxy:true]
abcd:1000::/112: map[is_anonymous:true is_public_proxy:true]

type Reader

type Reader struct {
	Metadata Metadata
	// contains filtered or unexported fields
}

Reader holds the data corresponding to the MaxMind DB file. Its only public field is Metadata, which contains the metadata from the MaxMind DB file.

Reader lookup, decode, and iteration methods are safe to call concurrently. Close must not be called concurrently with other Reader or Result methods, or with use of Reader-backed cursors or cursor-derived traversal handles.

func Open

func Open(file string, options ...ReaderOption) (*Reader, error)

Open takes a string path to a MaxMind DB file and any options. It returns a Reader structure or an error. The database file is opened using a memory map on supported platforms. On platforms without memory map support, such as WebAssembly or Google App Engine, or if the memory map attempt fails due to lack of support from the filesystem, the database is loaded into memory. Do not rewrite or truncate the opened file in place while the Reader is in use; memory-mapped changes may become visible to it. Open a new Reader for an updated database. Use the Close method on the Reader object to return the resources to the system.

func OpenBytes

func OpenBytes(buffer []byte, options ...ReaderOption) (*Reader, error)

OpenBytes takes a byte slice corresponding to a MaxMind DB file and any options. It returns a Reader structure or an error. The Reader retains the provided slice; callers must not modify it while the Reader is in use. Copy the slice before calling OpenBytes if another component may modify it.

func (*Reader) Close

func (r *Reader) Close() error

Close returns the resources used by the database to the system.

func (*Reader) Lookup

func (r *Reader) Lookup(ip netip.Addr) Result

Lookup retrieves the database record for ip and returns a Result, which can be used to decode the data.

Any zone identifier in ip is ignored and has no effect on the lookup result.

Example (Interface)

This example demonstrates how to decode to an any.

package main

import (
	"fmt"
	"log"
	"net/netip"

	"github.com/oschwald/maxminddb-golang/v2"
)

func main() {
	db, err := maxminddb.Open("testdata/test-data/GeoIP2-City-Test.mmdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	addr := netip.MustParseAddr("81.2.69.142")

	var record any
	err = db.Lookup(addr).Decode(&record)
	if err != nil {
		log.Panic(err)
	}
	fmt.Printf("%v", record)
}
Output:
map[city:map[geoname_id:2643743 names:map[de:London en:London es:Londres fr:Londres ja:ロンドン pt-BR:Londres ru:Лондон]] continent:map[code:EU geoname_id:6255148 names:map[de:Europa en:Europe es:Europa fr:Europe ja:ヨーロッパ pt-BR:Europa ru:Европа zh-CN:欧洲]] country:map[geoname_id:2635167 iso_code:GB names:map[de:Vereinigtes Königreich en:United Kingdom es:Reino Unido fr:Royaume-Uni ja:イギリス pt-BR:Reino Unido ru:Великобритания zh-CN:英国]] location:map[accuracy_radius:10 latitude:51.5142 longitude:-0.0931 time_zone:Europe/London] registered_country:map[geoname_id:6252001 iso_code:US names:map[de:USA en:United States es:Estados Unidos fr:États-Unis ja:アメリカ合衆国 pt-BR:Estados Unidos ru:США zh-CN:美国]] subdivisions:[map[geoname_id:6269131 iso_code:ENG names:map[en:England es:Inglaterra fr:Angleterre pt-BR:Inglaterra]]]]
Example (Struct)

This example shows how to decode to a struct.

package main

import (
	"fmt"
	"log"
	"net/netip"

	"github.com/oschwald/maxminddb-golang/v2"
)

func main() {
	db, err := maxminddb.Open("testdata/test-data/GeoIP2-City-Test.mmdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	addr := netip.MustParseAddr("81.2.69.142")

	var record struct {
		Country struct {
			ISOCode string `maxminddb:"iso_code"`
		} `maxminddb:"country"`
	} // Or any appropriate struct

	err = db.Lookup(addr).Decode(&record)
	if err != nil {
		log.Panic(err)
	}
	fmt.Print(record.Country.ISOCode)
}
Output:
GB

func (*Reader) LookupOffset

func (r *Reader) LookupOffset(offset uintptr) Result

LookupOffset returns the Result for the specified offset. Note that netip.Prefix returned by Networks will be invalid when using LookupOffset.

func (*Reader) Networks

func (r *Reader) Networks(options ...NetworksOption) iter.Seq[Result]

Networks returns an iterator that can be used to traverse the networks in the database.

Please note that a MaxMind DB may map IPv4 networks into several locations in an IPv6 database. This iterator will only iterate over these once by default. To iterate over all the IPv4 network locations, use the IncludeAliasedNetworks option.

Networks without data are excluded by default. To include them, use IncludeNetworksWithoutData.

Example

This example demonstrates how to iterate over all networks in the database.

package main

import (
	"fmt"
	"log"

	"github.com/oschwald/maxminddb-golang/v2"
)

func main() {
	db, err := maxminddb.Open("testdata/test-data/GeoIP2-Connection-Type-Test.mmdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	for result := range db.Networks() {
		record := struct {
			ConnectionType string `maxminddb:"connection_type"`
		}{}

		err := result.Decode(&record)
		if err != nil {
			log.Panic(err)
		}
		fmt.Printf("%s: %s\n", result.Prefix(), record.ConnectionType)
	}
}
Output:
1.0.0.0/24: Cable/DSL
1.0.1.0/24: Cellular
1.0.2.0/23: Cable/DSL
1.0.4.0/22: Cable/DSL
1.0.8.0/21: Cable/DSL
1.0.16.0/20: Cable/DSL
1.0.32.0/19: Cable/DSL
1.0.64.0/18: Cable/DSL
1.0.128.0/17: Cable/DSL
2.125.160.216/29: Cable/DSL
67.43.156.0/24: Cellular
80.214.0.0/20: Cellular
96.1.0.0/16: Cable/DSL
96.10.0.0/15: Cable/DSL
96.69.0.0/16: Cable/DSL
96.94.0.0/15: Cable/DSL
108.96.0.0/11: Cellular
149.101.100.0/28: Cellular
175.16.199.0/24: Cable/DSL
187.156.138.0/24: Cable/DSL
201.243.200.0/24: Corporate
207.179.48.0/20: Cellular
214.78.120.0/22: Satellite
216.160.83.56/29: Corporate
2003::/24: Cable/DSL

func (*Reader) NetworksWithin

func (r *Reader) NetworksWithin(prefix netip.Prefix, options ...NetworksOption) iter.Seq[Result]

NetworksWithin returns an iterator that can be used to traverse the networks in the database which are contained in a given prefix.

Please note that a MaxMind DB may map IPv4 networks into several locations in an IPv6 database. This iterator will only iterate over these once by default. To iterate over all the IPv4 network locations, use the IncludeAliasedNetworks option.

If the provided prefix is contained within a network in the database, the iterator will iterate over exactly one network, the containing network.

Networks without data are excluded by default. To include them, use IncludeNetworksWithoutData.

Example

This example demonstrates how to iterate over all networks in the database which are contained within an arbitrary network.

package main

import (
	"fmt"
	"log"
	"net/netip"

	"github.com/oschwald/maxminddb-golang/v2"
)

func main() {
	db, err := maxminddb.Open("testdata/test-data/GeoIP2-Connection-Type-Test.mmdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	prefix, err := netip.ParsePrefix("1.0.0.0/8")
	if err != nil {
		log.Panic(err)
	}

	for result := range db.NetworksWithin(prefix) {
		record := struct {
			ConnectionType string `maxminddb:"connection_type"`
		}{}
		err := result.Decode(&record)
		if err != nil {
			log.Panic(err)
		}
		fmt.Printf("%s: %s\n", result.Prefix(), record.ConnectionType)
	}

}
Output:
1.0.0.0/24: Cable/DSL
1.0.1.0/24: Cellular
1.0.2.0/23: Cable/DSL
1.0.4.0/22: Cable/DSL
1.0.8.0/21: Cable/DSL
1.0.16.0/20: Cable/DSL
1.0.32.0/19: Cable/DSL
1.0.64.0/18: Cable/DSL
1.0.128.0/17: Cable/DSL

func (*Reader) Verify

func (r *Reader) Verify() error

Verify performs comprehensive validation of the MaxMind DB file.

This method validates:

  • Metadata section: format versions, required fields, and value constraints. Values that the metadata map points to can follow the map, so the rest of the section must be a sequence of valid values. Each metadata pointer must point to the start of a field.
  • Search tree: traverses all networks to verify tree structure integrity
  • Data section separator: validates the 16-byte separator between tree and data
  • Data section: verifies all data records referenced by the search tree. A search-tree record can point to a top-level value or to a field nested in one. Each data pointer must point to the start of a field.

The verifier is stricter than the MaxMind DB specification and may return errors on some databases that are still readable by normal operations. This method is useful for:

  • Validating database files after download or generation
  • Debugging database corruption issues
  • Ensuring database integrity in critical applications

Note: Verification traverses the entire database and may be slow on large files. Each data record and the metadata section, including unknown metadata fields and values after the metadata map, receives an independent set of decoder operation limits while it is materialized for verification. A successful result applies only while the Reader's backing file or byte slice remains unchanged. The method is thread-safe and can be called on an active Reader.

Example

This example demonstrates how to validate a MaxMind DB file and access metadata.

package main

import (
	"fmt"
	"log"

	"github.com/oschwald/maxminddb-golang/v2"
)

func main() {
	db, err := maxminddb.Open("testdata/test-data/GeoIP2-City-Test.mmdb")
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Verify database integrity
	if err := db.Verify(); err != nil {
		log.Printf("Database validation failed: %v", err)
		return
	}

	// Access metadata information
	metadata := db.Metadata
	fmt.Printf("Database type: %s\n", metadata.DatabaseType)
	fmt.Printf("Build time: %s\n", metadata.BuildTime().UTC().Format("2006-01-02 15:04:05"))
	fmt.Printf("IP version: IPv%d\n", metadata.IPVersion)
	fmt.Printf("Languages: %v\n", metadata.Languages)

	if desc, ok := metadata.Description["en"]; ok {
		fmt.Printf("Description: %s\n", desc)
	}

}
Output:
Database type: GeoIP2-City
Build time: 2026-02-04 22:49:29
IP version: IPv6
Languages: [en zh]
Description: GeoIP2 City Test Database (fake GeoIP2 data, for example purposes only)

type ReaderOption

type ReaderOption func(*readerOptions)

ReaderOption are options for Open and OpenBytes.

This was added to allow for future options, e.g., for caching, without causing a breaking API change.

func DisableStringCache added in v2.5.0

func DisableStringCache() ReaderOption

DisableStringCache disables caching of repeatedly decoded strings. This avoids the 72 KiB cache table on 64-bit systems and storage for cached strings. The table is otherwise allocated on the first cache-eligible string decode. Disabling caching may increase allocations when records are decoded repeatedly.

type Result

type Result struct {
	// contains filtered or unexported fields
}

Result holds the result of the database lookup.

func (Result) Decode

func (r Result) Decode(v any) error

Decode unmarshals the data from the data section into the value pointed to by v. If v is nil or not a pointer, an error is returned. If the data in the database record cannot be stored in v because of type differences, an UnmarshalTypeError is returned. An InvalidDatabaseError is returned when the data is malformed or rejected by structural, resource, or schema validation, including maxsize and generated duplicate-field checks.

An error will also be returned if there was an error during the Reader.Lookup call.

If the Reader.Lookup call did not find a value for the IP address, no error will be returned and v will be unchanged.

Reflection decoding limits each operation to 32,768 declared container child slots and a separate exact 2 MiB allowance for all materialized string, byte-slice, and dynamic map-key payload. Maps and slices reserve their children before allocation or traversal; repeated pointer targets share the same limits. Decoding into any activates the limits even for a root scalar. A standalone scalar decoded into a directly typed destination or a named empty-interface type is decoded without them because it cannot amplify. Custom unmarshalers control and must bound their own traversal and allocation. Call Reader.Verify before using an untrusted database with custom or low-level decoding, and keep the verified backing data unchanged for the Reader's lifetime.

func (Result) DecodePath

func (r Result) DecodePath(v any, path ...any) error

DecodePath unmarshals a value from data section into v, following the specified path.

The v parameter should be a pointer to the value where the decoded data will be stored. If v is nil or not a pointer, an error is returned. If the data in the database record cannot be stored in v because of type differences, an UnmarshalTypeError is returned.

The path is a variadic list of keys (strings) and/or indices (ints) that describe the nested structure to traverse in the data to reach the desired value.

For maps, string path elements are used as keys. For arrays, int path elements are used as indices. A negative offset will return values from the end of the array, e.g., -1 will return the last element.

If the path is empty, the entire data structure is decoded into v. A non-empty path shares the operation limits described by Result.Decode across path navigation and the selected value. Every inspected map key consumes its full size from the shared payload allowance, and skipped inline containers consume child slots without following pointer targets.

To check if a path exists (rather than relying on zero values), decode into a pointer and check if it remains nil:

var city *string
err := result.DecodePath(&city, "city", "names", "en")
if err != nil {
	// Handle error
}
if city == nil {
	// Path not found
} else {
	// Path exists, city contains the value
}

Returns an error if:

  • the path is invalid
  • the data cannot be decoded into the type of v
  • v is not a pointer or the database record cannot be stored in v due to type mismatch
  • the Result does not contain valid data

Example usage:

var city string
err := result.DecodePath(&city, "city", "names", "en")

var geonameID int
err := result.DecodePath(&geonameID, "subdivisions", 0, "geoname_id")

func (Result) Err

func (r Result) Err() error

Err provides a way to check whether there was an error during the lookup without calling Result.Decode. If there was an error, it will also be returned from Result.Decode.

func (Result) Found

func (r Result) Found() bool

Found will return true if the IP was found in the search tree. It will return false if the IP was not found or if there was an error.

func (Result) Offset

func (r Result) Offset() uintptr

Offset returns the offset of the record in the database. This can be passed to (*Reader).LookupOffset. It can also be used as a unique identifier for the data record in the particular database to cache the data record across lookups. Note that while the offset uniquely identifies the data record, other data in Result may differ between lookups. The offset is only valid for the current database version. If you update the database file, you must invalidate any cache associated with the previous version.

func (Result) Prefix

func (r Result) Prefix() netip.Prefix

Prefix returns the netip.Prefix representing the network associated with the data record in the database.

type UnmarshalTypeError

type UnmarshalTypeError = mmdberrors.UnmarshalTypeError

UnmarshalTypeError is returned when the value in the database cannot be assigned to the specified data type.

Directories

Path Synopsis
internal
decoder
Package decoder provides low-level decoding utilities for MaxMind DB data.
Package decoder provides low-level decoding utilities for MaxMind DB data.
maxminddbtag
Package maxminddbtag parses maxminddb struct tag values.
Package maxminddbtag parses maxminddb struct tag values.
mmdberrors
Package mmdberrors is an internal package for the errors used in this module.
Package mmdberrors is an internal package for the errors used in this module.
Command maxminddb-gen generates reflection-free MaxMind DB decoders.
Command maxminddb-gen generates reflection-free MaxMind DB decoders.
Package mmdbdata provides low-level types and interfaces for custom MaxMind DB decoding.
Package mmdbdata provides low-level types and interfaces for custom MaxMind DB decoding.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL