java.lang.Object
net.i2p.router.tunnel.pool.ConnectChecker
net.i2p.router.tunnel.pool.TunnelPeerSelector
Direct Known Subclasses:
ClientPeerSelector, ExploratoryPeerSelector

public abstract class TunnelPeerSelectorextends ConnectChecker
Coordinate the selection of peers to go into a tunnel for one particular pool.
  • Field Details

    • DEFAULT_EXCLUDE_CAPS

      private static final String DEFAULT_EXCLUDE_CAPS
      The capacities that disqualify a peer by default.
    • _cfgCtx

      private static volatile RouterContext _cfgCtx
      Context the cached config values were read from.
    • _cfgRefreshed

      private static volatile long _cfgRefreshed
      When the cached config values were last read.
    • _cachedExcludeCaps

      private static volatile String _cachedExcludeCaps
      Cached capability letters to exclude.
    • _cachedExplicitPeers

      private static volatile String _cachedExplicitPeers
      Cached explicit peer list from the config.
    • _cachedIbExplUnreachable

      private static volatile boolean _cachedIbExplUnreachable
      Cached flag to skip unreachable inbound exploratory hops.
    • _cachedObExplUnreachable

      private static volatile boolean _cachedObExplUnreachable
      Cached flag to skip unreachable outbound exploratory hops.
    • _cachedIbClientUnreachable

      private static volatile boolean _cachedIbClientUnreachable
      Cached flag to skip unreachable inbound client hops.
    • _cachedObClientUnreachable

      private static volatile boolean _cachedObClientUnreachable
      Cached flag to skip unreachable outbound client hops.
    • _cachedIbExplSlow

      private static volatile boolean _cachedIbExplSlow
      Cached flag to skip slow inbound exploratory hops.
    • _cachedObExplSlow

      private static volatile boolean _cachedObExplSlow
      Cached flag to skip slow outbound exploratory hops.
    • _cachedIbClientSlow

      private static volatile boolean _cachedIbClientSlow
      Cached flag to skip slow inbound client hops.
    • _cachedObClientSlow

      private static volatile boolean _cachedObClientSlow
      Cached flag to skip slow outbound client hops.
    • CONFIG_REFRESH_MS

      private static final long CONFIG_REFRESH_MS
      How often the cached config values are refreshed.
      See Also:
    • ENDPOINT_CACHE_TTL_MS

      private static final long ENDPOINT_CACHE_TTL_MS
      Endpoint eligibility cache: avoids re-checking ~670 fast peers on every selectSingleHop / buildReplacementTunnels cycle. Caches the RouterInfo / canConnect decision only — the banlist is always consulted live (cheap map lookup) so a newly banlisted peer is never selected from cache. Separate caches for IBGW and OBEP because the same Hash can have different eligibility per role. Entries expire after ENDPOINT_CACHE_TTL_MS; oversized maps are pruned opportunistically at amortized intervals. Pure performance — correctness is preserved on cache miss (re-check) and stale entries are bounded to the TTL.
      See Also:
    • ENDPOINT_CACHE_MAX_SIZE

      private static final int ENDPOINT_CACHE_MAX_SIZE
      Entries held in each endpoint cache.
      See Also:
    • ENDPOINT_PRUNE_STRIDE

      private static final int ENDPOINT_PRUNE_STRIDE
      Prune at most every this many cache misses once over capacity.
      See Also:
    • _ibgwCache

      The cached inbound gateway endpoints.
    • _obepCache

      The cached outbound endpoint entries.
    • _endpointPruneCounter

      private static final AtomicInteger _endpointPruneCounter
      Counts selections to time the endpoint cache pruning.
    • ATTACK_THRESHOLD

      protected static final double ATTACK_THRESHOLD
      Threshold for detecting tunnel build attacks.
      See Also:
    • STARTUP_WARNING_SUPPRESS_MS

      protected static final long STARTUP_WARNING_SUPPRESS_MS
      Duration in ms to suppress startup warnings.
      See Also:
    • MIN_WINDOW_MULTIPLIER

      private static final int MIN_WINDOW_MULTIPLIER
      Multiplier applied to the base activity window by getActivityWindow(RouterContext, double).
      See Also:
    • MAX_WINDOW_MULTIPLIER

      private static final int MAX_WINDOW_MULTIPLIER
      Largest bandwidth window multiplier allowed.
      See Also:
    • DEFAULT_WINDOW_MULTIPLIER

      private static final int DEFAULT_WINDOW_MULTIPLIER
      Default bandwidth window multiplier.
      See Also:
    • _windowMultiplier

      private static volatile int _windowMultiplier
      Tuner-controlled multiplier for the peer-selection activity window. Widening it re-admits recently-good peers whose last successful test has aged out during a build slump. Adjusted by the Tuner's ActivityWindowParam from tunnel.buildSuccessRate; clamped to [1, 4].
    • PEER_SELECTION_COOLDOWN_MS

      protected static final long PEER_SELECTION_COOLDOWN_MS
      Peers selected within this window are excluded from further selection to ensure diversity.
      See Also:
    • MIN_PROVEN_RESPONDER_COUNT

      static final int MIN_PROVEN_RESPONDER_COUNT
      Minimum proven-responder entries before cold-start seeding kicks in. At startup the map is empty, so first-build selection has no proven preference. Seeding with random high-tier peers provides a warm start that converges to real data quickly.
      See Also:
    • _provenResponders

      protected static final Map<Hash,Long> _provenResponders
      Peers whose most recent tunnel participation succeeded, mapped to the time of that success. Selection prefers fresh entries via ClientPeerSelector.compareQuality; entries older than PROVEN_RESPONDER_WINDOW_MS carry no weight and are pruned.
    • PROVEN_RESPONDER_WINDOW_MS

      protected static final long PROVEN_RESPONDER_WINDOW_MS
      Recency window for proven-responder proof.
      See Also:
    • _peerCooldowns

      protected static final Map<Hash,Long> _peerCooldowns
      Shared cooldown map across all peer selectors.
    • FIRST_HOP_FAIL_COOLDOWN_MS

      protected static final long FIRST_HOP_FAIL_COOLDOWN_MS
      Peers that failed as first hop (first hop unreachable) excluded for this long.
      See Also:
    • _firstHopFails

      protected static final Map<Hash,Long> _firstHopFails
      Tracks when a peer last failed as first hop.
    • KEEPALIVE_INTERVAL_MS

      private static final long KEEPALIVE_INTERVAL_MS
      How often to send keepalive pings to established Fast/HighCap peers.
      See Also:
    • KEEPALIVE_TARGET_TIERS

      private static final int KEEPALIVE_TARGET_TIERS
      Peers requested from the fast tier in a keepalive cycle.
      See Also:
    • KEEPALIVE_HIGH_CAP_DEPTH

      private static final int KEEPALIVE_HIGH_CAP_DEPTH
      Peers requested from the high-capacity tier in a keepalive cycle.

      Set deeper than KEEPALIVE_TARGET_TIERS because the fast and high-capacity tiers are both ranked on throughput, so their top entries overlap heavily and equal depth returns nearly the same peers twice. Only KEEPALIVE_ACTION_BUDGET of the union is ever acted on, so the deeper request costs set insertions and a counter, not sends: the budget check runs first in the loop, and a peer past the budget is tallied without ever reaching the stale check or the RouterInfo lookup.

      Measured over an 85-minute run at equal depth, the union collapsed from ~395 to ~203 peers while the action budget held at 200 — so servicing never fell, only the surplus above budget did. Deeper high-cap selection lifted the union to ~600-800, restoring a surplus of several hundred.

      See Also:
    • KEEPALIVE_ACTION_BUDGET

      private static final int KEEPALIVE_ACTION_BUDGET
      Most peers acted on in one cycle, bounding a cycle's cost.
      See Also:
    • _lastKeepAlive

      private static final ConcurrentHashMap<Hash,Long> _lastKeepAlive
      Tracks last keepalive send time per peer.
    • FAILURE_MAP_MAX_SIZE

      protected static final int FAILURE_MAP_MAX_SIZE
      Size bound for the cooldown maps (_peerCooldowns, _firstHopFails, ExploratoryPeerSelector._exploratoryCooldowns). Entries are recorded on selection failures, tunnel rejects, and tunnel reuse. They expire lazily (read-time filtering during selection) and are bulk-pruned only when a map exceeds this bound, so these maps stay small — 128 entries is already generous. This bound only limits growth between selections; it does not evict live entries.
      See Also:
    • KEEPALIVE_MAP_MAX_SIZE

      private static final int KEEPALIVE_MAP_MAX_SIZE
      Size bound for _lastKeepAlive. Larger than the failure maps because it tracks one entry per Fast/HighCap peer being keepalived (keepalive budget is 400 peers per cycle), so hundreds of live entries are normal. Stale entries are expired time-based on every selection.
      See Also:
    • QUALITY_RELAXED

      static final int QUALITY_RELAXED
      Minimum evidence bar when builds are healthy. @since 0.9.71+.
      See Also:
    • QUALITY_STRICT

      static final int QUALITY_STRICT
      Minimum evidence bar under stress: a live session or a recent successful tunnel test. @since 0.9.71+.
      See Also:
    • ATTACK_RELAX_EXIT

      static final double ATTACK_RELAX_EXIT
      Whether the exclude caps are currently relaxed, and by how much build success has to recover before they are enforced again.

      Wider than ATTACK_THRESHOLD on purpose: relaxing the caps widens the peer pool, which raises build success, which would otherwise release the relaxation immediately and put the caps back — a loop that ratchets the peer pool between wide and narrow several times a minute. The gap is what lets the widening actually take hold.

      See Also:
    • _excludeCapsRelaxed

      private static volatile boolean _excludeCapsRelaxed
      Latch for the exclude-cap relaxation, shared by every selection.

      Process-wide on purpose: the caps are a property of the router's current state, not of one selection, so two concurrent selections must not disagree about whether the pool is relaxed.

    • MIN_VERSION

      private static final String MIN_VERSION
      SSU2 fixes (2.1.0), Congestion fixes (2.2.0).
      See Also:
    • PROP_OUTBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE

      private static final String PROP_OUTBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE
      Config key for excluding unreachable outbound exploratory hops.
      See Also:
    • PROP_OUTBOUND_CLIENT_EXCLUDE_UNREACHABLE

      private static final String PROP_OUTBOUND_CLIENT_EXCLUDE_UNREACHABLE
      Config key for excluding unreachable outbound client hops.
      See Also:
    • PROP_INBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE

      private static final String PROP_INBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE
      Config key for excluding unreachable inbound exploratory hops.
      See Also:
    • PROP_INBOUND_CLIENT_EXCLUDE_UNREACHABLE

      private static final String PROP_INBOUND_CLIENT_EXCLUDE_UNREACHABLE
      Config key for excluding unreachable inbound client hops.
      See Also:
    • DEFAULT_OUTBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE

      private static final boolean DEFAULT_OUTBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE
      Unreachable hops are kept by default here.
      See Also:
    • DEFAULT_OUTBOUND_CLIENT_EXCLUDE_UNREACHABLE

      private static final boolean DEFAULT_OUTBOUND_CLIENT_EXCLUDE_UNREACHABLE
      Unreachable hops are kept by default here.
      See Also:
    • DEFAULT_INBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE

      private static final boolean DEFAULT_INBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE
      Unreachable hops are kept by default here.
      See Also:
    • DEFAULT_INBOUND_CLIENT_EXCLUDE_UNREACHABLE

      private static final boolean DEFAULT_INBOUND_CLIENT_EXCLUDE_UNREACHABLE
      Unreachable hops are kept by default here.
      See Also:
    • PROP_OUTBOUND_EXPLORATORY_EXCLUDE_SLOW

      private static final String PROP_OUTBOUND_EXPLORATORY_EXCLUDE_SLOW
      Config key for excluding slow outbound exploratory hops.
      See Also:
    • PROP_OUTBOUND_CLIENT_EXCLUDE_SLOW

      private static final String PROP_OUTBOUND_CLIENT_EXCLUDE_SLOW
      Config key for excluding slow outbound client hops.
      See Also:
    • PROP_INBOUND_EXPLORATORY_EXCLUDE_SLOW

      private static final String PROP_INBOUND_EXPLORATORY_EXCLUDE_SLOW
      Config key for excluding slow inbound exploratory hops.
      See Also:
    • PROP_INBOUND_CLIENT_EXCLUDE_SLOW

      private static final String PROP_INBOUND_CLIENT_EXCLUDE_SLOW
      Config key for excluding slow inbound client hops.
      See Also:
    • HASH_BASE64_COMPARATOR

      private static final Comparator<Hash> HASH_BASE64_COMPARATOR
      Orders peers by base64 hash for stable selection.
    • REASON_LABELS

      private static final Map<String,String> REASON_LABELS
      Display labels for the peer skip reasons.
    • SCARCE_FAST_PEERS

      public static final int SCARCE_FAST_PEERS
      Number of fast peers at or above which the peer set is considered well supplied and the activity window is never widened on availability grounds.

      Deliberately far below the ~1000 a healthy router typically holds: the question is not "is the network healthy" but "are we short of the peers selection would otherwise use". Only then is trading recency for pool thickness the right trade.

      See Also:
    • MAX_WINDOW_MS

      public static final long MAX_WINDOW_MS
      Upper bound on the activity window, applied after the multiplier. @since 0.9.71+.
      See Also:
    • WINDOW_MULTIPLIER_STAT

      public static final String WINDOW_MULTIPLIER_STAT
      Stat name for the live window multiplier. @see #publishWindowMultiplier.
      See Also:
    • WINDOW_MULTIPLIER_RATES

      private static final long[] WINDOW_MULTIPLIER_RATES
      Rate periods for WINDOW_MULTIPLIER_STAT. Must not be null: RateStat dereferences the array in its constructor, so a null here throws before the stat is registered. The window is a level rather than a rate, so a single short period is enough to show the current value; the longer periods are kept so the entry renders consistently with its neighbours on /stats.
    • _lastPublishedMultiplier

      private static volatile int _lastPublishedMultiplier
      Last value handed to the stat, so repeated publishes stay quiet.
  • Constructor Details

    • TunnelPeerSelector

      protected TunnelPeerSelector(RouterContext context)
      Creates a selector that reads its peer profiles from the router context.
      Parameters:
      context - the router context
  • Method Details

    • pruneEndpointCache

      private static void pruneEndpointCache(ConcurrentHashMap<Hash, TunnelPeerSelector.EndpointCacheEntry> cache, long now)
      Prune expired entries when the cache grows too large. Called opportunistically before inserts — no background thread needed. Amortized: only one prune scan per ENDPOINT_PRUNE_STRIDE oversized misses so a hot selection loop cannot burn CPU on full-map removeIf every insert.
      Parameters:
      cache - the endpoint decision cache to prune
      now - the current time in ms, compared against each entry's expiry
    • refreshPeerConfig

      private static void refreshPeerConfig(RouterContext ctx)
      Refresh the cached peer-selection configuration from properties at most once per CONFIG_REFRESH_MS, or immediately when the context changes. Benign race: duplicate refreshes are idempotent writes. Values without a configured property cache as null, preserving the per-call default behavior of the callers.
      Parameters:
      ctx - the context whose properties are cached; a new context forces an immediate refresh
    • getCachedExcludeCaps

      private static String getCachedExcludeCaps(RouterContext ctx)
      Returns the cached exclude-caps config value.
      Parameters:
      ctx - the router context
      Returns:
      the capability letters
    • getCachedExplicitPeers

      private static String getCachedExplicitPeers(RouterContext ctx)
      Returns the cached explicit peers config value.
      Parameters:
      ctx - the router context
      Returns:
      the explicit peer list
    • getCachedIbExplUnreachable

      private static boolean getCachedIbExplUnreachable(RouterContext ctx)
      Returns the cached inbound exploratory unreachable flag.
      Parameters:
      ctx - the router context
      Returns:
      true to exclude unreachable peers
    • getCachedObExplUnreachable

      private static boolean getCachedObExplUnreachable(RouterContext ctx)
      Returns the cached outbound exploratory unreachable flag.
      Parameters:
      ctx - the router context
      Returns:
      true to exclude unreachable peers
    • getCachedIbClientUnreachable

      private static boolean getCachedIbClientUnreachable(RouterContext ctx)
      Returns the cached inbound client unreachable flag.
      Parameters:
      ctx - the router context
      Returns:
      true to exclude unreachable peers
    • getCachedObClientUnreachable

      private static boolean getCachedObClientUnreachable(RouterContext ctx)
      Returns the cached outbound client unreachable flag.
      Parameters:
      ctx - the router context
      Returns:
      true to exclude unreachable peers
    • getCachedIbExplSlow

      private static boolean getCachedIbExplSlow(RouterContext ctx)
      Returns the cached inbound exploratory slow flag.
      Parameters:
      ctx - the router context
      Returns:
      true to exclude slow peers
    • getCachedObExplSlow

      private static boolean getCachedObExplSlow(RouterContext ctx)
      Returns the cached outbound exploratory slow flag.
      Parameters:
      ctx - the router context
      Returns:
      true to exclude slow peers
    • getCachedIbClientSlow

      private static boolean getCachedIbClientSlow(RouterContext ctx)
      Returns the cached inbound client slow flag.
      Parameters:
      ctx - the router context
      Returns:
      true to exclude slow peers
    • getCachedObClientSlow

      private static boolean getCachedObClientSlow(RouterContext ctx)
      Returns the cached outbound client slow flag.
      Parameters:
      ctx - the router context
      Returns:
      true to exclude slow peers
    • formatExcludedPeers

      protected static String formatExcludedPeers(Set<Hash> peers)
      Format a set of excluded peers for logging, with exclusion reasons when the set is an TunnelPeerSelector.Excluder or ExcluderBase.
      Parameters:
      peers - the excluded peer hashes, or null for none
      Returns:
      a loggable string naming each excluded router by its 6-character base64 hash
    • isFirstHopFailing

      public static boolean isFirstHopFailing(RouterContext ctx, Hash peer)
      Check if a peer recently failed as first hop and should be excluded. During ghost cascades (high ghost count), the cooldown is shortened from 5 min to 60s to rehabilitate peers faster when the network is stressed. Many peers are ghosted through no fault of their own.
      Parameters:
      ctx - the router context
      peer - the candidate to test; its first-hop reachability is probed
      Returns:
      true if the peer should be excluded
    • getEffectiveFirstHopCooldown

      private static long getEffectiveFirstHopCooldown(RouterContext ctx)
      Effective first-hop fail cooldown. During ghost cascades (>50 ghosts), shorten from 5 min to 60s to rehabilitate peers faster when the network is stressed.
      Parameters:
      ctx - the context to read the current ghost count from
      Returns:
      the effective first hop cooldown
    • recordFirstHopFail

      protected static void recordFirstHopFail(RouterContext ctx, Hash peer)
      Record that a peer failed as first hop (first hop unreachable). Prevents re-selection as first hop for the cooldown, and bulk-prunes the shared static maps when they exceed their size caps.
      Parameters:
      ctx - the router context
      peer - the first-hop router to record as currently unreachable for new tunnel builds
    • prunePeerMaps

      protected static void prunePeerMaps(RouterContext ctx)
      Prune expired entries from static peer maps.

      Not on a timer - recordFirstHopFail() calls it once any of the maps has grown past its size cap. Read-time filtering in the selectors is what keeps selection correct; this is hygiene for the entries that filtering leaves in place.

      Parameters:
      ctx - the router context
    • seedProvenResponders

      static void seedProvenResponders(RouterContext ctx, long now)
      Seed the proven-responder map with random peers from the router's peer profiles when the map is below MIN_PROVEN_RESPONDER_COUNT. At cold start (after router restart or long idle period) the map is empty, so first-build selection has no proven preference and may pick unreliable peers. Seeding with random high-tier peers provides a warm start that converges to real proven data after the first few builds. Seeded entries use a near-expired timestamp (now - window + 5 min) so they expire quickly and only provide anti-concentration, not a quality boost over truly proven peers. Best-effort; concurrent access is safe.
      Parameters:
      ctx - the router context
      now - current time in ms
    • compareProven

      static int compareProven(long t1, long t2, long now)
      Proven-responder comparison for the quality cascade: a peer whose last tunnel participation succeeded within the recency window sorts before one without; two peers on the same side compare equal so later cascade stages decide.
      Parameters:
      t1 - last proven-join time of the first peer, 0 if none
      t2 - last proven-join time of the second peer, 0 if none
      now - current time from the router clock
      Returns:
      negative if only p1 is proven, positive if only p2 is, else 0
    • isProvenResponder

      static boolean isProvenResponder(long lastJoin, long now)
      Whether a proven-join timestamp falls inside the recency window.
      Parameters:
      lastJoin - the last successful-participation time, 0 if none
      now - current time from the router clock
      Returns:
      true if the proof is fresh
    • preferProven

      static int preferProven(List<Hash> hops, int from, int to, Map<Hash,Long> proof, long now, long window, Iterator<Hash> replacements)
      Replace hops lacking fresh proof with proven replacements, in order. Slots outside [from, to) are protected (never replaced); hops with fresh proof are kept and their peers reserved, so replacements stay unique and proven peers are never duplicated into another slot. Pure decision helper; package visible for tests.
      Parameters:
      hops - selected peers, modified in place
      from - first replaceable slot index
      to - index after the last replaceable slot
      proof - peer -> last successful-participation time
      now - current time from the router clock
      window - recency window for proof
      replacements - eligible proven candidates, most preferred first; consumed as used
      Returns:
      number of swaps performed
    • dropUnreliable

      static boolean dropUnreliable(List<Hash> hops, Set<Hash> unreliable, int minKeep)
      Remove unreliable hops while at least minKeep remain. Pure decision helper; package visible for tests.
      Parameters:
      hops - selected peers, modified in place
      unreliable - the peers to remove
      minKeep - minimum list size to leave
      Returns:
      true if any hop was removed
    • getPeersInPool

      protected static Set<Hash> getPeersInPool(RouterContext ctx, TunnelPool pool)
      All non-self peers in active tunnels of the given pool. Used to enforce per-pool diversity: no peer in more than 1 tunnel per pool.
      Parameters:
      ctx - the router context
      pool - the tunnel pool to scan
      Returns:
      set of peer hashes already in active tunnels of this pool
    • getPeersInAllPools

      protected static Set<Hash> getPeersInAllPools(RouterContext ctx)
      All non-self peers in active tunnels across ALL pools (client IB/OB + exploratory IB/OB). Used for cross-pool diversity: when the fast tier is large enough, each fast peer serves at most one pool, forcing the selector to use unused peers and accumulate tunnel history for proper retention/demotion decisions.
      Parameters:
      ctx - the router context
      Returns:
      set of peer hashes in any active tunnel
    • addFreshCooldownExclusions

      protected static int addFreshCooldownExclusions(Map<Hash,Long> cooldowns, long cutoff, Set<Hash> exclude)
      Add cooldown entries still inside their window (value > cutoff) to the exclusion set without mutating the map. Returns the count added. Shared by both selectors; the maps themselves stay separate (_peerCooldowns for client pools, the exploratory map for exploratory selections).
      Parameters:
      cooldowns - the cooldown map to scan (never mutated)
      cutoff - entries with value <= cutoff are expired and skipped
      exclude - the exclusion set to add fresh entries to
      Returns:
      the number of entries added
    • isZeroHopSettings

      protected static boolean isZeroHopSettings(TunnelPoolSettings settings)
      True if the settings describe a zero-hop pair: length <= 0, length override == 0, or length + variance <= 0. Used to decide whether the paired (inbound) pool needs a connected furthest hop (OBEP) so the build reply can be delivered.
      Parameters:
      settings - the paired pool's settings
      Returns:
      true if the pair is effectively zero-hop
    • hasTunnelLongerThanOne

      protected static boolean hasTunnelLongerThanOne(TunnelPool pool)
      True if the pool has at least one active tunnel longer than one hop.
      Parameters:
      pool - the tunnel pool to scan (non-null)
      Returns:
      true if any tunnel has length > 1
    • hasRecoveredFromFailure

      protected static boolean hasRecoveredFromFailure(RouterContext ctx, Hash peer)
      Check if a peer has recovered from failure and can be reconsidered. Uses the effective first-hop cooldown (shortened during ghost cascades).
      Parameters:
      ctx - the router context
      peer - the candidate to test; its first-hop reachability is probed
      Returns:
      true if the peer has recovered
    • isInStartupGracePeriod

      public static boolean isInStartupGracePeriod(RouterContext ctx)
      Is the router in the startup grace period? During startup, peers haven't accumulated test history yet, so quality filters (pre-qualification, tier capping) should be relaxed to allow tunnels to build. Public so the Tuner can share the same definition when deciding whether to widen the activity window.
      Parameters:
      ctx - the router context
      Returns:
      true if uptime is between 1ms and STARTUP_WARNING_SUPPRESS_MS
    • isInStartupGracePeriod

      protected boolean isInStartupGracePeriod()
      Convenience instance method wrapping the static helper.
      Returns:
      true if the router is in the startup grace period
    • selectPeers

      public abstract List<Hash> selectPeers(TunnelPoolSettings settings)
      Which peers should go into the next tunnel for the given settings?
      Parameters:
      settings - the tunnel pool settings
      Returns:
      ordered list of Hash objects (one per peer) specifying what order they should appear in a tunnel (ENDPOINT FIRST). This includes the local router in the list. Never null; an empty list means no peers could be selected.
    • selectPeers

      public List<Hash> selectPeers(TunnelPoolSettings settings, Set<Hash> excludeFirstHops)
      Select peers for a new tunnel, with an optional set of first-hop peers to exclude from selection. The default implementation ignores the exclusion set and delegates to selectPeers(TunnelPoolSettings); subclasses that support first-hop diversity (e.g. ClientPeerSelector) override this to exclude those peers.
      Parameters:
      settings - the tunnel pool settings
      excludeFirstHops - first-hop peers already targeted by concurrent builds in the same dispatch batch, may be null
      Returns:
      ordered list of Hash objects (ENDPOINT FIRST), never null
    • getLength

      protected int getLength(TunnelPoolSettings settings)
      Determine the tunnel length (number of hops).
      Parameters:
      settings - the tunnel pool settings
      Returns:
      randomized number of hops 0-7, not including ourselves
    • shouldSelectExplicit

      protected boolean shouldSelectExplicit(TunnelPoolSettings settings)
      For debugging, also possibly for restricted routes? Needs analysis and testing.
      Parameters:
      settings - the tunnel pool settings
      Returns:
      true on roughly one call in four when the pool names explicit peers, false otherwise (always false for an exploratory pool)
    • selectExplicit

      protected List<Hash> selectExplicit(TunnelPoolSettings settings, int length)
      For debugging, also possibly for restricted routes. Needs analysis and testing.
      Parameters:
      settings - the tunnel pool settings
      length - the desired length of the tunnel
      Returns:
      the list of explicit peer hashes for the tunnel
    • getExclude

      protected Set<Hash> getExclude(boolean isInbound, boolean isExploratory)
      Create the exclusion set for this pool, for passing to ProfileOrganizer. The set is seeded with TunnelManager.selectPeersInTooManyTunnels() and is then populated via the contains() calls.
      Parameters:
      isInbound - true for inbound tunnels
      isExploratory - true for exploratory tunnels
      Returns:
      set of excluded peers
    • getBuildSuccess

      static double getBuildSuccess(RouterContext ctx)
      Current tunnel build success ratio, 1.0 when no data is available.

      Expensive: each call performs 6 RateStat lookups and 6 rate fetches (ProfileOrganizer.getTunnelBuildSuccess()). Fetch once per selection or scan and pass the value down; never call per candidate peer. A value of 1.0 (no data) keeps the attack-threshold gates closed at boot instead of treating missing stats as an attack.

      Parameters:
      ctx - the router context
      Returns:
      the ratio in [0.0, 1.0]
    • getExclusionReason

      private String getExclusionReason(Hash peerHash, boolean isInbound, boolean isExploratory, double buildSuccess, long activityWindow)
      Check if a peer should be excluded from closest hop selection. This performs connectivity checks and version capability validation. Used by Excluder to classify exclusion reasons for diagnostics.
      Parameters:
      peerHash - the peer hash to check
      isInbound - true if this is for an inbound tunnel
      isExploratory - true if this is for exploratory tunnels
      buildSuccess - the build success ratio, fetched once per selection
      activityWindow - the connectivity-signal window in ms, computed once per selection
      Returns:
      the exclusion reason, or null if peer should not be excluded
    • getHardExclusionReason

      private String getHardExclusionReason(Hash peerHash, boolean isInbound, boolean isExploratory, double buildSuccess)
      Hard exclusion reasons only — skips the no-signal (pre-qualification) check. Used when peer scarcity demands that untested peers be allowed as tunnel candidates. Only bans, unreachable, no-RouterInfo, congestion, and slow/capped peers remain excluded.
      Parameters:
      peerHash - the candidate peer to check
      isInbound - true for inbound tunnel selection, chooses the applicable unreachable and slow filters
      isExploratory - true for exploratory selections, excludes floodfill peers
      buildSuccess - the current tunnel build success ratio, 0.0-1.0, cached by the caller
      Returns:
      the hard exclusion reason, or null if the peer passes all hard gates
    • shouldPreQual

      static boolean shouldPreQual(boolean isExploratory, boolean inStartupGrace, double buildSuccess)
      Whether strict client-pool pre-qualification should be applied to a candidate peer. Never for exploratory selections and never during the startup grace period; otherwise always.

      This used to also switch off below ATTACK_THRESHOLD, on the reasoning that a pool collapse is worsened by rejecting candidates that merely lack recent activity. That reasoning inverted the problem: switching off admitted peers with no connectivity evidence at all, those peers timed out, and the resulting drop in build success held the switch off — a self-sustaining loop. The bar now rises under stress instead (see requiredSignalQuality(double)), so this predicate no longer reads buildSuccess at all. A NaN success ratio is likewise irrelevant here. Pure decision — no context access, safe for unit tests.

      Parameters:
      isExploratory - true for exploratory selections
      inStartupGrace - true while the router is in its startup grace period
      buildSuccess - unused; retained so callers and tests keep one signature
      Returns:
      true if strict pre-qualification should run
    • requiredSignalQuality

      static int requiredSignalQuality(double buildSuccess)
      Which quality of connectivity evidence a candidate must show to be pre-qualified, as a function of how well tunnel building is going.

      This replaces the previous all-or-nothing switch that disabled pre-qualification entirely once build success fell below ATTACK_THRESHOLD. That switch was self-defeating: the peers it admitted were precisely the ones with no connectivity evidence, so they timed out, which depressed build success further and kept the switch off. A live observation of that loop: builds expiring as Hop1.establishedSilent, far ends classified FarEndUnknown at roughly 5:1 over FarEndUnreachable, and build success oscillating in the 29-46% band directly beneath the threshold.

      Under stress the bar is raised, not lowered. QUALITY_STRICT accepts only a recent successful test or a live transport session — the evidence that a peer is actually reachable — whereas QUALITY_RELAXED additionally accepts mere recent contact or an old favourable acceptance ratio. Requiring more proof when builds are failing is the correct response: a collapse means the marginal peer we can still reach is worth more than the marginal peer we merely hope is reachable.

      Exploratory pools and the startup grace period never pre-qualify at all, so they do not consult this.

      Parameters:
      buildSuccess - the tunnel build success ratio in [0.0, 1.0]
      Returns:
      QUALITY_STRICT when build success is at or below ATTACK_THRESHOLD, otherwise QUALITY_RELAXED
    • hasConnectivitySignal

      static boolean hasConnectivitySignal(PeerProfile profile, long now, long activityWindow, boolean established, int quality)
      Pre-qualification signal check at the given strictness.

      At QUALITY_RELAXED this is exactly hasConnectivitySignal(PeerProfile, long, long): recent contact, a recent successful test, or any favourable acceptance history.

      At QUALITY_STRICT only the two strong signals count — a successful tunnel test inside the activity window, or a live transport session (passed in by the caller, which already knows it). Recent contact alone is not enough: a peer that is merely sending us traffic is not thereby known to accept an inbound build request, which is what this gate is protecting against.

      Pure decision — no context access, safe for unit tests.

      Parameters:
      profile - the peer profile, may be null when no session is live
      now - current time in milliseconds
      activityWindow - the dynamic activity window in milliseconds
      established - whether a live transport session exists for the peer
      quality - one of QUALITY_RELAXED, QUALITY_STRICT
      Returns:
      true if the profile shows enough connectivity signal
    • hasConnectivitySignal

      static boolean hasConnectivitySignal(PeerProfile profile, long now, long activityWindow)
      Pre-qualification signal check: does the peer profile show recent connectivity evidence? Heard from or successfully sent to within the last 30 minutes, a successful tunnel test within the activity window, or any test history at all with a >50% acceptance ratio. Pure decision — no context access, safe for unit tests.
      Parameters:
      profile - the peer profile (non-null)
      now - current time in milliseconds
      activityWindow - the dynamic activity window in milliseconds
      Returns:
      true if the profile shows recent connectivity signal
    • getEffectiveExcludeCaps

      private static String getEffectiveExcludeCaps(RouterContext ctx, double buildSuccess)
      Exclusion caps to apply, adapting to build success. During low build success (<40%) the M, N, O, D and P exclusions are relaxed, as they are during the first five minutes of uptime when the ratio is not yet meaningful.

      Never null: getExcludeCaps(RouterContext) substitutes DEFAULT_EXCLUDE_CAPS for a missing setting, and this folds away any null that survives so the callers can iterate the result without a guard.

      Parameters:
      ctx - the router context
      buildSuccess - the build success ratio, fetched once per selection
      Returns:
      non-null, possibly empty
    • relaxedExcludeCaps

      static String relaxedExcludeCaps(String configured, double buildSuccess, long uptimeMs)
      Strip the M, N, O, D, P capability exclusions from the configured caps when the build success ratio is below ATTACK_THRESHOLD or the router is within its first STARTUP_WARNING_SUPPRESS_MS of uptime (when the ratio is not yet meaningful). A ratio of 0.0 (no data) relaxes as well, matching the conservative startup behavior. Pure decision — no context access, safe for unit tests.
      Parameters:
      configured - the configured exclude caps, possibly null or empty
      buildSuccess - the build success ratio in [0.0, 1.0]
      uptimeMs - router uptime in milliseconds
      Returns:
      the configured caps with M/N/O/D/P removed when relaxing, otherwise unchanged
    • relaxedExcludeCaps

      static String relaxedExcludeCaps(String configured, double buildSuccess, long uptimeMs, boolean alreadyRelaxed)
      Strip the M, N, O, D, P capability exclusions from the configured caps when the build success ratio is below ATTACK_THRESHOLD or the router is within its first STARTUP_WARNING_SUPPRESS_MS of uptime (when the ratio is not yet meaningful). A ratio of 0.0 (no data) relaxes as well, matching the conservative startup behavior.

      P4: alreadyRelaxed is the latch, and this is where the hysteresis actually lives. The previous form derived the decision from buildSuccess alone, which is a plain threshold wearing hysteresis's clothes — the buildSuccess >= 0.44 arm could only assign false to a variable already false, so it was dead code and the [0.40, 0.44) band was not a dead zone but the strict side of a 0.40 threshold. Build success oscillating across 0.40 therefore flipped the caps on every crossing. Passing the prior state makes the band real: enter below ATTACK_THRESHOLD, leave at or above ATTACK_RELAX_EXIT, hold in between.

      Pure decision — no context access and no shared state, safe for unit tests.

      Parameters:
      configured - the configured exclude caps, possibly null or empty
      buildSuccess - the build success ratio in [0.0, 1.0]
      uptimeMs - router uptime in milliseconds
      alreadyRelaxed - whether the caps were already relaxed, i.e. the latch state from the previous decision
      Returns:
      the configured caps with M/N/O/D/P removed when relaxing, otherwise unchanged
    • stripCaps

      private static String stripCaps(String configured)
      Remove the M/N/O/D/P capability exclusions, leaving every other cap intact.
      Parameters:
      configured - the configured capability exclusion string
      Returns:
      the same caps with M, N, O, D and P removed
    • allowFirewalledUnderAttack

      static boolean allowFirewalledUnderAttack(String capabilities, double buildSuccess)
      Should we allow a firewalled (U-cap) peer? During attacks (build success below ATTACK_THRESHOLD), allow U-cap peers that also publish M, N, O, P, or X capability. Peers without the U cap are always allowed. Pure decision — no context access, safe for unit tests.
      Parameters:
      capabilities - the peer's capability string, possibly null
      buildSuccess - the build success ratio, fetched once per selection
      Returns:
      true if the peer may be used despite the U cap
    • wasRecentlyRejected

      private boolean wasRecentlyRejected(PeerProfile profile, long cutoffMillis)
      Whether the peer was rejected as a gateway too recently.
      Parameters:
      profile - the peer's profile
      cutoffMillis - how far back to look
      Returns:
      true if a recent rejection blocks the peer
    • shouldExcludeFloodfillPeer

      private boolean shouldExcludeFloodfillPeer(RouterInfo routerInfo)
      Whether a floodfill is excluded from tunnel service.
      Parameters:
      routerInfo - the candidate peer
      Returns:
      true to skip the peer
    • isIPv6Only

      protected boolean isIPv6Only()
      Are we IPv6 only?
      Returns:
      true if configured for IPv6 only
    • allowAsOBEP

      protected boolean allowAsOBEP(Hash h)
      Should we allow as OBEP? This just checks for IPv4 support. Will return false for IPv6-only. This is intended for tunnel candidates, where we already have the RI. Will not force RI lookups. Default true.
      Parameters:
      h - the peer hash
      Returns:
      true if the peer can be used as OBEP
    • allowAsIBGW

      protected boolean allowAsIBGW(Hash h)
      Should we allow as IBGW? This just checks for the "R" capability and IPv4 support. Will return false for hidden or IPv6-only. This is intended for tunnel candidates, where we already have the RI. Will not force RI lookups. Default true.
      Parameters:
      h - the peer hash
      Returns:
      true if the peer can be used as IBGW
    • getClosestHopExclude

      protected Set<Hash> getClosestHopExclude(boolean isInbound, Set<Hash> toAdd)
      Pick peers that we want to avoid for the first OB hop or last IB hop. There's several cases of importance:
      1. Inbound and we are hidden - Exclude all unless connected. This is taken care of in ClientPeerSelector and TunnelPeerSelector selectPeers(TunnelPoolSettings), not here.
      2. We are IPv6-only. Exclude all v4-only peers, unless connected This is taken care of here.
      3. We have NTCP or SSU disabled. Exclude all incompatible peers, unless connected This is taken care of here.
      4. Minimum version check, if we are some brand-new sig type, or are using some new tunnel build method. Not currently used, but this is where to implement the checks if needed. Make sure that ClientPeerSelector and TunnelPeerSelector selectPeers() call this when needed.

      As of 0.9.58, this a set with only toAdd, for use in ProfileOrganizer. The set will be populated via the contains() calls.

      Parameters:
      isInbound - true for inbound tunnels
      toAdd - set of peers to initially populate the exclusion set
      Returns:
      the exclusion set to consult, never null
    • shouldExclude

      public static boolean shouldExclude(RouterContext ctx, RouterInfo peer)
      Should the peer be excluded based on its published caps, crypto, and version?
      Parameters:
      ctx - Router context for peer count checks
      peer - The peer to evaluate
      Returns:
      true if the peer should be excluded
    • shouldExclude

      public static boolean shouldExclude(RouterContext ctx, RouterInfo peer, double buildSuccess)
      Should the peer be excluded based on its published caps, crypto, and version?

      Variant for per-peer selection loops that already fetched the build success ratio once; avoids re-reading router statistics per peer.

      Parameters:
      ctx - Router context for peer count checks
      peer - The peer to evaluate
      buildSuccess - the build success ratio in [0.0, 1.0]
      Returns:
      true if the peer should be excluded
    • getExcludeCaps

      private static String getExcludeCaps(RouterContext ctx)
      Exclude caps to apply during peer selection.
      Parameters:
      ctx - the context to read the exclusion property from
      Returns:
      non-null, possibly empty
    • shouldExclude

      private static boolean shouldExclude(RouterContext ctx, RouterInfo peer, String excl, boolean isExploratory, double buildSuccess)
      Should the peer be excluded based on its published caps, crypto, and version?
      Parameters:
      ctx - Router context for peer count checks
      peer - The peer to evaluate
      excl - Characters representing capabilities we want to exclude
      isExploratory - true if this check is for an exploratory pool
      buildSuccess - the build success ratio, fetched once per selection
      Returns:
      true if the peer should be excluded
    • countKnownCaps

      static int countKnownCaps(String cap)
      Count the meaningful capabilities a peer publishes, used to reject peers with no useful caps when there are plenty of fast peers. Pure — safe for unit tests.
      Parameters:
      cap - the peer's capability string
      Returns:
      the count of known caps (0-2+)
    • isOutdatedVersion

      static boolean isOutdatedVersion(String version)
      True if the peer runs a version too old to interoperate: not the published version and older than MIN_VERSION. Pure — safe for unit tests.
      Parameters:
      version - the peer's version string
      Returns:
      true if the version is outdated
    • filterUnreachable

      private boolean filterUnreachable(boolean isInbound, boolean isExploratory)
      Whether to skip unreachable peers.
      Parameters:
      isInbound - true for inbound tunnel selection
      isExploratory - true for exploratory tunnel selection
      Returns:
      true if unreachable peers should be skipped
    • filterSlow

      protected boolean filterSlow(boolean isInbound, boolean isExploratory)
      Whether to skip peers that are slow.
      Parameters:
      isInbound - true for inbound tunnels
      isExploratory - true for exploratory tunnels
      Returns:
      true unless configured otherwise
    • orderPeers

      protected void orderPeers(List<Hash> rv, SessionKey key)
      Order peers using the given key.
      Parameters:
      rv - the list to order
      key - the session key for ordering
    • isDuplicateSequence

      protected boolean isDuplicateSequence(TunnelPoolSettings settings, List<Hash> newPeers)
      Check if the selected peer sequence matches an existing tunnel in the pool. Prevents duplicate peer sequences which could weaken anonymity.
      Parameters:
      settings - the tunnel pool settings
      newPeers - the newly selected peers (excluding self)
      Returns:
      true if duplicate detected
    • matchesExistingTunnel

      static boolean matchesExistingTunnel(TunnelInfo existing, List<Hash> newPeers, boolean isInbound)
      Compare a newly selected peer sequence (excluding self) against one existing tunnel. Lengths must match (existing includes self, so one longer than newPeers) and every peer must be equal in order, starting from the gateway side for inbound (index 0) or after self for outbound (index 1). Pure decision. No context access, safe for unit tests.
      Parameters:
      existing - the existing tunnel to compare against
      newPeers - the newly selected peers (excluding self)
      isInbound - true for inbound tunnels
      Returns:
      true if the sequences match
    • regeneratePeers

      protected List<Hash> regeneratePeers(TunnelPoolSettings settings, List<Hash> peers, int attempt)
      Regenerate tunnel peers to avoid duplicate sequence.

      The canonical order is the key-distance sort from orderPeers(List, SessionKey). Because that sort is deterministic, regenerating always reproduced the identical sequence and could not avoid duplicates. Each regeneration attempt now rotates the canonical order by a step derived from the pool key, so successive attempts yield different sequences while staying key-derived (attacker-unpredictable).

      Parameters:
      settings - the tunnel pool settings
      peers - the peers to regenerate (not modified)
      attempt - 1-based regeneration attempt number
      Returns:
      regenerated peer list (same or different)
    • checkTunnel

      protected boolean checkTunnel(boolean isInbound, boolean isExploratory, List<Hash> tunnel)
      Connectivity check. Check that each hop can connect to the next, including us. Check that the OBEP is not IPv6-only, and the IBGW is reachable and not hidden or IPv6-only. Tells the profile manager to blame the hop, and returns false on failure.
      Parameters:
      isInbound - true for inbound tunnels
      isExploratory - true for exploratory tunnels
      tunnel - ENDPOINT FIRST, GATEWAY LAST!!!!, length 2 or greater
      Returns:
      true if the exploratory hop checks passed, false if the OBEP is IPv6-only or the IBGW is unreachable, hidden or IPv6-only
    • checkTunnel

      private boolean checkTunnel(List<Hash> tunnel)
      Connectivity check. Check that each hop can connect to the next, including us.
      Parameters:
      tunnel - ENDPOINT FIRST, GATEWAY LAST!!!!
      Returns:
      true if every hop can reach the next, including us, false on the first hop pair that cannot
    • supportsNTCP2

      protected static boolean supportsNTCP2(RouterContext ctx, Hash peer)
      Check if a peer supports NTCP2 transport. NTCP2 is preferred for direct connections (first hop / IBGW) because SSU2-only peers are typically firewalled, requiring introduction-based connections that are slower and less reliable.
      Parameters:
      ctx - the router context
      peer - hash of the peer to check
      Returns:
      true if the peer has an NTCP2 address
    • hasValidTransportAddress

      protected static boolean hasValidTransportAddress(RouterContext ctx, Hash peer)
      Check if a peer's RouterInfo has at least one reachable SSU or NTCP address. Peers without valid transport addresses always fail as first hops and trigger bans in EstablishmentManager.establish(OutNetMessage) — they should be excluded from selection.
      Parameters:
      ctx - the router context
      peer - hash of the peer to check
      Returns:
      true if the peer has a valid SSU or NTCP address
    • hasValidTransportAddress

      static boolean hasValidTransportAddress(RouterInfo ri)
      Address half of hasValidTransportAddress(RouterContext, Hash), split out so a caller that already holds the RouterInfo does not have to probe the netdb a second time for the same entry. Pure decision, safe for unit tests.
      Parameters:
      ri - the RouterInfo to check (non-null)
      Returns:
      true if it has at least one usable SSU or NTCP address
    • lookupRouterInfoUnvalidated

      private static RouterInfo lookupRouterInfoUnvalidated(RouterContext ctx, Hash peer)
      Fetch a peer RouterInfo for a message we address to it, preferring the unvalidated store lookup.

      The peer reached this point through selection or through the already established transport, so it passed store-time validation, and the build requestor accepts unvalidated cached entries. Running the full validate() here (address walk, banlist, country, version and slow-router checks) per peer per cycle bought nothing, and for keepAlive(RouterContext, boolean) it ran for up to 200 already-established peers a cycle purely to build an OutNetMessage.

      Unvalidated first, validating second — the same ordering BuildRequestor uses for every hop of a build request, so a pre-connect addresses the RouterInfo a build would. The fallback runs only when the unvalidated probe missed: one extra store lookup for a peer we hold no entry for, and it resolves the peer when an entry landed in between the two probes.

      Parameters:
      ctx - the router context
      peer - hash of the peer
      Returns:
      the RouterInfo, or null if neither lookup resolves it
    • isUsableRouterAddress

      static boolean isUsableRouterAddress(RouterAddress ra)
      Is the RouterAddress usable for tunnel building? SSU requires protocol v2 plus a valid IP/port or an introduction; SSU2 requires a valid IP/port or an introduction; NTCP/NTCP2 require a valid IP/port. Pure decision — no context access, safe for unit tests.
      Parameters:
      ra - the router address to check (non-null)
      Returns:
      true if the address is usable
    • isLowAcceptanceRatio

      protected static boolean isLowAcceptanceRatio(RouterContext ctx, Hash peer)
      Check if a peer has a history of rejecting tunnel build requests. Returns true when the lifetime acceptance ratio drops below 30%. Defaults to false (accept) when no data is available.
      Parameters:
      ctx - the router context
      peer - hash of the peer to check
      Returns:
      true if the acceptance ratio is below threshold
    • preConnectTo

      protected static TunnelPeerSelector.PreConnectOutcome preConnectTo(RouterContext ctx, Hash peer)
      Send a DatabaseLookupMessage to a peer so it answers with our RouterInfo, which forces a full session handshake rather than the empty DataMessage that would leave the peer with nothing to reply to.

      The RouterInfo is fetched unvalidated first, and peers with no usable transport address are skipped here rather than inside establish(), so a peer with dead addresses cannot trigger a ban.

      Parameters:
      ctx - the router context
      peer - the peer to pre-contact
      Returns:
      what happened, so the caller can tally why a peer was skipped
    • noteFirstHopRtt

      static void noteFirstHopRtt(RouterContext ctx, Hash peer, Transport transport)
      Extract the transport's measured direct-link RTT and record it on the peer.

      The probe goes out over one specific transport and that transport already maintains a per-peer round trip time, so the first-hop latency tier selection needs is available here for free. Reading it from the transport also keeps it honest: it is the cost of the one hop, not the cost of a multi-hop tunnel that merely contained this peer.

      A freshly established session has not measured yet and reports 0, which is discarded rather than stored as a fast peer; the next pass picks it up.

      Parameters:
      ctx - the router context
      peer - the probed peer
      transport - the transport the probe was sent over
    • applyProbeFailure

      static void applyProbeFailure(RouterContext ctx, Hash peer)
      Act on a pre-connect probe that could not be delivered.

      Separated from the job so the decision is testable without a transport and a job queue: a probe that failed every transport retry means the peer is not reachable, so it leaves the fast tiers at once rather than after the three-strike send-failure path.

      Demotion is immediate because unlike a send failure on a live tunnel, this has no ambiguity to accumulate against. There was no reply at all. Waiting for strikes leaves exactly the peers this sweep exists to find sitting in the tier where selection will pick them again.

      Parameters:
      ctx - the router context
      peer - the peer that failed to answer the probe
    • isStalePeer

      static boolean isStalePeer(RouterContext ctx, Hash peer)
      Check whether a peer is stale — no contact (heard from or heard about) within the dynamic activity window. The window adapts to network visibility: 500+ active peers use 1 hour, 200+ use 2 hours, 100+ use 4 hours, fewer than 100 use 8 hours (fresh router building up picture).

      Stale peers are skipped during first-hop selection and keepalive to avoid wasting resources on peers that are likely offline. Skipped during the first 15 minutes of uptime (startup grace).

      Parameters:
      ctx - the router context
      peer - hash of the peer to check
      Returns:
      true if the peer has not been heard from or about within the activity window
    • isStalePeer

      static boolean isStalePeer(RouterContext ctx, Hash peer, double buildSuccess)
      Check whether a peer is stale — no contact (heard from or heard about) within the dynamic activity window. The window adapts to network visibility: 500+ active peers use 1 hour, 200+ use 2 hours, 100+ use 4 hours, fewer than 100 use 8 hours (fresh router building up picture).

      Unprofiled peers (never-heard-of) are never stale — excluding them would starve the pool of newcomers during recovery — the activity window below only applies once we have profile data to judge. Profiled peers are always checked against the activity window regardless of uptime. The startup grace period previously allowed stale profiled peers to be selected, which caused tunnel builds to fail on peers with no recent contact.

      Parameters:
      ctx - the router context
      peer - hash of the peer to check
      buildSuccess - the build success ratio, fetched once by the caller
      Returns:
      true if the peer has a profile and has not been heard from or about within the activity window
    • getActivityWindow

      public static long getActivityWindow(RouterContext ctx, double buildSuccess)
      Compute the activity window for peer selection based on current network visibility. When we hear from many peers, we can be selective (short window). When the router is fresh or the network is sparse, use a wider window to avoid starving peer pools.

      The base window (from active-peer count) is scaled by the Tuner-controlled multiplier (setWindowMultiplier(int)) and floored to at least 6 hours when build success is in the degraded/purgatory band, so good peers whose last successful test has aged out are re-admitted instead of pruned in a self-reinforcing loop.

      Parameters:
      ctx - the router context
      buildSuccess - the build success ratio, fetched once by the caller
      Returns:
      activity window in milliseconds
    • getActivityWindow

      public static long getActivityWindow(RouterContext ctx)
      Compute the activity window for peer selection from the base ladder only, scaled by whatever multiplier the availability gate has granted.

      The buildSuccess argument is ignored. It used to drive an acute floor that pinned the window at 4h whenever build success was in the degraded band, but build success measures outcomes, not peer supply: it is depressed by unresponsive peers, build timeouts and unreachable destinations just as readily as by an over-tight recency window. Keying the window to it therefore closed a positive feedback loop — worse builds widened the window, admitting older peers, which made builds worse still — and did so while ample fast and high-capacity peers were sitting unused. Peer supply is the only thing the window should respond to, and that decision now lives solely in windowMultiplierFor(int, boolean, int, int), so the Tuner and the pool starvation bypass cannot disagree with this ladder.

      Parameters:
      ctx - the router context
      Returns:
      activity window in milliseconds
    • windowMultiplierFor

      public static int windowMultiplierFor(int fastPeers, boolean startupGrace, int min, int max)
      Multiplier the peer-supply signal earns, independent of any build-outcome metric. This is the single gate on widening the activity window, shared by the Tuner param, the pool starvation bypass and getActivityWindow(RouterContext) so the three cannot drift apart.

      Rules, in order:

      1. Above SCARCE_FAST_PEERS the window is never widened, no matter how bad build success looks. Plenty of good peers are available, so admitting stale ones is a pure loss of quality.
      2. During the startup grace period the window is widened to the ceiling. This is the one legitimate exception: a cold router has an almost-empty profile set, so peers look stale purely for lack of test history rather than for any fault of their own.
      3. Below the threshold the multiplier ramps linearly to the ceiling as fast peers approach zero.

      Pure, so the policy is testable without a router, and public because the Tuner and the pool both consult it. A single shared definition of "we are short of peers" is the whole point; two copies would drift apart and reintroduce exactly the disagreement this change removes.

      Parameters:
      fastPeers - peers currently classified fast
      startupGrace - true while the router is within its startup grace period
      min - the multiplier returned when fast peers are plentiful
      max - the multiplier returned while the router is short of fast peers, or during the startup grace period
      Returns:
      multiplier in [min, max]
    • windowMultiplierFor

      public static int windowMultiplierFor(RouterContext ctx)
      windowMultiplierFor(int, boolean, int, int) bound to the live router: reads the current fast-peer count and startup state.
      Parameters:
      ctx - the router context
      Returns:
      multiplier in [MIN_WINDOW_MULTIPLIER, MAX_WINDOW_MULTIPLIER}
    • setWindowMultiplier

      public static void setWindowMultiplier(int mult)
      Tuner-controlled activity-window multiplier, clamped to [1, 4] (MIN_WINDOW_MULTIPLIER..MAX_WINDOW_MULTIPLIER). Values outside the range are clamped, not rejected, so a Tuner param change can never widen the window past the configured maximum.
      Parameters:
      mult - requested multiplier; clamped into [1, 4] before storing
    • getMaxWindowMultiplier

      public static int getMaxWindowMultiplier()
      Ceiling for getWindowMultiplier(). Exposed so the pool's starvation bypass can widen the window without duplicating the bound.
      Returns:
      the maximum multiplier
    • publishWindowMultiplier

      public static void publishWindowMultiplier(RouterContext ctx)
      Publish the current activity-window multiplier.

      The multiplier was a private field with no visible output, so a multiplier stuck at its floor looked identical to a healthy one. During a build slump that made the one control meant to relieve the slump impossible to diagnose from the console: the window policy could be inert and nothing on /stats or /tuning would say so.

      StatManager has no gauge setter, so the level is published as a rate sample and recorded only when the value actually changes. That keeps the stat's average equal to the current multiplier instead of an average over the history, and avoids a sample on every selection.

      Neither the stat creation nor the data call may propagate: this runs from TunnelPool's emergency path, and a failing instrument must not stop an emergency build. Failures are logged at WARN — which is how the original null-periods NPE was found — and TunnelPeerSelectorWindowStatTest asserts the stat is actually registered, so a silently dead instrument cannot recur.

      Parameters:
      ctx - router context; null is ignored so callers on odd paths do not need a null check of their own
    • getWindowMultiplier

      public static int getWindowMultiplier()
      Current Tuner-controlled activity-window multiplier.
      Returns:
      the current multiplier, in [1, 4]
    • keepAlive

      public static void keepAlive(RouterContext ctx, boolean aggressive)
      Periodically called to keep transport sessions alive for top-tier peers and proactively establish connections to Fast/HighCap peers before builds need them.

      This prevents the natural session aging that drops the active peer count from ~600 to ~300 in the first 30 minutes, which starves first-hop selection and causes tunnel pool collapse.

      Parameters:
      ctx - the router context
      aggressive - if true, also pre-connect to non-established eligible peers (used when any pool has 0 tunnels)