- Direct Known Subclasses:
ClientPeerSelector, ExploratoryPeerSelector
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate classExcludes peers that cannot connect as closest hops.private static final classOne cached gateway or endpoint decision.protected classExcluder that automatically adds peers to the set when they should be excluded.private static classImplement a deterministic comparison that cannot be predicted by others.(package private) static final classPer-cycle tally of why keepalive did not act on the peers it selected.private static final classDemotes a peer that could not be reached by a pre-connect probe.(package private) static enumWhat a pre-connect probe actually achieved.Field Summary
Modifier and TypeFieldDescriptionprivate static StringCached capability letters to exclude.private static StringCached explicit peer list from the config.private static booleanCached flag to skip slow inbound client hops.private static booleanCached flag to skip unreachable inbound client hops.private static booleanCached flag to skip slow inbound exploratory hops.private static booleanCached flag to skip unreachable inbound exploratory hops.private static booleanCached flag to skip slow outbound client hops.private static booleanCached flag to skip unreachable outbound client hops.private static booleanCached flag to skip slow outbound exploratory hops.private static booleanCached flag to skip unreachable outbound exploratory hops.private static RouterContextContext the cached config values were read from.private static longWhen the cached config values were last read.private static final AtomicIntegerCounts selections to time the endpoint cache pruning.private static booleanLatch for the exclude-cap relaxation, shared by every selection.Tracks when a peer last failed as first hop.private static final ConcurrentHashMap<Hash, TunnelPeerSelector.EndpointCacheEntry> The cached inbound gateway endpoints.private static final ConcurrentHashMap<Hash, Long> Tracks last keepalive send time per peer.private static intLast value handed to the stat, so repeated publishes stay quiet.private static final ConcurrentHashMap<Hash, TunnelPeerSelector.EndpointCacheEntry> The cached outbound endpoint entries.Shared cooldown map across all peer selectors.Peers whose most recent tunnel participation succeeded, mapped to the time of that success.private static intTuner-controlled multiplier for the peer-selection activity window.(package private) static final doubleWhether the exclude caps are currently relaxed, and by how much build success has to recover before they are enforced again.protected static final doubleThreshold for detecting tunnel build attacks.private static final longHow often the cached config values are refreshed.private static final StringThe capacities that disqualify a peer by default.private static final booleanUnreachable hops are kept by default here.private static final booleanUnreachable hops are kept by default here.private static final booleanUnreachable hops are kept by default here.private static final booleanUnreachable hops are kept by default here.private static final intDefault bandwidth window multiplier.private static final intEntries held in each endpoint cache.private static final longEndpoint eligibility cache: avoids re-checking ~670 fast peers on every selectSingleHop / buildReplacementTunnels cycle.private static final intPrune at most every this many cache misses once over capacity.protected static final intSize bound for the cooldown maps (_peerCooldowns,_firstHopFails,ExploratoryPeerSelector._exploratoryCooldowns).protected static final longPeers that failed as first hop (first hop unreachable) excluded for this long.private static final Comparator<Hash> Orders peers by base64 hash for stable selection.private static final intMost peers acted on in one cycle, bounding a cycle's cost.private static final intPeers requested from the high-capacity tier in a keepalive cycle.private static final longHow often to send keepalive pings to established Fast/HighCap peers.private static final intSize bound for_lastKeepAlive.private static final intPeers requested from the fast tier in a keepalive cycle.static final longUpper bound on the activity window, applied after the multiplier.private static final intLargest bandwidth window multiplier allowed.(package private) static final intMinimum proven-responder entries before cold-start seeding kicks in.private static final StringSSU2 fixes (2.1.0), Congestion fixes (2.2.0).private static final intMultiplier applied to the base activity window bygetActivityWindow(RouterContext, double).protected static final longPeers selected within this window are excluded from further selection to ensure diversity.private static final StringConfig key for excluding slow inbound client hops.private static final StringConfig key for excluding unreachable inbound client hops.private static final StringConfig key for excluding slow inbound exploratory hops.private static final StringConfig key for excluding unreachable inbound exploratory hops.private static final StringConfig key for excluding slow outbound client hops.private static final StringConfig key for excluding unreachable outbound client hops.private static final StringConfig key for excluding slow outbound exploratory hops.private static final StringConfig key for excluding unreachable outbound exploratory hops.protected static final longRecency window for proven-responder proof.(package private) static final intMinimum evidence bar when builds are healthy.(package private) static final intMinimum evidence bar under stress: a live session or a recent successful tunnel test.Display labels for the peer skip reasons.static final intNumber of fast peers at or above which the peer set is considered well supplied and the activity window is never widened on availability grounds.protected static final longDuration in ms to suppress startup warnings.private static final long[]Rate periods forWINDOW_MULTIPLIER_STAT.static final StringStat name for the live window multiplier.Fields inherited from class ConnectChecker
ANY_V4, ctx, logModifier and TypeFieldDescriptionstatic final intAny IPv4 transport.protected final RouterContextThe router context.protected final LogThe logger.Constructor Summary
ConstructorsModifierConstructorDescriptionprotectedTunnelPeerSelector(RouterContext context) Creates a selector that reads its peer profiles from the router context.Method Summary
Modifier and TypeMethodDescriptionprotected static intAdd cooldown entries still inside their window (value > cutoff) to the exclusion set without mutating the map.protected booleanallowAsIBGW(Hash h) Should we allow as IBGW? This just checks for the "R" capability and IPv4 support.protected booleanallowAsOBEP(Hash h) Should we allow as OBEP? This just checks for IPv4 support.(package private) static booleanallowFirewalledUnderAttack(String capabilities, double buildSuccess) Should we allow a firewalled (U-cap) peer? During attacks (build success belowATTACK_THRESHOLD), allow U-cap peers that also publish M, N, O, P, or X capability.(package private) static voidapplyProbeFailure(RouterContext ctx, Hash peer) Act on a pre-connect probe that could not be delivered.protected booleancheckTunnel(boolean isInbound, boolean isExploratory, List<Hash> tunnel) Connectivity check.private booleancheckTunnel(List<Hash> tunnel) Connectivity check.(package private) static intcompareProven(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.(package private) static intcountKnownCaps(String cap) Count the meaningful capabilities a peer publishes, used to reject peers with no useful caps when there are plenty of fast peers.(package private) static booleandropUnreliable(List<Hash> hops, Set<Hash> unreliable, int minKeep) Remove unreliable hops while at leastminKeepremain.protected booleanfilterSlow(boolean isInbound, boolean isExploratory) Whether to skip peers that are slow.private booleanfilterUnreachable(boolean isInbound, boolean isExploratory) Whether to skip unreachable peers.protected static StringformatExcludedPeers(Set<Hash> peers) Format a set of excluded peers for logging, with exclusion reasons when the set is anTunnelPeerSelector.ExcluderorExcluderBase.static longCompute the activity window for peer selection from the base ladder only, scaled by whatever multiplier the availability gate has granted.static longgetActivityWindow(RouterContext ctx, double buildSuccess) Compute the activity window for peer selection based on current network visibility.(package private) static doubleCurrent tunnel build success ratio, 1.0 when no data is available.private static StringReturns the cached exclude-caps config value.private static StringReturns the cached explicit peers config value.private static booleanReturns the cached inbound client slow flag.private static booleanReturns the cached inbound client unreachable flag.private static booleanReturns the cached inbound exploratory slow flag.private static booleanReturns the cached inbound exploratory unreachable flag.private static booleanReturns the cached outbound client slow flag.private static booleanReturns the cached outbound client unreachable flag.private static booleanReturns the cached outbound exploratory slow flag.private static booleanReturns the cached outbound exploratory unreachable flag.getClosestHopExclude(boolean isInbound, Set<Hash> toAdd) Pick peers that we want to avoid for the first OB hop or last IB hop.private static StringgetEffectiveExcludeCaps(RouterContext ctx, double buildSuccess) Exclusion caps to apply, adapting to build success.private static longEffective first-hop fail cooldown.getExclude(boolean isInbound, boolean isExploratory) Create the exclusion set for this pool, for passing to ProfileOrganizer.private static StringExclude caps to apply during peer selection.private StringgetExclusionReason(Hash peerHash, boolean isInbound, boolean isExploratory, double buildSuccess, long activityWindow) Check if a peer should be excluded from closest hop selection.private StringgetHardExclusionReason(Hash peerHash, boolean isInbound, boolean isExploratory, double buildSuccess) Hard exclusion reasons only — skips the no-signal (pre-qualification) check.protected intgetLength(TunnelPoolSettings settings) Determine the tunnel length (number of hops).static intCeiling forgetWindowMultiplier().All non-self peers in active tunnels across ALL pools (client IB/OB + exploratory IB/OB).getPeersInPool(RouterContext ctx, TunnelPool pool) All non-self peers in active tunnels of the given pool.static intCurrent Tuner-controlled activity-window multiplier.(package private) static booleanhasConnectivitySignal(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.(package private) static booleanhasConnectivitySignal(PeerProfile profile, long now, long activityWindow, boolean established, int quality) Pre-qualification signal check at the given strictness.protected static booleanhasRecoveredFromFailure(RouterContext ctx, Hash peer) Check if a peer has recovered from failure and can be reconsidered.protected static booleanTrue if the pool has at least one active tunnel longer than one hop.(package private) static booleanAddress half ofhasValidTransportAddress(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.protected static booleanhasValidTransportAddress(RouterContext ctx, Hash peer) Check if a peer's RouterInfo has at least one reachable SSU or NTCP address.protected booleanisDuplicateSequence(TunnelPoolSettings settings, List<Hash> newPeers) Check if the selected peer sequence matches an existing tunnel in the pool.static booleanisFirstHopFailing(RouterContext ctx, Hash peer) Check if a peer recently failed as first hop and should be excluded.protected booleanConvenience instance method wrapping the static helper.static booleanIs 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.protected booleanAre we IPv6 only?protected static booleanisLowAcceptanceRatio(RouterContext ctx, Hash peer) Check if a peer has a history of rejecting tunnel build requests.(package private) static booleanisOutdatedVersion(String version) True if the peer runs a version too old to interoperate: not the published version and older thanMIN_VERSION.(package private) static booleanisProvenResponder(long lastJoin, long now) Whether a proven-join timestamp falls inside the recency window.(package private) static booleanisStalePeer(RouterContext ctx, Hash peer) Check whether a peer is stale — no contact (heard from or heard about) within the dynamic activity window.(package private) static booleanisStalePeer(RouterContext ctx, Hash peer, double buildSuccess) Check whether a peer is stale — no contact (heard from or heard about) within the dynamic activity window.(package private) static booleanIs 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.protected static booleanisZeroHopSettings(TunnelPoolSettings settings) True if the settings describe a zero-hop pair: length <= 0, length override == 0, or length + variance <= 0.static voidkeepAlive(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.private static RouterInfolookupRouterInfoUnvalidated(RouterContext ctx, Hash peer) Fetch a peer RouterInfo for a message we address to it, preferring the unvalidated store lookup.(package private) static booleanmatchesExistingTunnel(TunnelInfo existing, List<Hash> newPeers, boolean isInbound) Compare a newly selected peer sequence (excluding self) against one existing tunnel.(package private) static voidnoteFirstHopRtt(RouterContext ctx, Hash peer, Transport transport) Extract the transport's measured direct-link RTT and record it on the peer.protected voidorderPeers(List<Hash> rv, SessionKey key) Order peers using the given key.protected static TunnelPeerSelector.PreConnectOutcomepreConnectTo(RouterContext ctx, Hash peer) Send aDatabaseLookupMessageto a peer so it answers with our RouterInfo, which forces a full session handshake rather than the emptyDataMessagethat would leave the peer with nothing to reply to.(package private) static intpreferProven(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.private static voidpruneEndpointCache(ConcurrentHashMap<Hash, TunnelPeerSelector.EndpointCacheEntry> cache, long now) Prune expired entries when the cache grows too large.protected static voidPrune expired entries from static peer maps.static voidPublish the current activity-window multiplier.protected static voidrecordFirstHopFail(RouterContext ctx, Hash peer) Record that a peer failed as first hop (first hop unreachable).private static voidRefresh the cached peer-selection configuration from properties at most once per CONFIG_REFRESH_MS, or immediately when the context changes.regeneratePeers(TunnelPoolSettings settings, List<Hash> peers, int attempt) Regenerate tunnel peers to avoid duplicate sequence.(package private) static StringrelaxedExcludeCaps(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 belowATTACK_THRESHOLDor the router is within its firstSTARTUP_WARNING_SUPPRESS_MSof uptime (when the ratio is not yet meaningful).(package private) static StringrelaxedExcludeCaps(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 belowATTACK_THRESHOLDor the router is within its firstSTARTUP_WARNING_SUPPRESS_MSof uptime (when the ratio is not yet meaningful).(package private) static intrequiredSignalQuality(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.(package private) static voidseedProvenResponders(RouterContext ctx, long now) Seed the proven-responder map with random peers from the router's peer profiles when the map is belowMIN_PROVEN_RESPONDER_COUNT.selectExplicit(TunnelPoolSettings settings, int length) For debugging, also possibly for restricted routes.selectPeers(TunnelPoolSettings settings) Which peers should go into the next tunnel for the given settings?selectPeers(TunnelPoolSettings settings, Set<Hash> excludeFirstHops) Select peers for a new tunnel, with an optional set of first-hop peers to exclude from selection.static voidsetWindowMultiplier(int mult) Tuner-controlled activity-window multiplier, clamped to [1, 4] (MIN_WINDOW_MULTIPLIER..MAX_WINDOW_MULTIPLIER).static booleanshouldExclude(RouterContext ctx, RouterInfo peer) Should the peer be excluded based on its published caps, crypto, and version?static booleanshouldExclude(RouterContext ctx, RouterInfo peer, double buildSuccess) Should the peer be excluded based on its published caps, crypto, and version?private static booleanshouldExclude(RouterContext ctx, RouterInfo peer, String excl, boolean isExploratory, double buildSuccess) Should the peer be excluded based on its published caps, crypto, and version?private booleanshouldExcludeFloodfillPeer(RouterInfo routerInfo) Whether a floodfill is excluded from tunnel service.(package private) static booleanshouldPreQual(boolean isExploratory, boolean inStartupGrace, double buildSuccess) Whether strict client-pool pre-qualification should be applied to a candidate peer.protected booleanshouldSelectExplicit(TunnelPoolSettings settings) For debugging, also possibly for restricted routes? Needs analysis and testing.private static StringRemove the M/N/O/D/P capability exclusions, leaving every other cap intact.protected static booleansupportsNTCP2(RouterContext ctx, Hash peer) Check if a peer supports NTCP2 transport.private booleanwasRecentlyRejected(PeerProfile profile, long cutoffMillis) Whether the peer was rejected as a gateway too recently.static intwindowMultiplierFor(int fastPeers, boolean startupGrace, int min, int max) Multiplier the peer-supply signal earns, independent of any build-outcome metric.static intwindowMultiplierFor(int, boolean, int, int)bound to the live router: reads the current fast-peer count and startup state.Methods inherited from class ConnectChecker
canConnect, canConnect, canConnect, getInboundMask, getOutboundMask, isNTCPDisabled, isSSUDisabledModifier and TypeMethodDescriptionbooleancanConnect(int ourMask, RouterInfo to) Can we connect to "to" based on published addresses?booleancanConnect(Hash from, Hash to) Can "from" connect to "to" based on published addresses?booleancanConnect(RouterInfo from, int ourMask) Can "from" connect to us based on published addresses?intOur inbound mask.intOur outbound mask.protected booleanIs NTCP disabled?protected booleanIs SSU disabled?
Field Details
DEFAULT_EXCLUDE_CAPS
The capacities that disqualify a peer by default._cfgCtx
Context the cached config values were read from._cfgRefreshed
private static volatile long _cfgRefreshedWhen the cached config values were last read._cachedExcludeCaps
Cached capability letters to exclude._cachedExplicitPeers
Cached explicit peer list from the config._cachedIbExplUnreachable
private static volatile boolean _cachedIbExplUnreachableCached flag to skip unreachable inbound exploratory hops._cachedObExplUnreachable
private static volatile boolean _cachedObExplUnreachableCached flag to skip unreachable outbound exploratory hops._cachedIbClientUnreachable
private static volatile boolean _cachedIbClientUnreachableCached flag to skip unreachable inbound client hops._cachedObClientUnreachable
private static volatile boolean _cachedObClientUnreachableCached flag to skip unreachable outbound client hops._cachedIbExplSlow
private static volatile boolean _cachedIbExplSlowCached flag to skip slow inbound exploratory hops._cachedObExplSlow
private static volatile boolean _cachedObExplSlowCached flag to skip slow outbound exploratory hops._cachedIbClientSlow
private static volatile boolean _cachedIbClientSlowCached flag to skip slow inbound client hops._cachedObClientSlow
private static volatile boolean _cachedObClientSlowCached flag to skip slow outbound client hops.CONFIG_REFRESH_MS
private static final long CONFIG_REFRESH_MSHow often the cached config values are refreshed.- See Also:
ENDPOINT_CACHE_TTL_MS
private static final long ENDPOINT_CACHE_TTL_MSEndpoint 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 afterENDPOINT_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_SIZEEntries held in each endpoint cache.- See Also:
ENDPOINT_PRUNE_STRIDE
private static final int ENDPOINT_PRUNE_STRIDEPrune 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
Counts selections to time the endpoint cache pruning.ATTACK_THRESHOLD
protected static final double ATTACK_THRESHOLDThreshold for detecting tunnel build attacks.- See Also:
STARTUP_WARNING_SUPPRESS_MS
protected static final long STARTUP_WARNING_SUPPRESS_MSDuration in ms to suppress startup warnings.- See Also:
MIN_WINDOW_MULTIPLIER
private static final int MIN_WINDOW_MULTIPLIERMultiplier applied to the base activity window bygetActivityWindow(RouterContext, double).- See Also:
MAX_WINDOW_MULTIPLIER
private static final int MAX_WINDOW_MULTIPLIERLargest bandwidth window multiplier allowed.- See Also:
DEFAULT_WINDOW_MULTIPLIER
private static final int DEFAULT_WINDOW_MULTIPLIERDefault bandwidth window multiplier.- See Also:
_windowMultiplier
private static volatile int _windowMultiplierTuner-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 fromtunnel.buildSuccessRate; clamped to [1, 4].PEER_SELECTION_COOLDOWN_MS
protected static final long PEER_SELECTION_COOLDOWN_MSPeers selected within this window are excluded from further selection to ensure diversity.- See Also:
MIN_PROVEN_RESPONDER_COUNT
static final int MIN_PROVEN_RESPONDER_COUNTMinimum 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
Peers whose most recent tunnel participation succeeded, mapped to the time of that success. Selection prefers fresh entries viaClientPeerSelector.compareQuality; entries older thanPROVEN_RESPONDER_WINDOW_MScarry no weight and are pruned.PROVEN_RESPONDER_WINDOW_MS
protected static final long PROVEN_RESPONDER_WINDOW_MSRecency window for proven-responder proof.- See Also:
_peerCooldowns
FIRST_HOP_FAIL_COOLDOWN_MS
protected static final long FIRST_HOP_FAIL_COOLDOWN_MSPeers that failed as first hop (first hop unreachable) excluded for this long.- See Also:
_firstHopFails
KEEPALIVE_INTERVAL_MS
private static final long KEEPALIVE_INTERVAL_MSHow often to send keepalive pings to established Fast/HighCap peers.- See Also:
KEEPALIVE_TARGET_TIERS
private static final int KEEPALIVE_TARGET_TIERSPeers requested from the fast tier in a keepalive cycle.- See Also:
KEEPALIVE_HIGH_CAP_DEPTH
private static final int KEEPALIVE_HIGH_CAP_DEPTHPeers requested from the high-capacity tier in a keepalive cycle.Set deeper than
KEEPALIVE_TARGET_TIERSbecause 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. OnlyKEEPALIVE_ACTION_BUDGETof 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_BUDGETMost peers acted on in one cycle, bounding a cycle's cost.- See Also:
_lastKeepAlive
Tracks last keepalive send time per peer.FAILURE_MAP_MAX_SIZE
protected static final int FAILURE_MAP_MAX_SIZESize 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_SIZESize 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_RELAXEDMinimum evidence bar when builds are healthy. @since 0.9.71+.- See Also:
QUALITY_STRICT
static final int QUALITY_STRICTMinimum 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_EXITWhether the exclude caps are currently relaxed, and by how much build success has to recover before they are enforced again.Wider than
ATTACK_THRESHOLDon 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 _excludeCapsRelaxedLatch 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
SSU2 fixes (2.1.0), Congestion fixes (2.2.0).- See Also:
PROP_OUTBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE
Config key for excluding unreachable outbound exploratory hops.- See Also:
PROP_OUTBOUND_CLIENT_EXCLUDE_UNREACHABLE
Config key for excluding unreachable outbound client hops.- See Also:
PROP_INBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE
Config key for excluding unreachable inbound exploratory hops.- See Also:
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_UNREACHABLEUnreachable hops are kept by default here.- See Also:
DEFAULT_OUTBOUND_CLIENT_EXCLUDE_UNREACHABLE
private static final boolean DEFAULT_OUTBOUND_CLIENT_EXCLUDE_UNREACHABLEUnreachable hops are kept by default here.- See Also:
DEFAULT_INBOUND_EXPLORATORY_EXCLUDE_UNREACHABLE
private static final boolean DEFAULT_INBOUND_EXPLORATORY_EXCLUDE_UNREACHABLEUnreachable hops are kept by default here.- See Also:
DEFAULT_INBOUND_CLIENT_EXCLUDE_UNREACHABLE
private static final boolean DEFAULT_INBOUND_CLIENT_EXCLUDE_UNREACHABLEUnreachable hops are kept by default here.- See Also:
PROP_OUTBOUND_EXPLORATORY_EXCLUDE_SLOW
Config key for excluding slow outbound exploratory hops.- See Also:
PROP_OUTBOUND_CLIENT_EXCLUDE_SLOW
Config key for excluding slow outbound client hops.- See Also:
PROP_INBOUND_EXPLORATORY_EXCLUDE_SLOW
Config key for excluding slow inbound exploratory hops.- See Also:
PROP_INBOUND_CLIENT_EXCLUDE_SLOW
Config key for excluding slow inbound client hops.- See Also:
HASH_BASE64_COMPARATOR
Orders peers by base64 hash for stable selection.REASON_LABELS
SCARCE_FAST_PEERS
public static final int SCARCE_FAST_PEERSNumber 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_MSUpper bound on the activity window, applied after the multiplier. @since 0.9.71+.- See Also:
WINDOW_MULTIPLIER_STAT
Stat name for the live window multiplier. @see #publishWindowMultiplier.- See Also:
WINDOW_MULTIPLIER_RATES
private static final long[] WINDOW_MULTIPLIER_RATESRate periods forWINDOW_MULTIPLIER_STAT. Must not be null:RateStatdereferences 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 _lastPublishedMultiplierLast value handed to the stat, so repeated publishes stay quiet.
Constructor Details
TunnelPeerSelector
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 perENDPOINT_PRUNE_STRIDEoversized misses so a hot selection loop cannot burn CPU on full-map removeIf every insert.- Parameters:
cache- the endpoint decision cache to prunenow- the current time in ms, compared against each entry's expiry
refreshPeerConfig
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
Returns the cached exclude-caps config value.- Parameters:
ctx- the router context- Returns:
- the capability letters
getCachedExplicitPeers
Returns the cached explicit peers config value.- Parameters:
ctx- the router context- Returns:
- the explicit peer list
getCachedIbExplUnreachable
Returns the cached inbound exploratory unreachable flag.- Parameters:
ctx- the router context- Returns:
- true to exclude unreachable peers
getCachedObExplUnreachable
Returns the cached outbound exploratory unreachable flag.- Parameters:
ctx- the router context- Returns:
- true to exclude unreachable peers
getCachedIbClientUnreachable
Returns the cached inbound client unreachable flag.- Parameters:
ctx- the router context- Returns:
- true to exclude unreachable peers
getCachedObClientUnreachable
Returns the cached outbound client unreachable flag.- Parameters:
ctx- the router context- Returns:
- true to exclude unreachable peers
getCachedIbExplSlow
Returns the cached inbound exploratory slow flag.- Parameters:
ctx- the router context- Returns:
- true to exclude slow peers
getCachedObExplSlow
Returns the cached outbound exploratory slow flag.- Parameters:
ctx- the router context- Returns:
- true to exclude slow peers
getCachedIbClientSlow
Returns the cached inbound client slow flag.- Parameters:
ctx- the router context- Returns:
- true to exclude slow peers
getCachedObClientSlow
Returns the cached outbound client slow flag.- Parameters:
ctx- the router context- Returns:
- true to exclude slow peers
formatExcludedPeers
Format a set of excluded peers for logging, with exclusion reasons when the set is anTunnelPeerSelector.ExcluderorExcluderBase.- 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
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 contextpeer- the candidate to test; its first-hop reachability is probed- Returns:
- true if the peer should be excluded
getEffectiveFirstHopCooldown
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
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 contextpeer- the first-hop router to record as currently unreachable for new tunnel builds
prunePeerMaps
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
Seed the proven-responder map with random peers from the router's peer profiles when the map is belowMIN_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 contextnow- 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 nonet2- last proven-join time of the second peer, 0 if nonenow- 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 nonenow- 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 placefrom- first replaceable slot indexto- index after the last replaceable slotproof- peer -> last successful-participation timenow- current time from the router clockwindow- recency window for proofreplacements- eligible proven candidates, most preferred first; consumed as used- Returns:
- number of swaps performed
dropUnreliable
Remove unreliable hops while at leastminKeepremain. Pure decision helper; package visible for tests.- Parameters:
hops- selected peers, modified in placeunreliable- the peers to removeminKeep- minimum list size to leave- Returns:
- true if any hop was removed
getPeersInPool
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 contextpool- the tunnel pool to scan- Returns:
- set of peer hashes already in active tunnels of this pool
getPeersInAllPools
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 (_peerCooldownsfor 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 skippedexclude- the exclusion set to add fresh entries to- Returns:
- the number of entries added
isZeroHopSettings
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
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
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 contextpeer- the candidate to test; its first-hop reachability is probed- Returns:
- true if the peer has recovered
isInStartupGracePeriod
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
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
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 toselectPeers(TunnelPoolSettings); subclasses that support first-hop diversity (e.g. ClientPeerSelector) override this to exclude those peers.- Parameters:
settings- the tunnel pool settingsexcludeFirstHops- 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
Determine the tunnel length (number of hops).- Parameters:
settings- the tunnel pool settings- Returns:
- randomized number of hops 0-7, not including ourselves
shouldSelectExplicit
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
For debugging, also possibly for restricted routes. Needs analysis and testing.- Parameters:
settings- the tunnel pool settingslength- the desired length of the tunnel- Returns:
- the list of explicit peer hashes for the tunnel
getExclude
Create the exclusion set for this pool, for passing to ProfileOrganizer. The set is seeded withTunnelManager.selectPeersInTooManyTunnels()and is then populated via the contains() calls.- Parameters:
isInbound- true for inbound tunnelsisExploratory- true for exploratory tunnels- Returns:
- set of excluded peers
getBuildSuccess
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 checkisInbound- true if this is for an inbound tunnelisExploratory- true if this is for exploratory tunnelsbuildSuccess- the build success ratio, fetched once per selectionactivityWindow- 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 checkisInbound- true for inbound tunnel selection, chooses the applicable unreachable and slow filtersisExploratory- true for exploratory selections, excludes floodfill peersbuildSuccess- 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 (seerequiredSignalQuality(double)), so this predicate no longer readsbuildSuccessat all. A NaN success ratio is likewise irrelevant here. Pure decision — no context access, safe for unit tests.- Parameters:
isExploratory- true for exploratory selectionsinStartupGrace- true while the router is in its startup grace periodbuildSuccess- 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 asHop1.establishedSilent, far ends classifiedFarEndUnknownat roughly 5:1 overFarEndUnreachable, and build success oscillating in the 29-46% band directly beneath the threshold.Under stress the bar is raised, not lowered.
QUALITY_STRICTaccepts only a recent successful test or a live transport session — the evidence that a peer is actually reachable — whereasQUALITY_RELAXEDadditionally 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_STRICTwhen build success is at or belowATTACK_THRESHOLD, otherwiseQUALITY_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_RELAXEDthis is exactlyhasConnectivitySignal(PeerProfile, long, long): recent contact, a recent successful test, or any favourable acceptance history.At
QUALITY_STRICTonly 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 livenow- current time in millisecondsactivityWindow- the dynamic activity window in millisecondsestablished- whether a live transport session exists for the peerquality- one ofQUALITY_RELAXED,QUALITY_STRICT- Returns:
- true if the profile shows enough connectivity signal
hasConnectivitySignal
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 millisecondsactivityWindow- the dynamic activity window in milliseconds- Returns:
- true if the profile shows recent connectivity signal
getEffectiveExcludeCaps
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 contextbuildSuccess- the build success ratio, fetched once per selection- Returns:
- non-null, possibly empty
relaxedExcludeCaps
Strip the M, N, O, D, P capability exclusions from the configured caps when the build success ratio is belowATTACK_THRESHOLDor the router is within its firstSTARTUP_WARNING_SUPPRESS_MSof 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 emptybuildSuccess- 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 belowATTACK_THRESHOLDor the router is within its firstSTARTUP_WARNING_SUPPRESS_MSof uptime (when the ratio is not yet meaningful). A ratio of 0.0 (no data) relaxes as well, matching the conservative startup behavior.P4:
alreadyRelaxedis the latch, and this is where the hysteresis actually lives. The previous form derived the decision frombuildSuccessalone, which is a plain threshold wearing hysteresis's clothes — thebuildSuccess >= 0.44arm could only assignfalseto a variable alreadyfalse, 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 belowATTACK_THRESHOLD, leave at or aboveATTACK_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 emptybuildSuccess- the build success ratio in [0.0, 1.0]uptimeMs- router uptime in millisecondsalreadyRelaxed- 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
allowFirewalledUnderAttack
Should we allow a firewalled (U-cap) peer? During attacks (build success belowATTACK_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 nullbuildSuccess- the build success ratio, fetched once per selection- Returns:
- true if the peer may be used despite the U cap
wasRecentlyRejected
Whether the peer was rejected as a gateway too recently.- Parameters:
profile- the peer's profilecutoffMillis- how far back to look- Returns:
- true if a recent rejection blocks the peer
shouldExcludeFloodfillPeer
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
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
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
Pick peers that we want to avoid for the first OB hop or last IB hop. There's several cases of importance:- Inbound and we are hidden - Exclude all unless connected. This is taken care of in ClientPeerSelector and TunnelPeerSelector
selectPeers(TunnelPoolSettings), not here. - We are IPv6-only. Exclude all v4-only peers, unless connected This is taken care of here.
- We have NTCP or SSU disabled. Exclude all incompatible peers, unless connected This is taken care of here.
- 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 tunnelstoAdd- set of peers to initially populate the exclusion set- Returns:
- the exclusion set to consult, never null
- Inbound and we are hidden - Exclude all unless connected. This is taken care of in ClientPeerSelector and TunnelPeerSelector
shouldExclude
Should the peer be excluded based on its published caps, crypto, and version?- Parameters:
ctx- Router context for peer count checkspeer- The peer to evaluate- Returns:
- true if the peer should be excluded
shouldExclude
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 checkspeer- The peer to evaluatebuildSuccess- the build success ratio in [0.0, 1.0]- Returns:
- true if the peer should be excluded
getExcludeCaps
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 checkspeer- The peer to evaluateexcl- Characters representing capabilities we want to excludeisExploratory- true if this check is for an exploratory poolbuildSuccess- the build success ratio, fetched once per selection- Returns:
- true if the peer should be excluded
countKnownCaps
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
True if the peer runs a version too old to interoperate: not the published version and older thanMIN_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 selectionisExploratory- 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 tunnelsisExploratory- true for exploratory tunnels- Returns:
- true unless configured otherwise
orderPeers
Order peers using the given key.- Parameters:
rv- the list to orderkey- the session key for ordering
isDuplicateSequence
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 settingsnewPeers- the newly selected peers (excluding self)- Returns:
- true if duplicate detected
matchesExistingTunnel
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 againstnewPeers- the newly selected peers (excluding self)isInbound- true for inbound tunnels- Returns:
- true if the sequences match
regeneratePeers
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 settingspeers- the peers to regenerate (not modified)attempt- 1-based regeneration attempt number- Returns:
- regenerated peer list (same or different)
checkTunnel
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 tunnelsisExploratory- true for exploratory tunnelstunnel- 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
supportsNTCP2
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 contextpeer- hash of the peer to check- Returns:
- true if the peer has an NTCP2 address
hasValidTransportAddress
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 inEstablishmentManager.establish(OutNetMessage)— they should be excluded from selection.- Parameters:
ctx- the router contextpeer- hash of the peer to check- Returns:
- true if the peer has a valid SSU or NTCP address
hasValidTransportAddress
Address half ofhasValidTransportAddress(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
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
BuildRequestoruses 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 contextpeer- hash of the peer- Returns:
- the RouterInfo, or null if neither lookup resolves it
isUsableRouterAddress
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
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 contextpeer- hash of the peer to check- Returns:
- true if the acceptance ratio is below threshold
preConnectTo
Send aDatabaseLookupMessageto a peer so it answers with our RouterInfo, which forces a full session handshake rather than the emptyDataMessagethat 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 contextpeer- the peer to pre-contact- Returns:
- what happened, so the caller can tally why a peer was skipped
noteFirstHopRtt
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 contextpeer- the probed peertransport- the transport the probe was sent over
applyProbeFailure
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 contextpeer- the peer that failed to answer the probe
isStalePeer
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 contextpeer- hash of the peer to check- Returns:
- true if the peer has not been heard from or about within the activity window
isStalePeer
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 contextpeer- hash of the peer to checkbuildSuccess- 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
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 contextbuildSuccess- the build success ratio, fetched once by the caller- Returns:
- activity window in milliseconds
getActivityWindow
Compute the activity window for peer selection from the base ladder only, scaled by whatever multiplier the availability gate has granted.The
buildSuccessargument 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 inwindowMultiplierFor(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 andgetActivityWindow(RouterContext)so the three cannot drift apart.Rules, in order:
- Above
SCARCE_FAST_PEERSthe 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. - 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.
- 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 faststartupGrace- true while the router is within its startup grace periodmin- the multiplier returned when fast peers are plentifulmax- the multiplier returned while the router is short of fast peers, or during the startup grace period- Returns:
- multiplier in [min, max]
- Above
windowMultiplierFor
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 forgetWindowMultiplier(). Exposed so the pool's starvation bypass can widen the window without duplicating the bound.- Returns:
- the maximum multiplier
publishWindowMultiplier
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
/statsor/tuningwould 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 — andTunnelPeerSelectorWindowStatTestasserts 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
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 contextaggressive- if true, also pre-connect to non-established eligible peers (used when any pool has 0 tunnels)