CloakHQ/CloakBrowser · error · InvalidOperationException

GeoIP resolution failed: could not discover the egress IP

Error message

GeoIP resolution failed: could not discover the egress IP

What it means

The GeoIP resolver exhausted every egress-IP discovery strategy (echo services, and — when a proxy is configured — direct DNS resolution of the proxy hostname) without learning the public IP. Without an IP the MaxMind lookup cannot proceed, so the library throws this distinct 'discovery failed' error (as opposed to the timeout variant when a deadline expired).

Source

Thrown at dotnet/src/CloakBrowser/GeoIp.cs:120

        var dbPath = await EnsureGeoIpDbAsync(ct).ConfigureAwait(false);

        var timeout = GetGeoIpTimeoutSeconds();
        var deadline = DeadlineFromTimeout(timeout);

        // Exit IP (through proxy, or the machine's own public IP when proxyUrl is
        // null/empty) is most accurate - gateway DNS may differ from exit. Resolved
        // even when the DB is unavailable: the IP does not need the DB, and dropping
        // it on a DB hiccup would let WebRTC fall back to the real IP behind a proxy
        // while the connection shows the proxy IP - a real deanonymization.
        var ip = await ResolveExitIpAsync(proxyUrl, RemainingSeconds(deadline), ct).ConfigureAwait(false);
        // Hostname fallback only applies to a proxy; no proxy -> echo services only.
        if (ip == null && !string.IsNullOrEmpty(proxyUrl) && !DeadlineExpired(deadline))
            ip = ResolveProxyIp(proxyUrl);
        if (ip == null || DeadlineExpired(deadline))
        {
            if (deadline != null && DeadlineExpired(deadline))
                throw new InvalidOperationException($"GeoIP resolution timed out after {timeout:0.0}s");
            throw new InvalidOperationException("GeoIP resolution failed: could not discover the egress IP");
        }

        if (dbPath == null)
            throw new InvalidOperationException("GeoIP resolution failed: GeoIP database is unavailable");

        try
        {
            using var reader = new DatabaseReader(dbPath);
            var resp = reader.City(ip);
            var timezone = resp.Location?.TimeZone;
            var country = resp.Country?.IsoCode;
            string? locale = country != null && CountryLocaleMap.TryGetValue(country, out var l) ? l : null;
            CloakLog.Debug("GeoIP: {0} -> tz={1}, country={2}, locale={3}", ip, timezone, country, locale);
            return (timezone, locale, ip);
        }
        catch (Exception exc)
        {
            throw new InvalidOperationException($"GeoIP lookup failed for {ip}: {exc.Message}", exc);

View on GitHub (pinned to d6bad5de26)

Solutions

  1. Check network egress: curl an IP echo service from the same machine/proxy to confirm it is reachable.
  2. Validate the proxyUrl string is a well-formed, non-empty URL before calling the GeoIP API.
  3. Allow the necessary echo-service domains through the firewall/proxy, or provide the egress IP via the API that accepts one.
  4. If GeoIP is optional for your flow, call the non-Geo path or disable GeoIP resolution.

Example fix

// before
var geo = await browser.GeoIp.ResolveProxyGeoAsync(proxyUrl: ""); // no proxy, echo services blocked

// after
if (!string.IsNullOrWhiteSpace(proxyUrl) || await Network.CanReachEchoServiceAsync())
    var geo = await browser.GeoIp.ResolveProxyGeoAsync(proxyUrl);
else
    var geo = GeoResult.Unknown; // degrade gracefully instead of throwing
Defensive patterns

Strategy: fallback

Validate before calling

bool canDiscover = !string.IsNullOrWhiteSpace(proxyUrl) || await Network.AnyEchoServiceReachableAsync();
if (!canDiscover) geo = GeoResult.Unknown; // skip resolution

Try / catch

catch (InvalidOperationException ex) when (ex.Message.Contains("could not discover the egress IP"))
{
    geo = GeoResult.Unknown; // degrade gracefully: proceed without geo data
}

Prevention

When it happens

Trigger: Calling ResolveProxyGeoAsync / MaybeResolveGeoIpAsync / Collect when: no echo service responds AND there is no proxyUrl, or the proxyUrl is empty, or ResolveProxyIp returned null (DNS failure / deadline already passed), and no deadline expiration is involved.

Common situations: Air-gapped or heavily firewalled networks blocking all IP echo endpoints; passing an empty or whitespace proxyUrl by mistake; offline development machines; DNS failures resolving the proxy host; IPv6-only environments unsupported by the echo services.

Related errors


AI-assisted analysis of CloakHQ/CloakBrowser@d6bad5de26 (2026-08-28). Data as JSON: /api/errors/e0ba80bb02a65a59. Report an issue: GitHub.