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