// Package geoip2 provides a wrapper around the maxminddb package for // easy use with the MaxMind GeoIP2 and GeoLite2 databases. The records for // the IP address is returned from this package as well-formed structures // that match the internal layout of data from MaxMind. package geoip2 import ( "fmt" "net" "github.com/oschwald/maxminddb-golang" ) // The City structure corresponds to the data in the GeoIP2/GeoLite2 City // databases. type City struct { City struct { GeoNameID uint `maxminddb:"geoname_id"` Names map[string]string `maxminddb:"names"` } `maxminddb:"city"` Continent struct { Code string `maxminddb:"code"` GeoNameID uint `maxminddb:"geoname_id"` Names map[string]string `maxminddb:"names"` } `maxminddb:"continent"` Country struct { GeoNameID uint `maxminddb:"geoname_id"` IsoCode string `maxminddb:"iso_code"` Names map[string]string `maxminddb:"names"` } `maxminddb:"country"` Location struct { AccuracyRadius uint16 `maxminddb:"accuracy_radius"` Latitude float64 `maxminddb:"latitude"` Longitude float64 `maxminddb:"longitude"` MetroCode uint `maxminddb:"metro_code"` TimeZone string `maxminddb:"time_zone"` } `maxminddb:"location"` Postal struct { Code string `maxminddb:"code"` } `maxminddb:"postal"` RegisteredCountry struct { GeoNameID uint `maxminddb:"geoname_id"` IsoCode string `maxminddb:"iso_code"` Names map[string]string `maxminddb:"names"` } `maxminddb:"registered_country"` RepresentedCountry struct { GeoNameID uint `maxminddb:"geoname_id"` IsoCode string `maxminddb:"iso_code"` Names map[string]string `maxminddb:"names"` Type string `maxminddb:"type"` } `maxminddb:"represented_country"` Subdivisions []struct { GeoNameID uint `maxminddb:"geoname_id"` IsoCode string `maxminddb:"iso_code"` Names map[string]string `maxminddb:"names"` } `maxminddb:"subdivisions"` Traits struct { IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"` IsSatelliteProvider bool `maxminddb:"is_satellite_provider"` } `maxminddb:"traits"` } // The Country structure corresponds to the data in the GeoIP2/GeoLite2 // Country databases. type Country struct { Continent struct { Code string `maxminddb:"code"` GeoNameID uint `maxminddb:"geoname_id"` Names map[string]string `maxminddb:"names"` } `maxminddb:"continent"` Country struct { GeoNameID uint `maxminddb:"geoname_id"` IsoCode string `maxminddb:"iso_code"` Names map[string]string `maxminddb:"names"` } `maxminddb:"country"` RegisteredCountry struct { GeoNameID uint `maxminddb:"geoname_id"` IsoCode string `maxminddb:"iso_code"` Names map[string]string `maxminddb:"names"` } `maxminddb:"registered_country"` RepresentedCountry struct { GeoNameID uint `maxminddb:"geoname_id"` IsoCode string `maxminddb:"iso_code"` Names map[string]string `maxminddb:"names"` Type string `maxminddb:"type"` } `maxminddb:"represented_country"` Traits struct { IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"` IsSatelliteProvider bool `maxminddb:"is_satellite_provider"` } `maxminddb:"traits"` } // The AnonymousIP structure corresponds to the data in the GeoIP2 // Anonymous IP database. type AnonymousIP struct { IsAnonymous bool `maxminddb:"is_anonymous"` IsAnonymousVPN bool `maxminddb:"is_anonymous_vpn"` IsHostingProvider bool `maxminddb:"is_hosting_provider"` IsPublicProxy bool `maxminddb:"is_public_proxy"` IsTorExitNode bool `maxminddb:"is_tor_exit_node"` } // The ConnectionType structure corresponds to the data in the GeoIP2 // Connection-Type database. type ConnectionType struct { ConnectionType string `maxminddb:"connection_type"` } // The Domain structure corresponds to the data in the GeoIP2 Domain database. type Domain struct { Domain string `maxminddb:"domain"` } // The ISP structure corresponds to the data in the GeoIP2 ISP database. type ISP struct { AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` ISP string `maxminddb:"isp"` Organization string `maxminddb:"organization"` } type databaseType int const ( isAnonymousIP = 1 << iota isCity isConnectionType isCountry isDomain isEnterprise isISP ) // Reader holds the maxminddb.Reader structure. It should be created // using the Open function. type Reader struct { mmdbReader *maxminddb.Reader databaseType databaseType } // InvalidMethodError is returned when a lookup method is called on a // database that it does not support. For instance, calling the ISP method // on a City database. type InvalidMethodError struct { Method string DatabaseType string } func (e InvalidMethodError) Error() string { return fmt.Sprintf(`geoip2: the %s method does not support the %s database`, e.Method, e.DatabaseType) } // UnknownDatabaseTypeError is returned when an unknown database type is // opened. type UnknownDatabaseTypeError struct { DatabaseType string } func (e UnknownDatabaseTypeError) Error() string { return fmt.Sprintf(`geoip2: reader does not support the "%s" database type`, e.DatabaseType) } // Open takes a string path to a file and returns a Reader structure or an // error. The database file is opened using a memory map. Use the Close method // on the Reader object to return the resources to the system. func Open(file string) (*Reader, error) { reader, err := maxminddb.Open(file) if err != nil { return nil, err } dbType, err := getDBType(reader) return &Reader{reader, dbType}, err } // FromBytes takes a byte slice corresponding to a GeoIP2/GeoLite2 database // file and returns a Reader structure or an error. func FromBytes(bytes []byte) (*Reader, error) { reader, err := maxminddb.FromBytes(bytes) if err != nil { return nil, err } dbType, err := getDBType(reader) return &Reader{reader, dbType}, err } func getDBType(reader *maxminddb.Reader) (databaseType, error) { switch reader.Metadata.DatabaseType { case "GeoIP2-Anonymous-IP": return isAnonymousIP, nil // We allow City lookups on Country for back compat case "GeoLite2-City", "GeoIP2-City", "GeoIP2-Precision-City", "GeoLite2-Country", "GeoIP2-Country": return isCity | isCountry, nil case "GeoIP2-Connection-Type": return isConnectionType, nil case "GeoIP2-Domain": return isDomain, nil case "GeoIP2-Enterprise": return isEnterprise | isCity | isCountry, nil case "GeoIP2-ISP", "GeoIP2-Precision-ISP": return isISP, nil default: return 0, UnknownDatabaseTypeError{reader.Metadata.DatabaseType} } } // City takes an IP address as a net.IP struct and returns a City struct // and/or an error. Although this can be used with other databases, this // method generally should be used with the GeoIP2 or GeoLite2 City databases. func (r *Reader) City(ipAddress net.IP) (*City, error) { if isCity&r.databaseType == 0 { return nil, InvalidMethodError{"City", r.Metadata().DatabaseType} } var city City err := r.mmdbReader.Lookup(ipAddress, &city) return &city, err } // Country takes an IP address as a net.IP struct and returns a Country struct // and/or an error. Although this can be used with other databases, this // method generally should be used with the GeoIP2 or GeoLite2 Country // databases. func (r *Reader) Country(ipAddress net.IP) (*Country, error) { if isCountry&r.databaseType == 0 { return nil, InvalidMethodError{"Country", r.Metadata().DatabaseType} } var country Country err := r.mmdbReader.Lookup(ipAddress, &country) return &country, err } // AnonymousIP takes an IP address as a net.IP struct and returns a // AnonymousIP struct and/or an error. func (r *Reader) AnonymousIP(ipAddress net.IP) (*AnonymousIP, error) { if isAnonymousIP&r.databaseType == 0 { return nil, InvalidMethodError{"AnonymousIP", r.Metadata().DatabaseType} } var anonIP AnonymousIP err := r.mmdbReader.Lookup(ipAddress, &anonIP) return &anonIP, err } // ConnectionType takes an IP address as a net.IP struct and returns a // ConnectionType struct and/or an error func (r *Reader) ConnectionType(ipAddress net.IP) (*ConnectionType, error) { if isConnectionType&r.databaseType == 0 { return nil, InvalidMethodError{"ConnectionType", r.Metadata().DatabaseType} } var val ConnectionType err := r.mmdbReader.Lookup(ipAddress, &val) return &val, err } // Domain takes an IP address as a net.IP struct and returns a // Domain struct and/or an error func (r *Reader) Domain(ipAddress net.IP) (*Domain, error) { if isDomain&r.databaseType == 0 { return nil, InvalidMethodError{"Domain", r.Metadata().DatabaseType} } var val Domain err := r.mmdbReader.Lookup(ipAddress, &val) return &val, err } // ISP takes an IP address as a net.IP struct and returns a ISP struct and/or // an error func (r *Reader) ISP(ipAddress net.IP) (*ISP, error) { if isISP&r.databaseType == 0 { return nil, InvalidMethodError{"ISP", r.Metadata().DatabaseType} } var val ISP err := r.mmdbReader.Lookup(ipAddress, &val) return &val, err } // Metadata takes no arguments and returns a struct containing metadata about // the MaxMind database in use by the Reader. func (r *Reader) Metadata() maxminddb.Metadata { return r.mmdbReader.Metadata } // Close unmaps the database file from virtual memory and returns the // resources to the system. func (r *Reader) Close() error { return r.mmdbReader.Close() }