diff --git a/history.txt b/history.txt index 1e4e7de6068e23ed59720c3c1568ec5dd990af2c..3eaea4745d5431f949153c975aa7adfe458009f5 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,9 @@ +2011-02-22 zzz + * BuildHandler: Prelmiinary participating tunnel throttler + * I2PTunnel: + - Add spellcheck=false to textareas + - Fix HTML error in 503 error page + 2011-02-19 zzz * I2PTunnel: Fix standalone server tunnels http://forum.i2p/viewtopic.php?t=5376 diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 421ff4f630110e645f24b485d06a7151f306e1cd..098e16d499456339b4699536706ff45ebf0c4769 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 16; + public final static long BUILD = 17; /** for example "-test" */ public final static String EXTRA = "-rc"; diff --git a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java index 7be46e0c73e555fe707c23c3178b775eb9b34e08..ca9c6ad635916c0211f3a52777466ebdf3267506 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java +++ b/router/java/src/net/i2p/router/tunnel/pool/BuildHandler.java @@ -37,18 +37,19 @@ import net.i2p.util.Log; * */ class BuildHandler { - private RouterContext _context; - private Log _log; - private BuildExecutor _exec; - private Job _buildMessageHandlerJob; - private Job _buildReplyMessageHandlerJob; + private final RouterContext _context; + private final Log _log; + private final BuildExecutor _exec; + private final Job _buildMessageHandlerJob; + private final Job _buildReplyMessageHandlerJob; /** list of BuildMessageState, oldest first */ private final List<BuildMessageState> _inboundBuildMessages; /** list of BuildReplyMessageState, oldest first - unused unless HANDLE_REPLIES_INLINE == false */ private final List<BuildReplyMessageState> _inboundBuildReplyMessages; /** list of BuildEndMessageState, oldest first - unused unless HANDLE_REPLIES_INLINE == false */ private final List<BuildEndMessageState> _inboundBuildEndMessages; - private BuildMessageProcessor _processor; + private final BuildMessageProcessor _processor; + private final ParticipatingThrottler _throttler; private static final boolean HANDLE_REPLIES_INLINE = true; @@ -101,6 +102,7 @@ class BuildHandler { ctx.inNetMessagePool().registerHandlerJobBuilder(TunnelBuildReplyMessage.MESSAGE_TYPE, tbrmhjb); ctx.inNetMessagePool().registerHandlerJobBuilder(VariableTunnelBuildMessage.MESSAGE_TYPE, tbmhjb); ctx.inNetMessagePool().registerHandlerJobBuilder(VariableTunnelBuildReplyMessage.MESSAGE_TYPE, tbrmhjb); + _throttler = new ParticipatingThrottler(ctx); } private static final int MAX_HANDLE_AT_ONCE = 2; @@ -498,6 +500,7 @@ class BuildHandler { long nextId = req.readNextTunnelId(); boolean isInGW = req.readIsInboundGateway(); boolean isOutEnd = req.readIsOutboundEndpoint(); + // 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. long time = req.readRequestTime(); @@ -531,7 +534,7 @@ class BuildHandler { _context.statManager().addRateData("tunnel.acceptLoad", recvDelay, recvDelay); } } - + /* * Being a IBGW or OBEP generally leads to more connections, so if we are * approaching our connection limit (i.e. !haveCapacity()), @@ -550,6 +553,28 @@ class BuildHandler { _context.throttle().setTunnelStatus(_x("Rejecting tunnels: Connection limit")); response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH; } + + // Check participating throttle counters for previous and next hops + // This is at the end as it compares to a percentage of created tunnels. + // We may need another counter above for requests. + if (response == 0 && !isInGW) { + Hash from = state.fromHash; + if (from == null) + from = state.from.calculateHash(); + if (_throttler.shouldThrottle(from)) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Rejecting tunnel (hop throttle), previous hop: " + from); + // no setTunnelStatus() indication + response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH; + } + } + if (response == 0 && (!isOutEnd) && + _throttler.shouldThrottle(req.readNextIdentity())) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Rejecting tunnel (hop throttle), next hop: " + req.readNextIdentity()); + // no setTunnelStatus() indication + response = TunnelHistory.TUNNEL_REJECT_BANDWIDTH; + } if (_log.shouldLog(Log.DEBUG)) _log.debug("Responding to " + state.msg.getUniqueId() + "/" + ourId diff --git a/router/java/src/net/i2p/router/tunnel/pool/ParticpatingThrottler.java b/router/java/src/net/i2p/router/tunnel/pool/ParticpatingThrottler.java new file mode 100644 index 0000000000000000000000000000000000000000..06d6826793a30a1c7c8d6bdca643fe5acc4ded38 --- /dev/null +++ b/router/java/src/net/i2p/router/tunnel/pool/ParticpatingThrottler.java @@ -0,0 +1,57 @@ +package net.i2p.router.tunnel.pool; + +import net.i2p.data.Hash; +import net.i2p.router.RouterContext; +import net.i2p.util.ObjectCounter; +import net.i2p.util.SimpleScheduler; +import net.i2p.util.SimpleTimer; + +/** + * Count how often we have accepted a tunnel with the peer + * as the previous or next hop. + * We limit each peer to a percentage of all participating tunnels, + * subject to minimum and maximum values for the limit. + * + * 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. + * + * Note that the counts are of previous + next hops, so the total will + * be higher than the participating tunnel count, and will also grow + * as the network uses more 3-hop tunnels. + * + * @since 0.8.4 + */ +class ParticipatingThrottler { + private final RouterContext context; + private final ObjectCounter<Hash> counter; + + /** portion of the tunnel lifetime */ + private static final int LIFETIME_PORTION = 3; + private static final int MIN_LIMIT = 18 / LIFETIME_PORTION; + private static final int MAX_LIMIT = 81 / LIFETIME_PORTION; + private static final int PERCENT_LIMIT = 12 / LIFETIME_PORTION; + private static final long CLEAN_TIME = 10*60*1000 / LIFETIME_PORTION; + + ParticipatingThrottler(RouterContext ctx) { + this.context = ctx; + this.counter = new ObjectCounter(); + SimpleScheduler.getInstance().addPeriodicEvent(new Cleaner(), CLEAN_TIME); + } + + /** increments before checking */ + boolean shouldThrottle(Hash h) { + int numTunnels = this.context.tunnelManager().getParticipatingCount(); + int limit = Math.max(MIN_LIMIT, Math.min(MAX_LIMIT, numTunnels * PERCENT_LIMIT / 100)); + return this.counter.increment(h) > limit; + } + + private class Cleaner implements SimpleTimer.TimedEvent { + public void timeReached() { + ParticipatingThrottler.this.counter.clear(); + } + } +}