- Add basic DOS prevention for lookups

- Move flood throttle check so we don't throttle ourselves
This commit is contained in:
zzz
2010-01-29 13:53:14 +00:00
parent 81664bc776
commit 0a93466999
8 changed files with 123 additions and 25 deletions

View File

@@ -134,8 +134,8 @@ public class RouterContext extends I2PAppContext {
_shitlist = new Shitlist(this);
_blocklist = new Blocklist(this);
_messageValidator = new MessageValidator(this);
//_throttle = new RouterThrottleImpl(this);
_throttle = new RouterDoSThrottle(this);
_throttle = new RouterThrottleImpl(this);
//_throttle = new RouterDoSThrottle(this);
_integrationCalc = new IntegrationCalculator(this);
_speedCalc = new SpeedCalculator(this);
_capacityCalc = new CapacityCalculator(this);

View File

@@ -6,6 +6,7 @@ import net.i2p.data.Hash;
* Minor extention of the router throttle to handle some DoS events and
* throttle accordingly.
*
* @deprecated unused
*/
class RouterDoSThrottle extends RouterThrottleImpl {
public RouterDoSThrottle(RouterContext context) {

View File

@@ -72,6 +72,7 @@ class RouterThrottleImpl implements RouterThrottle {
}
}
/** @deprecated unused, function moved to netdb */
public boolean acceptNetDbLookupRequest(Hash key) {
long lag = _context.jobQueue().getMaxLag();
if (lag > JOB_LAG_LIMIT) {

View File

@@ -7,6 +7,9 @@ import net.i2p.util.SimpleTimer;
/**
* Count how often we have recently flooded a key
* This offers basic DOS protection but is not a complete solution.
*
* @since 0.7.11
*/
class FloodThrottler {
private ObjectCounter<Hash> counter;

View File

@@ -23,12 +23,15 @@ import net.i2p.util.Log;
*/
public class FloodfillDatabaseLookupMessageHandler implements HandlerJobBuilder {
private RouterContext _context;
private FloodfillNetworkDatabaseFacade _facade;
private Log _log;
public FloodfillDatabaseLookupMessageHandler(RouterContext context) {
public FloodfillDatabaseLookupMessageHandler(RouterContext context, FloodfillNetworkDatabaseFacade facade) {
_context = context;
_facade = facade;
_log = context.logManager().getLog(FloodfillDatabaseLookupMessageHandler.class);
_context.statManager().createRateStat("netDb.lookupsReceived", "How many netDb lookups have we received?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.lookupsDropped", "How many netDb lookups did we drop due to throttling?", "NetworkDatabase", new long[] { 5*60*1000l, 60*60*1000l, 24*60*60*1000l });
_context.statManager().createRateStat("netDb.lookupsReceived", "How many netDb lookups have we received?", "NetworkDatabase", new long[] { 60*60*1000l });
_context.statManager().createRateStat("netDb.lookupsDropped", "How many netDb lookups did we drop due to throttling?", "NetworkDatabase", new long[] { 60*60*1000l });
// following are for ../HDLMJ
_context.statManager().createRateStat("netDb.lookupsHandled", "How many netDb lookups have we handled?", "NetworkDatabase", new long[] { 60*60*1000l });
_context.statManager().createRateStat("netDb.lookupsMatched", "How many netDb lookups did we have the data for?", "NetworkDatabase", new long[] { 60*60*1000l });
@@ -42,18 +45,19 @@ public class FloodfillDatabaseLookupMessageHandler implements HandlerJobBuilder
public Job createJob(I2NPMessage receivedMessage, RouterIdentity from, Hash fromHash) {
_context.statManager().addRateData("netDb.lookupsReceived", 1, 0);
if (true || _context.throttle().acceptNetDbLookupRequest(((DatabaseLookupMessage)receivedMessage).getSearchKey())) {
Job j = new HandleFloodfillDatabaseLookupMessageJob(_context, (DatabaseLookupMessage)receivedMessage, from, fromHash);
if (false) {
// might as well inline it, all the heavy lifting is queued up in later jobs, if necessary
j.runJob();
return null;
} else {
DatabaseLookupMessage dlm = (DatabaseLookupMessage)receivedMessage;
if (!_facade.shouldThrottleLookup(dlm.getFrom(), dlm.getReplyTunnel())) {
Job j = new HandleFloodfillDatabaseLookupMessageJob(_context, dlm, from, fromHash);
//if (false) {
// // might as well inline it, all the heavy lifting is queued up in later jobs, if necessary
// j.runJob();
// return null;
//} else {
return j;
}
//}
} else {
if (_log.shouldLog(Log.INFO))
_log.info("Dropping lookup request as throttled");
if (_log.shouldLog(Log.WARN))
_log.warn("Dropping lookup request for " + dlm.getSearchKey() + " (throttled), reply was to: " + dlm.getFrom() + " tunnel: " + dlm.getReplyTunnel());
_context.statManager().addRateData("netDb.lookupsDropped", 1, 1);
return null;
}

View File

@@ -12,6 +12,7 @@ import net.i2p.data.DataStructure;
import net.i2p.data.Hash;
import net.i2p.data.LeaseSet;
import net.i2p.data.RouterInfo;
import net.i2p.data.TunnelId;
import net.i2p.data.i2np.DatabaseLookupMessage;
import net.i2p.data.i2np.DatabaseSearchReplyMessage;
import net.i2p.data.i2np.DatabaseStoreMessage;
@@ -38,6 +39,7 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
private static String _alwaysQuery;
private final Set<Hash> _verifiesInProgress;
private FloodThrottler _floodThrottler;
private LookupThrottler _lookupThrottler;
public FloodfillNetworkDatabaseFacade(RouterContext context) {
super(context);
@@ -63,11 +65,12 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
public void startup() {
super.startup();
_context.jobQueue().addJob(new FloodfillMonitorJob(_context, this));
_lookupThrottler = new LookupThrottler();
}
@Override
protected void createHandlers() {
_context.inNetMessagePool().registerHandlerJobBuilder(DatabaseLookupMessage.MESSAGE_TYPE, new FloodfillDatabaseLookupMessageHandler(_context));
_context.inNetMessagePool().registerHandlerJobBuilder(DatabaseLookupMessage.MESSAGE_TYPE, new FloodfillDatabaseLookupMessageHandler(_context, this));
_context.inNetMessagePool().registerHandlerJobBuilder(DatabaseStoreMessage.MESSAGE_TYPE, new FloodfillDatabaseStoreMessageHandler(_context, this));
}
@@ -103,6 +106,22 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
}
}
/**
* Increments and tests.
* @since 0.7.11
*/
boolean shouldThrottleFlood(Hash key) {
return _floodThrottler != null && _floodThrottler.shouldThrottle(key);
}
/**
* Increments and tests.
* @since 0.7.11
*/
boolean shouldThrottleLookup(Hash from, TunnelId id) {
return _lookupThrottler.shouldThrottle(from, id);
}
private static final int MAX_TO_FLOOD = 7;
/**
@@ -116,13 +135,6 @@ public class FloodfillNetworkDatabaseFacade extends KademliaNetworkDatabaseFacad
key = ((LeaseSet)ds).getDestination().calculateHash();
else
key = ((RouterInfo)ds).getIdentity().calculateHash();
// DOS prevention
if (_floodThrottler != null && _floodThrottler.shouldThrottle(key)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too many recent stores, not flooding key: " + key);
_context.statManager().addRateData("netDb.floodThrottled", 1, 0);
return;
}
Hash rkey = _context.routingKeyGenerator().getRoutingKey(key);
FloodfillPeerSelector sel = (FloodfillPeerSelector)getPeerSelector();
List peers = sel.selectFloodfillParticipants(rkey, MAX_TO_FLOOD, getKBuckets());

View File

@@ -54,9 +54,9 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
String invalidMessage = null;
boolean wasNew = false;
RouterInfo prevNetDb = null;
Hash key = _message.getKey();
if (_message.getValueType() == DatabaseStoreMessage.KEY_TYPE_LEASESET) {
getContext().statManager().addRateData("netDb.storeLeaseSetHandled", 1, 0);
Hash key = _message.getKey();
if (_log.shouldLog(Log.INFO))
_log.info("Handling dbStore of leaseset " + _message);
//_log.info("Handling dbStore of leasset " + key + " with expiration of "
@@ -92,7 +92,6 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
}
} else if (_message.getValueType() == DatabaseStoreMessage.KEY_TYPE_ROUTERINFO) {
getContext().statManager().addRateData("netDb.storeRouterInfoHandled", 1, 0);
Hash key = _message.getKey();
if (_log.shouldLog(Log.INFO))
_log.info("Handling dbStore of router " + key + " with publishDate of "
+ new Date(_message.getRouterInfo().getPublished()));
@@ -163,6 +162,14 @@ public class HandleFloodfillDatabaseStoreMessageJob extends JobImpl {
FloodfillNetworkDatabaseFacade.floodfillEnabled(getContext()) &&
_message.getReplyToken() > 0) {
if (wasNew) {
// DOS prevention
// Note this does not throttle the ack above
if (_facade.shouldThrottleFlood(key)) {
if (_log.shouldLog(Log.WARN))
_log.warn("Too many recent stores, not flooding key: " + key);
getContext().statManager().addRateData("netDb.floodThrottled", 1, 0);
return;
}
long floodBegin = System.currentTimeMillis();
if (_message.getValueType() == DatabaseStoreMessage.KEY_TYPE_LEASESET)
_facade.flood(_message.getLeaseSet());

View File

@@ -0,0 +1,70 @@
package net.i2p.router.networkdb.kademlia;
import net.i2p.data.Hash;
import net.i2p.data.TunnelId;
import net.i2p.util.ObjectCounter;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
* Count how often we have recently received a lookup request with
* the reply specified to go to a peer/TunnelId pair.
* This offers basic DOS protection but is not a complete solution.
* The reply peer/tunnel could be spoofed, for example.
* And a requestor could have up to 6 reply tunnels.
*
* @since 0.7.11
*/
class LookupThrottler {
private ObjectCounter<ReplyTunnel> counter;
/** the id of this is -1 */
private static final TunnelId DUMMY_ID = new TunnelId();
/** this seems like plenty */
private static final int MAX_LOOKUPS = 30;
private static final long CLEAN_TIME = 60*1000;
LookupThrottler() {
this.counter = new ObjectCounter();
SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME);
}
/**
* increments before checking
* @param key non-null
* @param id null if for direct lookups
*/
boolean shouldThrottle(Hash key, TunnelId id) {
return this.counter.increment(new ReplyTunnel(key, id)) > MAX_LOOKUPS;
}
private class Cleaner implements SimpleTimer.TimedEvent {
public void timeReached() {
LookupThrottler.this.counter.clear();
}
}
/** yes, we could have a two-level lookup, or just do h.tostring() + id.tostring() */
private static class ReplyTunnel {
public Hash h;
public TunnelId id;
ReplyTunnel(Hash h, TunnelId id) {
this.h = h;
if (id != null)
this.id = id;
else
this.id = DUMMY_ID;
}
@Override
public boolean equals(Object obj) {
return this.h.equals(((ReplyTunnel)obj).h) &&
this.id.equals(((ReplyTunnel)obj).id);
}
@Override
public int hashCode() {
return this.h.hashCode() + this.id.hashCode();
}
}
}