This offers basic protection against simple attacks but is not a complete solution, as by design, we don't know the originator of a tunnel request.
This also effectively limits the number of tunnels between any given pair of routers, which probably isn't a bad thing.
Nested Class Summary
Modifier and TypeClassDescriptionprivate classPeriodic timer event that clears the participation counts to reset throttling.private classTimer event that disconnects a router after a delay.static enumResult of throttling decision for tunnel participation requests.Field Summary
Modifier and TypeFieldDescriptionprivate BanLoggerLogs the routers rejected as too old.private static RateStatRate stat tracking the participation queue.private static RouterContextContext the cached limit invariants were read against; a different context invalidates them.private static longWall-clock ms the cached limit invariants were read, or 0 if never.private static floatCached 0.0-1.0 load score; only valid once_loadScoreQueriedis set.private static RouterContextContext the cached load score was computed against; a different context invalidates it.private static longWall-clock ms the cached load score was computed, or 0 if never.static intLoad weight: how much load inflates effective ratio (0-300%, default 100%).private final LogLogger for the participation throttler.static intMax tunnels per peer before throttling.private static intCachedrouter.maxParticipatingTunnels; only valid once_limitInvQueriedis set.static intMin tunnels per peer before throttling.No-version offense state per hash: {count, lastOffenseMs}.static intMax pct of participating tunnels per peer.Router hashes looked up recently, with the time.static intRejection steepness: 100=linear ramp, 200=quadratic, higher=faster escalation.static intRejection threshold: start rejecting at count/limit ratio this high (30-100, default 70%).private static RouterContextCached RateStat handle for the shared load-score computation; refreshed when the context changes.private static intCached system load 0-100; only valid once_limitInvQueriedis set.private static final longInterval between clearing the participation counts.private final RouterContextThe router context.private final ObjectCounter<Hash> How many tunnels each router participates in.private static final booleanWhether old routers are blocked unless configured.(package private) static final booleanTrue on platforms we treat as slow.private static final longMinimum interval between re-reading the two limit inputs that cannot change under us within a request, in ms.private static final longMinimum interval between two full load-score recomputations, in ms.private static final StringOldest router version allowed to participate.private static final intOffenses inside the escalation window before a ban is issued.private static final longRepeated no-version joins inside this window escalate to a ban.private static final StringConfig key for blocking the old router versions.private static final long[]Time windows of the bandwidth rate stats.private static final longTime before a failed router lookup is retried.Constructor Summary
ConstructorsMethod Summary
Modifier and TypeMethodDescriptionprivate void_logAcceptRequest(String caps, int count) Logs debug information for accepted tunnel requests.private void_logHighRequestCount(Hash h, String caps, int count, int limit) Logs warnings for high count tunnel request rejections.(package private) static intbaseLimitFor(int numTunnels, boolean isUnreachable, boolean isLowShare, int minLimit, int maxLimit, int pctLimit, boolean isSlow) Percentage share of participating tunnels granted to one peer, before any relaxation.private intcalculateLimit(int numTunnels, boolean isUnreachable, boolean isLowShare, boolean isFast) Calculates the participation limit for tunnels based on the number of tunnels and router capabilities such as reachability and bandwidth share.(package private) static floatcalculateLoadScore(RouterContext context) Computes a 0.0–1.0 load score from job queue lag, CPU load, system load, and bandwidth queue pressure, reusing the score computed within the lastLOAD_SCORE_CACHE_MS.private booleancheckLowShareAndVersion(String version, boolean isLU, boolean shouldBlockOldRouters, Hash h, boolean shouldDisconnect, boolean isBanned, String caps, int bantime, RouterInfo ri) Checks if a low-share, old-version router should be banned and disconnected.private booleancheckUnreachableAndOld(String version, boolean isUnreachable, boolean isFast, boolean shouldBlockOldRouters, Hash h, boolean shouldDisconnect, boolean isBanned, String caps) Checks if an unreachable and old router should be banned or ignored.private booleancheckVersionAndCompressibility(String version, boolean isCompressible, boolean shouldDisconnect, Hash h, boolean isBanned, String caps, RouterInfo ri) Checks router version and compressibility of RouterInfo to decide on banning.private static intclamp(int val, int lo, int hi) Constrain a configured property to an allowed range, so a single expression both reads the property and clamps it rather than storing the raw value and overwriting it unread on the next line.(package private) static intcomputeLimit(int numTunnels, boolean isUnreachable, boolean isLowShare, boolean isFast, int maxTunnels, double bwUsage, int sysLoad) The participation-limit arithmetic, with every input supplied, so it can be exercised without a RouterContext.(package private) static floatcomputeLoadScore(RouterContext context) Uncached 0.0–1.0 load score from job queue lag, CPU load, system load, and bandwidth queue pressure.private ParticipatingThrottler.ResultevaluateThrottleConditions(int count, int limit, boolean shouldThrottle, boolean isFast, boolean isLowShare, boolean isUnreachable, Hash h, String caps, boolean isBanned, int bantime, RouterInfo ri, boolean countRequest) Evaluates whether to accept, reject, or drop tunnel requests based on probabilistic rejection curve modulated by system load.private static RateStatgetBwQueueRateStat(RouterContext context) Resolves the "bwLimiter.participatingBandwidthQueue" RateStat once per context.static intThe load weight.static intThe participating maximum limit.static intThe participating minimum limit.static intThe participating percent limit.static intThe rejection steepness.static intThe rejection threshold.private voidhandleExcessiveRequests(Hash h, String caps, int count, int limit, int bantime, RouterInfo ri) Handles excessive tunnel request counts by banning and scheduling disconnect.private voidhandleNoVersion(boolean shouldDisconnect, Hash h, boolean isBanned, String caps, RouterInfo ri, String version) Handles joins from routers whose held RouterInfo carries no version.(package private) static booleanlimitInvariantCacheStale(long lastQueried, long now, RouterContext cachedCtx, RouterContext ctx) Whether the two invariant limit inputs are old enough to re-read.(package private) static booleanloadScoreCacheStale(long lastQueried, long now, RouterContext cachedCtx, RouterContext ctx) Whether the cached load score is old enough to recompute.private voidRe-reads the two invariant limit inputs when the cache is stale.private voidRate-limited background resolution for transit peers missing from our local netDb, so throttler decisions graduate from count-only to full classification without blocking or hammering the netdb.static voidsetLoadWeight(int val) The load weight.static voidsetParticipatingMaxLimit(int val) The participating maximum limit.static voidsetParticipatingMinLimit(int val) The participating minimum limit.static voidsetParticipatingPctLimit(int val) The participating percent limit.static voidsetRejectSteepness(int val) The rejection steepness.static voidsetRejectThreshold(int val) The rejection threshold.(package private) ParticipatingThrottler.ResultDetermines whether to throttle tunnel participation for the given router, counting the request against the peer's limit.(package private) ParticipatingThrottler.ResultshouldThrottle(Hash h, boolean countRequest) Determines whether to throttle tunnel participation for the given router.
Field Details
context
The router context.counter
How many tunnels each router participates in._log
Logger for the participation throttler._banLogger
Logs the routers rejected as too old.IS_SLOW
static final boolean IS_SLOWTrue on platforms we treat as slow. Selects a more conservative percentage divisor (300 rather than 100) and a lower_minLimitcap inbaseLimitFor(int, boolean, boolean, int, int, int, boolean). Package-visible so the limit arithmetic can be pinned for both branches without a RouterContext.DEFAULT_BLOCK_OLD_ROUTERS
private static final boolean DEFAULT_BLOCK_OLD_ROUTERSWhether old routers are blocked unless configured.- See Also:
PROP_BLOCK_OLD_ROUTERS
Config key for blocking the old router versions.- See Also:
_minLimit
public static volatile int _minLimitMin tunnels per peer before throttling._maxLimit
public static volatile int _maxLimitMax tunnels per peer before throttling._percentLimit
public static volatile int _percentLimitMax pct of participating tunnels per peer._rejectThreshold
public static volatile int _rejectThresholdRejection threshold: start rejecting at count/limit ratio this high (30-100, default 70%)._rejectSteepness
public static volatile int _rejectSteepnessRejection steepness: 100=linear ramp, 200=quadratic, higher=faster escalation._loadWeight
public static volatile int _loadWeightLoad weight: how much load inflates effective ratio (0-300%, default 100%)._scoreCtx
Cached RateStat handle for the shared load-score computation; refreshed when the context changes._bwQueueRateStat
Rate stat tracking the participation queue.LOAD_SCORE_CACHE_MS
private static final long LOAD_SCORE_CACHE_MSMinimum interval between two full load-score recomputations, in ms.The score is consulted on every throttled participating request and is shared with
RequestThrottler, but its inputs are all coarse: job queue lag in whole milliseconds, a 60s-smoothed CPU load, a 1/5/15-minute load average and a 60s bandwidth rate. None of them move meaningfully inside one second, so recomputing per request only buys CPU. Staleness is bounded atLOAD_SCORE_CACHE_MSof wall clock plus whatever the underlying inputs are themselves smoothed over, which is invisible against a rejection curve that turns over on a per-request random draw.- See Also:
_loadScoreQueried
private static volatile long _loadScoreQueriedWall-clock ms the cached load score was computed, or 0 if never._loadScore
private static volatile float _loadScoreCached 0.0-1.0 load score; only valid once_loadScoreQueriedis set._loadScoreCtx
Context the cached load score was computed against; a different context invalidates it.LIMIT_INVARIANT_CACHE_MS
private static final long LIMIT_INVARIANT_CACHE_MSMinimum interval between re-reading the two limit inputs that cannot change under us within a request, in ms.router.maxParticipatingTunnelsis a static configuration property andgetSystemLoad()reports a load average; both were re-read on every participating request even though neither moves on a second timescale. Staleness is bounded atLIMIT_INVARIANT_CACHE_MS: a limit can therefore lag a live property change or a load swing by at most that much. The TTL is deliberately the same short 1s the load score uses rather than something longer - the limit is a policy value that gates participation, andcomputeLimit(int, boolean, boolean, boolean, int, double, int)still re-reads every live input (participating count, bandwidth usage) on each call, so only the two invariants are smoothed.- See Also:
_limitInvQueried
private static volatile long _limitInvQueriedWall-clock ms the cached limit invariants were read, or 0 if never._maxParticipatingTunnels
private static volatile int _maxParticipatingTunnelsCachedrouter.maxParticipatingTunnels; only valid once_limitInvQueriedis set._systemLoad
private static volatile int _systemLoadCached system load 0-100; only valid once_limitInvQueriedis set._limitInvCtx
Context the cached limit invariants were read against; a different context invalidates them.CLEAN_TIME
private static final long CLEAN_TIMEInterval between clearing the participation counts.- See Also:
RATES
private static final long[] RATESTime windows of the bandwidth rate stats.MIN_VERSION
Oldest router version allowed to participate.- See Also:
_noVersionOffenses
NO_VERSION_ESCALATION_MS
private static final long NO_VERSION_ESCALATION_MSRepeated no-version joins inside this window escalate to a ban.- See Also:
NO_VERSION_BAN_THRESHOLD
private static final int NO_VERSION_BAN_THRESHOLDOffenses inside the escalation window before a ban is issued.- See Also:
_recentLookups
ROUTER_LOOKUP_RETRY_MS
private static final long ROUTER_LOOKUP_RETRY_MSTime before a failed router lookup is retried.- See Also:
Constructor Details
ParticipatingThrottler
ParticipatingThrottler(RouterContext ctx) Participating throttler.- Parameters:
ctx- the router context supplying properties, rate stats and the ban logger that records throttled peers
Method Details
getParticipatingMinLimit
public static int getParticipatingMinLimit()The participating minimum limit.- Returns:
- tunnels per peer below which throttling never applies
setParticipatingMinLimit
public static void setParticipatingMinLimit(int val) The participating minimum limit.- Parameters:
val- tunnels per peer below which throttling never applies, clamped to 20-500
getParticipatingMaxLimit
public static int getParticipatingMaxLimit()The participating maximum limit.- Returns:
- tunnels per peer at which throttling is at full strength
setParticipatingMaxLimit
public static void setParticipatingMaxLimit(int val) The participating maximum limit.- Parameters:
val- tunnels per peer at which throttling is at full strength, clamped to 50-1000
getParticipatingPctLimit
public static int getParticipatingPctLimit()The participating percent limit.- Returns:
- max percentage of a peer's tunnels that may participate
setParticipatingPctLimit
public static void setParticipatingPctLimit(int val) The participating percent limit.- Parameters:
val- max percentage of a peer's tunnels that may participate, clamped to 5-100
getRejectThreshold
public static int getRejectThreshold()The rejection threshold.- Returns:
- count/limit ratio percentage at which rejection starts
setRejectThreshold
public static void setRejectThreshold(int val) The rejection threshold.- Parameters:
val- count/limit ratio percentage at which rejection starts, clamped to 30-100
getRejectSteepness
public static int getRejectSteepness()The rejection steepness.- Returns:
- how fast rejection probability ramps past the threshold; 100 is linear
setRejectSteepness
public static void setRejectSteepness(int val) The rejection steepness.- Parameters:
val- how fast rejection probability ramps past the threshold, clamped to 100-500
getLoadWeight
public static int getLoadWeight()The load weight.- Returns:
- how much system load inflates the effective ratio, as a percentage
setLoadWeight
public static void setLoadWeight(int val) The load weight.- Parameters:
val- how much system load inflates the effective ratio, clamped to 0-300
clamp
private static int clamp(int val, int lo, int hi) Constrain a configured property to an allowed range, so a single expression both reads the property and clamps it rather than storing the raw value and overwriting it unread on the next line.- Parameters:
val- the value read from the propertieslo- the lowest permitted valuehi- the highest permitted value- Returns:
- val limited to [lo, hi]
shouldThrottle
Determines whether to throttle tunnel participation for the given router, counting the request against the peer's limit.- Parameters:
h- the hash of the router to check- Returns:
- the throttling Result (ACCEPT, REJECT, or DROP)
shouldThrottle
Determines whether to throttle tunnel participation for the given router. WhencountRequestis set the request increments the peer's counter; consult-only checks read the current count without advancing it, and never DROP or ban on it: a count this request did not contribute to must not silently kill it, and mid-chain hops would otherwise accumulate roughly twice per tunnel via both their prev-hop and next-hop roles.- Parameters:
h- the hash of the router to checkcountRequest- true to increment the peer's counter- Returns:
- the throttling Result (ACCEPT, REJECT, or DROP)
calculateLimit
private int calculateLimit(int numTunnels, boolean isUnreachable, boolean isLowShare, boolean isFast) Calculates the participation limit for tunnels based on the number of tunnels and router capabilities such as reachability and bandwidth share. Relaxes limits when we have spare capacity (bandwidth headroom + available transit slots). Enforces a floor (2 for low-share/unreachable, 5 for normal) to prevent zero limits from integer division truncation at low percentage values.Only the two inputs that cannot move under us - the
router.maxParticipatingTunnelsproperty and the system load average - are served from aLIMIT_INVARIANT_CACHE_MScache. Everything that reflects current router state (participating count, outbound bandwidth limit, bandwidth queue usage) is re-read on every call, and all of the arithmetic lives incomputeLimit(int, boolean, boolean, boolean, int, double, int).- Parameters:
numTunnels- the current count of participating tunnelsisUnreachable- true if the router is unreachableisLowShare- true if the router has low bandwidth shareisFast- true if the router has high bandwidth share- Returns:
- the per-peer tunnel participation limit, floored to prevent starvation
refreshLimitInvariants
private void refreshLimitInvariants()Re-reads the two invariant limit inputs when the cache is stale. Publication order matters: both values and the context are written before the timestamp, so a thread that sees a fresh timestamp also sees the matching values. A concurrent duplicate refresh is harmless - the worst case is one extra property read plus one extra load sample, so no lock.limitInvariantCacheStale
static boolean limitInvariantCacheStale(long lastQueried, long now, RouterContext cachedCtx, RouterContext ctx) Whether the two invariant limit inputs are old enough to re-read. Never-read (lastQueried == 0) counts as stale so the first call reads them rather than trusting the field initializers. A swapped RouterContext (unit tests build their own) invalidates regardless of age, as does a wall-clock step backwards, which a plain age comparison would keep serving until the clock caught up again.- Parameters:
lastQueried- the wall-clock ms of the previous read, or 0now- the current wall-clock mscachedCtx- the context the cached values were read againstctx- the context being asked for now- Returns:
- true if the cached values should be refreshed
computeLimit
static int computeLimit(int numTunnels, boolean isUnreachable, boolean isLowShare, boolean isFast, int maxTunnels, double bwUsage, int sysLoad) The participation-limit arithmetic, with every input supplied, so it can be exercised without a RouterContext. Reads the policy fields (_minLimit,_maxLimit,_percentLimit,IS_SLOW) and applies the capacity relaxation, the idle-CPU bonus and the participation floor.- Parameters:
numTunnels- the current count of participating tunnelsisUnreachable- true if the router is unreachableisLowShare- true if the router has low bandwidth shareisFast- true if the router has high bandwidth share; not consumed by the current arithmetic, kept so the signature matchescalculateLimit(int, boolean, boolean, boolean)andshouldThrottle(Hash, boolean)maxTunnels- the configured participating tunnel ceilingbwUsage- outbound bandwidth usage as a fraction of the limit, 0 when unknownsysLoad- system load 0-100- Returns:
- the per-peer tunnel participation limit, floored to prevent starvation
baseLimitFor
static int baseLimitFor(int numTunnels, boolean isUnreachable, boolean isLowShare, int minLimit, int maxLimit, int pctLimit, boolean isSlow) Percentage share of participating tunnels granted to one peer, before any relaxation. Unreachable and low-share peers are held to a fifth of the nominal share (a tenth on slow platforms) and capped at the min limit. Everyone else gets the full share and may reach three times the min limit, bounded below by half the max limit. The divisors are applied in floating point so a lowpctLimitcannot truncate the share to zero.- Parameters:
numTunnels- the current count of participating tunnelsisUnreachable- true if the router is unreachableisLowShare- true if the router has low bandwidth shareminLimit- the min limitmaxLimit- the max limitpctLimit- the max pct of participating tunnels per peerisSlow- true to use the conservative slow-platform divisor- Returns:
- the unrelaxed percentage-based limit
handleNoVersion
private void handleNoVersion(boolean shouldDisconnect, Hash h, boolean isBanned, String caps, RouterInfo ri, String version) Handles joins from routers whose held RouterInfo carries no version.Minor infraction: the offending join is answered with a transient reject (the caller maps Result.REJECT accordingly) and only repeated no-version offenses within
NO_VERSION_ESCALATION_MSgraduate to a banlist entry. A single sighting is far more likely a stale or buggy router than an attacker, and an immediate multi-hour ban on first sight was poisoning the peer pool.- Parameters:
shouldDisconnect- whether to disconnect the router after banningh- the router hashisBanned- true if already bannedcaps- router capabilities string, used in log lines onlyri- RouterInfo for IP extraction and loggingversion- router version string, used if disconnect is scheduled
scheduleRouterLookup
Rate-limited background resolution for transit peers missing from our local netDb, so throttler decisions graduate from count-only to full classification without blocking or hammering the netdb.- Parameters:
h- the hash of the transit peer to resolve
checkVersionAndCompressibility
private boolean checkVersionAndCompressibility(String version, boolean isCompressible, boolean shouldDisconnect, Hash h, boolean isBanned, String caps, RouterInfo ri) Checks router version and compressibility of RouterInfo to decide on banning.- Parameters:
version- router software version stringisCompressible- true if RouterInfo data is compressible (potentially suspicious)shouldDisconnect- whether to disconnect after banningh- hash of the router being evaluatedisBanned- true if already bannedcaps- router capabilities stringri- the router info, used for the IP:port in the ban log- Returns:
- true if the router should be dropped (banned), false otherwise
checkUnreachableAndOld
private boolean checkUnreachableAndOld(String version, boolean isUnreachable, boolean isFast, boolean shouldBlockOldRouters, Hash h, boolean shouldDisconnect, boolean isBanned, String caps) Checks if an unreachable and old router should be banned or ignored.- Parameters:
version- router version stringisUnreachable- true if router is unreachableisFast- true if router has high bandwidthshouldBlockOldRouters- whether to block old routersh- hash of the router being evaluatedshouldDisconnect- whether to disconnect after banningisBanned- true if already bannedcaps- router capabilities string- Returns:
- true if the router should be dropped (ignored), false otherwise
evaluateThrottleConditions
private ParticipatingThrottler.Result evaluateThrottleConditions(int count, int limit, boolean shouldThrottle, boolean isFast, boolean isLowShare, boolean isUnreachable, Hash h, String caps, boolean isBanned, int bantime, RouterInfo ri, boolean countRequest) Evaluates whether to accept, reject, or drop tunnel requests based on probabilistic rejection curve modulated by system load. Replaces the old deterministic count > limit cutoff with a smooth probability function that ramps from 0 at the reject threshold to near-1 under load or high ratio.- Parameters:
count- current participation count for the routerlimit- maximum allowed participationshouldThrottle- true if throttling is enabledisFast- true if router is high bandwidthisLowShare- true if router is low bandwidthisUnreachable- true if router is unreachableh- hash of the router being evaluatedcaps- router capabilities stringisBanned- true if router is already bannedbantime- ban duration in millisecondsri- the RouterInfo, for ban loggingcountRequest- true when this request advanced the counter; consult-only checks downgrade DROP to REJECT and never ban on a count they did not contribute to- Returns:
- Result indicating ACCEPT, REJECT, or DROP action
calculateLoadScore
Computes a 0.0–1.0 load score from job queue lag, CPU load, system load, and bandwidth queue pressure, reusing the score computed within the lastLOAD_SCORE_CACHE_MS. Used to shift the rejection probability curve left when the router is under load. Shared by ParticipatingThrottler and RequestThrottler; both the score and the RateStat handle are cached per context.Publication order matters: the score and its context are written before the timestamp, so a thread that sees a fresh timestamp also sees the matching score. Concurrent callers may both compute - the worst case is one redundant computation per interval, so no lock is taken.
- Parameters:
context- the context to read the load inputs from- Returns:
- the load score, 0.0 (idle) to 1.0 (saturated)
- See Also:
loadScoreCacheStale
static boolean loadScoreCacheStale(long lastQueried, long now, RouterContext cachedCtx, RouterContext ctx) Whether the cached load score is old enough to recompute. Never-computed (lastQueried == 0) counts as stale so the first call computes rather than returning the 0.0 initializer. A swapped RouterContext (unit tests build their own) invalidates regardless of age, as does a wall-clock step backwards, which a plain age comparison would keep serving until the clock caught up again.- Parameters:
lastQueried- the wall-clock ms of the previous computation, or 0now- the current wall-clock mscachedCtx- the context the cached score was computed againstctx- the context being asked for now- Returns:
- true if the score should be recomputed
computeLoadScore
Uncached 0.0–1.0 load score from job queue lag, CPU load, system load, and bandwidth queue pressure. Job lag contributes 40% of the score and saturates at 1000ms, CPU load 25%, system load 20%, and bandwidth queue pressure 15%, saturating at 100KB/s. Each input is clamped to its own range so a lagging counter cannot push the total past 1.0.- Parameters:
context- the context to read the load inputs from- Returns:
- the load score, 0.0 (idle) to 1.0 (saturated)
getBwQueueRateStat
Resolves the "bwLimiter.participatingBandwidthQueue" RateStat once per context. The stat is registered during startup and never replaced, so the handle is only looked up again when the context changes. A stat that is not registered yet leaves the cache untouched and returns null, so a later call retries instead of caching the absence.- Parameters:
context- the context to resolve against- Returns:
- the cached stat, or null if it is not registered yet
handleExcessiveRequests
private void handleExcessiveRequests(Hash h, String caps, int count, int limit, int bantime, RouterInfo ri) Handles excessive tunnel request counts by banning and scheduling disconnect.- Parameters:
h- hash of the router requesting too many tunnelscaps- router capabilities stringcount- current participation countlimit- maximum allowed participationbantime- ban duration in millisecondsri- the router info for IP extraction and logging
_logHighRequestCount
Logs warnings for high count tunnel request rejections.- Parameters:
h- the router whose requests are being rejectedcaps- the router capabilities string, may be nullcount- the request count in the 90 second windowlimit- the allowed maximum in the 90 second window
_logAcceptRequest
Logs debug information for accepted tunnel requests.- Parameters:
caps- the router capabilities string, empty when nonecount- the request count in the 90 second window