forked from I2P_Developers/i2p.i2p
BuildHandler: Enforce request record timestamp
BuildRequestor: Randomize timestamp to prevent hop ID at top of hour
This commit is contained in:
@ -146,10 +146,10 @@ public class BuildRequestRecord {
|
||||
return (_data.getData()[_data.getOffset() + OFF_FLAG] & FLAG_OUTBOUND_ENDPOINT) != 0;
|
||||
}
|
||||
/**
|
||||
* Time that the request was sent, truncated to the nearest hour
|
||||
* Time that the request was sent (ms), truncated to the nearest hour
|
||||
*/
|
||||
public long readRequestTime() {
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * 60l * 60l * 1000l;
|
||||
return DataHelper.fromLong(_data.getData(), _data.getOffset() + OFF_REQ_TIME, 4) * (60 * 60 * 1000L);
|
||||
}
|
||||
/**
|
||||
* What message ID should we send the request to the next hop with. If this is the outbound tunnel endpoint,
|
||||
@ -250,6 +250,8 @@ public class BuildRequestRecord {
|
||||
else if (isOutEndpoint)
|
||||
buf[OFF_FLAG] |= FLAG_OUTBOUND_ENDPOINT;
|
||||
long truncatedHour = ctx.clock().now();
|
||||
// prevent hop identification at top of the hour
|
||||
truncatedHour -= ctx.random().nextInt(90*1000);
|
||||
truncatedHour /= (60l*60l*1000l);
|
||||
DataHelper.toLong(buf, OFF_REQ_TIME, 4, truncatedHour);
|
||||
DataHelper.toLong(buf, OFF_SEND_MSG_ID, 4, nextMsgId);
|
||||
|
@ -85,6 +85,11 @@ class BuildHandler implements Runnable {
|
||||
*/
|
||||
private static final int NEXT_HOP_SEND_TIMEOUT = 25*1000;
|
||||
|
||||
private static final long MAX_REQUEST_FUTURE = 5*60*1000;
|
||||
/** must be > 1 hour due to rouding down */
|
||||
private static final long MAX_REQUEST_AGE = 65*60*1000;
|
||||
|
||||
|
||||
public BuildHandler(RouterContext ctx, TunnelPoolManager manager, BuildExecutor exec) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(getClass());
|
||||
@ -101,8 +106,10 @@ class BuildHandler implements Runnable {
|
||||
_context.statManager().createRateStat("tunnel.reject.50", "How often we reject a tunnel because of a critical issue (shutdown, etc)", "Tunnels", new long[] { 60*1000, 10*60*1000 });
|
||||
|
||||
_context.statManager().createRequiredRateStat("tunnel.decryptRequestTime", "Time to decrypt a build request (ms)", "Tunnels", new long[] { 60*1000, 10*60*1000 });
|
||||
_context.statManager().createRequiredRateStat("tunnel.rejectTimeout", "Reject tunnel count (unknown next hop)", "Tunnels", new long[] { 60*1000, 10*60*1000 });
|
||||
_context.statManager().createRequiredRateStat("tunnel.rejectTimeout2", "Reject tunnel count (can't contact next hop)", "Tunnels", new long[] { 60*1000, 10*60*1000 });
|
||||
_context.statManager().createRateStat("tunnel.rejectTooOld", "Reject tunnel count (too old)", "Tunnels", new long[] { 3*60*60*1000 });
|
||||
_context.statManager().createRateStat("tunnel.rejectFuture", "Reject tunnel count (time in future)", "Tunnels", new long[] { 3*60*60*1000 });
|
||||
_context.statManager().createRateStat("tunnel.rejectTimeout", "Reject tunnel count (unknown next hop)", "Tunnels", new long[] { 60*60*1000 });
|
||||
_context.statManager().createRateStat("tunnel.rejectTimeout2", "Reject tunnel count (can't contact next hop)", "Tunnels", new long[] { 60*60*1000 });
|
||||
_context.statManager().createRequiredRateStat("tunnel.rejectDupID", "Part. tunnel dup ID", "Tunnels", new long[] { 24*60*60*1000 });
|
||||
_context.statManager().createRequiredRateStat("tunnel.ownDupID", "Our tunnel dup. ID", "Tunnels", new long[] { 24*60*60*1000 });
|
||||
_context.statManager().createRequiredRateStat("tunnel.rejectHostile", "Reject malicious tunnel", "Tunnels", new long[] { 24*60*60*1000 });
|
||||
@ -587,11 +594,24 @@ class BuildHandler implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
// time is in hours, and only for log below - what's the point?
|
||||
// tunnel-alt-creation.html specifies that this is enforced +/- 1 hour but it is not.
|
||||
// time is in hours, rounded down.
|
||||
// tunnel-alt-creation.html specifies that this is enforced +/- 1 hour but it was not.
|
||||
// As of 0.9.16, allow + 5 minutes to - 65 minutes.
|
||||
long time = req.readRequestTime();
|
||||
long now = (_context.clock().now() / (60l*60l*1000l)) * (60*60*1000);
|
||||
int ourSlot = -1;
|
||||
long timeDiff = now - time;
|
||||
if (timeDiff > MAX_REQUEST_AGE) {
|
||||
_context.statManager().addRateData("tunnel.rejectTooOld", 1);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping build request too old... replay attack? " + DataHelper.formatDuration(timeDiff));
|
||||
return;
|
||||
}
|
||||
if (timeDiff < 0 - MAX_REQUEST_FUTURE) {
|
||||
_context.statManager().addRateData("tunnel.rejectFuture", 1);
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping build request too far in future " + DataHelper.formatDuration(0 - timeDiff));
|
||||
return;
|
||||
}
|
||||
|
||||
int response;
|
||||
if (_context.router().isHidden()) {
|
||||
@ -764,6 +784,7 @@ class BuildHandler implements Runnable {
|
||||
|
||||
byte reply[] = BuildResponseRecord.create(_context, response, req.readReplyKey(), req.readReplyIV(), state.msg.getUniqueId());
|
||||
int records = state.msg.getRecordCount();
|
||||
int ourSlot = -1;
|
||||
for (int j = 0; j < records; j++) {
|
||||
if (state.msg.getRecord(j) == null) {
|
||||
ourSlot = j;
|
||||
@ -780,7 +801,7 @@ class BuildHandler implements Runnable {
|
||||
+ " accepted? " + response + " receiving on " + ourId
|
||||
+ " sending to " + nextId
|
||||
+ " on " + nextPeer
|
||||
+ " inGW? " + isInGW + " outEnd? " + isOutEnd + " time difference " + (now-time)
|
||||
+ " inGW? " + isInGW + " outEnd? " + isOutEnd
|
||||
+ " recvDelay " + recvDelay + " replyMessage " + req.readReplyMessageId()
|
||||
+ " replyKey " + req.readReplyKey() + " replyIV " + Base64.encode(req.readReplyIV()));
|
||||
|
||||
|
Reference in New Issue
Block a user