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.SimpleTimer; import net.i2p.util.SimpleTimer2; /** * 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 final 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 = 2*60*1000; LookupThrottler() { this.counter = new ObjectCounter<ReplyTunnel>(); SimpleTimer2.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 final Hash h; public final 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) { if (obj == null || !(obj instanceof ReplyTunnel)) return false; return this.h.equals(((ReplyTunnel)obj).h) && this.id.equals(((ReplyTunnel)obj).id); } @Override public int hashCode() { return this.h.hashCode() ^ this.id.hashCode(); } } }