geoip.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. // This package utilizes the MaxMind GeoLite2 GeoIP database https://dev.maxmind.com/geoip/geoip2/geolite2/.
  2. // You must provide your own copy of this database for it to work.
  3. // Read more about how this works at http://owncast.online/docs/geoip
  4. package geoip
  5. import (
  6. "net"
  7. "sync"
  8. "sync/atomic"
  9. "github.com/oschwald/geoip2-golang"
  10. log "github.com/sirupsen/logrus"
  11. )
  12. const geoIPDatabasePath = "data/GeoLite2-City.mmdb"
  13. // Client can look up geography information for IP addresses.
  14. type Client struct {
  15. cache sync.Map
  16. enabled int32
  17. }
  18. // NewClient creates a new Client.
  19. func NewClient() *Client {
  20. return &Client{
  21. enabled: 1, // Try to use GeoIP support by default.
  22. }
  23. }
  24. // GeoDetails stores details about a location.
  25. type GeoDetails struct {
  26. CountryCode string `json:"countryCode"`
  27. RegionName string `json:"regionName"`
  28. TimeZone string `json:"timeZone"`
  29. }
  30. // GetGeoFromIP returns geo details associated with an IP address if we
  31. // have previously fetched it.
  32. func (c *Client) GetGeoFromIP(ip string) *GeoDetails {
  33. if cachedGeoDetails, ok := c.cache.Load(ip); ok {
  34. return cachedGeoDetails.(*GeoDetails)
  35. }
  36. if ip == "::1" || ip == "127.0.0.1" {
  37. return &GeoDetails{
  38. CountryCode: "N/A",
  39. RegionName: "Localhost",
  40. TimeZone: "",
  41. }
  42. }
  43. return c.fetchGeoForIP(ip)
  44. }
  45. // fetchGeoForIP makes an API call to get geo details for an IP address.
  46. func (c *Client) fetchGeoForIP(ip string) *GeoDetails {
  47. // If GeoIP has been disabled then don't try to access it.
  48. if atomic.LoadInt32(&c.enabled) == 0 {
  49. return nil
  50. }
  51. db, err := geoip2.Open(geoIPDatabasePath)
  52. if err != nil {
  53. log.Traceln("GeoIP support is disabled. visit https://owncast.online/docs/geoip to learn how to enable.", err)
  54. atomic.StoreInt32(&c.enabled, 0)
  55. return nil
  56. }
  57. defer db.Close()
  58. var response *GeoDetails
  59. ipObject := net.ParseIP(ip)
  60. record, err := db.City(ipObject)
  61. if err == nil {
  62. // If no country is available then exit
  63. // If we believe this IP to be anonymous then no reason to report it
  64. if record.Country.IsoCode != "" && !record.Traits.IsAnonymousProxy {
  65. var regionName = "Unknown"
  66. if len(record.Subdivisions) > 0 {
  67. if region, ok := record.Subdivisions[0].Names["en"]; ok {
  68. regionName = region
  69. }
  70. }
  71. response = &GeoDetails{
  72. CountryCode: record.Country.IsoCode,
  73. RegionName: regionName,
  74. TimeZone: record.Location.TimeZone,
  75. }
  76. }
  77. } else {
  78. log.Warnln(err)
  79. }
  80. c.cache.Store(ip, response)
  81. return response
  82. }