github.com/jimpick/sp-kyc-checks@v0.0.0-20230201194251-fa84fca72da8/checks/geoip/check_geo.go (about) 1 package geoip 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "strconv" 8 "strings" 9 10 "github.com/jftuga/geodist" 11 "github.com/savaki/geoip2" 12 "googlemaps.github.io/maps" 13 ) 14 15 const MAX_DISTANCE = 600 16 17 type GeoData struct { 18 MultiaddrsIPs []MultiaddrsIPsRecord 19 IPsGeolite2 map[string]IPsGeolite2Record 20 IPsBaidu map[string]IPsBaiduRecord 21 IPsGeoIP2 map[string]geoip2.Response 22 } 23 24 func LoadGeoData() (*GeoData, error) { 25 multiaddrsIPs, err := LoadMultiAddrsIPs() 26 if err != nil { 27 return nil, err 28 } 29 30 ipsGeolite2, err := LoadIPsGeolite2() 31 if err != nil { 32 return nil, err 33 } 34 35 ipsBaidu, err := LoadIPsBaidu() 36 if err != nil { 37 return nil, err 38 } 39 40 return &GeoData{ 41 multiaddrsIPs, 42 ipsGeolite2, 43 ipsBaidu, 44 make(map[string]geoip2.Response), 45 }, nil 46 } 47 48 func (g *GeoData) filterByMinerID(ctx context.Context, minerID string, 49 currentEpoch int64) (*GeoData, error) { 50 minEpoch := currentEpoch - 14*24*60*2 // 2 weeks 51 multiaddrsIPs := []MultiaddrsIPsRecord{} 52 ipsGeoLite2 := make(map[string]IPsGeolite2Record) 53 ipsBaidu := make(map[string]IPsBaiduRecord) 54 ipsGeoIP2 := make(map[string]geoip2.Response) 55 for _, m := range g.MultiaddrsIPs { 56 if m.Miner == minerID { 57 if int64(m.Epoch) < minEpoch { 58 log.Printf("IP address %s rejected, too old: %d < %d\n", 59 m.IP, m.Epoch, minEpoch) 60 } else { 61 multiaddrsIPs = append(multiaddrsIPs, m) 62 if r, ok := g.IPsGeolite2[m.IP]; ok { 63 ipsGeoLite2[m.IP] = r 64 } 65 if r, ok := g.IPsBaidu[m.IP]; ok { 66 ipsBaidu[m.IP] = r 67 } 68 r, err := getGeoIP2(ctx, m.IP) 69 if err != nil { 70 return &GeoData{}, err 71 } 72 ipsGeoIP2[m.IP] = r 73 } 74 } 75 } 76 77 return &GeoData{ 78 multiaddrsIPs, 79 ipsGeoLite2, 80 ipsBaidu, 81 ipsGeoIP2, 82 }, nil 83 } 84 85 type ExtraArtifacts struct { 86 GeoData *GeoData 87 GeocodeLocations []geodist.Coord 88 GoogleGeocodeData []maps.GeocodingResult 89 } 90 91 func findMatchGeoLite2(g *GeoData, minerID string, city string, 92 countryCode string, locations []geodist.Coord) bool { 93 var match_found bool = false 94 for ip, geolite2 := range g.IPsGeolite2 { 95 // Match country 96 if geolite2.Country != countryCode { 97 log.Printf("No Geolite2 country match for %s (%s != GeoLite2:%s), IP: %s\n", 98 minerID, countryCode, geolite2.Country, ip) 99 continue 100 } 101 log.Printf("Matching Geolite2 country for %s (%s) found, IP: %s\n", 102 minerID, countryCode, ip) 103 104 // Try to match city 105 if geolite2.City == city { 106 log.Printf("Match found! %s matches Geolite2 city name (%s), IP: %s\n", 107 minerID, city, ip) 108 match_found = true 109 continue 110 } 111 log.Printf("No Geolite2 city match for %s (%s != GeoLite2:%s), IP: %s\n", 112 minerID, city, geolite2.City, ip) 113 114 // Try to match based on Lat/Lng 115 l := geolite2.Geolite2["location"].(map[string]interface{}) 116 geolite2Location := geodist.Coord{ 117 Lat: l["latitude"].(float64), 118 Lon: l["longitude"].(float64), 119 } 120 log.Printf("Geolite2 Lat/Lng: %v for IP %s\n", geolite2Location, ip) 121 122 // Distance based matching 123 for i, location := range locations { 124 log.Printf("Geocoded via Google %s, %s #%d Lat/Long %v", city, 125 countryCode, i+1, location) 126 _, distance, err := geodist.VincentyDistance(location, geolite2Location) 127 if err != nil { 128 log.Println("Unable to compute Vincenty Distance.") 129 continue 130 } else { 131 if distance <= MAX_DISTANCE { 132 log.Printf("Match found! Distance %f km\n", distance) 133 match_found = true 134 continue 135 } 136 log.Printf("No match, distance %f km > %d km\n", distance, MAX_DISTANCE) 137 } 138 } 139 } 140 return match_found 141 } 142 143 func findMatchGeoIP2(g *GeoData, minerID string, city string, 144 countryCode string, locations []geodist.Coord) bool { 145 provisional_match := false 146 match_found := false 147 GEOIP2_LOOP: 148 for ip, geoip2 := range g.IPsGeoIP2 { 149 // Match country 150 if geoip2.Country.IsoCode != countryCode { 151 log.Printf("No GeoIP2 country match for %s (%s != GeoIP2:%s), IP: %s\n", 152 minerID, countryCode, geoip2.Country.IsoCode, ip) 153 continue 154 } 155 log.Printf("Matching GeoIP2 country for %s (%s) found, IP: %s\n", 156 minerID, countryCode, ip) 157 158 // Try to match city 159 for _, cityName := range geoip2.City.Names { 160 if cityName == city { 161 log.Printf("Match found! %s matches GeoIP2 city name (%s), IP: %s\n", 162 minerID, city, ip) 163 match_found = true 164 continue GEOIP2_LOOP 165 } 166 } 167 log.Printf("No GeoIP2 city match for %s (%s != GeoIP2:%s), IP: %s\n", 168 minerID, city, geoip2.City.Names["en"], ip) 169 if geoip2.City.Names["en"] == "" { 170 provisional_match = true 171 } 172 173 // Try to match based on Lat/Lng 174 geoip2Location := geodist.Coord{ 175 Lat: geoip2.Location.Latitude, 176 Lon: geoip2.Location.Longitude, 177 } 178 log.Printf("GeoIP2 Lat/Lng: %v for IP %s\n", geoip2Location, ip) 179 180 // Distance based matching 181 for i, location := range locations { 182 log.Printf("Geocoded via Google %s, %s #%d Lat/Long %v", city, 183 countryCode, i+1, location) 184 _, distance, err := geodist.VincentyDistance(location, geoip2Location) 185 if err != nil { 186 log.Println("Unable to compute Vincenty Distance.") 187 continue 188 } else { 189 if distance <= MAX_DISTANCE { 190 log.Printf("Match found! Distance %f km\n", distance) 191 match_found = true 192 continue 193 } 194 log.Printf("No match, distance %f km > %d km\n", distance, MAX_DISTANCE) 195 } 196 } 197 } 198 if provisional_match { 199 log.Printf("Match found! %s had GeoIP2 entries that matched country, "+ 200 "all with no city data.\n", 201 minerID) 202 match_found = true 203 } 204 return match_found 205 } 206 207 func findMatchBaidu(g *GeoData, minerID string, city string, 208 countryCode string, locations []geodist.Coord) bool { 209 match_found := false 210 for ip, baidu := range g.IPsBaidu { 211 // Try to match city 212 if baidu.City == city { 213 log.Printf("Match found! %s matches city name (%s), IP: %s\n", 214 minerID, city, ip) 215 match_found = true 216 continue 217 } 218 log.Printf("No city match for %s (%s != Baidu:%s), IP: %s\n", 219 minerID, city, baidu.City, ip) 220 221 baiduContent := baidu.Baidu["content"].(map[string]interface{}) 222 baiduPoint := baiduContent["point"].(map[string]interface{}) 223 lon, err := strconv.ParseFloat(baiduPoint["x"].(string), 64) 224 if err != nil { 225 log.Println("Error parsing baidu longitude (x)", err) 226 continue 227 } 228 lat, err := strconv.ParseFloat(baiduPoint["y"].(string), 64) 229 if err != nil { 230 log.Println("Error parsing baidu latitude (y)", err) 231 continue 232 } 233 baiduLocation := geodist.Coord{ 234 Lat: lat, 235 Lon: lon, 236 } 237 log.Printf("Baidu Lat/Lng: %v for IP %s\n", baiduLocation, ip) 238 // Distance based matching 239 for i, location := range locations { 240 log.Printf("Geocoded via Google %s, %s #%d Lat/Long %v", city, 241 countryCode, i+1, location) 242 _, distance, err := geodist.VincentyDistance(location, baiduLocation) 243 if err != nil { 244 log.Println("Unable to compute Vincenty Distance.") 245 continue 246 } else { 247 if distance <= MAX_DISTANCE { 248 log.Printf("Match found! Distance %f km\n", distance) 249 match_found = true 250 continue 251 } 252 log.Printf("No match, distance %f km > %d km\n", distance, MAX_DISTANCE) 253 } 254 } 255 } 256 return match_found 257 } 258 259 // GeoMatchExists checks if the miner has an IP address with a location close to the city/country 260 func GeoMatchExists(ctx context.Context, geodata *GeoData, 261 geocodeClient *maps.Client, currentEpoch int64, minerID string, city string, 262 countryCode string) (bool, ExtraArtifacts, error) { 263 264 // Quick fixes for bad input data 265 if countryCode == "United States" || countryCode == "San Jose, CA" { 266 countryCode = "US" 267 } 268 if countryCode == "Canada" { 269 countryCode = "CA" 270 } 271 countryCode = strings.ToUpper(countryCode) 272 273 log.Printf("Searching for geo matches for %s (%s, %s)", minerID, 274 city, countryCode) 275 g, err := geodata.filterByMinerID(ctx, minerID, currentEpoch) 276 extraArtifacts := ExtraArtifacts{GeoData: g} 277 if err != nil { 278 return false, extraArtifacts, err 279 } 280 281 if len(g.MultiaddrsIPs) == 0 { 282 log.Printf("No Multiaddrs/IPs found for %s\n", minerID) 283 return false, extraArtifacts, nil 284 } 285 286 locations, googleResponse, err := geocodeAddress(ctx, geocodeClient, 287 fmt.Sprintf("%s, %s", city, countryCode)) 288 if err != nil { 289 log.Fatalf("Geocode error: %s", err) 290 } 291 extraArtifacts.GeocodeLocations = locations 292 extraArtifacts.GoogleGeocodeData = googleResponse 293 294 match_found := false 295 296 // First, try with Baidu data 297 if countryCode == "CN" { 298 match_found = findMatchBaidu(g, minerID, city, countryCode, locations) 299 } 300 301 // Next, try with Geolite2 data 302 if !match_found { 303 match_found = findMatchGeoLite2(g, minerID, city, countryCode, locations) 304 } 305 306 // last, try with GeoIP2 API data 307 if !match_found { 308 match_found = findMatchGeoIP2(g, minerID, city, countryCode, locations) 309 } 310 311 if !match_found { 312 log.Println("No match found.") 313 } 314 return match_found, extraArtifacts, nil 315 }