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