You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
99 lines
2.4 KiB
99 lines
2.4 KiB
// This package utilizes the MaxMind GeoLite2 GeoIP database https://dev.maxmind.com/geoip/geoip2/geolite2/. |
|
// You must provide your own copy of this database for it to work. |
|
// Read more about how this works at http://owncast.online/docs/geoip |
|
|
|
package geoip |
|
|
|
import ( |
|
"net" |
|
"sync" |
|
"sync/atomic" |
|
|
|
"github.com/oschwald/geoip2-golang" |
|
log "github.com/sirupsen/logrus" |
|
) |
|
|
|
const geoIPDatabasePath = "data/GeoLite2-City.mmdb" |
|
|
|
// Client can look up geography information for IP addresses. |
|
type Client struct { |
|
cache sync.Map |
|
enabled int32 |
|
} |
|
|
|
// NewClient creates a new Client. |
|
func NewClient() *Client { |
|
return &Client{ |
|
enabled: 1, // Try to use GeoIP support by default. |
|
} |
|
} |
|
|
|
// GeoDetails stores details about a location. |
|
type GeoDetails struct { |
|
CountryCode string `json:"countryCode"` |
|
RegionName string `json:"regionName"` |
|
TimeZone string `json:"timeZone"` |
|
} |
|
|
|
// GetGeoFromIP returns geo details associated with an IP address if we |
|
// have previously fetched it. |
|
func (c *Client) GetGeoFromIP(ip string) *GeoDetails { |
|
if cachedGeoDetails, ok := c.cache.Load(ip); ok { |
|
return cachedGeoDetails.(*GeoDetails) |
|
} |
|
|
|
if ip == "::1" || ip == "127.0.0.1" { |
|
return &GeoDetails{ |
|
CountryCode: "N/A", |
|
RegionName: "Localhost", |
|
TimeZone: "", |
|
} |
|
} |
|
|
|
return c.fetchGeoForIP(ip) |
|
} |
|
|
|
// fetchGeoForIP makes an API call to get geo details for an IP address. |
|
func (c *Client) fetchGeoForIP(ip string) *GeoDetails { |
|
// If GeoIP has been disabled then don't try to access it. |
|
if atomic.LoadInt32(&c.enabled) == 0 { |
|
return nil |
|
} |
|
|
|
db, err := geoip2.Open(geoIPDatabasePath) |
|
if err != nil { |
|
log.Traceln("GeoIP support is disabled. visit https://owncast.online/docs/geoip to learn how to enable.", err) |
|
atomic.StoreInt32(&c.enabled, 0) |
|
return nil |
|
} |
|
defer db.Close() |
|
|
|
var response *GeoDetails |
|
ipObject := net.ParseIP(ip) |
|
|
|
record, err := db.City(ipObject) |
|
if err == nil { |
|
// If no country is available then exit |
|
// If we believe this IP to be anonymous then no reason to report it |
|
if record.Country.IsoCode != "" && !record.Traits.IsAnonymousProxy { |
|
regionName := "Unknown" |
|
if len(record.Subdivisions) > 0 { |
|
if region, ok := record.Subdivisions[0].Names["en"]; ok { |
|
regionName = region |
|
} |
|
} |
|
|
|
response = &GeoDetails{ |
|
CountryCode: record.Country.IsoCode, |
|
RegionName: regionName, |
|
TimeZone: record.Location.TimeZone, |
|
} |
|
} |
|
} else { |
|
log.Warnln(err) |
|
} |
|
|
|
c.cache.Store(ip, response) |
|
|
|
return response |
|
}
|
|
|