package app import ( "fmt" egressutilpkg "selective-vpn-api/app/egressutil" "strings" "time" ) func (s *egressIdentityService) lookupGeo(ip string, force bool) (string, string, error) { ip = strings.TrimSpace(ip) if ip == "" { return "", "", fmt.Errorf("empty ip") } now := time.Now() s.geoMu.Lock() if entry, ok := s.geoCache[ip]; ok && !entry.ExpiresAt.IsZero() && now.Before(entry.ExpiresAt) { code := egressutilpkg.NormalizeCountryCode(entry.CountryCode) name := strings.TrimSpace(entry.CountryName) errMsg := strings.TrimSpace(entry.LastError) s.geoMu.Unlock() if code != "" || name != "" { return code, name, nil } if errMsg != "" && !force { return "", "", fmt.Errorf("%s", errMsg) } if !force { return "", "", nil } // Force refresh bypasses negative geo cache to recover country flag quickly. } stale := s.geoCache[ip] s.geoMu.Unlock() geoURLs := egressGeoEndpointsForIP(ip) errs := make([]string, 0, len(geoURLs)) for _, rawURL := range geoURLs { body, err := egressutilpkg.HTTPGetBody(egressHTTPClient, rawURL, egressIdentityGeoTimeout, "selective-vpn-api/egress-identity", 8*1024) if err != nil { errs = append(errs, err.Error()) continue } code, name, err := egressutilpkg.ParseGeoResponse(body) if err != nil { errs = append(errs, err.Error()) continue } s.geoMu.Lock() s.geoCache[ip] = egressGeoCacheEntry{ CountryCode: egressutilpkg.NormalizeCountryCode(code), CountryName: strings.TrimSpace(name), ExpiresAt: now.Add(egressIdentityGeoCacheTTL), } s.geoMu.Unlock() return egressutilpkg.NormalizeCountryCode(code), strings.TrimSpace(name), nil } if strings.TrimSpace(stale.CountryCode) != "" || strings.TrimSpace(stale.CountryName) != "" { return egressutilpkg.NormalizeCountryCode(stale.CountryCode), strings.TrimSpace(stale.CountryName), nil } msg := "geo lookup failed" if len(errs) > 0 { msg = strings.Join(errs, "; ") } s.geoMu.Lock() s.geoCache[ip] = egressGeoCacheEntry{ LastError: msg, ExpiresAt: now.Add(egressIdentityGeoFailTTL), } s.geoMu.Unlock() return "", "", fmt.Errorf("%s", msg) }