123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- package geoip
- import (
- "archive/tar"
- "bytes"
- "compress/gzip"
- "crypto/md5"
- "encoding/hex"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "os"
- "strings"
- "sync"
- gg "github.com/oschwald/geoip2-golang"
- )
- const (
- dbURL = "https://files.phlip.it/GeoLite2-City.tar.gz"
- dbMd5URL = "https://files.phlip.it/GeoLite2-City.tar.gz.md5"
- //bURL = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz"
- //dbMd5URL = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz.md5"
- )
- // Anonymous contains information about whether an IP address is anonymous
- type Anonymous struct {
- // from geoip2-golang.AnonymousIP
- IsAnonymous bool `json:"is_anonymous"`
- IsAnonymousVPN bool `json:"is_anonymous_vpn"`
- IsHostingProvider bool `json:"is_hosting_provider"`
- IsPublicProxy bool `json:"is_public_proxy"`
- IsTorExitNode bool `json:"is_tor_exit_node"`
- }
- // City contains geographic data on a city level for a given IP address
- type City struct {
- Name string `json:"name"`
- Continent string `json:"continent"`
- ContinentCode string `json:"continent_code"`
- Country string `json:"country"`
- CountryCode string `json:"country_code"`
- AccuracyRadius uint16 `json:"accuracy_radius"`
- Latitude float64 `json:"latitude"`
- Longitude float64 `json:"longitude"`
- MetroCode uint `json:"metro_code"`
- Timezone string `json:"timezone"`
- Postcode string `json:"postcode"`
- RegisteredCountry string `json:"registered_country"`
- RegisteredCountryCode string `json:"registered_country_code"`
- RepresentedCountry string `json:"represented_country"`
- RepresentedCountryCode string `json:"represented_country_code"`
- RepresentedCountryType string `json:"represented_country_type"`
- Subdivisions []string `json:"subdivisions"`
- IsAnonymousProxy bool `json:"is_anonymous_proxy"`
- IsSatelliteProvider bool `json:"is_satellite_provider"`
- }
- // ConnectionType denotes the connection type for a given IP address
- type ConnectionType struct {
- Type string `json:"connection_type"`
- }
- // Country contains geographic data on a country level for a given IP address
- type Country struct {
- ContinentCode string `json:"continent_code"`
- Continent string `json:"continent"`
- CountryCode string `json:"country_code"`
- Country string `json:"country"`
- RegisteredCountryCode string `json:"registered_country_code"`
- RegisteredCountry string `json:"registered_country"`
- RepresentedCountryCode string `json:"represented_country_code"`
- RepresentedCountryType string `json:"represented_country_type"`
- RepresentedCountry string `json:"represented_country"`
- IsAnonymousProxy bool `json:"is_anonymous_proxy"`
- IsSatelliteProvider bool `json:"is_satellite_provider"`
- }
- // Domain denotes the domain for a given IP address
- type Domain struct {
- Domain string `json:"domain"`
- }
- // ISP contains information about the autonomous system for the given IP address
- type ISP struct {
- AutonomousSystemNumber uint `json:"autonomous_system_number"`
- AutonomousSystemOrganization string `json:"autonomous_system_organization"`
- ISP string `json:"isp"`
- Organization string `json:"organization"`
- }
- // GeoIP contains metadata for an IP address from the Maxmind GeoIP databases
- type GeoIP struct {
- db *gg.Reader
- IP net.IP `json:"ip" bson:"ip"`
- Anonymous Anonymous `json:"anonymous" bson:"anon"`
- City City `json:"city" bson:"city"`
- Country Country `json:"country" bson:"country"`
- Domain Domain `json:"domain" bson:"domain"`
- ISP ISP `json:"isp" bson:"isp"`
- mutex sync.RWMutex
- ipMutex sync.RWMutex
- }
- // NewGeoIP creates a new GeoIP data structure
- func NewGeoIP() (*GeoIP, error) {
- g := GeoIP{
- mutex: sync.RWMutex{},
- ipMutex: sync.RWMutex{},
- }
- err := g.Load()
- if err != nil {
- return nil, err
- }
- return &g, nil
- }
- // NewGeoIPNoLoad creates a new geoip type without loading the data
- func NewGeoIPNoLoad() (*GeoIP, error) {
- return &GeoIP{
- mutex: sync.RWMutex{},
- ipMutex: sync.RWMutex{},
- }, nil
- }
- // Load loads the GeoIP City Database from Maxmind
- func (g *GeoIP) Load() error {
- // Get MD5 sum for tar.gz file
- resp, err := http.Get(dbMd5URL)
- if err != nil {
- return err
- }
- md5Sum, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- resp.Body.Close()
- // Load the tar.gz file
- resp, err = http.Get(dbURL)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- bodyData, err := ioutil.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- // Build the MD5 sum of the downloaded tar.gz
- hash := md5.New()
- if _, err := io.Copy(hash, bytes.NewReader(bodyData)); err != nil {
- return err
- }
- if string(md5Sum) != hex.EncodeToString(hash.Sum(nil)) {
- return fmt.Errorf("checksum mismatch: %s != %s", md5Sum, hash.Sum(nil))
- }
- // Extract the mmdb file
- gzReader, err := gzip.NewReader(bytes.NewReader(bodyData))
- if err != nil {
- return err
- }
- defer gzReader.Close()
- tarReader := tar.NewReader(gzReader)
- if err != nil {
- return err
- }
- for {
- header, err := tarReader.Next()
- if err == io.EOF {
- break
- }
- if err != nil {
- return err
- }
- // this is the mmdb database
- if header.Typeflag == tar.TypeReg && strings.HasSuffix(header.Name, "GeoLite2-City.mmdb") {
- tmpF, err := ioutil.TempFile("/tmp", "geoip-mmdb-")
- if err != nil {
- return err
- }
- _, err = io.Copy(tmpF, tarReader)
- if err != nil {
- return err
- }
- tmpF.Close()
- defer os.Remove(tmpF.Name())
- ggReader, err := gg.Open(tmpF.Name())
- if err != nil {
- return err
- }
- g.mutex.Lock()
- defer g.mutex.Unlock()
- if g.db != nil {
- g.db.Close()
- }
- g.db = ggReader
- return nil
- }
- }
- return nil
- }
- // Close closes the GeoIP database
- func (g *GeoIP) Close() {
- g.db.Close()
- }
- // LookupIP looks up the geo ip data for the given IP address
- func (g *GeoIP) LookupIP(ip string) (*GeoIP, error) {
- g.mutex.Lock()
- defer g.mutex.Unlock()
- x, err := NewGeoIPNoLoad()
- if err != nil {
- return nil, fmt.Errorf("failed to create new GeoIP type: %s", err)
- }
- x.IP = net.ParseIP(ip)
- if x.IP == nil {
- return nil, fmt.Errorf("%s is not a valid IP address", ip)
- }
- // ANONYMOUS IP
- //
- anon, err := g.db.AnonymousIP(x.IP)
- if err == nil {
- g.Anonymous = Anonymous{
- IsAnonymous: anon.IsAnonymous,
- IsAnonymousVPN: anon.IsAnonymousVPN,
- IsHostingProvider: anon.IsHostingProvider,
- IsPublicProxy: anon.IsPublicProxy,
- IsTorExitNode: anon.IsTorExitNode,
- }
- }
- // CITY
- //
- city, err := g.db.City(x.IP)
- if err == nil {
- subdivisions := make([]string, len(city.Subdivisions), len(city.Subdivisions))
- for i, sd := range city.Subdivisions {
- subdivisions[i] = sd.Names["en"]
- }
- x.City = City{
- AccuracyRadius: city.Location.AccuracyRadius,
- Continent: city.Continent.Names["en"],
- ContinentCode: city.Continent.Code,
- Country: city.Country.Names["en"],
- CountryCode: city.Country.IsoCode,
- IsAnonymousProxy: city.Traits.IsAnonymousProxy,
- IsSatelliteProvider: city.Traits.IsSatelliteProvider,
- Latitude: city.Location.Latitude,
- Longitude: city.Location.Longitude,
- MetroCode: city.Location.MetroCode,
- Name: city.City.Names["en"],
- Postcode: city.Postal.Code,
- RegisteredCountry: city.RegisteredCountry.Names["en"],
- RegisteredCountryCode: city.RegisteredCountry.IsoCode,
- RepresentedCountry: city.RepresentedCountry.Names["en"],
- RepresentedCountryCode: city.RepresentedCountry.IsoCode,
- RepresentedCountryType: city.RepresentedCountry.Type,
- Subdivisions: subdivisions,
- Timezone: city.Location.TimeZone,
- }
- } else {
- return nil, fmt.Errorf("failed to load city data for %s", ip)
- }
- // COUNTRY
- //
- country, err := g.db.Country(x.IP)
- if err == nil {
- x.Country = Country{
- Continent: country.Continent.Names["en"],
- ContinentCode: country.Continent.Code,
- Country: country.Country.Names["en"],
- CountryCode: country.Country.IsoCode,
- IsAnonymousProxy: country.Traits.IsAnonymousProxy,
- IsSatelliteProvider: country.Traits.IsSatelliteProvider,
- RegisteredCountry: country.RegisteredCountry.Names["en"],
- RegisteredCountryCode: country.RegisteredCountry.IsoCode,
- RepresentedCountry: country.RepresentedCountry.Names["en"],
- RepresentedCountryCode: country.RepresentedCountry.IsoCode,
- RepresentedCountryType: country.RepresentedCountry.Type,
- }
- } else {
- return nil, fmt.Errorf("failed to load country data for %s", ip)
- }
- return x, nil
- }
- // Lookup performs a geo ip lookup for ipAddr in the maxmind geoip database
- func (g *GeoIP) Lookup(ipAddr string) error {
- g.mutex.Lock()
- defer g.mutex.Unlock()
- g.IP = net.ParseIP(ipAddr)
- if g.IP == nil {
- return fmt.Errorf("%s is not a valid IP address", ipAddr)
- }
- // ANONYMOUS IP
- //
- anon, err := g.db.AnonymousIP(g.IP)
- if err == nil {
- g.Anonymous = Anonymous{
- IsAnonymous: anon.IsAnonymous,
- IsAnonymousVPN: anon.IsAnonymousVPN,
- IsHostingProvider: anon.IsHostingProvider,
- IsPublicProxy: anon.IsPublicProxy,
- IsTorExitNode: anon.IsTorExitNode,
- }
- }
- // CITY
- //
- city, err := g.db.City(g.IP)
- if err == nil {
- subdivisions := make([]string, len(city.Subdivisions), len(city.Subdivisions))
- for i, sd := range city.Subdivisions {
- subdivisions[i] = sd.Names["en"]
- }
- g.City = City{
- AccuracyRadius: city.Location.AccuracyRadius,
- Continent: city.Continent.Names["en"],
- ContinentCode: city.Continent.Code,
- Country: city.Country.Names["en"],
- CountryCode: city.Country.IsoCode,
- IsAnonymousProxy: city.Traits.IsAnonymousProxy,
- IsSatelliteProvider: city.Traits.IsSatelliteProvider,
- Latitude: city.Location.Latitude,
- Longitude: city.Location.Longitude,
- MetroCode: city.Location.MetroCode,
- Name: city.City.Names["en"],
- Postcode: city.Postal.Code,
- RegisteredCountry: city.RegisteredCountry.Names["en"],
- RegisteredCountryCode: city.RegisteredCountry.IsoCode,
- RepresentedCountry: city.RepresentedCountry.Names["en"],
- RepresentedCountryCode: city.RepresentedCountry.IsoCode,
- RepresentedCountryType: city.RepresentedCountry.Type,
- Subdivisions: subdivisions,
- Timezone: city.Location.TimeZone,
- }
- }
- // COUNTRY
- //
- country, err := g.db.Country(g.IP)
- if err == nil {
- g.Country = Country{
- Continent: country.Continent.Names["en"],
- ContinentCode: country.Continent.Code,
- Country: country.Country.Names["en"],
- CountryCode: country.Country.IsoCode,
- IsAnonymousProxy: country.Traits.IsAnonymousProxy,
- IsSatelliteProvider: country.Traits.IsSatelliteProvider,
- RegisteredCountry: country.RegisteredCountry.Names["en"],
- RegisteredCountryCode: country.RegisteredCountry.IsoCode,
- RepresentedCountry: country.RepresentedCountry.Names["en"],
- RepresentedCountryCode: country.RepresentedCountry.IsoCode,
- RepresentedCountryType: country.RepresentedCountry.Type,
- }
- }
- return nil
- }
|