java.lang.Object
net.i2p.router.transport.CountryLookup

public class CountryLookupextends Object
Country-code lookup, caching, and strict-country policy for the router.

Extracted from CommSystemFacadeImpl (2026-09-06) so the country cluster - a per-peer country cache with periodic cleanup, over-capacity eviction, strict-country gating, and startup GeoIP queueing - lives apart from the facade core. The facade retains thin delegation stubs so the public static and commSystem() APIs are unchanged.

All pure decision logic (cache eviction decisions, memory-independent helpers) is exposed as package-visible static methods so it can be tested without a running router; see CountryLookupDecisionTest.

  • Constructor Details

    • CountryLookup

      public CountryLookup(RouterContext ctx, GeoIP geoIP)
      Create a lookup over the netDb's router IPs, backed by the GeoIP database.
      Parameters:
      ctx - non-null
      geoIP - non-null
  • Method Details

    • start

      public void start()
      Collect the IPs for all routers in the DB and queue them for lookup. The recurring lookup pipeline starts on the first pass only.
    • queueLookup

      public void queueLookup(byte[] ip)
      Queues a GeoIP lookup for the given IP.
      Parameters:
      ip - ipv4 or ipv6
    • getOurCountry

      public String getOurCountry()
      Country code for this router, from the GeoIP lookup.
      Returns:
      two-letter lower-case country code or null
    • isInStrictCountry

      public boolean isInStrictCountry()
      Are we in a strict country.
      Returns:
      whether in strict country
    • isInStrictCountry

      public boolean isInStrictCountry(Hash peer)
      Are they in a strict country. Not recommended for our local router hash, as we may not be either in the cache or netdb, or may not be publishing an IP.
      Parameters:
      peer - non-null
      Returns:
      whether in strict country
    • isInStrictCountry

      public boolean isInStrictCountry(RouterInfo ri)
      Are they in a strict country.
      Parameters:
      ri - non-null
      Returns:
      whether in strict country
    • getCountry

      public String getCountry(Hash peer)
      Uses the transport IP first because that lookup is fast, then the IP from the netDb. Not recommended for our local router hash, as we may not be either in the cache or netdb, or may not be publishing an IP.

      As of 0.9.32, works only for literal IPs, returns null for hostnames.

      Parameters:
      peer - not ourselves - use getOurCountry() for that
      Returns:
      two-letter lower-case country code or xx for non-banned peers, or null otherwise
    • getCountry

      public String getCountry(String ip)
      The two-letter country code for an IP address string.
      Parameters:
      ip - IP address string
      Returns:
      two-letter country code or null if unknown
    • getCountryName

      public String getCountryName(String c)
      Full name for a country code, or the code if we don't know the name.
      Parameters:
      c - the two letter country code to resolve
      Returns:
      the untranslated country name, or the code itself when geoIP data is unavailable
    • getCountries

      public Map<String,String> getCountries()
      Provides country code mappings.
      Returns:
      Unmodifiable map of lower-case country codes to untranslated names. Returns empty map if geoIP data is unavailable.
    • getDomain

      public static String getDomain(String hostname)
      Domain name from a reverse DNS hostname.

      Multi-part TLDs (co.uk, com.au, ...) are recognized so the registrable domain is returned rather than a bare second-level label.

      Parameters:
      hostname - the reverse DNS host name to reduce to its registrable domain
      Returns:
      domain name only from reverse dns hostname lookups
    • getIP

      static byte[] getIP(RouterInfo ri)
      Return first IP (v4 or v6) we find, any transport. Not validated, may be local, etc.

      As of 0.9.32, works only for literal IPs, returns null for host names.

      Parameters:
      ri - the RouterInfo whose addresses are scanned for a literal IP
      Returns:
      IP or null
    • getValidIP

      public static byte[] getValidIP(RouterInfo ri)
      Return first valid IP (v4 or v6) we find, any transport. Local and other invalid IPs will not be returned.

      As of 0.9.32, works only for literal IPs, returns null for host names.

      Parameters:
      ri - the RouterInfo whose addresses are scanned for a publicly routable IP
      Returns:
      IP or null
    • getCompatibleIP

      public static byte[] getCompatibleIP(RouterInfo ri)
      IP address compatible with our capabilities (IPv4/IPv6). Prefers IPv4 if we support it.
      Parameters:
      ri - RouterInfo to get IP from
      Returns:
      IP or null
    • getFirstValidIPOfType

      static byte[] getFirstValidIPOfType(RouterInfo ri, boolean wantIPv6)
      First valid IP of the specified type (IPv4 or IPv6).
      Parameters:
      ri - the RouterInfo whose addresses are scanned
      wantIPv6 - true to accept a 16 byte address, false to accept a 4 byte one
      Returns:
      the first valid IP of type
    • supportsIPv4

      static boolean supportsIPv4(RouterContext ctx)
      Check if we support outbound IPv4 connections.
      Parameters:
      ctx - the router context supplying the transport enablement properties
      Returns:
      true when NTCP or UDP is enabled, so an outbound IPv4 connection is possible
    • supportsIPv6

      static boolean supportsIPv6(RouterContext ctx)
      Check if we support outbound IPv6 connections.
      Parameters:
      ctx - the router context supplying the transport enablement properties
      Returns:
      true when neither NTCP nor UDP has IPv6 explicitly disabled
    • evictExpired

      static int evictExpired(Map<Hash,String> cache, Map<Hash,Long> timestamps, long now, long expiryMs)
      Remove stale and unresolved ("xx") entries from the country cache.

      An entry is expired when its timestamp is strictly older than expiryMs; "xx" (unresolved) entries are always dropped. Called both periodically from getCountry(Hash) and as the first stage of trimToCapacity(Map, Map, long, long, int) so the hot path never recomputes the loop.

      Parameters:
      cache - country cache to drain (entries removed in place)
      timestamps - parallel timestamp map for the same keys
      now - cache age reference in millis
      expiryMs - age threshold in millis (strict >)
      Returns:
      number of entries removed
    • trimToCapacity

      static void trimToCapacity(Map<Hash,String> cache, Map<Hash,Long> timestamps, long now, long expiryMs, int maxSize)
      Bring an over-capacity country cache back under its size bound.

      First advances the expired-entry pass via evictExpired(Map, Map, long, long); if still at or above maxSize, drops the oldest quarter by timestamp. The redundant removeIf()/stream cascade in the original facade is preserved, so hot-path behavior (evict expired first, then oldest) is unchanged.

      Parameters:
      cache - country cache to shrink (entries removed in place)
      timestamps - parallel timestamp map for the same keys
      now - cache age reference in millis
      expiryMs - age threshold in millis for the expired-entry pass
      maxSize - size bound that triggers trimming
    • isUnknownCountryCode

      static boolean isUnknownCountryCode(String code)
      True when the cache stores an unresolved-country marker ("xx").
      Parameters:
      code - the cached country name to test
      Returns:
      true for the unresolved "xx" marker, and for a null code