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