Unify and improve country info

This commit is contained in:
Daniel 2023-08-28 15:26:12 +02:00
parent a2c24f131a
commit 28a4ea4709
8 changed files with 857 additions and 1290 deletions

View file

@ -49,7 +49,7 @@ func collectData() interface{} {
if ok && locs.Best().LocationOrNil() != nil { if ok && locs.Best().LocationOrNil() != nil {
loc := locs.Best() loc := locs.Best()
data["Location"] = &Location{ data["Location"] = &Location{
Country: loc.Location.Country.ISOCode, Country: loc.Location.Country.Code,
Coordinates: loc.Location.Coordinates, Coordinates: loc.Location.Coordinates,
ASN: loc.Location.AutonomousSystemNumber, ASN: loc.Location.AutonomousSystemNumber,
ASOrg: loc.Location.AutonomousSystemOrganization, ASOrg: loc.Location.AutonomousSystemOrganization,

View file

@ -251,7 +251,7 @@ func (e *Entity) getLocation(ctx context.Context) {
return return
} }
e.location = loc e.location = loc
e.Country = loc.Country.ISOCode e.Country = loc.Country.Code
e.Coordinates = &loc.Coordinates e.Coordinates = &loc.Coordinates
e.ASN = loc.AutonomousSystemNumber e.ASN = loc.AutonomousSystemNumber
e.ASOrg = loc.AutonomousSystemOrganization e.ASOrg = loc.AutonomousSystemOrganization
@ -272,9 +272,10 @@ func (e *Entity) getLocation(ctx context.Context) {
// Log location // Log location
log.Tracer(ctx).Tracef( log.Tracer(ctx).Tracef(
"intel: located %s in %s (AS%d by %s)%s", "intel: located %s in %s (%s), as part of AS%d by %s%s",
e.IP, e.IP,
loc.Country.ISOCode, loc.Country.Name,
loc.Country.Code,
loc.AutonomousSystemNumber, loc.AutonomousSystemNumber,
loc.AutonomousSystemOrganization, loc.AutonomousSystemOrganization,
flags, flags,
@ -303,6 +304,16 @@ func (e *Entity) GetCountry(ctx context.Context) (string, bool) {
return e.Country, true return e.Country, true
} }
// GetCountryInfo returns the two letter ISO country code and whether it is set.
func (e *Entity) GetCountryInfo(ctx context.Context) *geoip.CountryInfo {
e.getLocation(ctx)
if e.LocationError != "" {
return nil
}
return &e.location.Country
}
// GetASN returns the AS number and whether it is set. // GetASN returns the AS number and whether it is set.
func (e *Entity) GetASN(ctx context.Context) (uint, bool) { func (e *Entity) GetASN(ctx context.Context) (uint, bool) {
e.getLocation(ctx) e.getLocation(ctx)

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
package geoip package geoip
import ( import (
"strings"
"testing" "testing"
) )
@ -8,21 +9,31 @@ func TestCountryInfo(t *testing.T) {
t.Parallel() t.Parallel()
for key, country := range countries { for key, country := range countries {
if key != country.ID { if key != country.Code {
t.Errorf("%s has a wrong ID of %q", key, country.ID) t.Errorf("%s has a wrong country code of %q", key, country.Code)
} }
if country.Name == "" { if country.Name == "" {
t.Errorf("%s is missing name", key) t.Errorf("%s is missing name", key)
} }
if country.Region == "" { if country.Continent.Code == "" {
t.Errorf("%s is missing region", key)
}
if country.ContinentCode == "" {
t.Errorf("%s is missing continent", key) t.Errorf("%s is missing continent", key)
} }
if country.Continent.Region == "" {
t.Errorf("%s is missing continent region", key)
}
if country.Continent.Name == "" {
t.Errorf("%s is missing continent name", key)
}
generatedContinentCode, _, _ := strings.Cut(country.Continent.Region, "-")
if country.Continent.Code != generatedContinentCode {
t.Errorf("%s is has wrong continent code or region", key)
}
if country.Center.Latitude == 0 && country.Center.Longitude == 0 { if country.Center.Latitude == 0 && country.Center.Longitude == 0 {
t.Errorf("%s is missing coords", key) t.Errorf("%s is missing coords", key)
} }
if country.Center.AccuracyRadius == 0 {
t.Errorf("%s is missing accuracy radius", key)
}
// Generate map source from data: // Generate map source from data:
// fmt.Printf( // fmt.Printf(

View file

@ -18,13 +18,7 @@ const (
// Location holds information regarding the geographical and network location of an IP address. // Location holds information regarding the geographical and network location of an IP address.
// TODO: We are currently re-using the Continent-Code for the region. Update this and all dependencies. // TODO: We are currently re-using the Continent-Code for the region. Update this and all dependencies.
type Location struct { type Location struct {
Continent struct { Country CountryInfo `maxminddb:"country"`
Code string `maxminddb:"code"`
} `maxminddb:"continent"`
Country struct {
Name string
ISOCode string `maxminddb:"iso_code"`
} `maxminddb:"country"`
Coordinates Coordinates `maxminddb:"location"` Coordinates Coordinates `maxminddb:"location"`
AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"` AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"`
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"` AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
@ -96,10 +90,9 @@ const (
// EstimateNetworkProximity aims to calculate the distance between two network locations. Returns a proximity value between 0 (far away) and 100 (nearby). // EstimateNetworkProximity aims to calculate the distance between two network locations. Returns a proximity value between 0 (far away) and 100 (nearby).
func (l *Location) EstimateNetworkProximity(to *Location) (proximity float32) { func (l *Location) EstimateNetworkProximity(to *Location) (proximity float32) {
switch { switch {
case l.Country.ISOCode != "" && l.Country.ISOCode == to.Country.ISOCode: case l.Country.Code != "" && l.Country.Code == to.Country.Code:
proximity += weightCountryMatch + weightRegionMatch + weightRegionalNeighborMatch proximity += weightCountryMatch + weightRegionMatch + weightRegionalNeighborMatch
case l.Continent.Code != "" && l.Continent.Code == to.Continent.Code: case l.Country.Continent.Region != "" && l.Country.Continent.Region == to.Country.Continent.Region:
// FYI: This is the region code!
proximity += weightRegionMatch + weightRegionalNeighborMatch proximity += weightRegionMatch + weightRegionalNeighborMatch
case l.IsRegionalNeighbor(to): case l.IsRegionalNeighbor(to):
proximity += weightRegionalNeighborMatch proximity += weightRegionalNeighborMatch

View file

@ -6,11 +6,11 @@ import (
// IsRegionalNeighbor returns whether the supplied location is a regional neighbor. // IsRegionalNeighbor returns whether the supplied location is a regional neighbor.
func (l *Location) IsRegionalNeighbor(other *Location) bool { func (l *Location) IsRegionalNeighbor(other *Location) bool {
if l.Continent.Code == "" || other.Continent.Code == "" { if l.Country.Continent.Region == "" || other.Country.Continent.Region == "" {
return false return false
} }
if region, ok := regions[l.Continent.Code]; ok { if region, ok := regions[l.Country.Continent.Region]; ok {
return utils.StringInSlice(region.Neighbors, other.Continent.Code) return utils.StringInSlice(region.Neighbors, other.Country.Continent.Region)
} }
return false return false
} }

View file

@ -135,12 +135,8 @@ func (dl *DeviceLocation) IsMoreAccurateThan(other *DeviceLocation) bool {
other.Location.AutonomousSystemNumber == 0: other.Location.AutonomousSystemNumber == 0:
// Having an ASN is better than having none. // Having an ASN is better than having none.
return true return true
case dl.Location.Continent.Code != "" && case dl.Location.Country.Code != "" &&
other.Location.Continent.Code == "": other.Location.Country.Code == "":
// Having a Continent is better than having none.
return true
case dl.Location.Country.ISOCode != "" &&
other.Location.Country.ISOCode == "":
// Having a Country is better than having none. // Having a Country is better than having none.
return true return true
case (dl.Location.Coordinates.Latitude != 0 || case (dl.Location.Coordinates.Latitude != 0 ||
@ -178,7 +174,13 @@ func (dl *DeviceLocation) String() string {
dl.Location.Coordinates.Longitude, dl.Location.Coordinates.Longitude,
) )
default: default:
return fmt.Sprintf("%s (AS%d in %s)", dl.IP, dl.Location.AutonomousSystemNumber, dl.Location.Country.ISOCode) return fmt.Sprintf(
"%s (AS%d in %s - %s)",
dl.IP,
dl.Location.AutonomousSystemNumber,
dl.Location.Country.Name,
dl.Location.Country.Code,
)
} }
} }
@ -255,7 +257,7 @@ func (dls *DeviceLocations) AddIP(ip net.IP, source DeviceLocationSource) (dl *D
return nil, false return nil, false
} }
// Only use location if there is data for it. // Only use location if there is data for it.
if geoLoc.Country.ISOCode == "" { if geoLoc.Country.Code == "" {
return nil, false return nil, false
} }
loc.Location = geoLoc loc.Location = geoLoc

View file

@ -2,6 +2,7 @@ package endpoints
import ( import (
"context" "context"
"fmt"
"regexp" "regexp"
"strings" "strings"
@ -14,7 +15,7 @@ var countryRegex = regexp.MustCompile(`^[A-Z]{2}$`)
type EndpointCountry struct { type EndpointCountry struct {
EndpointBase EndpointBase
Country string CountryCode string
} }
// Matches checks whether the given entity matches this endpoint definition. // Matches checks whether the given entity matches this endpoint definition.
@ -27,25 +28,29 @@ func (ep *EndpointCountry) Matches(ctx context.Context, entity *intel.Entity) (E
return NoMatch, nil return NoMatch, nil
} }
country, ok := entity.GetCountry(ctx) countryInfo := entity.GetCountryInfo(ctx)
if !ok { if countryInfo == nil {
return MatchError, ep.makeReason(ep, country, "country data not available to match") return MatchError, ep.makeReason(ep, "", "country data not available to match")
} }
if country == ep.Country { if ep.CountryCode == countryInfo.Code {
return ep.match(ep, entity, country, "IP is located in") return ep.match(
ep, entity,
fmt.Sprintf("%s (%s)", countryInfo.Name, countryInfo.Code),
"IP is located in",
)
} }
return NoMatch, nil return NoMatch, nil
} }
func (ep *EndpointCountry) String() string { func (ep *EndpointCountry) String() string {
return ep.renderPPP(ep.Country) return ep.renderPPP(ep.CountryCode)
} }
func parseTypeCountry(fields []string) (Endpoint, error) { func parseTypeCountry(fields []string) (Endpoint, error) {
if countryRegex.MatchString(fields[1]) { if countryRegex.MatchString(fields[1]) {
ep := &EndpointCountry{ ep := &EndpointCountry{
Country: strings.ToUpper(fields[1]), CountryCode: strings.ToUpper(fields[1]),
} }
return ep.parsePPP(ep, fields) return ep.parsePPP(ep, fields)
} }