safing-portmaster/intel/geoip/location.go

167 lines
4.6 KiB
Go

package geoip
import (
"encoding/binary"
"net"
"github.com/umahmood/haversine"
)
const (
earthCircumferenceInKm float64 = 40100 // earth circumference in km
)
// Location holds information regarding the geographical and network location of an IP address.
type Location struct {
Continent struct {
Code string `maxminddb:"code"`
} `maxminddb:"continent"`
Country struct {
ISOCode string `maxminddb:"iso_code"`
} `maxminddb:"country"`
Coordinates Coordinates `maxminddb:"location"`
AutonomousSystemNumber uint `maxminddb:"autonomous_system_number"`
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
}
type Coordinates struct {
AccuracyRadius uint16 `maxminddb:"accuracy_radius"`
Latitude float64 `maxminddb:"latitude"`
Longitude float64 `maxminddb:"longitude"`
}
// 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 int) {
/*
Distance Value
- 0: Other side of the Internet.
- 100: Very near, up to same network / datacenter.
Weighting Goal
- Exposure to different networks shall be limited as much as possible.
- A single network should not see a connection over a large distance.
- Latency should be low.
Weighting Intentions
- Being on the same continent is better than being in the same AS.
- Being in the same country is better than having low coordinate distance.
- Coordinate distance is only a tie breaker, as accuracy varies heavily.
- Same AS with lower coordinate distance beats being on the same continent.
Weighting Configuration
- Continent match: 30
- Country match: 25
- ASOrg match: 20
- ASN match: 15
- Coordinate distance: 0-10
*/
if l.Continent.Code != "" &&
l.Continent.Code == to.Continent.Code {
proximity += 30
if l.Country.ISOCode != "" &&
l.Country.ISOCode == to.Country.ISOCode {
proximity += 25
}
}
if l.AutonomousSystemOrganization != "" &&
l.AutonomousSystemOrganization == to.AutonomousSystemOrganization {
proximity += 20
if l.AutonomousSystemNumber != 0 &&
l.AutonomousSystemNumber == to.AutonomousSystemNumber {
proximity += 15
}
}
// Check coordinates and adjust accuracy value.
accuracy := l.Coordinates.AccuracyRadius
switch {
case l.Coordinates.Latitude == 0 && l.Coordinates.Longitude == 0:
fallthrough
case to.Coordinates.Latitude == 0 && to.Coordinates.Longitude == 0:
// If we don't have any coordinates, return.
return proximity
case to.Coordinates.AccuracyRadius > accuracy:
// If the destination accuracy is worse, use that one.
accuracy = to.Coordinates.AccuracyRadius
}
/*
About the Accuracy Radius
- Range: 1-1000
- Seen values (estimation): 1,5,10,20,50,100,200,500,1000
- The default seems to be 100.
Cxamples
- 1.1.1/24 has 1000: Anycast
- 8.8.0/19 has 1000: Anycast
- 8.8.52/22 has 1: City of Westfield
Conclusion
- Ignore or penalize high accuracy radius.
*/
// Calculate coordinate distance in kilometers.
fromCoords := haversine.Coord{Lat: l.Coordinates.Latitude, Lon: l.Coordinates.Longitude}
toCoords := haversine.Coord{Lat: to.Coordinates.Latitude, Lon: to.Coordinates.Longitude}
_, km := haversine.Distance(fromCoords, toCoords)
if km <= 50 && accuracy <= 100 {
// Give a flat out ten for highly accurate coordinates within 50km.
proximity += 10
} else {
// Else, take a percentage.
distanceInPercent := (earthCircumferenceInKm - km) * 100 / earthCircumferenceInKm
// Apply penalty for locations with low accuracy (targeting accuracy radius >100).
accuracyModifier := 1 - float64(accuracy)/2000
proximity += int(distanceInPercent * 0.10 * accuracyModifier)
}
return proximity
}
// PrimitiveNetworkProximity calculates the numerical distance between two IP addresses. Returns a proximity value between 0 (far away) and 100 (nearby).
func PrimitiveNetworkProximity(from net.IP, to net.IP, ipVersion uint8) int {
var diff float64
switch ipVersion {
case 4:
// TODO: use ip.To4() and :4
a := binary.BigEndian.Uint32(from[12:])
b := binary.BigEndian.Uint32(to[12:])
if a > b {
diff = float64(a - b)
} else {
diff = float64(b - a)
}
case 6:
a := binary.BigEndian.Uint64(from[:8])
b := binary.BigEndian.Uint64(to[:8])
if a > b {
diff = float64(a - b)
} else {
diff = float64(b - a)
}
default:
return 0
}
switch ipVersion {
case 4:
diff /= 256
return int((1 - diff/16777216) * 100)
case 6:
return int((1 - diff/18446744073709552000) * 100)
default:
return 0
}
}