Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a4c409d
Bound connection throttle state
BenCodez Sep 7, 2026
36d178c
Bound connection throttle state
BenCodez Sep 7, 2026
2c9ffa2
Bound connection throttle state
BenCodez Sep 7, 2026
c614557
Bound connection throttle state
BenCodez Sep 7, 2026
43bdd30
Preserve shared tunnel client isolation
BenCodez Sep 7, 2026
630374d
Preserve shared tunnel client isolation
BenCodez Sep 7, 2026
bf1d686
Preserve tracked throttle state at capacity
BenCodez Sep 7, 2026
5709c9b
Preserve tracked throttle state at capacity
BenCodez Sep 7, 2026
dc99592
Address review feedback with regression coverage
BenCodez Sep 7, 2026
811dc1b
Address review feedback with regression coverage
BenCodez Sep 7, 2026
e277ad5
Address review feedback with regression coverage
BenCodez Sep 7, 2026
b8a5d7b
Address review feedback with regression coverage
BenCodez Sep 7, 2026
de56202
Address latest review feedback
BenCodez Sep 7, 2026
af191c8
Address latest review feedback
BenCodez Sep 7, 2026
7d09b87
Address latest review feedback
BenCodez Sep 7, 2026
f7675a6
Address latest review feedback
BenCodez Sep 7, 2026
07d3fdf
Make vote throttling state updates atomic
BenCodez Sep 8, 2026
222fca7
Bound trusted tunnel throttle overflow
BenCodez Sep 8, 2026
cc6a9f3
Merge remote-tracking branch 'origin/master' into security/pr-170-local
BenCodez Sep 8, 2026
cf37fe1
Preserve active throttle state at capacity
BenCodez Sep 8, 2026
10240fa
Throttle overflow for non-tunnel proxy peers
BenCodez Sep 8, 2026
fc7bb48
Harden aggregate throttle overflow
BenCodez Sep 8, 2026
570e340
fix: enforce shared proxy throttles
BenCodez Sep 8, 2026
f04f46d
Preserve shared overflow throttle failures
BenCodez Sep 8, 2026
afd73d9
Preserve aggregate throttle failure windows
BenCodez Sep 8, 2026
13798d7
Recheck throttles under state lock
BenCodez Sep 8, 2026
1847654
Bound saturated throttle lookup work
BenCodez Sep 8, 2026
7aa45c3
Reclaim inactive throttle states promptly
BenCodez Sep 8, 2026
fdb4144
Reject aggregate-blocked peers before payload
BenCodez Sep 8, 2026
ff3c94c
Bound saturated throttle maintenance
BenCodez Sep 8, 2026
5d59a02
Retain saturated log expiry deadline
BenCodez Sep 8, 2026
b63ef9a
Retain reclaimed throttle deadlines
BenCodez Sep 8, 2026
9aa5068
Preserve shared aggregate throttle failures
BenCodez Sep 8, 2026
8e531fb
Track earliest deadline at throttle capacity
BenCodez Sep 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VotifierPlus/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
<version>3.5.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.5.0</version>
<configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public Vote handle(Socket socket) {
String remoteIp = "unknown";
String address = "";
String throttleKey = null;
String aggregateThrottleKey = null;
boolean tunnelMode = false;
boolean realIpKnown = false;

Expand All @@ -57,9 +58,21 @@ public Vote handle(Socket socket) {
address = accepted.getRemoteSocketAddress() == null ? "/" + remoteIp
: accepted.getRemoteSocketAddress().toString();

throttleKey = "tunnel:" + remoteIp;
tunnelMode = throttleService.isTunnelMode(remoteIp);
aggregateThrottleKey = throttleKey;
receiver.debug("Accepted connection from: " + address);
accepted.setSoTimeout(5000);

if (throttleService.isAggregateBlocked(aggregateThrottleKey)) {
long retry = throttleService.aggregateRetryAfterMs(aggregateThrottleKey);
String blockedKey = throttleService.aggregateBlockedKey(aggregateThrottleKey);
throttleService.logWarning(receiver, "throttle|" + blockedKey,
"Votifier throttling " + blockedKey + " (tunnel=" + tunnelMode + "), retry in "
+ Math.max(0, retry / 1000) + "s");
return null;
}

String challenge = receiver.getChallenge();
sendHandshakeIfNeeded(in, writer, challenge);

Expand All @@ -73,12 +86,12 @@ public Vote handle(Socket socket) {
realIp = proxyResult.getRealIp();

realIpKnown = realIp != null && !realIp.isEmpty();
tunnelMode = throttleService.isTunnelMode(remoteIp);
throttleKey = realIpKnown ? "ip:" + realIp : "tunnel:" + remoteIp;

if (throttleService.isBlocked(throttleKey)) {
long retry = throttleService.retryAfterMs(throttleKey);
throttleService.logWarning(receiver, "throttle|" + throttleKey, "Votifier throttling " + throttleKey
if (throttleService.isBlocked(throttleKey, aggregateThrottleKey)) {
Comment thread
BenCodez marked this conversation as resolved.
long retry = throttleService.retryAfterMs(throttleKey, aggregateThrottleKey);
String blockedKey = throttleService.blockedKey(throttleKey, aggregateThrottleKey);
throttleService.logWarning(receiver, "throttle|" + blockedKey, "Votifier throttling " + blockedKey
+ " (tunnel=" + tunnelMode + "), retry in " + Math.max(0, retry / 1000) + "s");
return null;
}
Expand All @@ -97,7 +110,7 @@ public Vote handle(Socket socket) {
}

receiver.log("Received vote record -> " + vote);
throttleService.success(throttleKey);
throttleService.success(throttleKey, aggregateThrottleKey);

if (!"TestVote".equalsIgnoreCase(vote.getTimeStamp())) {
sendOkResponse(writer);
Expand All @@ -109,31 +122,31 @@ public Vote handle(Socket socket) {
throttleKey = "tunnel:" + remoteIp;
}

throttleService.fail(throttleKey, tunnelMode, realIpKnown);
throttleService.fail(throttleKey, aggregateThrottleKey, tunnelMode, realIpKnown);
throttleService.logWarning(receiver, "invalid|" + throttleKey,
"Invalid vote format from " + remoteIp + ": " + ex.getMessage());
} catch (VoteAuthenticationException ex) {
if (throttleKey == null) {
throttleKey = "tunnel:" + remoteIp;
}

throttleService.fail(throttleKey, tunnelMode, realIpKnown);
throttleService.fail(throttleKey, aggregateThrottleKey, tunnelMode, realIpKnown);
throttleService.logWarning(receiver, "auth|" + throttleKey,
"Authentication failed from " + remoteIp + ": " + ex.getMessage());
} catch (MalformedJsonException ex) {
if (throttleKey == null) {
throttleKey = "tunnel:" + remoteIp;
}

throttleService.fail(throttleKey, tunnelMode, false);
throttleService.fail(throttleKey, aggregateThrottleKey, tunnelMode, false);
throttleService.logWarning(receiver, "malformedjson|" + throttleKey,
"Invalid vote format: Malformed JSON payload from " + remoteIp + " - " + ex.getMessage());
} catch (BadPaddingException ex) {
if (throttleKey == null) {
throttleKey = "tunnel:" + remoteIp;
}

throttleService.fail(throttleKey, tunnelMode, realIpKnown);
throttleService.fail(throttleKey, aggregateThrottleKey, tunnelMode, realIpKnown);
throttleService.logWarning(receiver, "badpadding|" + throttleKey,
"Decryption failed: Invalid V1 vote block / public key mismatch from " + remoteIp);
} catch (SocketTimeoutException ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,8 @@ private VoteRequest parseV2(byte[] data, VoteReceiver receiver, String address,

String serviceName = requireString(votePayload, FIELD_SERVICE_NAME, "Inner JSON from " + address + ": ");
String username = requireString(votePayload, FIELD_USERNAME, "Inner JSON from " + address + ": ");
String voteAddress = votePayload.get(FIELD_ADDRESS).getAsString();
String voteAddress = requirePossiblyEmptyString(votePayload, FIELD_ADDRESS,
"Inner JSON from " + address + ": ");
String timeStamp = requireString(votePayload, FIELD_TIMESTAMP, "Inner JSON from " + address + ": ");
String receivedChallenge = requireString(votePayload, FIELD_CHALLENGE, "Inner JSON from " + address + ": ").trim();

Expand Down Expand Up @@ -591,6 +592,18 @@ private String requireString(JsonObject obj, String field, String errorPrefix) t
return value;
}

private String requirePossiblyEmptyString(JsonObject obj, String field, String errorPrefix)
throws InvalidVoteException {
if (!obj.has(field)) {
throw new InvalidVoteException(errorPrefix + "missing field '" + field + "'");
}
try {
return obj.get(field).getAsString();
} catch (Exception ex) {
throw new InvalidVoteException(errorPrefix + "invalid field '" + field + "'", ex);
}
}

private String readString(byte[] data, int offset) {
StringBuilder builder = new StringBuilder();
for (int i = offset; i < data.length; i++) {
Expand Down
Loading
Loading