1// Package geoip2 provides an easy-to-use API for the MaxMind GeoIP2 and 2// GeoLite2 databases; this package does not support GeoIP Legacy databases. 3// 4// The structs provided by this package match the internal structure of 5// the data in the MaxMind databases. 6// 7// See github.com/oschwald/maxminddb-golang for more advanced used cases. 8package geoip2 9 10import ( 11 "fmt" 12 "net" 13 14 "github.com/oschwald/maxminddb-golang" 15) 16 17// The Enterprise struct corresponds to the data in the GeoIP2 Enterprise 18// database. 19type Enterprise struct { 20 City struct { 21 Confidence uint8 `maxminddb:"confidence"` 22 GeoNameID uint `maxminddb:"geoname_id"` 23 Names map[string]string `maxminddb:"names"` 24 } `maxminddb:"city"` 25 Continent struct { 26 Code string `maxminddb:"code"` 27 GeoNameID uint `maxminddb:"geoname_id"` 28 Names map[string]string `maxminddb:"names"` 29 } `maxminddb:"continent"` 30 Country struct { 31 GeoNameID uint `maxminddb:"geoname_id"` 32 IsoCode string `maxminddb:"iso_code"` 33 Names map[string]string `maxminddb:"names"` 34 Confidence uint8 `maxminddb:"confidence"` 35 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 36 } `maxminddb:"country"` 37 Location struct { 38 AccuracyRadius uint16 `maxminddb:"accuracy_radius"` 39 Latitude float64 `maxminddb:"latitude"` 40 Longitude float64 `maxminddb:"longitude"` 41 MetroCode uint `maxminddb:"metro_code"` 42 TimeZone string `maxminddb:"time_zone"` 43 } `maxminddb:"location"` 44 Postal struct { 45 Code string `maxminddb:"code"` 46 Confidence uint8 `maxminddb:"confidence"` 47 } `maxminddb:"postal"` 48 RegisteredCountry struct { 49 GeoNameID uint `maxminddb:"geoname_id"` 50 IsoCode string `maxminddb:"iso_code"` 51 Names map[string]string `maxminddb:"names"` 52 Confidence uint8 `maxminddb:"confidence"` 53 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 54 } `maxminddb:"registered_country"` 55 RepresentedCountry struct { 56 GeoNameID uint `maxminddb:"geoname_id"` 57 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 58 IsoCode string `maxminddb:"iso_code"` 59 Names map[string]string `maxminddb:"names"` 60 Type string `maxminddb:"type"` 61 } `maxminddb:"represented_country"` 62 Subdivisions []struct { 63 Confidence uint8 `maxminddb:"confidence"` 64 GeoNameID uint `maxminddb:"geoname_id"` 65 IsoCode string `maxminddb:"iso_code"` 66 Names map[string]string `maxminddb:"names"` 67 } `maxminddb:"subdivisions"` 68 Traits struct { 69 AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` 70 AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` 71 ConnectionType string `maxminddb:"connection_type"` 72 Domain string `maxminddb:"domain"` 73 IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"` 74 IsLegitimateProxy bool `maxminddb:"is_legitimate_proxy"` 75 IsSatelliteProvider bool `maxminddb:"is_satellite_provider"` 76 ISP string `maxminddb:"isp"` 77 StaticIPScore float64 `maxminddb:"static_ip_score"` 78 Organization string `maxminddb:"organization"` 79 UserType string `maxminddb:"user_type"` 80 } `maxminddb:"traits"` 81} 82 83// The City struct corresponds to the data in the GeoIP2/GeoLite2 City 84// databases. 85type City struct { 86 City struct { 87 GeoNameID uint `maxminddb:"geoname_id"` 88 Names map[string]string `maxminddb:"names"` 89 } `maxminddb:"city"` 90 Continent struct { 91 Code string `maxminddb:"code"` 92 GeoNameID uint `maxminddb:"geoname_id"` 93 Names map[string]string `maxminddb:"names"` 94 } `maxminddb:"continent"` 95 Country struct { 96 GeoNameID uint `maxminddb:"geoname_id"` 97 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 98 IsoCode string `maxminddb:"iso_code"` 99 Names map[string]string `maxminddb:"names"` 100 } `maxminddb:"country"` 101 Location struct { 102 AccuracyRadius uint16 `maxminddb:"accuracy_radius"` 103 Latitude float64 `maxminddb:"latitude"` 104 Longitude float64 `maxminddb:"longitude"` 105 MetroCode uint `maxminddb:"metro_code"` 106 TimeZone string `maxminddb:"time_zone"` 107 } `maxminddb:"location"` 108 Postal struct { 109 Code string `maxminddb:"code"` 110 } `maxminddb:"postal"` 111 RegisteredCountry struct { 112 GeoNameID uint `maxminddb:"geoname_id"` 113 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 114 IsoCode string `maxminddb:"iso_code"` 115 Names map[string]string `maxminddb:"names"` 116 } `maxminddb:"registered_country"` 117 RepresentedCountry struct { 118 GeoNameID uint `maxminddb:"geoname_id"` 119 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 120 IsoCode string `maxminddb:"iso_code"` 121 Names map[string]string `maxminddb:"names"` 122 Type string `maxminddb:"type"` 123 } `maxminddb:"represented_country"` 124 Subdivisions []struct { 125 GeoNameID uint `maxminddb:"geoname_id"` 126 IsoCode string `maxminddb:"iso_code"` 127 Names map[string]string `maxminddb:"names"` 128 } `maxminddb:"subdivisions"` 129 Traits struct { 130 IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"` 131 IsSatelliteProvider bool `maxminddb:"is_satellite_provider"` 132 } `maxminddb:"traits"` 133} 134 135// The Country struct corresponds to the data in the GeoIP2/GeoLite2 136// Country databases. 137type Country struct { 138 Continent struct { 139 Code string `maxminddb:"code"` 140 GeoNameID uint `maxminddb:"geoname_id"` 141 Names map[string]string `maxminddb:"names"` 142 } `maxminddb:"continent"` 143 Country struct { 144 GeoNameID uint `maxminddb:"geoname_id"` 145 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 146 IsoCode string `maxminddb:"iso_code"` 147 Names map[string]string `maxminddb:"names"` 148 } `maxminddb:"country"` 149 RegisteredCountry struct { 150 GeoNameID uint `maxminddb:"geoname_id"` 151 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 152 IsoCode string `maxminddb:"iso_code"` 153 Names map[string]string `maxminddb:"names"` 154 } `maxminddb:"registered_country"` 155 RepresentedCountry struct { 156 GeoNameID uint `maxminddb:"geoname_id"` 157 IsInEuropeanUnion bool `maxminddb:"is_in_european_union"` 158 IsoCode string `maxminddb:"iso_code"` 159 Names map[string]string `maxminddb:"names"` 160 Type string `maxminddb:"type"` 161 } `maxminddb:"represented_country"` 162 Traits struct { 163 IsAnonymousProxy bool `maxminddb:"is_anonymous_proxy"` 164 IsSatelliteProvider bool `maxminddb:"is_satellite_provider"` 165 } `maxminddb:"traits"` 166} 167 168// The AnonymousIP struct corresponds to the data in the GeoIP2 169// Anonymous IP database. 170type AnonymousIP struct { 171 IsAnonymous bool `maxminddb:"is_anonymous"` 172 IsAnonymousVPN bool `maxminddb:"is_anonymous_vpn"` 173 IsHostingProvider bool `maxminddb:"is_hosting_provider"` 174 IsPublicProxy bool `maxminddb:"is_public_proxy"` 175 IsResidentialProxy bool `maxminddb:"is_residential_proxy"` 176 IsTorExitNode bool `maxminddb:"is_tor_exit_node"` 177} 178 179// The ASN struct corresponds to the data in the GeoLite2 ASN database. 180type ASN struct { 181 AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` 182 AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` 183} 184 185// The ConnectionType struct corresponds to the data in the GeoIP2 186// Connection-Type database. 187type ConnectionType struct { 188 ConnectionType string `maxminddb:"connection_type"` 189} 190 191// The Domain struct corresponds to the data in the GeoIP2 Domain database. 192type Domain struct { 193 Domain string `maxminddb:"domain"` 194} 195 196// The ISP struct corresponds to the data in the GeoIP2 ISP database. 197type ISP struct { 198 AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` 199 AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` 200 ISP string `maxminddb:"isp"` 201 Organization string `maxminddb:"organization"` 202} 203 204type databaseType int 205 206const ( 207 isAnonymousIP = 1 << iota 208 isASN 209 isCity 210 isConnectionType 211 isCountry 212 isDomain 213 isEnterprise 214 isISP 215) 216 217// Reader holds the maxminddb.Reader struct. It can be created using the 218// Open and FromBytes functions. 219type Reader struct { 220 mmdbReader *maxminddb.Reader 221 databaseType databaseType 222} 223 224// InvalidMethodError is returned when a lookup method is called on a 225// database that it does not support. For instance, calling the ISP method 226// on a City database. 227type InvalidMethodError struct { 228 Method string 229 DatabaseType string 230} 231 232func (e InvalidMethodError) Error() string { 233 return fmt.Sprintf(`geoip2: the %s method does not support the %s database`, 234 e.Method, e.DatabaseType) 235} 236 237// UnknownDatabaseTypeError is returned when an unknown database type is 238// opened. 239type UnknownDatabaseTypeError struct { 240 DatabaseType string 241} 242 243func (e UnknownDatabaseTypeError) Error() string { 244 return fmt.Sprintf(`geoip2: reader does not support the "%s" database type`, 245 e.DatabaseType) 246} 247 248// Open takes a string path to a file and returns a Reader struct or an error. 249// The database file is opened using a memory map. Use the Close method on the 250// Reader object to return the resources to the system. 251func Open(file string) (*Reader, error) { 252 reader, err := maxminddb.Open(file) 253 if err != nil { 254 return nil, err 255 } 256 dbType, err := getDBType(reader) 257 return &Reader{reader, dbType}, err 258} 259 260// FromBytes takes a byte slice corresponding to a GeoIP2/GeoLite2 database 261// file and returns a Reader struct or an error. Note that the byte slice is 262// use directly; any modification of it after opening the database will result 263// in errors while reading from the database. 264func FromBytes(bytes []byte) (*Reader, error) { 265 reader, err := maxminddb.FromBytes(bytes) 266 if err != nil { 267 return nil, err 268 } 269 dbType, err := getDBType(reader) 270 return &Reader{reader, dbType}, err 271} 272 273func getDBType(reader *maxminddb.Reader) (databaseType, error) { 274 switch reader.Metadata.DatabaseType { 275 case "GeoIP2-Anonymous-IP": 276 return isAnonymousIP, nil 277 case "DBIP-ASN-Lite (compat=GeoLite2-ASN)", 278 "GeoLite2-ASN": 279 return isASN, nil 280 // We allow City lookups on Country for back compat 281 case "DBIP-City-Lite", 282 "DBIP-Country-Lite", 283 "DBIP-Country", 284 "DBIP-Location (compat=City)", 285 "GeoLite2-City", 286 "GeoIP2-City", 287 "GeoIP2-City-Africa", 288 "GeoIP2-City-Asia-Pacific", 289 "GeoIP2-City-Europe", 290 "GeoIP2-City-North-America", 291 "GeoIP2-City-South-America", 292 "GeoIP2-Precision-City", 293 "GeoLite2-Country", 294 "GeoIP2-Country": 295 return isCity | isCountry, nil 296 case "GeoIP2-Connection-Type": 297 return isConnectionType, nil 298 case "GeoIP2-Domain": 299 return isDomain, nil 300 case "DBIP-ISP (compat=Enterprise)", 301 "DBIP-Location-ISP (compat=Enterprise)", 302 "GeoIP2-Enterprise": 303 return isEnterprise | isCity | isCountry, nil 304 case "GeoIP2-ISP", 305 "GeoIP2-Precision-ISP": 306 return isISP | isASN, nil 307 default: 308 return 0, UnknownDatabaseTypeError{reader.Metadata.DatabaseType} 309 } 310} 311 312// Enterprise takes an IP address as a net.IP struct and returns an Enterprise 313// struct and/or an error. This is intended to be used with the GeoIP2 314// Enterprise database. 315func (r *Reader) Enterprise(ipAddress net.IP) (*Enterprise, error) { 316 if isEnterprise&r.databaseType == 0 { 317 return nil, InvalidMethodError{"Enterprise", r.Metadata().DatabaseType} 318 } 319 var enterprise Enterprise 320 err := r.mmdbReader.Lookup(ipAddress, &enterprise) 321 return &enterprise, err 322} 323 324// City takes an IP address as a net.IP struct and returns a City struct 325// and/or an error. Although this can be used with other databases, this 326// method generally should be used with the GeoIP2 or GeoLite2 City databases. 327func (r *Reader) City(ipAddress net.IP) (*City, error) { 328 if isCity&r.databaseType == 0 { 329 return nil, InvalidMethodError{"City", r.Metadata().DatabaseType} 330 } 331 var city City 332 err := r.mmdbReader.Lookup(ipAddress, &city) 333 return &city, err 334} 335 336// Country takes an IP address as a net.IP struct and returns a Country struct 337// and/or an error. Although this can be used with other databases, this 338// method generally should be used with the GeoIP2 or GeoLite2 Country 339// databases. 340func (r *Reader) Country(ipAddress net.IP) (*Country, error) { 341 if isCountry&r.databaseType == 0 { 342 return nil, InvalidMethodError{"Country", r.Metadata().DatabaseType} 343 } 344 var country Country 345 err := r.mmdbReader.Lookup(ipAddress, &country) 346 return &country, err 347} 348 349// AnonymousIP takes an IP address as a net.IP struct and returns a 350// AnonymousIP struct and/or an error. 351func (r *Reader) AnonymousIP(ipAddress net.IP) (*AnonymousIP, error) { 352 if isAnonymousIP&r.databaseType == 0 { 353 return nil, InvalidMethodError{"AnonymousIP", r.Metadata().DatabaseType} 354 } 355 var anonIP AnonymousIP 356 err := r.mmdbReader.Lookup(ipAddress, &anonIP) 357 return &anonIP, err 358} 359 360// ASN takes an IP address as a net.IP struct and returns a ASN struct and/or 361// an error 362func (r *Reader) ASN(ipAddress net.IP) (*ASN, error) { 363 if isASN&r.databaseType == 0 { 364 return nil, InvalidMethodError{"ASN", r.Metadata().DatabaseType} 365 } 366 var val ASN 367 err := r.mmdbReader.Lookup(ipAddress, &val) 368 return &val, err 369} 370 371// ConnectionType takes an IP address as a net.IP struct and returns a 372// ConnectionType struct and/or an error 373func (r *Reader) ConnectionType(ipAddress net.IP) (*ConnectionType, error) { 374 if isConnectionType&r.databaseType == 0 { 375 return nil, InvalidMethodError{"ConnectionType", r.Metadata().DatabaseType} 376 } 377 var val ConnectionType 378 err := r.mmdbReader.Lookup(ipAddress, &val) 379 return &val, err 380} 381 382// Domain takes an IP address as a net.IP struct and returns a 383// Domain struct and/or an error 384func (r *Reader) Domain(ipAddress net.IP) (*Domain, error) { 385 if isDomain&r.databaseType == 0 { 386 return nil, InvalidMethodError{"Domain", r.Metadata().DatabaseType} 387 } 388 var val Domain 389 err := r.mmdbReader.Lookup(ipAddress, &val) 390 return &val, err 391} 392 393// ISP takes an IP address as a net.IP struct and returns a ISP struct and/or 394// an error 395func (r *Reader) ISP(ipAddress net.IP) (*ISP, error) { 396 if isISP&r.databaseType == 0 { 397 return nil, InvalidMethodError{"ISP", r.Metadata().DatabaseType} 398 } 399 var val ISP 400 err := r.mmdbReader.Lookup(ipAddress, &val) 401 return &val, err 402} 403 404// Metadata takes no arguments and returns a struct containing metadata about 405// the MaxMind database in use by the Reader. 406func (r *Reader) Metadata() maxminddb.Metadata { 407 return r.mmdbReader.Metadata 408} 409 410// Close unmaps the database file from virtual memory and returns the 411// resources to the system. 412func (r *Reader) Close() error { 413 return r.mmdbReader.Close() 414} 415