diff --git a/http/http.go b/http/http.go index b57eb90..09d1fca 100644 --- a/http/http.go +++ b/http/http.go @@ -34,21 +34,21 @@ type Server struct { type Response struct { IP net.IP `json:"ip"` IPDecimal *big.Int `json:"ip_decimal"` + Country string `json:"country,omitempty"` + CountryISO string `json:"country_iso,omitempty"` + CountryEU *bool `json:"country_eu,omitempty"` RegionName string `json:"region_name,omitempty"` RegionCode string `json:"region_code,omitempty"` - Country string `json:"country,omitempty"` - CountryEU *bool `json:"country_eu,omitempty"` - CountryISO string `json:"country_iso,omitempty"` + MetroCode uint `json:"metro_code,omitempty"` + PostalCode string `json:"zip_code,omitempty"` City string `json:"city,omitempty"` - Hostname string `json:"hostname,omitempty"` Latitude float64 `json:"latitude,omitempty"` Longitude float64 `json:"longitude,omitempty"` + Timezone string `json:"time_zone,omitempty"` ASN string `json:"asn,omitempty"` ASNOrg string `json:"asn_org,omitempty"` + Hostname string `json:"hostname,omitempty"` UserAgent *useragent.UserAgent `json:"user_agent,omitempty"` - PostalCode string `json:"zip_code,omitempty"` - Timezone string `json:"time_zone,omitempty"` - MetroCode uint `json:"metro_code,omitempty"` } type PortResponse struct { @@ -127,18 +127,18 @@ func (s *Server) newResponse(r *http.Request) (Response, error) { Country: country.Name, CountryISO: country.ISO, CountryEU: country.IsEU, - City: city.Name, - Hostname: hostname, - Latitude: city.Latitude, - Longitude: city.Longitude, - ASN: autonomousSystemNumber, - ASNOrg: asn.AutonomousSystemOrganization, - UserAgent: userAgent, - PostalCode: city.PostalCode, - Timezone: city.Timezone, - MetroCode: city.MetroCode, RegionName: city.RegionName, RegionCode: city.RegionCode, + MetroCode: city.MetroCode, + PostalCode: city.PostalCode, + City: city.Name, + Latitude: city.Latitude, + Longitude: city.Longitude, + Timezone: city.Timezone, + ASN: autonomousSystemNumber, + ASNOrg: asn.AutonomousSystemOrganization, + Hostname: hostname, + UserAgent: userAgent, } s.cache.Set(ip, response) return *response, nil diff --git a/http/http_test.go b/http/http_test.go index 60bc048..ab3a789 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -21,7 +21,7 @@ func (t *testDb) Country(net.IP) (geo.Country, error) { } func (t *testDb) City(net.IP) (geo.City, error) { - return geo.City{Name: "Bornyasherk", Latitude: 63.416667, Longitude: 10.416667}, nil + return geo.City{Name: "Bornyasherk", RegionName: "North Elbonia", RegionCode: "1234", MetroCode: 1234, PostalCode: "1234", Latitude: 63.416667, Longitude: 10.416667, Timezone: "Europe/Bornyasherk"}, nil } func (t *testDb) ASN(net.IP) (geo.ASN, error) { @@ -134,7 +134,7 @@ func TestJSONHandlers(t *testing.T) { out string status int }{ - {s.URL, `{"ip":"127.0.0.1","ip_decimal":2130706433,"country":"Elbonia","country_eu":false,"country_iso":"EB","city":"Bornyasherk","hostname":"localhost","latitude":63.416667,"longitude":10.416667,"asn":"AS59795","asn_org":"Hosting4Real","user_agent":{"product":"curl","version":"7.2.6.0","raw_value":"curl/7.2.6.0"}}`, 200}, + {s.URL, `{"ip":"127.0.0.1","ip_decimal":2130706433,"country":"Elbonia","country_iso":"EB","country_eu":false,"region_name":"North Elbonia","region_code":"1234","metro_code":1234,"zip_code":"1234","city":"Bornyasherk","latitude":63.416667,"longitude":10.416667,"time_zone":"Europe/Bornyasherk","asn":"AS59795","asn_org":"Hosting4Real","hostname":"localhost","user_agent":{"product":"curl","version":"7.2.6.0","raw_value":"curl/7.2.6.0"}}`, 200}, {s.URL + "/port/foo", `{"error":"invalid port: foo"}`, 400}, {s.URL + "/port/0", `{"error":"invalid port: 0"}`, 400}, {s.URL + "/port/65537", `{"error":"invalid port: 65537"}`, 400}, diff --git a/iputil/geo/geo.go b/iputil/geo/geo.go index 9708936..6fba74a 100644 --- a/iputil/geo/geo.go +++ b/iputil/geo/geo.go @@ -106,29 +106,30 @@ func (g *geoip) City(ip net.IP) (City, error) { if c, exists := record.City.Names["en"]; exists { city.Name = c } - if len(record.Subdivisions)>0 { + if len(record.Subdivisions) > 0 { if c, exists := record.Subdivisions[0].Names["en"]; exists { city.RegionName = c } if record.Subdivisions[0].IsoCode != "" { city.RegionCode = record.Subdivisions[0].IsoCode } - } + } if !math.IsNaN(record.Location.Latitude) { city.Latitude = record.Location.Latitude } if !math.IsNaN(record.Location.Longitude) { city.Longitude = record.Location.Longitude } - if record.Location.TimeZone != "" { - city.Timezone = record.Location.TimeZone - } if record.Location.MetroCode > 0 && record.Country.IsoCode == "US" { city.MetroCode = record.Location.MetroCode } if record.Postal.Code != "" { city.PostalCode = record.Postal.Code } + if record.Location.TimeZone != "" { + city.Timezone = record.Location.TimeZone + } + return city, nil } diff --git a/iputil/geo/geo/geo.go b/iputil/geo/geo/geo.go new file mode 100644 index 0000000..6fba74a --- /dev/null +++ b/iputil/geo/geo/geo.go @@ -0,0 +1,156 @@ +package geo + +import ( + "math" + "net" + + geoip2 "github.com/oschwald/geoip2-golang" +) + +type Reader interface { + Country(net.IP) (Country, error) + City(net.IP) (City, error) + ASN(net.IP) (ASN, error) + IsEmpty() bool +} + +type Country struct { + Name string + ISO string + IsEU *bool +} + +type City struct { + Name string + Latitude float64 + Longitude float64 + PostalCode string + Timezone string + MetroCode uint + RegionName string + RegionCode string +} + +type ASN struct { + AutonomousSystemNumber uint + AutonomousSystemOrganization string +} + +type geoip struct { + country *geoip2.Reader + city *geoip2.Reader + asn *geoip2.Reader +} + +func Open(countryDB, cityDB string, asnDB string) (Reader, error) { + var country, city, asn *geoip2.Reader + if countryDB != "" { + r, err := geoip2.Open(countryDB) + if err != nil { + return nil, err + } + country = r + } + if cityDB != "" { + r, err := geoip2.Open(cityDB) + if err != nil { + return nil, err + } + city = r + } + if asnDB != "" { + r, err := geoip2.Open(asnDB) + if err != nil { + return nil, err + } + asn = r + } + return &geoip{country: country, city: city, asn: asn}, nil +} + +func (g *geoip) Country(ip net.IP) (Country, error) { + country := Country{} + if g.country == nil { + return country, nil + } + record, err := g.country.Country(ip) + if err != nil { + return country, err + } + if c, exists := record.Country.Names["en"]; exists { + country.Name = c + } + if c, exists := record.RegisteredCountry.Names["en"]; exists && country.Name == "" { + country.Name = c + } + if record.Country.IsoCode != "" { + country.ISO = record.Country.IsoCode + } + if record.RegisteredCountry.IsoCode != "" && country.ISO == "" { + country.ISO = record.RegisteredCountry.IsoCode + } + isEU := record.Country.IsInEuropeanUnion || record.RegisteredCountry.IsInEuropeanUnion + country.IsEU = &isEU + return country, nil +} + +func (g *geoip) City(ip net.IP) (City, error) { + city := City{} + if g.city == nil { + return city, nil + } + record, err := g.city.City(ip) + if err != nil { + return city, err + } + if c, exists := record.City.Names["en"]; exists { + city.Name = c + } + if len(record.Subdivisions) > 0 { + if c, exists := record.Subdivisions[0].Names["en"]; exists { + city.RegionName = c + } + if record.Subdivisions[0].IsoCode != "" { + city.RegionCode = record.Subdivisions[0].IsoCode + } + } + if !math.IsNaN(record.Location.Latitude) { + city.Latitude = record.Location.Latitude + } + if !math.IsNaN(record.Location.Longitude) { + city.Longitude = record.Location.Longitude + } + if record.Location.MetroCode > 0 && record.Country.IsoCode == "US" { + city.MetroCode = record.Location.MetroCode + } + if record.Postal.Code != "" { + city.PostalCode = record.Postal.Code + } + if record.Location.TimeZone != "" { + city.Timezone = record.Location.TimeZone + } + + return city, nil +} + +func (g *geoip) ASN(ip net.IP) (ASN, error) { + asn := ASN{} + if g.asn == nil { + return asn, nil + } + record, err := g.asn.ASN(ip) + if err != nil { + return asn, err + } + if record.AutonomousSystemNumber > 0 { + asn.AutonomousSystemNumber = record.AutonomousSystemNumber + } + if record.AutonomousSystemOrganization != "" { + asn.AutonomousSystemOrganization = record.AutonomousSystemOrganization + } + return asn, nil +} + +func (g *geoip) IsEmpty() bool { + return g.country == nil && g.city == nil +}