geoip.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. package geoip
  2. import (
  3. "archive/tar"
  4. "bytes"
  5. "compress/gzip"
  6. "crypto/md5"
  7. "encoding/hex"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "net"
  12. "net/http"
  13. "os"
  14. "strings"
  15. "sync"
  16. gg "github.com/oschwald/geoip2-golang"
  17. )
  18. const (
  19. dbURL = "https://files.phlip.it/GeoLite2-City.tar.gz"
  20. dbMd5URL = "https://files.phlip.it/GeoLite2-City.tar.gz.md5"
  21. //bURL = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz"
  22. //dbMd5URL = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz.md5"
  23. )
  24. // Anonymous contains information about whether an IP address is anonymous
  25. type Anonymous struct {
  26. // from geoip2-golang.AnonymousIP
  27. IsAnonymous bool `json:"is_anonymous"`
  28. IsAnonymousVPN bool `json:"is_anonymous_vpn"`
  29. IsHostingProvider bool `json:"is_hosting_provider"`
  30. IsPublicProxy bool `json:"is_public_proxy"`
  31. IsTorExitNode bool `json:"is_tor_exit_node"`
  32. }
  33. // City contains geographic data on a city level for a given IP address
  34. type City struct {
  35. Name string `json:"name"`
  36. Continent string `json:"continent"`
  37. ContinentCode string `json:"continent_code"`
  38. Country string `json:"country"`
  39. CountryCode string `json:"country_code"`
  40. AccuracyRadius uint16 `json:"accuracy_radius"`
  41. Latitude float64 `json:"latitude"`
  42. Longitude float64 `json:"longitude"`
  43. MetroCode uint `json:"metro_code"`
  44. Timezone string `json:"timezone"`
  45. Postcode string `json:"postcode"`
  46. RegisteredCountry string `json:"registered_country"`
  47. RegisteredCountryCode string `json:"registered_country_code"`
  48. RepresentedCountry string `json:"represented_country"`
  49. RepresentedCountryCode string `json:"represented_country_code"`
  50. RepresentedCountryType string `json:"represented_country_type"`
  51. Subdivisions []string `json:"subdivisions"`
  52. IsAnonymousProxy bool `json:"is_anonymous_proxy"`
  53. IsSatelliteProvider bool `json:"is_satellite_provider"`
  54. }
  55. // ConnectionType denotes the connection type for a given IP address
  56. type ConnectionType struct {
  57. Type string `json:"connection_type"`
  58. }
  59. // Country contains geographic data on a country level for a given IP address
  60. type Country struct {
  61. ContinentCode string `json:"continent_code"`
  62. Continent string `json:"continent"`
  63. CountryCode string `json:"country_code"`
  64. Country string `json:"country"`
  65. RegisteredCountryCode string `json:"registered_country_code"`
  66. RegisteredCountry string `json:"registered_country"`
  67. RepresentedCountryCode string `json:"represented_country_code"`
  68. RepresentedCountryType string `json:"represented_country_type"`
  69. RepresentedCountry string `json:"represented_country"`
  70. IsAnonymousProxy bool `json:"is_anonymous_proxy"`
  71. IsSatelliteProvider bool `json:"is_satellite_provider"`
  72. }
  73. // Domain denotes the domain for a given IP address
  74. type Domain struct {
  75. Domain string `json:"domain"`
  76. }
  77. // ISP contains information about the autonomous system for the given IP address
  78. type ISP struct {
  79. AutonomousSystemNumber uint `json:"autonomous_system_number"`
  80. AutonomousSystemOrganization string `json:"autonomous_system_organization"`
  81. ISP string `json:"isp"`
  82. Organization string `json:"organization"`
  83. }
  84. // GeoIP contains metadata for an IP address from the Maxmind GeoIP databases
  85. type GeoIP struct {
  86. db *gg.Reader
  87. IP net.IP `json:"ip" bson:"ip"`
  88. Anonymous Anonymous `json:"anonymous" bson:"anon"`
  89. City City `json:"city" bson:"city"`
  90. Country Country `json:"country" bson:"country"`
  91. Domain Domain `json:"domain" bson:"domain"`
  92. ISP ISP `json:"isp" bson:"isp"`
  93. mutex sync.RWMutex
  94. ipMutex sync.RWMutex
  95. }
  96. // NewGeoIP creates a new GeoIP data structure
  97. func NewGeoIP() (*GeoIP, error) {
  98. g := GeoIP{
  99. mutex: sync.RWMutex{},
  100. ipMutex: sync.RWMutex{},
  101. }
  102. err := g.Load()
  103. if err != nil {
  104. return nil, err
  105. }
  106. return &g, nil
  107. }
  108. // NewGeoIPNoLoad creates a new geoip type without loading the data
  109. func NewGeoIPNoLoad() (*GeoIP, error) {
  110. return &GeoIP{
  111. mutex: sync.RWMutex{},
  112. ipMutex: sync.RWMutex{},
  113. }, nil
  114. }
  115. // Load loads the GeoIP City Database from Maxmind
  116. func (g *GeoIP) Load() error {
  117. // Get MD5 sum for tar.gz file
  118. resp, err := http.Get(dbMd5URL)
  119. if err != nil {
  120. return err
  121. }
  122. md5Sum, err := ioutil.ReadAll(resp.Body)
  123. if err != nil {
  124. return err
  125. }
  126. resp.Body.Close()
  127. // Load the tar.gz file
  128. resp, err = http.Get(dbURL)
  129. if err != nil {
  130. return err
  131. }
  132. defer resp.Body.Close()
  133. bodyData, err := ioutil.ReadAll(resp.Body)
  134. if err != nil {
  135. return err
  136. }
  137. // Build the MD5 sum of the downloaded tar.gz
  138. hash := md5.New()
  139. if _, err := io.Copy(hash, bytes.NewReader(bodyData)); err != nil {
  140. return err
  141. }
  142. if string(md5Sum) != hex.EncodeToString(hash.Sum(nil)) {
  143. return fmt.Errorf("checksum mismatch: %s != %s", md5Sum, hash.Sum(nil))
  144. }
  145. // Extract the mmdb file
  146. gzReader, err := gzip.NewReader(bytes.NewReader(bodyData))
  147. if err != nil {
  148. return err
  149. }
  150. defer gzReader.Close()
  151. tarReader := tar.NewReader(gzReader)
  152. if err != nil {
  153. return err
  154. }
  155. for {
  156. header, err := tarReader.Next()
  157. if err == io.EOF {
  158. break
  159. }
  160. if err != nil {
  161. return err
  162. }
  163. // this is the mmdb database
  164. if header.Typeflag == tar.TypeReg && strings.HasSuffix(header.Name, "GeoLite2-City.mmdb") {
  165. tmpF, err := ioutil.TempFile("/tmp", "geoip-mmdb-")
  166. if err != nil {
  167. return err
  168. }
  169. _, err = io.Copy(tmpF, tarReader)
  170. if err != nil {
  171. return err
  172. }
  173. tmpF.Close()
  174. defer os.Remove(tmpF.Name())
  175. ggReader, err := gg.Open(tmpF.Name())
  176. if err != nil {
  177. return err
  178. }
  179. g.mutex.Lock()
  180. defer g.mutex.Unlock()
  181. if g.db != nil {
  182. g.db.Close()
  183. }
  184. g.db = ggReader
  185. return nil
  186. }
  187. }
  188. return nil
  189. }
  190. // Close closes the GeoIP database
  191. func (g *GeoIP) Close() {
  192. g.db.Close()
  193. }
  194. // LookupIP looks up the geo ip data for the given IP address
  195. func (g *GeoIP) LookupIP(ip string) (*GeoIP, error) {
  196. g.mutex.Lock()
  197. defer g.mutex.Unlock()
  198. x, err := NewGeoIPNoLoad()
  199. if err != nil {
  200. return nil, fmt.Errorf("failed to create new GeoIP type: %s", err)
  201. }
  202. x.IP = net.ParseIP(ip)
  203. if x.IP == nil {
  204. return nil, fmt.Errorf("%s is not a valid IP address", ip)
  205. }
  206. // ANONYMOUS IP
  207. //
  208. anon, err := g.db.AnonymousIP(x.IP)
  209. if err == nil {
  210. g.Anonymous = Anonymous{
  211. IsAnonymous: anon.IsAnonymous,
  212. IsAnonymousVPN: anon.IsAnonymousVPN,
  213. IsHostingProvider: anon.IsHostingProvider,
  214. IsPublicProxy: anon.IsPublicProxy,
  215. IsTorExitNode: anon.IsTorExitNode,
  216. }
  217. }
  218. // CITY
  219. //
  220. city, err := g.db.City(x.IP)
  221. if err == nil {
  222. subdivisions := make([]string, len(city.Subdivisions), len(city.Subdivisions))
  223. for i, sd := range city.Subdivisions {
  224. subdivisions[i] = sd.Names["en"]
  225. }
  226. x.City = City{
  227. AccuracyRadius: city.Location.AccuracyRadius,
  228. Continent: city.Continent.Names["en"],
  229. ContinentCode: city.Continent.Code,
  230. Country: city.Country.Names["en"],
  231. CountryCode: city.Country.IsoCode,
  232. IsAnonymousProxy: city.Traits.IsAnonymousProxy,
  233. IsSatelliteProvider: city.Traits.IsSatelliteProvider,
  234. Latitude: city.Location.Latitude,
  235. Longitude: city.Location.Longitude,
  236. MetroCode: city.Location.MetroCode,
  237. Name: city.City.Names["en"],
  238. Postcode: city.Postal.Code,
  239. RegisteredCountry: city.RegisteredCountry.Names["en"],
  240. RegisteredCountryCode: city.RegisteredCountry.IsoCode,
  241. RepresentedCountry: city.RepresentedCountry.Names["en"],
  242. RepresentedCountryCode: city.RepresentedCountry.IsoCode,
  243. RepresentedCountryType: city.RepresentedCountry.Type,
  244. Subdivisions: subdivisions,
  245. Timezone: city.Location.TimeZone,
  246. }
  247. } else {
  248. return nil, fmt.Errorf("failed to load city data for %s", ip)
  249. }
  250. // COUNTRY
  251. //
  252. country, err := g.db.Country(x.IP)
  253. if err == nil {
  254. x.Country = Country{
  255. Continent: country.Continent.Names["en"],
  256. ContinentCode: country.Continent.Code,
  257. Country: country.Country.Names["en"],
  258. CountryCode: country.Country.IsoCode,
  259. IsAnonymousProxy: country.Traits.IsAnonymousProxy,
  260. IsSatelliteProvider: country.Traits.IsSatelliteProvider,
  261. RegisteredCountry: country.RegisteredCountry.Names["en"],
  262. RegisteredCountryCode: country.RegisteredCountry.IsoCode,
  263. RepresentedCountry: country.RepresentedCountry.Names["en"],
  264. RepresentedCountryCode: country.RepresentedCountry.IsoCode,
  265. RepresentedCountryType: country.RepresentedCountry.Type,
  266. }
  267. } else {
  268. return nil, fmt.Errorf("failed to load country data for %s", ip)
  269. }
  270. return x, nil
  271. }
  272. // Lookup performs a geo ip lookup for ipAddr in the maxmind geoip database
  273. func (g *GeoIP) Lookup(ipAddr string) error {
  274. g.mutex.Lock()
  275. defer g.mutex.Unlock()
  276. g.IP = net.ParseIP(ipAddr)
  277. if g.IP == nil {
  278. return fmt.Errorf("%s is not a valid IP address", ipAddr)
  279. }
  280. // ANONYMOUS IP
  281. //
  282. anon, err := g.db.AnonymousIP(g.IP)
  283. if err == nil {
  284. g.Anonymous = Anonymous{
  285. IsAnonymous: anon.IsAnonymous,
  286. IsAnonymousVPN: anon.IsAnonymousVPN,
  287. IsHostingProvider: anon.IsHostingProvider,
  288. IsPublicProxy: anon.IsPublicProxy,
  289. IsTorExitNode: anon.IsTorExitNode,
  290. }
  291. }
  292. // CITY
  293. //
  294. city, err := g.db.City(g.IP)
  295. if err == nil {
  296. subdivisions := make([]string, len(city.Subdivisions), len(city.Subdivisions))
  297. for i, sd := range city.Subdivisions {
  298. subdivisions[i] = sd.Names["en"]
  299. }
  300. g.City = City{
  301. AccuracyRadius: city.Location.AccuracyRadius,
  302. Continent: city.Continent.Names["en"],
  303. ContinentCode: city.Continent.Code,
  304. Country: city.Country.Names["en"],
  305. CountryCode: city.Country.IsoCode,
  306. IsAnonymousProxy: city.Traits.IsAnonymousProxy,
  307. IsSatelliteProvider: city.Traits.IsSatelliteProvider,
  308. Latitude: city.Location.Latitude,
  309. Longitude: city.Location.Longitude,
  310. MetroCode: city.Location.MetroCode,
  311. Name: city.City.Names["en"],
  312. Postcode: city.Postal.Code,
  313. RegisteredCountry: city.RegisteredCountry.Names["en"],
  314. RegisteredCountryCode: city.RegisteredCountry.IsoCode,
  315. RepresentedCountry: city.RepresentedCountry.Names["en"],
  316. RepresentedCountryCode: city.RepresentedCountry.IsoCode,
  317. RepresentedCountryType: city.RepresentedCountry.Type,
  318. Subdivisions: subdivisions,
  319. Timezone: city.Location.TimeZone,
  320. }
  321. }
  322. // COUNTRY
  323. //
  324. country, err := g.db.Country(g.IP)
  325. if err == nil {
  326. g.Country = Country{
  327. Continent: country.Continent.Names["en"],
  328. ContinentCode: country.Continent.Code,
  329. Country: country.Country.Names["en"],
  330. CountryCode: country.Country.IsoCode,
  331. IsAnonymousProxy: country.Traits.IsAnonymousProxy,
  332. IsSatelliteProvider: country.Traits.IsSatelliteProvider,
  333. RegisteredCountry: country.RegisteredCountry.Names["en"],
  334. RegisteredCountryCode: country.RegisteredCountry.IsoCode,
  335. RepresentedCountry: country.RepresentedCountry.Names["en"],
  336. RepresentedCountryCode: country.RepresentedCountry.IsoCode,
  337. RepresentedCountryType: country.RepresentedCountry.Type,
  338. }
  339. }
  340. return nil
  341. }