From ab4f3008cb78a7ef0ea6b25b04e9b6eeb83f8443 Mon Sep 17 00:00:00 2001 From: jrandom <jrandom> Date: Fri, 9 Dec 2005 08:05:44 +0000 Subject: [PATCH] 2005-12-09 zzz * Create different strategies for exploratory tunnels (which are difficult to create) and client tunnels (which are much easier) * Gradually increase number of parallel build attempts as tunnel expiry nears. * Temporarily shorten attempted build tunnel length if builds using configured tunnel length are unsuccessful * React more aggressively to tunnel failure than routine tunnel replacement * Make tunnel creation times randomized - there is existing code to randomize the tunnels but it isn't effective due to the tunnel creation strategy. Currently, most tunnels get built all at once, at about 2 1/2 to 3 minutes before expiration. The patch fixes this by fixing the randomization, and by changing the overlap time (with old tunnels) to a range of 2 to 4 minutes. * Reduce number of excess tunnels. Lots of excess tunnels get created due to overlapping calls. Just about anything generated a call which could build many tunnels all at once, even if tunnel building was already in process. * Miscellaneous router console enhancements --- .../i2p/router/web/ConfigLoggingHelper.java | 2 + .../i2p/router/web/ConfigTunnelsHandler.java | 2 +- .../net/i2p/router/web/JobQueueHelper.java | 46 +++++++ .../src/net/i2p/router/web/SummaryHelper.java | 47 +++++-- apps/routerconsole/jsp/configtunnels.jsp | 3 + apps/routerconsole/jsp/jobs.jsp | 21 ++++ apps/routerconsole/jsp/nav.jsp | 2 + apps/routerconsole/jsp/summary.jsp | 6 +- history.txt | 23 +++- .../src/net/i2p/router/RouterVersion.java | 4 +- .../src/net/i2p/router/RouterWatchdog.java | 4 +- .../net/i2p/router/TunnelManagerFacade.java | 6 + .../net/i2p/router/TunnelPoolSettings.java | 6 + .../net/i2p/router/tunnel/pool/ExpireJob.java | 17 ++- .../i2p/router/tunnel/pool/RebuildJob.java | 45 ++++++- .../router/tunnel/pool/RequestTunnelJob.java | 32 ++++- .../i2p/router/tunnel/pool/TunnelBuilder.java | 2 +- .../tunnel/pool/TunnelPeerSelector.java | 5 +- .../i2p/router/tunnel/pool/TunnelPool.java | 119 +++++++++++++----- .../router/tunnel/pool/TunnelPoolManager.java | 44 ++++++- 20 files changed, 365 insertions(+), 71 deletions(-) create mode 100644 apps/routerconsole/java/src/net/i2p/router/web/JobQueueHelper.java create mode 100644 apps/routerconsole/jsp/jobs.jsp diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java index 4ca03b1e5d..0f2188bffd 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigLoggingHelper.java @@ -59,6 +59,8 @@ public class ConfigLoggingHelper { buf.append(prefix).append('=').append(level).append('\n'); } buf.append("</textarea><br />\n"); + buf.append("<i>Add additional logging statements above. Example: net.i2p.router.tunnel=WARN</i><br>"); + buf.append("<i>Or put entries in the logger.config file. Example: logger.record.net.i2p.router.tunnel=WARN</i><br>"); buf.append("<i>Valid levels are DEBUG, INFO, WARN, ERROR, CRIT</i>\n"); return buf.toString(); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHandler.java index 73ebcf87ea..c64fbc4e9f 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/ConfigTunnelsHandler.java @@ -135,7 +135,7 @@ public class ConfigTunnelsHandler extends FormHandler { if (saveRequired) { boolean saved = _context.router().saveConfig(); if (saved) - addFormNotice("Configuration saved successfully"); + addFormNotice("Exploratory tunnel configuration saved successfully"); else addFormNotice("Error saving the configuration (applied but not saved) - please see the error logs"); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/JobQueueHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/JobQueueHelper.java new file mode 100644 index 0000000000..a56cce19af --- /dev/null +++ b/apps/routerconsole/java/src/net/i2p/router/web/JobQueueHelper.java @@ -0,0 +1,46 @@ +package net.i2p.router.web; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; + +import net.i2p.router.RouterContext; + +public class JobQueueHelper { + private RouterContext _context; + private Writer _out; + /** + * Configure this bean to query a particular router context + * + * @param contextId begging few characters of the routerHash, or null to pick + * the first one we come across. + */ + public void setContextId(String contextId) { + try { + _context = ContextHelper.getContext(contextId); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + public JobQueueHelper() {} + + public void setWriter(Writer writer) { _out = writer; } + + public String getJobQueueSummary() { + try { + if (_out != null) { + _context.jobQueue().renderStatusHTML(_out); + return ""; + } else { + ByteArrayOutputStream baos = new ByteArrayOutputStream(32*1024); + _context.jobQueue().renderStatusHTML(new OutputStreamWriter(baos)); + return new String(baos.toByteArray()); + } + } catch (IOException ioe) { + ioe.printStackTrace(); + return ""; + } + } +} diff --git a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java index 1c26f5bd07..bcaee116f0 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/SummaryHelper.java @@ -83,14 +83,23 @@ public class SummaryHelper { long ms = _context.clock().getOffset(); - if (ms < 60 * 1000) { - return now + " (" + (ms / 1000) + "s)"; - } else if (ms < 60 * 60 * 1000) { - return now + " (" + (ms / (60 * 1000)) + "m)"; - } else if (ms < 24 * 60 * 60 * 1000) { - return now + " (" + (ms / (60 * 60 * 1000)) + "h)"; + long diff = ms; + if (diff < 0) + diff = 0 - diff; + if (diff == 0) { + return now + " (no skew)"; + } else if (diff < 1000) { + return now + " (" + ms + "ms skew)"; + } else if (diff < 5 * 1000) { + return now + " (" + (ms / 1000) + "s skew)"; + } else if (diff < 60 * 1000) { + return now + " <b>(" + (ms / 1000) + "s skew)</b>"; + } else if (diff < 60 * 60 * 1000) { + return now + " <b>(" + (ms / (60 * 1000)) + "m skew)</b>"; + } else if (diff < 24 * 60 * 60 * 1000) { + return now + " <b>(" + (ms / (60 * 60 * 1000)) + "h skew)</b>"; } else { - return now + " (" + (ms / (24 * 60 * 60 * 1000)) + "d)"; + return now + " <b>(" + (ms / (24 * 60 * 60 * 1000)) + "d skew)</b>"; } } @@ -408,6 +417,28 @@ public class SummaryHelper { return _context.tunnelManager().getOutboundTunnelCount(); } + /** + * How many inbound client tunnels we have. + * + */ + public int getInboundClientTunnels() { + if (_context == null) + return 0; + else + return _context.tunnelManager().getInboundClientTunnelCount(); + } + + /** + * How many active outbound client tunnels we have. + * + */ + public int getOutboundClientTunnels() { + if (_context == null) + return 0; + else + return _context.tunnelManager().getOutboundClientTunnelCount(); + } + /** * How many tunnels we are participating in. * @@ -459,4 +490,4 @@ public class SummaryHelper { public boolean updateAvailable() { return NewsFetcher.getInstance(_context).updateAvailable(); } -} \ No newline at end of file +} diff --git a/apps/routerconsole/jsp/configtunnels.jsp b/apps/routerconsole/jsp/configtunnels.jsp index 84ecf43418..0b6fcb714b 100644 --- a/apps/routerconsole/jsp/configtunnels.jsp +++ b/apps/routerconsole/jsp/configtunnels.jsp @@ -33,6 +33,9 @@ <input type="hidden" name="action" value="blah" /> <jsp:getProperty name="tunnelshelper" property="form" /> <hr /> + <i>Note - Exploratory tunnel setting changes are stored in the router.config file.</i></br> + <i>Client tunnel changes are temporary and are not saved.</i><br> + <i>To make permanent client tunnel changes see the </i><a href="i2ptunnel/index.jsp">i2ptunnel page</a>.<br> <input type="submit" name="shouldsave" value="Save changes" /> <input type="reset" value="Cancel" /> </form> </div> diff --git a/apps/routerconsole/jsp/jobs.jsp b/apps/routerconsole/jsp/jobs.jsp new file mode 100644 index 0000000000..56701af6b4 --- /dev/null +++ b/apps/routerconsole/jsp/jobs.jsp @@ -0,0 +1,21 @@ +<%@page contentType="text/html"%> +<%@page pageEncoding="UTF-8"%> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> + +<html><head> +<title>I2P Router Console - job queue</title> +<link rel="stylesheet" href="default.css" type="text/css" /> +</head><body> + +<%@include file="nav.jsp" %> +<%@include file="summary.jsp" %> + +<div class="main" id="main"> + <jsp:useBean class="net.i2p.router.web.JobQueueHelper" id="jobQueueHelper" scope="request" /> + <jsp:setProperty name="jobQueueHelper" property="contextId" value="<%=(String)session.getAttribute("i2p.contextId")%>" /> + <jsp:setProperty name="jobQueueHelper" property="writer" value="<%=out%>" /> + <jsp:getProperty name="jobQueueHelper" property="jobQueueSummary" /> +</div> + +</body> +</html> diff --git a/apps/routerconsole/jsp/nav.jsp b/apps/routerconsole/jsp/nav.jsp index 414b7c7f25..57c8264667 100644 --- a/apps/routerconsole/jsp/nav.jsp +++ b/apps/routerconsole/jsp/nav.jsp @@ -18,11 +18,13 @@ <a href="susimail/susimail">Susimail</a> | <a href="susidns/index.jsp">SusiDNS</a> | <a href="syndie/">Syndie</a> | + <a href="http://localhost:7658/">My Eepsite</a> <br> <a href="i2ptunnel/index.jsp">I2PTunnel</a> | <a href="tunnels.jsp">Tunnels</a> | <a href="profiles.jsp">Profiles</a> | <a href="netdb.jsp">NetDB</a> | <a href="logs.jsp">Logs</a> | + <a href="jobs.jsp">Jobs</a> | <a href="oldstats.jsp">Stats</a> | <a href="oldconsole.jsp">Internals</a> <jsp:useBean class="net.i2p.router.web.NavHelper" id="navhelper" scope="request" /> diff --git a/apps/routerconsole/jsp/summary.jsp b/apps/routerconsole/jsp/summary.jsp index ad45e9328c..9d246ddb68 100644 --- a/apps/routerconsole/jsp/summary.jsp +++ b/apps/routerconsole/jsp/summary.jsp @@ -72,9 +72,9 @@ <jsp:getProperty name="helper" property="destinations" /> - <u><b>Tunnels</b></u><br /> - <b>Inbound:</b> <jsp:getProperty name="helper" property="inboundTunnels" /><br /> - <b>Outbound:</b> <jsp:getProperty name="helper" property="outboundTunnels" /><br /> + <u><b>Tunnels in/out</b></u><br /> + <b>Exploratory:</b> <jsp:getProperty name="helper" property="inboundTunnels" />/<jsp:getProperty name="helper" property="outboundTunnels" /><br /> + <b>Client:</b> <jsp:getProperty name="helper" property="inboundClientTunnels" />/<jsp:getProperty name="helper" property="outboundClientTunnels" /><br /> <b>Participating:</b> <jsp:getProperty name="helper" property="participatingTunnels" /><br /> <hr /> diff --git a/history.txt b/history.txt index bc98dcadbf..553e0093ef 100644 --- a/history.txt +++ b/history.txt @@ -1,4 +1,25 @@ -$Id: history.txt,v 1.350 2005/12/07 19:50:35 jrandom Exp $ +$Id: history.txt,v 1.351 2005/12/08 15:53:43 jrandom Exp $ + +2005-12-09 zzz + * Create different strategies for exploratory tunnels (which are difficult + to create) and client tunnels (which are much easier) + * Gradually increase number of parallel build attempts as tunnel expiry + nears. + * Temporarily shorten attempted build tunnel length if builds using + configured tunnel length are unsuccessful + * React more aggressively to tunnel failure than routine tunnel + replacement + * Make tunnel creation times randomized - there is existing code to + randomize the tunnels but it isn't effective due to the tunnel creation + strategy. Currently, most tunnels get built all at once, at about 2 1/2 + to 3 minutes before expiration. The patch fixes this by fixing the + randomization, and by changing the overlap time (with old tunnels) to a + range of 2 to 4 minutes. + * Reduce number of excess tunnels. Lots of excess tunnels get created due + to overlapping calls. Just about anything generated a call which could + build many tunnels all at once, even if tunnel building was already in + process. + * Miscellaneous router console enhancements 2005-12-08 jrandom * Minor bugfix in SSU for dealing with corrupt packets diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 12b75cd616..dff197ec62 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -15,9 +15,9 @@ import net.i2p.CoreVersion; * */ public class RouterVersion { - public final static String ID = "$Revision: 1.307 $ $Date: 2005/12/01 12:16:54 $"; + public final static String ID = "$Revision: 1.308 $ $Date: 2005/12/08 15:53:41 $"; public final static String VERSION = "0.6.1.7"; - public final static long BUILD = 1; + public final static long BUILD = 2; public static void main(String args[]) { System.out.println("I2P Router version: " + VERSION + "-" + BUILD); System.out.println("Router ID: " + RouterVersion.ID); diff --git a/router/java/src/net/i2p/router/RouterWatchdog.java b/router/java/src/net/i2p/router/RouterWatchdog.java index eafdd46d11..f8f78a7631 100644 --- a/router/java/src/net/i2p/router/RouterWatchdog.java +++ b/router/java/src/net/i2p/router/RouterWatchdog.java @@ -90,7 +90,9 @@ class RouterWatchdog implements Runnable { public void monitorRouter() { boolean ok = verifyJobQueueLiveliness(); - ok = ok && verifyClientLiveliness(); + // If we aren't connected to the network that's why there's nobody to talk to + int netErrors = (int) _context.statManager().getRate("udp.sendException").getRate(60*1000).getLastEventCount(); + ok = ok && (verifyClientLiveliness() || netErrors >= 5); if (!ok) { dumpStatus(); diff --git a/router/java/src/net/i2p/router/TunnelManagerFacade.java b/router/java/src/net/i2p/router/TunnelManagerFacade.java index 67a2c4033a..052af69b8a 100644 --- a/router/java/src/net/i2p/router/TunnelManagerFacade.java +++ b/router/java/src/net/i2p/router/TunnelManagerFacade.java @@ -48,6 +48,10 @@ public interface TunnelManagerFacade extends Service { public int getFreeTunnelCount(); /** how many outbound tunnels do we have available? */ public int getOutboundTunnelCount(); + /** how many free inbound client tunnels do we have available? */ + public int getInboundClientTunnelCount(); + /** how many outbound client tunnels do we have available? */ + public int getOutboundClientTunnelCount(); /** When does the last tunnel we are participating in expire? */ public long getLastParticipatingExpiration(); @@ -81,6 +85,8 @@ class DummyTunnelManagerFacade implements TunnelManagerFacade { public int getParticipatingCount() { return 0; } public int getFreeTunnelCount() { return 0; } public int getOutboundTunnelCount() { return 0; } + public int getInboundClientTunnelCount() { return 0; } + public int getOutboundClientTunnelCount() { return 0; } public long getLastParticipatingExpiration() { return -1; } public void buildTunnels(Destination client, ClientTunnelSettings settings) {} public TunnelPoolSettings getInboundSettings() { return null; } diff --git a/router/java/src/net/i2p/router/TunnelPoolSettings.java b/router/java/src/net/i2p/router/TunnelPoolSettings.java index 8df755a28c..aaeb5cec37 100644 --- a/router/java/src/net/i2p/router/TunnelPoolSettings.java +++ b/router/java/src/net/i2p/router/TunnelPoolSettings.java @@ -18,6 +18,7 @@ public class TunnelPoolSettings { private int _duration; private int _length; private int _lengthVariance; + private int _lengthOverride; private boolean _isInbound; private boolean _isExploratory; private boolean _allowZeroHop; @@ -54,6 +55,7 @@ public class TunnelPoolSettings { _duration = DEFAULT_DURATION; _length = DEFAULT_LENGTH; _lengthVariance = DEFAULT_LENGTH_VARIANCE; + _lengthOverride = 0; _allowZeroHop = DEFAULT_ALLOW_ZERO_HOP; _isInbound = false; _isExploratory = false; @@ -90,6 +92,10 @@ public class TunnelPoolSettings { */ public int getLengthVariance() { return _lengthVariance; } public void setLengthVariance(int variance) { _lengthVariance = variance; } + + /* Set to a nonzero value to override the length setting */ + public int getLengthOverride() { return _lengthOverride; } + public void setLengthOverride(int variance) { _lengthOverride = variance; } /** is this an inbound tunnel? */ public boolean isInbound() { return _isInbound; } diff --git a/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java b/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java index 83ec39570c..93dff3b542 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/ExpireJob.java @@ -25,11 +25,16 @@ class ExpireJob extends JobImpl { return "Expire exploratory outbound tunnel"; } } else { - if (_pool.getSettings().isInbound()) { - return "Expire client inbound tunnel"; - } else { - return "Expire client outbound tunnel"; - } + StringBuffer rv = new StringBuffer(32); + if (_pool.getSettings().isInbound()) + rv.append("Expire inbound client tunnel for "); + else + rv.append("Expire outbound client tunnel for "); + if (_pool.getSettings().getDestinationNickname() != null) + rv.append(_pool.getSettings().getDestinationNickname()); + else + rv.append(_pool.getSettings().getDestination().toBase64().substring(0,4)); + return rv.toString(); } } public void runJob() { @@ -44,4 +49,4 @@ class ExpireJob extends JobImpl { getContext().tunnelDispatcher().remove(_cfg); } } -} \ No newline at end of file +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java b/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java index fb5cad7cb7..fd4a6eda3d 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/RebuildJob.java @@ -7,7 +7,10 @@ import net.i2p.router.tunnel.TunnelCreatorConfig; /** * Build a new tunnel to replace the existing one before it expires. This job * should be removed (or scheduled to run immediately) if the tunnel fails. - * + * If an exploratory tunnel build at a random time between 3 1/2 and 4 minutes early; + * else if only one tunnel in pool build 4 minutes early; + * otherwise build at a random time between 2 and 4 minutes early. + * Five build attempts in parallel if an exploratory tunnel. */ class RebuildJob extends JobImpl { private TunnelPool _pool; @@ -17,12 +20,42 @@ class RebuildJob extends JobImpl { super(ctx); _pool = pool; _cfg = cfg; - long rebuildOn = cfg.getExpiration() - pool.getSettings().getRebuildPeriod(); - rebuildOn -= ctx.random().nextInt(pool.getSettings().getRebuildPeriod()*2); + long rebuildOn; + if (_pool.getSettings().isExploratory()) { + rebuildOn = cfg.getExpiration() - (((pool.getSettings().getRebuildPeriod() * 7) / 2)); + rebuildOn -= ctx.random().nextInt(pool.getSettings().getRebuildPeriod() / 2); + } else if ((pool.getSettings().getQuantity() + pool.getSettings().getBackupQuantity()) == 1) { + rebuildOn = cfg.getExpiration() - (pool.getSettings().getRebuildPeriod() * 4); + } else { + rebuildOn = cfg.getExpiration() - (pool.getSettings().getRebuildPeriod() * 2); + rebuildOn -= ctx.random().nextInt(pool.getSettings().getRebuildPeriod() * 2); + } getTiming().setStartAfter(rebuildOn); } - public String getName() { return "Rebuild tunnel"; } + public String getName() { + if (_pool.getSettings().isExploratory()) { + if (_pool.getSettings().isInbound()) { + return "Rebuild exploratory inbound tunnel"; + } else { + return "Rebuild exploratory outbound tunnel"; + } + } else { + StringBuffer rv = new StringBuffer(32); + if (_pool.getSettings().isInbound()) + rv.append("Rebuild inbound client tunnel for "); + else + rv.append("Rebuild outbound client tunnel for "); + if (_pool.getSettings().getDestinationNickname() != null) + rv.append(_pool.getSettings().getDestinationNickname()); + else + rv.append(_pool.getSettings().getDestination().toBase64().substring(0,4)); + return rv.toString(); + } + } public void runJob() { - _pool.refreshBuilders(); + if (_pool.getSettings().isExploratory()) + _pool.refreshBuilders(4, 4); + else + _pool.refreshBuilders(1, 4); } -} \ No newline at end of file +} diff --git a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java index 208d8031f5..44f075ebf2 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/RequestTunnelJob.java @@ -66,9 +66,15 @@ public class RequestTunnelJob extends JobImpl { ctx.statManager().createRateStat("tunnel.receiveRejectionBandwidth", "How often we are rejected due to bandwidth overload?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); ctx.statManager().createRateStat("tunnel.receiveRejectionCritical", "How often we are rejected due to critical failure?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); ctx.statManager().createRateStat("tunnel.buildFailure", "What hop was being requested when a nonexploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); - ctx.statManager().createRateStat("tunnel.buildExploratoryFailure", "What hop was beiing requested when an exploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.buildExploratoryFailure", "What hop was being requested when an exploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.buildExploratoryFailure1Hop", "What hop was being requested when a 1 hop exploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.buildExploratoryFailure2Hop", "What hop was being requested when a 2 hop exploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.buildExploratoryFailure3Hop", "What hop was being requested when a 3 hop exploratory tunnel request failed?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); ctx.statManager().createRateStat("tunnel.buildSuccess", "How often we succeed building a non-exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); ctx.statManager().createRateStat("tunnel.buildExploratorySuccess", "How often we succeed building an exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.buildExploratorySuccess1Hop", "How often we succeed building a 1 hop exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.buildExploratorySuccess2Hop", "How often we succeed building a 2 hop exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); + ctx.statManager().createRateStat("tunnel.buildExploratorySuccess3Hop", "How often we succeed building a 3 hop exploratory tunnel?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); ctx.statManager().createRateStat("tunnel.buildPartialTime", "How long a non-exploratory request took to be accepted?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); ctx.statManager().createRateStat("tunnel.buildExploratoryPartialTime", "How long an exploratory request took to be accepted?", "Tunnels", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l }); @@ -263,9 +269,16 @@ public class RequestTunnelJob extends JobImpl { _log.info("tunnel building failed: " + _config + " at hop " + _currentHop); if (_onFailed != null) getContext().jobQueue().addJob(_onFailed); - if (_isExploratory) - getContext().statManager().addRateData("tunnel.buildExploratoryFailure", _currentHop, _config.getLength()); - else + if (_isExploratory) { + int i = _config.getLength(); + getContext().statManager().addRateData("tunnel.buildExploratoryFailure", _currentHop, i); + if (i == 2) + getContext().statManager().addRateData("tunnel.buildExploratoryFailure1Hop", _currentHop, i); + else if (i == 3) + getContext().statManager().addRateData("tunnel.buildExploratoryFailure2Hop", _currentHop, i); + else if (i == 4) + getContext().statManager().addRateData("tunnel.buildExploratoryFailure3Hop", _currentHop, i); + } else getContext().statManager().addRateData("tunnel.buildFailure", _currentHop, _config.getLength()); } @@ -284,9 +297,16 @@ public class RequestTunnelJob extends JobImpl { } else { if (_onCreated != null) getContext().jobQueue().addJob(_onCreated); - if (_isExploratory) + if (_isExploratory) { + int i = _config.getLength(); getContext().statManager().addRateData("tunnel.buildExploratorySuccess", 1, 0); - else + if (i == 2) + getContext().statManager().addRateData("tunnel.buildExploratorySuccess1Hop", 1, 0); + else if (i == 3) + getContext().statManager().addRateData("tunnel.buildExploratorySuccess2Hop", 1, 0); + else if (i == 4) + getContext().statManager().addRateData("tunnel.buildExploratorySuccess3Hop", 1, 0); + } else getContext().statManager().addRateData("tunnel.buildSuccess", 1, 0); } } diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java index 2655fa2040..1d0a5adbaa 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelBuilder.java @@ -110,7 +110,7 @@ public class TunnelBuilder { public void runJob() { // yikes, nothing left, lets get some backup (if we're allowed) _pool.getManager().buildComplete(); - _pool.refreshBuilders(); + _pool.refreshBuilders(1, 4); } } } diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java index 980b1cc55b..0a07dc3eaf 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPeerSelector.java @@ -26,7 +26,10 @@ abstract class TunnelPeerSelector { protected int getLength(RouterContext ctx, TunnelPoolSettings settings) { int length = settings.getLength(); - if (settings.getLengthVariance() != 0) { + int override = settings.getLengthOverride(); + if (override != 0) + length = override; + else if (settings.getLengthVariance() != 0) { int skew = settings.getLengthVariance(); if (skew > 0) length += ctx.random().nextInt(skew+1); diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java index 16f3d06ea0..34ec54c269 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java @@ -37,7 +37,7 @@ public class TunnelPool { private long _lastSelectionPeriod; /** - * Only 5 builds per minute per pool, even if we have failing tunnels, + * Only 10 builds per minute per pool, even if we have failing tunnels, * etc. On overflow, the necessary additional tunnels are built by the * RefreshJob */ @@ -65,7 +65,7 @@ public class TunnelPool { _alive = true; _refreshJob.getTiming().setStartAfter(_context.clock().now() + 60*1000); _context.jobQueue().addJob(_refreshJob); - int added = refreshBuilders(); + int added = refreshBuilders(0, 0); if (added <= 0) { // we just reconnected and didn't require any new tunnel builders. // however, we /do/ want a leaseSet, so build one @@ -85,12 +85,17 @@ public class TunnelPool { _lastSelected = null; } - private int countUsableTunnels() { + /** + * Return number of tunnels expiring greater than + * timeFactor * RebuildPeriod from now + * + */ + private int countUsableTunnels(int timeFactor) { int valid = 0; synchronized (_tunnels) { for (int i = 0; i < _tunnels.size(); i++) { TunnelInfo info = (TunnelInfo)_tunnels.get(i); - if (info.getExpiration() > _context.clock().now() + 3*_settings.getRebuildPeriod()) + if (info.getExpiration() > _context.clock().now() + (timeFactor * _settings.getRebuildPeriod())) valid++; } } @@ -99,21 +104,31 @@ public class TunnelPool { /** * Fire up as many buildTunnel tasks as necessary, returning how many - * were added + * were added. + * Build maxBuild tunnels (0 = unlimited), use timeFactor * RebuildPeriod. + * Fire off up to six extra jobs if an exploratory tunnel is + * requested by RebuildJob or tunnelFailed (maxBuild > 1). + * Throttle builds to a maximum per minute; reduce maximum if job lag is high, + * or if we have network errors which indicate we are disconnected from the network. + * Override pool length setting and build a 1-hop tunnel if time is short. * */ - int refreshBuilders() { + int refreshBuilders(int maxBuild, int timeFactor) { if ( (_settings.getDestination() != null) && (!_context.clientManager().isLocal(_settings.getDestination())) ) _alive = false; if (!_alive) return 0; // only start up new build tasks if we need more of 'em - int target = _settings.getQuantity() + _settings.getBackupQuantity(); - int usableTunnels = countUsableTunnels(); + int baseTarget = _settings.getQuantity() + _settings.getBackupQuantity(); + int target = baseTarget; + int usableTunnels = countUsableTunnels(timeFactor); + if (_settings.isExploratory() && target > 0 && maxBuild > 1) + target+= 6; - if ( (target > usableTunnels) && (_log.shouldLog(Log.INFO)) ) - _log.info(toString() + ": refreshing builders, previously had " + usableTunnels + if ( (target > usableTunnels) ) + if ( (target > usableTunnels) && (_log.shouldLog(Log.INFO)) ) + _log.info(toString() + ": refreshing builders, previously had " + usableTunnels + ", want a total of " + target + ", creating " - + (target-usableTunnels) + " new ones."); + + (target-usableTunnels) + " new ones (" + maxBuild + " max)."); if (target > usableTunnels) { long minute = _context.clock().now(); @@ -123,9 +138,34 @@ public class TunnelPool { _buildsThisMinute = 0; } int build = (target - usableTunnels); - if (build > (MAX_BUILDS_PER_MINUTE - _buildsThisMinute)) - build = (MAX_BUILDS_PER_MINUTE - _buildsThisMinute); - + if (maxBuild > 0 && build > maxBuild) + build = maxBuild; + int buildThrottle = MAX_BUILDS_PER_MINUTE; + int lag = (int) _context.statManager().getRate("jobQueue.jobLag").getRate(60*1000).getAverageValue(); + int netErrors = (int) _context.statManager().getRate("udp.sendException").getRate(60*1000).getLastEventCount(); + if (lag > 3 * 1000 || netErrors > 5) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Throttling tunnel builds lag = " + lag + "; net errors = " + netErrors); + if (_settings.isExploratory()) + buildThrottle = 3; + else + buildThrottle = 1; + } else if (lag > 1 * 1000) { + if (_settings.isExploratory()) + buildThrottle = 5; + else + buildThrottle = 2; + } + if (build > (buildThrottle - _buildsThisMinute)) + build = (buildThrottle - _buildsThisMinute); + if (build <= 0) return 0; + + if ((_settings.isExploratory() && baseTarget > countUsableTunnels(1)) || + ((!_settings.isExploratory()) && baseTarget > countUsableTunnels(0))) + _settings.setLengthOverride(1); + else + _settings.setLengthOverride(0); + int wanted = build; build = _manager.allocateBuilds(build); @@ -255,7 +295,7 @@ public class TunnelPool { if (_settings != null) { if (_log.shouldLog(Log.INFO)) _log.info(toString() + ": Settings updated on the pool: " + settings); - refreshBuilders(); // to start/stop new sequences, in case the quantities changed + refreshBuilders(1, 4); // to start/stop new sequences, in case the quantities changed } } public TunnelPeerSelector getSelector() { return _peerSelector; } @@ -278,8 +318,6 @@ public class TunnelPool { if (ls != null) _context.clientManager().requestLeaseSet(_settings.getDestination(), ls); - - refreshBuilders(); } public void removeTunnel(TunnelInfo info) { @@ -319,7 +357,6 @@ public class TunnelPool { _manager.removeTunnels(_settings.getDestination()); return; } - refreshBuilders(); } public void tunnelFailed(PooledTunnelCreatorConfig cfg) { @@ -343,15 +380,27 @@ public class TunnelPool { if (_settings.isInbound() && (_settings.getDestination() != null) ) { if (ls != null) { _context.clientManager().requestLeaseSet(_settings.getDestination(), ls); + if (_settings.isExploratory()) + refreshBuilders(3, 4); + else + refreshBuilders(1, 4); } else { if (_log.shouldLog(Log.WARN)) _log.warn(toString() + ": unable to build a new leaseSet on failure (" + remaining + " remaining), request a new tunnel"); if (remaining < _settings.getBackupQuantity() + _settings.getQuantity()) - buildFallback(); + if (!buildFallback()) + if (_settings.isExploratory()) + refreshBuilders(3, 4); + else + refreshBuilders(1, 4); } + } else { + if (_settings.isExploratory()) + refreshBuilders(3, 4); + else + refreshBuilders(1, 4); } - refreshBuilders(); } void refreshLeaseSet() { @@ -377,18 +426,24 @@ public class TunnelPool { } } - void buildFallback() { + /** + * Return true if a fallback tunnel is built + * + */ + boolean buildFallback() { int quantity = _settings.getBackupQuantity() + _settings.getQuantity(); - int usable = countUsableTunnels(); - if (usable >= quantity) return; + int usable = countUsableTunnels(1); + if (usable >= quantity) return false; - if (_log.shouldLog(Log.INFO)) - _log.info(toString() + ": building a fallback tunnel (usable: " + usable + " needed: " + quantity + ")"); - if ( (usable == 0) && (_settings.getAllowZeroHop()) ) + if ( (usable == 0) && (_settings.getAllowZeroHop()) ) { + if (_log.shouldLog(Log.INFO)) + _log.info(toString() + ": building a fallback tunnel (usable: " + usable + " needed: " + quantity + ")"); _builder.buildTunnel(_context, this, true); + return true; + } //else // _builder.buildTunnel(_context, this); - refreshBuilders(); + return false; } /** @@ -476,12 +531,16 @@ public class TunnelPool { public RefreshJob(RouterContext ctx) { super(ctx); } - public String getName() { return "Refresh pool"; } + public String getName() { return "Refresh " + TunnelPool.this.toString(); } public void runJob() { if (!_alive) return; - int added = refreshBuilders(); + int added; + if (_settings.isExploratory()) + added = refreshBuilders(0, 2); + else + added = refreshBuilders(0, 1); if ( (added > 0) && (_log.shouldLog(Log.WARN)) ) - _log.warn("Passive rebuilding a tunnel for " + TunnelPool.this.toString()); + _log.warn("Additional parallel rebuilding of tunnel for " + TunnelPool.this.toString()); requeue(30*1000); } } diff --git a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java index 4367b437a0..a2a9de0195 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java @@ -154,6 +154,38 @@ public class TunnelPoolManager implements TunnelManagerFacade { else return _outboundExploratory.size(); } + public int getInboundClientTunnelCount() { + int count = 0; + List destinations = null; + synchronized (_clientInboundPools) { + destinations = new ArrayList(_clientInboundPools.keySet()); + } + for (int i = 0; i < destinations.size(); i++) { + Hash client = (Hash)destinations.get(i); + TunnelPool pool = null; + synchronized (_clientInboundPools) { + pool = (TunnelPool)_clientInboundPools.get(client); + } + count += pool.listTunnels().size(); + } + return count; + } + public int getOutboundClientTunnelCount() { + int count = 0; + List destinations = null; + synchronized (_clientInboundPools) { + destinations = new ArrayList(_clientOutboundPools.keySet()); + } + for (int i = 0; i < destinations.size(); i++) { + Hash client = (Hash)destinations.get(i); + TunnelPool pool = null; + synchronized (_clientOutboundPools) { + pool = (TunnelPool)_clientOutboundPools.get(client); + } + count += pool.listTunnels().size(); + } + return count; + } public int getParticipatingCount() { return _context.tunnelDispatcher().getParticipatingCount(); } public long getLastParticipatingExpiration() { return _context.tunnelDispatcher().getLastParticipatingExpiration(); } @@ -240,6 +272,7 @@ public class TunnelPoolManager implements TunnelManagerFacade { } } inbound.startup(); + try { Thread.sleep(3*1000); } catch (InterruptedException ie) {} outbound.startup(); } @@ -316,6 +349,7 @@ public class TunnelPoolManager implements TunnelManagerFacade { _inboundExploratory = new TunnelPool(_context, this, inboundSettings, selector, builder); _inboundExploratory.startup(); + try { Thread.sleep(3*1000); } catch (InterruptedException ie) {} TunnelPoolSettings outboundSettings = new TunnelPoolSettings(); outboundSettings.setIsExploratory(true); outboundSettings.setIsInbound(false); @@ -406,10 +440,10 @@ public class TunnelPoolManager implements TunnelManagerFacade { out.write("<td>n/a</td>"); long timeLeft = cfg.getExpiration()-_context.clock().now(); if (timeLeft > 0) - out.write("<td>" + DataHelper.formatDuration(timeLeft) + "</td>"); + out.write("<td align=right>" + DataHelper.formatDuration(timeLeft) + "</td>"); else - out.write("<td>(grace period)</td>"); - out.write("<td>" + cfg.getProcessedMessagesCount() + "KB</td>"); + out.write("<td align=right>(grace period)</td>"); + out.write("<td align=right>" + cfg.getProcessedMessagesCount() + "KB</td>"); out.write("</tr>\n"); processed += cfg.getProcessedMessagesCount(); } @@ -441,8 +475,8 @@ public class TunnelPoolManager implements TunnelManagerFacade { out.write("<tr><td><b>inbound</b></td>"); else out.write("<tr><td><b>outbound</b></td>"); - out.write("<td>" + DataHelper.formatDuration(timeLeft) + "</td>\n"); - out.write("<td>" + info.getProcessedMessagesCount() + "KB</td>\n"); + out.write("<td align=right>" + DataHelper.formatDuration(timeLeft) + "</td>\n"); + out.write("<td align=right>" + info.getProcessedMessagesCount() + "KB</td>\n"); for (int j = 0; j < info.getLength(); j++) { Hash peer = info.getPeer(j); TunnelId id = (info.isInbound() ? info.getReceiveTunnelId(j) : info.getSendTunnelId(j)); -- GitLab