merge of '5cbdacfbeb8f09f9ac497016e429eaf6fe7225c6'

and 'dae5a2b76e24fc3215d46d5b96ea0f547c83f63c'
This commit is contained in:
zzz
2009-12-20 14:52:57 +00:00
28 changed files with 289 additions and 132 deletions

View File

@ -1,10 +1,17 @@
I2P source installation instructions
Prerequisites to build from source:
Java SDK (preferably Sun) 1.5.0 or higher (1.6 recommended)
Apache Ant 1.7.0 or higher
Optional, For multilanguage support: The xgettext, msgfmt, and msgmerge tools installed
from the GNU gettext package http://www.gnu.org/software/gettext/
To build and install I2P from source, you must first build
and package up the appropriate installer by running:
ant pkg
This will produce a few key files:
* install.jar: the GUI and console installer
* i2pinstall.exe: the GUI and console installer wrapped for cross-platform execution
@ -18,9 +25,6 @@ Or run the GUI installer:
Or move the update file into an existing installation directory and restart.
You will need to have ant installed from http://ant.apache.org/
(1.7.0 or newer)
Supported JVMs:
Windows: Latest available from http://java.sun.com/ (1.5+ supported)
Linux: Latest available from http://java.sun.com/ (1.5+ supported)

View File

@ -1,11 +1,13 @@
Prerequisites to build from source:
Java SDK (preferably Sun) 1.5.0 or higher (1.6 recommended)
Apache Ant 1.7.0 or higher
Optional, For multilanguage support: The xgettext, msgfmt, and msgmerge tools installed
from the GNU gettext package http://www.gnu.org/software/gettext/
To build:
ant pkg
Run 'ant' with no arguments to see other build options.
See http://www.i2p2.de/download.html for installation instructions.
See INSTALL.txt or http://www.i2p2.de/download.html for installation instructions.
Documentation:
http://www.i2p2.de/

View File

@ -179,7 +179,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
if (get.fetch()) {
String lastmod = get.getLastModified();
if (lastmod != null) {
if (!(_context instanceof RouterContext)) return;
if (!(_context.isRouterContext())) return;
long modtime = parse822Date(lastmod);
if (modtime <= 0) return;
String lastUpdate = _context.getProperty(UpdateHandler.PROP_LAST_UPDATE_TIME);
@ -310,7 +310,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Policy requests update, so we update");
UpdateHandler handler = null;
if (_context instanceof RouterContext) {
if (_context.isRouterContext()) {
handler = new UpdateHandler((RouterContext)_context);
} else {
List contexts = RouterContext.listContexts();

View File

@ -40,7 +40,7 @@ public class StatsGenerator {
String group = (String)entry.getKey();
Set stats = (Set)entry.getValue();
buf.append("<option value=\"/stats.jsp#").append(group).append("\">");
buf.append(group).append("</option>\n");
buf.append(_(group)).append("</option>\n");
for (Iterator statIter = stats.iterator(); statIter.hasNext(); ) {
String stat = (String)statIter.next();
buf.append("<option value=\"/stats.jsp#");
@ -52,7 +52,7 @@ public class StatsGenerator {
out.write(buf.toString());
buf.setLength(0);
}
buf.append("</select> <input type=\"submit\" value=\"GO\" />");
buf.append("</select> <input type=\"submit\" value=\"").append(_("GO")).append("\" />");
buf.append("</form>");
buf.append(_("Statistics gathered during this router's uptime")).append(" (");
@ -69,7 +69,7 @@ public class StatsGenerator {
buf.append("<h3><a name=\"");
buf.append(group);
buf.append("\">");
buf.append(group);
buf.append(_(group));
buf.append("</a></h3>");
buf.append("<ul>");
out.write(buf.toString());
@ -104,7 +104,7 @@ public class StatsGenerator {
for (int i = 0; i < periods.length; i++) {
if (periods[i] > uptime)
break;
renderPeriod(buf, periods[i], "frequency");
renderPeriod(buf, periods[i], _("frequency"));
Frequency curFreq = freq.getFrequency(periods[i]);
buf.append(" <i>avg per period:</i> (");
buf.append(num(curFreq.getAverageEventsPerPeriod()));
@ -138,7 +138,7 @@ public class StatsGenerator {
buf.append("</i><br>");
}
if (rate.getLifetimeEventCount() <= 0) {
buf.append("No lifetime events<br>\n");
buf.append(_("No lifetime events")).append("<br>\n");
return;
}
long now = _context.clock().now();
@ -150,9 +150,9 @@ public class StatsGenerator {
if (curRate.getLastCoalesceDate() <= curRate.getCreationDate())
break;
buf.append("<li>");
renderPeriod(buf, periods[i], "rate");
renderPeriod(buf, periods[i], _("rate"));
if (curRate.getLastEventCount() > 0) {
buf.append( "<i>avg value:</i> (");
buf.append( "<i>").append(_("avg value")).append(":</i> (");
buf.append(num(curRate.getAverageValue()));
buf.append(" peak ");
buf.append(num(curRate.getExtremeAverageValue()));
@ -181,21 +181,21 @@ public class StatsGenerator {
buf.append(num(curRate.getExtremeSaturationLimit()));
buf.append(")");
}
buf.append(" <i>events:</i> ");
buf.append(" <i>").append(_("events")).append(":</i> ");
buf.append(curRate.getLastEventCount());
buf.append(" <i>in this period which ended:</i> ");
buf.append(DataHelper.formatDuration(now - curRate.getLastCoalesceDate()));
buf.append(" ago ");
} else {
buf.append(" <i>No events</i> ");
buf.append(" <i>").append(_("No events")).append("</i> ");
}
long numPeriods = curRate.getLifetimePeriods();
if (numPeriods > 0) {
double avgFrequency = curRate.getLifetimeEventCount() / (double)numPeriods;
double peakFrequency = curRate.getExtremeEventCount();
buf.append(" (lifetime average: ");
buf.append(" (").append(_("lifetime average")).append(": ");
buf.append(num(avgFrequency));
buf.append(", peak average: ");
buf.append(", ").append(_("peak average")).append(": ");
buf.append(curRate.getExtremeEventCount());
buf.append(")");
}
@ -213,7 +213,7 @@ public class StatsGenerator {
buf.append("</li>\n");
}
// Display the strict average
buf.append("<li><b>lifetime average value:</b> ");
buf.append("<li><b>").append(_("lifetime average value")).append(":</b> ");
buf.append(num(rate.getLifetimeAverageValue()));
buf.append(" over ");
buf.append(rate.getLifetimeEventCount());
@ -240,4 +240,9 @@ public class StatsGenerator {
private String _(String s) {
return Messages.getString(s, _context);
}
/** translate a string */
private String _(String s, Object o) {
return Messages.getString(s, o, _context);
}
}

View File

@ -62,6 +62,9 @@ public class SummaryHelper extends HelperBase {
return DataHelper.formatDuration(router.getUptime());
}
/**
this displayed offset, not skew - now handled in reachability()
private String timeSkew() {
if (_context == null) return "";
//if (!_context.clock().getUpdatedSuccessfully())
@ -72,6 +75,7 @@ public class SummaryHelper extends HelperBase {
return "";
return " (" + DataHelper.formatDuration(diff) + " " + _("skew") + ")";
}
**/
public boolean allowReseed() {
return _context.netDb().isInitialized() &&
@ -83,15 +87,20 @@ public class SummaryHelper extends HelperBase {
public int getAllPeers() { return Math.max(_context.netDb().getKnownRouters() - 1, 0); }
public String getReachability() {
return reachability() + timeSkew();
return reachability(); // + timeSkew();
}
private String reachability() {
if (_context.router().getUptime() > 60*1000 && (!_context.router().gracefulShutdownInProgress()) &&
!_context.clientManager().isAlive())
return _("ERR-Client Manager I2CP Error - check logs"); // not a router problem but the user should know
if (!_context.clock().getUpdatedSuccessfully())
return _("ERR-ClockSkew");
// Warn based on actual skew from peers, not update status, so if we successfully offset
// the clock, we don't complain.
//if (!_context.clock().getUpdatedSuccessfully())
Long skew = _context.commSystem().getFramedAveragePeerClockSkew(33);
// Display the actual skew, not the offset
if (skew != null && Math.abs(skew.longValue()) > 45)
return _("ERR-Clock Skew of {0}", DataHelper.formatDuration(Math.abs(skew.longValue()) * 1000));
if (_context.router().isHidden())
return _("Hidden");
@ -118,7 +127,9 @@ public class SummaryHelper extends HelperBase {
default:
ra = _context.router().getRouterInfo().getTargetAddress("SSU");
if (ra == null && _context.router().getUptime() > 5*60*1000) {
if (_context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME) == null ||
if (getActivePeers() <= 0)
return _("ERR-No Active Peers, Check Network Connection and Firewall");
else if (_context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_HOSTNAME) == null ||
_context.getProperty(ConfigNetHelper.PROP_I2NP_NTCP_PORT) == null)
return _("ERR-UDP Disabled and Inbound TCP host/port not set");
else

View File

@ -233,7 +233,7 @@ public class TunnelRenderer {
Collections.sort(peerList, new CountryComparator(this._context.commSystem()));
out.write("<h2><a name=\"peers\"></a>" + _("Tunnel Counts By Peer") + "</h2>\n");
out.write("<table><tr><th>" + _("Peer") + "</th><th>" + _("Expl. + Client") + "</th><th>" + _("% of total") + "</th><th>" + _("Part. from + to") + "</th><th>" + _("% of total") + "</th></tr>\n");
out.write("<table><tr><th>" + _("Peer") + "</th><th>" + _("Our Tunnels") + "</th><th>" + _("% of total") + "</th><th>" + _("Participating Tunnels") + "</th><th>" + _("% of total") + "</th></tr>\n");
for (Hash h : peerList) {
out.write("<tr> <td class=\"cells\" align=\"center\">");
out.write(netDbLink(h));
@ -251,7 +251,7 @@ public class TunnelRenderer {
out.write('0');
out.write('\n');
}
out.write("<tr class=\"tablefooter\"> <td align=\"center\"><b>" + _("Tunnels") + "</b> <td align=\"center\"><b>" + tunnelCount);
out.write("<tr class=\"tablefooter\"> <td align=\"center\"><b>" + _("Totals") + "</b> <td align=\"center\"><b>" + tunnelCount);
out.write("</b> <td>&nbsp;</td> <td align=\"center\"><b>" + partCount);
out.write("</b> <td>&nbsp;</td></tr></table></div>\n");
}

View File

@ -52,5 +52,24 @@ class Dummy {
_("dark");
_("light");
_("midnight");
// stat groups for stats.jsp
_("Bandwidth");
_("BandwidthLimiter");
_("ClientMessages");
_("Encryption");
_("i2cp");
_("I2PTunnel");
_("InNetPool");
_("JobQueue");
_("NetworkDatabase");
_("ntcp");
_("Peers");
_("Router");
_("Stream");
_("Throttle");
_("Transport");
_("Tunnels");
_("udp");
}
}

View File

@ -722,4 +722,11 @@ public class I2PAppContext {
return new HashSet(_shutdownTasks);
}
/**
* Use this instead of context instanceof RouterContext
* @since 0.7.9
*/
public boolean isRouterContext() {
return false;
}
}

View File

@ -170,6 +170,7 @@ public class RateStat {
}
}
/*********
public static void main(String args[]) {
RateStat rs = new RateStat("moo", "moo moo moo", "cow trueisms", new long[] { 60 * 1000, 60 * 60 * 1000,
24 * 60 * 60 * 1000});
@ -206,4 +207,5 @@ public class RateStat {
} catch (InterruptedException ie) { // nop
}
}
*********/
}

View File

@ -189,7 +189,10 @@ public class Timestamper implements Runnable {
long expectedDelta = 0;
_wellSynced = false;
for (int i = 0; i < _concurringServers; i++) {
try { Thread.sleep(10*1000); } catch (InterruptedException ie) {}
if (i > 0) {
// this delays startup when net is disconnected or the timeserver list is bad, don't make it too long
try { Thread.sleep(2*1000); } catch (InterruptedException ie) {}
}
now = NtpClient.currentTime(serverList);
long delta = now - _context.clock().now();
found[i] = delta;

View File

@ -41,6 +41,7 @@ import net.i2p.stat.Rate;
import net.i2p.stat.RateStat;
import net.i2p.stat.StatManager;
import net.i2p.util.FileUtil;
import net.i2p.util.I2PAppThread;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.SimpleScheduler;
@ -201,6 +202,8 @@ public class Router {
installUpdates();
// Apps may use this as an easy way to determine if they are in the router JVM
// But context.isRouterContext() is even easier...
// Both of these as of 0.7.9
System.setProperty("router.version", RouterVersion.VERSION);
// NOW we start all the activity
@ -228,14 +231,10 @@ public class Router {
}
};
_shutdownHook = new ShutdownHook(_context);
_gracefulShutdownDetector = new I2PThread(new GracefulShutdown());
_gracefulShutdownDetector.setDaemon(true);
_gracefulShutdownDetector.setName("Graceful shutdown hook");
_gracefulShutdownDetector = new I2PAppThread(new GracefulShutdown(), "Graceful shutdown hook", true);
_gracefulShutdownDetector.start();
I2PThread watchdog = new I2PThread(new RouterWatchdog(_context));
watchdog.setName("RouterWatchdog");
watchdog.setDaemon(true);
Thread watchdog = new I2PAppThread(new RouterWatchdog(_context), "RouterWatchdog", true);
watchdog.start();
}
@ -339,7 +338,7 @@ public class Router {
long waited = System.currentTimeMillis() - before;
if (_log.shouldLog(Log.INFO))
_log.info("Waited " + waited + "ms to initialize");
_context.jobQueue().addJob(new StartupJob(_context));
}

View File

@ -379,4 +379,12 @@ public class RouterContext extends I2PAppContext {
}
}
/**
* Use this instead of context instanceof RouterContext
* @return true
* @since 0.7.9
*/
public boolean isRouterContext() {
return true;
}
}

View File

@ -15,7 +15,7 @@ import java.util.StringTokenizer;
import net.i2p.I2PAppContext;
import net.i2p.router.RouterContext;
import net.i2p.util.EepGet;
import net.i2p.util.I2PThread;
import net.i2p.util.I2PAppThread;
import net.i2p.util.Log;
/**
@ -52,13 +52,15 @@ public class Reseeder {
return;
} else {
System.setProperty(PROP_INPROGRESS, "true");
I2PThread reseed = new I2PThread(_reseedRunner, "Reseed");
// set to daemon so it doesn't hang a shutdown
Thread reseed = new I2PAppThread(_reseedRunner, "Reseed", true);
reseed.start();
}
}
}
/** Todo: translate the messages sent via PROP_STATUS */
public class ReseedRunner implements Runnable, EepGet.StatusListener {
private boolean _isRunning;

View File

@ -105,6 +105,7 @@ public class DBHistory {
*/
public RateStat getFailedLookupRate() { return _failedLookupRate; }
/** not sure how much this is used, to be investigated */
public RateStat getInvalidReplyRate() { return _invalidReplyRate; }
/**
@ -115,6 +116,7 @@ public class DBHistory {
public void lookupSuccessful() {
_successfulLookups++;
_failedLookupRate.addData(0, 0);
_context.statManager().addRateData("peer.failedLookupRate", 0, 0);
_lastLookupSuccessful = _context.clock().now();
}
@ -124,6 +126,7 @@ public class DBHistory {
public void lookupFailed() {
_failedLookups++;
_failedLookupRate.addData(1, 0);
_context.statManager().addRateData("peer.failedLookupRate", 1, 0);
_lastLookupFailed = _context.clock().now();
}
@ -136,6 +139,7 @@ public class DBHistory {
// Fixme, redefined this to include both lookup and store fails,
// need to fix the javadocs
_failedLookupRate.addData(0, 0);
_context.statManager().addRateData("peer.failedLookupRate", 0, 0);
_lastStoreSuccessful = _context.clock().now();
}
@ -275,9 +279,9 @@ public class DBHistory {
private void createRates(String statGroup) {
if (_failedLookupRate == null)
_failedLookupRate = new RateStat("dbHistory.failedLookupRate", "How often does this peer to respond to a lookup?", statGroup, new long[] { 60*1000l, 60*60*1000l, 24*60*60*1000l });
_failedLookupRate = new RateStat("dbHistory.failedLookupRate", "How often does this peer to respond to a lookup?", statGroup, new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
if (_invalidReplyRate == null)
_invalidReplyRate = new RateStat("dbHistory.invalidReplyRate", "How often does this peer give us a bad (nonexistant, forged, etc) peer?", statGroup, new long[] { 30*60*1000l, 60*60*1000l, 24*60*60*1000l });
_invalidReplyRate = new RateStat("dbHistory.invalidReplyRate", "How often does this peer give us a bad (nonexistant, forged, etc) peer?", statGroup, new long[] { 30*60*1000l });
_failedLookupRate.setStatLog(_context.statManager().getStatLog());
_invalidReplyRate.setStatLog(_context.statManager().getStatLog());
}

View File

@ -97,10 +97,10 @@ public class ProfileOrganizer {
_log = context.logManager().getLog(ProfileOrganizer.class);
_comp = new InverseCapacityComparator();
_fastPeers = new HashMap(16);
_highCapacityPeers = new HashMap(16);
_highCapacityPeers = new HashMap(32);
_wellIntegratedPeers = new HashMap(16);
_notFailingPeers = new HashMap(64);
_notFailingPeersList = new ArrayList(64);
_notFailingPeers = new HashMap(256);
_notFailingPeersList = new ArrayList(256);
_failingPeers = new HashMap(16);
_strictCapacityOrder = new TreeSet(_comp);
_thresholdSpeedValue = 0.0d;
@ -113,6 +113,8 @@ public class ProfileOrganizer {
_context.statManager().createRateStat("peer.profileThresholdTime", "How long the reorg takes determining the tier thresholds", "Peers", new long[] { 10*60*1000 });
_context.statManager().createRateStat("peer.profilePlaceTime", "How long the reorg takes placing peers in the tiers", "Peers", new long[] { 10*60*1000 });
_context.statManager().createRateStat("peer.profileReorgTime", "How long the reorg takes overall", "Peers", new long[] { 10*60*1000 });
// used in DBHistory
_context.statManager().createRateStat("peer.failedLookupRate", "DB Lookup fail rate", "Peers", new long[] { 10*60*1000l, 60*60*1000l, 24*60*60*1000l });
}
private void getReadLock() {

View File

@ -77,31 +77,26 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
public boolean haveHighOutboundCapacity() { return (_manager == null ? false : _manager.haveHighOutboundCapacity()); }
/**
* Framed average clock skew of connected peers in seconds, or null if we cannot answer.
* Framed average clock skew of connected peers in seconds, or the clock offset if we cannot answer.
* Average is calculated over the middle "percentToInclude" peers.
*/
@Override
public Long getFramedAveragePeerClockSkew(int percentToInclude) {
if (_manager == null) {
if (_log.shouldLog(Log.INFO))
_log.info("Returning null for framed averege peer clock skew (no transport manager)!");
return null;
// round toward zero
return Long.valueOf(_context.clock().getOffset() / 1000);
}
Vector skews = _manager.getClockSkews();
if (skews == null) {
if (_log.shouldLog(Log.ERROR))
_log.error("Returning null for framed average peer clock skew (no data)!");
return null;
return Long.valueOf(_context.clock().getOffset() / 1000);
}
if (skews.size() < 20) {
if (_log.shouldLog(Log.WARN))
_log.warn("Returning null for framed average peer clock skew (only " + skews.size() + " peers)!");
return null;
if (skews.size() < 5) {
return Long.valueOf(_context.clock().getOffset() / 1000);
}
// Going to calculate, sort them
Collections.sort(skews);
// Calculate frame size
int frameSize = (skews.size() * percentToInclude / 100);
int frameSize = Math.min((skews.size() * percentToInclude / 100), 2);
int first = (skews.size() / 2) - (frameSize / 2);
int last = (skews.size() / 2) + (frameSize / 2);
// Sum skew values
@ -112,11 +107,8 @@ public class CommSystemFacadeImpl extends CommSystemFacade {
_log.debug("Adding clock skew " + i + " valued " + value + " s.");
sum = sum + value;
}
// Calculate average
Long framedAverageClockSkew = new Long(sum / frameSize);
if (_log.shouldLog(Log.INFO))
_log.info("Our framed average peer clock skew is " + framedAverageClockSkew + " s.");
return framedAverageClockSkew;
// Calculate average (round toward zero)
return Long.valueOf(sum / frameSize);
}
public List getBids(OutNetMessage msg) {

View File

@ -2,7 +2,7 @@ package net.i2p.router.tunnel;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
/**
@ -45,11 +45,10 @@ import net.i2p.util.Log;
* }
*/
public class BatchedPreprocessor extends TrivialPreprocessor {
private Log _log;
private long _pendingSince;
private String _name;
public BatchedPreprocessor(I2PAppContext ctx, String name) {
public BatchedPreprocessor(RouterContext ctx, String name) {
super(ctx);
_log = ctx.logManager().getLog(BatchedPreprocessor.class);
_name = name;
@ -98,8 +97,9 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
return rv;
}
/* See TunnelGateway.QueuePreprocessor for Javadoc */
@Override
public boolean preprocessQueue(List pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
public boolean preprocessQueue(List<TunnelGateway.Pending> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
StringBuilder timingBuf = null;
if (_log.shouldLog(Log.DEBUG)) {
_log.debug("Preprocess queue with " + pending.size() + " to send");
@ -116,12 +116,15 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
int batchCount = 0;
int beforeLooping = pending.size();
// loop until the queue is empty
while (pending.size() > 0) {
int allocated = 0;
long beforePendingLoop = System.currentTimeMillis();
// loop until we fill up a single message
for (int i = 0; i < pending.size(); i++) {
long pendingStart = System.currentTimeMillis();
TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.get(i);
TunnelGateway.Pending msg = pending.get(i);
int instructionsSize = getInstructionsSize(msg);
instructionsSize += getInstructionAugmentationSize(msg, allocated, instructionsSize);
int curWanted = msg.getData().length - msg.getOffset() + instructionsSize;
@ -135,7 +138,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
// the instructions alone exceed the size, so we won't get any
// of the message into it. don't include it
i--;
msg = (TunnelGateway.Pending)pending.get(i);
msg = pending.get(i);
allocated -= curWanted;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Pushback of " + curWanted + " (message " + (i+1) + " in " + pending + ")");
@ -144,6 +147,8 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
long waited = _context.clock().now() - _pendingSince;
_context.statManager().addRateData("tunnel.batchDelaySent", pending.size(), waited);
}
// Send the message
long beforeSend = System.currentTimeMillis();
_pendingSince = 0;
send(pending, 0, i, sender, rec);
@ -154,8 +159,9 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
+ " (last complete? " + (msg.getOffset() >= msg.getData().length)
+ ", off=" + msg.getOffset() + ", count=" + pending.size() + ")");
// Remove what we sent from the pending queue
for (int j = 0; j < i; j++) {
TunnelGateway.Pending cur = (TunnelGateway.Pending)pending.remove(0);
TunnelGateway.Pending cur = pending.remove(0);
if (cur.getOffset() < cur.getData().length)
throw new IllegalArgumentException("i=" + i + " j=" + j + " off=" + cur.getOffset()
+ " len=" + cur.getData().length + " alloc=" + allocated);
@ -167,7 +173,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
}
if (msg.getOffset() >= msg.getData().length) {
// ok, this last message fit perfectly, remove it too
TunnelGateway.Pending cur = (TunnelGateway.Pending)pending.remove(0);
TunnelGateway.Pending cur = pending.remove(0);
if (timingBuf != null)
timingBuf.append(" sent perfect fit " + cur).append(".");
notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), msg.getData().length, msg.getMessageIds(), "flushed tail, remaining: " + pending);
@ -186,18 +192,18 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
+ "/" + (beforeSend-start)
+ " pending current " + (pendingEnd-pendingStart)).append(".");
break;
}
} // if >= full size
if (timingBuf != null)
timingBuf.append(" After pending loop " + (System.currentTimeMillis()-beforePendingLoop)).append(".");
}
} // for
long afterCleared = System.currentTimeMillis();
if (_log.shouldLog(Log.INFO))
display(allocated, pending, "after looping to clear " + (beforeLooping - pending.size()));
long afterDisplayed = System.currentTimeMillis();
if (allocated > 0) {
// after going through the entire pending list, we still don't
// have enough data to send a full message
// After going through the entire pending list, we have only a partial message.
// We might flush it or might not, but we are returning either way.
if ( (pending.size() > FORCE_BATCH_FLUSH) || ( (_pendingSince > 0) && (getDelayAmount() <= 0) ) ) {
// not even a full message, but we want to flush it anyway
@ -209,9 +215,10 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
send(pending, 0, pending.size()-1, sender, rec);
_context.statManager().addRateData("tunnel.batchSmallFragments", FULL_SIZE - allocated, 0);
// Remove everything in the message from the pending queue
int beforeSize = pending.size();
for (int i = 0; i < pending.size(); i++) {
TunnelGateway.Pending cur = (TunnelGateway.Pending)pending.get(i);
TunnelGateway.Pending cur = pending.get(i);
if (cur.getOffset() >= cur.getData().length) {
pending.remove(i);
notePreprocessing(cur.getMessageId(), cur.getFragmentNumber(), cur.getData().length, cur.getMessageIds(), "flushed remaining");
@ -246,7 +253,9 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
}
return false;
}
// won't get here, we returned
} else {
// We didn't flush. Note that the messages remain on the pending list.
_context.statManager().addRateData("tunnel.batchDelay", pending.size(), 0);
if (_pendingSince <= 0)
_pendingSince = _context.clock().now();
@ -262,14 +271,15 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
}
return true;
}
// won't get here, we returned
} else {
// ok, we sent some, but haven't gone back for another
// pass yet. keep looping
if (timingBuf != null)
timingBuf.append(" Keep looping");
}
}
} // if allocated
} // while
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sent everything on the list (pending=" + pending.size() + ")");
@ -283,7 +293,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
return false;
}
private void display(long allocated, List pending, String title) {
private void display(long allocated, List<TunnelGateway.Pending> pending, String title) {
if (_log.shouldLog(Log.INFO)) {
long highestDelay = 0;
StringBuilder buf = new StringBuilder();
@ -294,7 +304,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
if (_pendingSince > 0)
buf.append(" delay: ").append(getDelayAmount(false));
for (int i = 0; i < pending.size(); i++) {
TunnelGateway.Pending curPending = (TunnelGateway.Pending)pending.get(i);
TunnelGateway.Pending curPending = pending.get(i);
buf.append(" pending[").append(i).append("]: ");
buf.append(curPending.getOffset()).append("/").append(curPending.getData().length).append('/');
buf.append(curPending.getLifetime());
@ -314,7 +324,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
* @param startAt first index in pending to send (inclusive)
* @param sendThrough last index in pending to send (inclusive)
*/
protected void send(List pending, int startAt, int sendThrough, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
protected void send(List<TunnelGateway.Pending> pending, int startAt, int sendThrough, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Sending " + startAt + ":" + sendThrough + " out of " + pending);
byte preprocessed[] = _dataCache.acquire().getData();
@ -346,7 +356,7 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
long msgId = sender.sendPreprocessed(preprocessed, rec);
for (int i = 0; i < pending.size(); i++) {
TunnelGateway.Pending cur = (TunnelGateway.Pending)pending.get(i);
TunnelGateway.Pending cur = pending.get(i);
cur.addMessageId(msgId);
}
if (_log.shouldLog(Log.DEBUG))
@ -359,9 +369,9 @@ public class BatchedPreprocessor extends TrivialPreprocessor {
*
* @return new offset into the target for further bytes to be written
*/
private int writeFragments(List pending, int startAt, int sendThrough, byte target[], int offset) {
private int writeFragments(List<TunnelGateway.Pending> pending, int startAt, int sendThrough, byte target[], int offset) {
for (int i = startAt; i <= sendThrough; i++) {
TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.get(i);
TunnelGateway.Pending msg = pending.get(i);
int prevOffset = offset;
if (msg.getOffset() == 0) {
offset = writeFirstFragment(msg, target, offset);

View File

@ -11,9 +11,9 @@ import net.i2p.router.RouterContext;
*
*/
public class BatchedRouterPreprocessor extends BatchedPreprocessor {
private RouterContext _routerContext;
protected RouterContext _routerContext;
private TunnelCreatorConfig _config;
private HopConfig _hopConfig;
protected HopConfig _hopConfig;
/**
* How frequently should we flush non-full messages, in milliseconds
@ -79,7 +79,7 @@ public class BatchedRouterPreprocessor extends BatchedPreprocessor {
}
@Override
protected void notePreprocessing(long messageId, int numFragments, int totalLength, List messageIds, String msg) {
protected void notePreprocessing(long messageId, int numFragments, int totalLength, List<Long> messageIds, String msg) {
if (_config != null)
_routerContext.messageHistory().fragmentMessage(messageId, numFragments, totalLength, messageIds, _config, msg);
else

View File

@ -21,6 +21,65 @@ import net.i2p.util.SimpleTimer;
* Handle fragments at the endpoint of a tunnel, peeling off fully completed
* I2NPMessages when they arrive, and dropping fragments if they take too long
* to arrive.
*
* From tunnel-alt.html:
<p>When the gateway wants to deliver data through the tunnel, it first
gathers zero or more <a href="i2np.html">I2NP</a> messages, selects how much padding will be used,
fragments it across the necessary number of 1KB tunnel messages, and decides how
each I2NP message should be handled by the tunnel endpoint, encoding that
data into the raw tunnel payload:</p>
<ul>
<li>the first 4 bytes of the SHA256 of (the remaining preprocessed data concatenated
with the IV), using the IV as will be seen on the tunnel endpoint (for
outbound tunnels), or the IV as was seen on the tunnel gateway (for inbound
tunnels) (see below for IV processing).</li>
<li>0 or more bytes containing random nonzero integers</li>
<li>1 byte containing 0x00</li>
<li>a series of zero or more { instructions, message } pairs</li>
</ul>
<p>The instructions are encoded with a single control byte, followed by any
necessary additional information. The first bit in that control byte determines
how the remainder of the header is interpreted - if it is not set, the message
is either not fragmented or this is the first fragment in the message. If it is
set, this is a follow on fragment.</p>
<p>With the first bit being 0, the instructions are:</p>
<ul>
<li>1 byte control byte:<pre>
bit 0: is follow on fragment? (1 = true, 0 = false, must be 0)
bits 1-2: delivery type
(0x0 = LOCAL, 0x01 = TUNNEL, 0x02 = ROUTER)
bit 3: delay included? (1 = true, 0 = false)
bit 4: fragmented? (1 = true, 0 = false)
bit 5: extended options? (1 = true, 0 = false)
bits 6-7: reserved</pre></li>
<li>if the delivery type was TUNNEL, a 4 byte tunnel ID</li>
<li>if the delivery type was TUNNEL or ROUTER, a 32 byte router hash</li>
<li>if the delay included flag is true, a 1 byte value:<pre>
bit 0: type (0 = strict, 1 = randomized)
bits 1-7: delay exponent (2^value minutes)</pre></li>
<li>if the fragmented flag is true, a 4 byte message ID</li>
<li>if the extended options flag is true:<pre>
= a 1 byte option size (in bytes)
= that many bytes</pre></li>
<li>2 byte size of the I2NP message or this fragment</li>
</ul>
<p>If the first bit being 1, the instructions are:</p>
<ul>
<li>1 byte control byte:<pre>
bit 0: is follow on fragment? (1 = true, 0 = false, must be 1)
bits 1-6: fragment number
bit 7: is last? (1 = true, 0 = false)</pre></li>
<li>4 byte message ID (same one defined in the first fragment)</li>
<li>2 byte size of this fragment</li>
</ul>
<p>The I2NP message is encoded in its standard form, and the
preprocessed payload must be padded to a multiple of 16 bytes.</p>
*
*/
public class FragmentHandler {
@ -149,7 +208,7 @@ public class FragmentHandler {
if (_log.shouldLog(Log.WARN))
_log.warn("cannot verify, going past the end [off="
+ offset + " len=" + length + " paddingEnd="
+ paddingEnd + " data:\n"
+ paddingEnd + " data: "
+ Base64.encode(preprocessed, offset, length));
return false;
}
@ -165,21 +224,19 @@ public class FragmentHandler {
_log.debug("endpoint IV: " + Base64.encode(preV, validLength - HopProcessor.IV_LENGTH, HopProcessor.IV_LENGTH));
Hash v = _context.sha().calculateHash(preV, 0, validLength);
_validateCache.release(ba);
//Hash v = _context.sha().calculateHash(preV, 0, validLength);
boolean eq = DataHelper.eq(v.getData(), 0, preprocessed, offset + HopProcessor.IV_LENGTH, 4);
if (!eq) {
if (_log.shouldLog(Log.WARN))
_log.warn("Corrupt tunnel message - verification fails: \n" + Base64.encode(preprocessed, offset+HopProcessor.IV_LENGTH, 4)
+ "\n" + Base64.encode(v.getData(), 0, 4));
if (_log.shouldLog(Log.WARN))
_log.warn("nomatching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1) + "\n"
+ " offset=" + offset + " length=" + length + " paddingEnd=" + paddingEnd
if (_log.shouldLog(Log.WARN)) {
_log.warn("Corrupt tunnel message - verification fails: " + Base64.encode(preprocessed, offset+HopProcessor.IV_LENGTH, 4)
+ " != " + Base64.encode(v.getData(), 0, 4));
_log.warn("No matching endpoint: # pad bytes: " + (paddingEnd-(HopProcessor.IV_LENGTH+4)-1)
+ " offset=" + offset + " length=" + length + " paddingEnd=" + paddingEnd + ' '
+ Base64.encode(preprocessed, offset, length));
}
}
_validateCache.release(ba);
if (eq) {
int excessPadding = paddingEnd - (HopProcessor.IV_LENGTH + 4 + 1);
if (excessPadding > 0) // suboptimal fragmentation

View File

@ -1,6 +1,5 @@
package net.i2p.router.tunnel;
import net.i2p.I2PAppContext;
import net.i2p.data.ByteArray;
import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
@ -16,7 +15,7 @@ import net.i2p.util.Log;
*
*/
public class InboundEndpointProcessor {
private I2PAppContext _context;
private RouterContext _context;
private Log _log;
private TunnelCreatorConfig _config;
private IVValidator _validator;
@ -24,10 +23,10 @@ public class InboundEndpointProcessor {
static final boolean USE_ENCRYPTION = HopProcessor.USE_ENCRYPTION;
private static final ByteCache _cache = ByteCache.getInstance(128, HopProcessor.IV_LENGTH);
public InboundEndpointProcessor(I2PAppContext ctx, TunnelCreatorConfig cfg) {
public InboundEndpointProcessor(RouterContext ctx, TunnelCreatorConfig cfg) {
this(ctx, cfg, DummyValidator.getInstance());
}
public InboundEndpointProcessor(I2PAppContext ctx, TunnelCreatorConfig cfg, IVValidator validator) {
public InboundEndpointProcessor(RouterContext ctx, TunnelCreatorConfig cfg, IVValidator validator) {
_context = ctx;
_log = ctx.logManager().getLog(InboundEndpointProcessor.class);
_config = cfg;
@ -73,23 +72,19 @@ public class InboundEndpointProcessor {
_cache.release(ba);
// now for a little bookkeeping
RouterContext ctx = null;
if (_context instanceof RouterContext)
ctx = (RouterContext)_context;
if ( (ctx != null) && (_config.getLength() > 0) ) {
if (_config.getLength() > 0) {
int rtt = 0; // dunno... may not be related to an rtt
if (_log.shouldLog(Log.DEBUG))
_log.debug("Received a " + length + "byte message through tunnel " + _config);
for (int i = 0; i < _config.getLength(); i++)
ctx.profileManager().tunnelDataPushed(_config.getPeer(i), rtt, length);
_context.profileManager().tunnelDataPushed(_config.getPeer(i), rtt, length);
_config.incrementVerifiedBytesTransferred(length);
}
return true;
}
private void decrypt(I2PAppContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) {
private void decrypt(RouterContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) {
Log log = ctx.logManager().getLog(OutboundGatewayProcessor.class);
ByteArray ba = _cache.acquire();
byte cur[] = ba.getData(); // new byte[HopProcessor.IV_LENGTH]; // so we dont malloc

View File

@ -3,11 +3,11 @@ package net.i2p.router.tunnel;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.ByteArray;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
import net.i2p.util.ByteCache;
import net.i2p.util.Log;
@ -19,8 +19,8 @@ import net.i2p.util.Log;
*
*/
public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
protected I2PAppContext _context;
private Log _log;
protected RouterContext _context;
protected Log _log;
public static final int PREPROCESSED_SIZE = 1024;
protected static final int IV_SIZE = HopProcessor.IV_LENGTH;
@ -28,7 +28,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
protected static final ByteCache _ivCache = ByteCache.getInstance(128, IV_SIZE);
protected static final ByteCache _hashCache = ByteCache.getInstance(128, Hash.HASH_LENGTH);
public TrivialPreprocessor(I2PAppContext ctx) {
public TrivialPreprocessor(RouterContext ctx) {
_context = ctx;
_log = ctx.logManager().getLog(TrivialPreprocessor.class);
}
@ -41,7 +41,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
* a delayed flush to clear them
*
*/
public boolean preprocessQueue(List pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
public boolean preprocessQueue(List<TunnelGateway.Pending> pending, TunnelGateway.Sender sender, TunnelGateway.Receiver rec) {
long begin = System.currentTimeMillis();
StringBuilder buf = null;
if (_log.shouldLog(Log.DEBUG)) {
@ -49,7 +49,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
buf.append("Trivial preprocessing of ").append(pending.size()).append(" ");
}
while (pending.size() > 0) {
TunnelGateway.Pending msg = (TunnelGateway.Pending)pending.remove(0);
TunnelGateway.Pending msg = pending.remove(0);
long beforePreproc = System.currentTimeMillis();
byte preprocessed[][] = preprocess(msg);
long afterPreproc = System.currentTimeMillis();
@ -84,7 +84,7 @@ public class TrivialPreprocessor implements TunnelGateway.QueuePreprocessor {
return false;
}
protected void notePreprocessing(long messageId, int numFragments, int totalLength, List messageIds, String msg) {}
protected void notePreprocessing(long messageId, int numFragments, int totalLength, List<Long> messageIds, String msg) {}
private byte[][] preprocess(TunnelGateway.Pending msg) {
List fragments = new ArrayList(1);

View File

@ -7,6 +7,7 @@ import net.i2p.router.RouterContext;
/**
* Minor extension to track fragmentation
*
* @deprecated unused
*/
public class TrivialRouterPreprocessor extends TrivialPreprocessor {
private RouterContext _routerContext;
@ -16,7 +17,7 @@ public class TrivialRouterPreprocessor extends TrivialPreprocessor {
_routerContext = ctx;
}
protected void notePreprocessing(long messageId, int numFragments, int totalLength, List messageIds) {
protected void notePreprocessing(long messageId, int numFragments, int totalLength, List<Long> messageIds) {
_routerContext.messageHistory().fragmentMessage(messageId, numFragments, totalLength, messageIds, null);
}
}

View File

@ -131,17 +131,20 @@ public class TunnelDispatcher implements Service {
new long[] { 60*1000l, 10*60*1000l, 60*60*1000l });
}
/** for IBGW */
private TunnelGateway.QueuePreprocessor createPreprocessor(HopConfig cfg) {
if (true)
return new BatchedRouterPreprocessor(_context, cfg);
else
return new TrivialRouterPreprocessor(_context);
//if (true)
return new DroppingBatchedRouterPreprocessor(_context, cfg);
//else
// return new TrivialRouterPreprocessor(_context);
}
/** for OBGW */
private TunnelGateway.QueuePreprocessor createPreprocessor(TunnelCreatorConfig cfg) {
if (true)
//if (true)
return new BatchedRouterPreprocessor(_context, cfg);
else
return new TrivialRouterPreprocessor(_context);
//else
// return new TrivialRouterPreprocessor(_context);
}
/**
@ -605,12 +608,17 @@ public class TunnelDispatcher implements Service {
if (pctDrop <= 0)
return false;
// increase the drop probability for OBEP,
// (except lower it for tunnel build messages (type 21)),
// and lower it for IBGW, for network efficiency
double len = length;
if (type.startsWith("OBEP"))
len *= 1.5;
else if (type.startsWith("IBGW"))
if (type.startsWith("OBEP")) {
if (type.equals("OBEP 21"))
len /= 1.5;
else
len *= 1.5;
} else if (type.startsWith("IBGW")) {
len /= 1.5;
}
// drop in proportion to size w.r.t. a standard 1024-byte message
// this is a little expensive but we want to adjust the curve between 0 and 1
// Most messages are 1024, only at the OBEP do we see other sizes

View File

@ -154,12 +154,18 @@ public class TunnelGateway {
public interface QueuePreprocessor {
/**
* Caller must synchronize on the list!
*
* @param pending list of Pending objects for messages either unsent
* or partly sent. This list should be update with any
* values removed (the preprocessor owns the lock)
* Messages are not removed from the list until actually sent.
* The status of unsent and partially-sent messages is stored in
* the Pending structure.
*
* @return true if we should delay before preprocessing again
*/
public boolean preprocessQueue(List pending, Sender sender, Receiver receiver);
public boolean preprocessQueue(List<Pending> pending, Sender sender, Receiver receiver);
/** how long do we want to wait before flushing */
public long getDelayAmount();
@ -173,6 +179,9 @@ public class TunnelGateway {
public long receiveEncrypted(byte encrypted[]);
}
/**
* Stores all the state for an unsent or partially-sent message
*/
public static class Pending {
protected Hash _toRouter;
protected TunnelId _toTunnel;
@ -182,7 +191,7 @@ public class TunnelGateway {
protected int _offset;
protected int _fragmentNumber;
protected long _created;
private List _messageIds;
private List<Long> _messageIds;
public Pending(I2NPMessage message, Hash toRouter, TunnelId toTunnel) {
this(message, toRouter, toTunnel, System.currentTimeMillis());
@ -222,7 +231,7 @@ public class TunnelGateway {
_messageIds.add(new Long(id));
}
}
public List getMessageIds() {
public List<Long> getMessageIds() {
synchronized (Pending.this) {
if (_messageIds != null)
return new ArrayList(_messageIds);
@ -231,6 +240,8 @@ public class TunnelGateway {
}
}
}
/** Extend for debugging */
class PendingImpl extends Pending {
public PendingImpl(I2NPMessage message, Hash toRouter, TunnelId toTunnel) {
super(message, toRouter, toTunnel, _context.clock().now());

View File

@ -101,8 +101,11 @@ public class TunnelParticipant {
}
}
/****
private int _periodMessagesTransferred;
private long _lastCoallesced = System.currentTimeMillis();
****/
/**
* take note that the peers specified were able to push us data. hmm, is this safe?
* this could be easily gamed to get us to rank some peer of their choosing as quite

View File

@ -54,9 +54,7 @@ class BuildExecutor implements Runnable {
// Get stat manager, get recognized bandwidth tiers
StatManager statMgr = _context.statManager();
@SuppressWarnings("static-access")
/* FIXME Accessing static field "BW_CAPABILITY_CHARS" FIXME */
String bwTiers = _context.router().getRouterInfo().BW_CAPABILITY_CHARS;
String bwTiers = RouterInfo.BW_CAPABILITY_CHARS;
// For each bandwidth tier, create tunnel build agree/reject/expire stats
for (int i = 0; i < bwTiers.length(); i++) {
String bwTier = String.valueOf(bwTiers.charAt(i));

View File

@ -79,8 +79,9 @@ public class PooledTunnelCreatorConfig extends TunnelCreatorConfig {
public TunnelPool getTunnelPool() { return _pool; }
/* FIXME Exporting non-public type through public API FIXME */
public void setTestJob(TestJob job) { _testJob = job; }
/** @deprecated unused, which makes _testJob unused - why is it here */
void setTestJob(TestJob job) { _testJob = job; }
/** does nothing, to be deprecated */
public void setExpireJob(Job job) { /* _expireJob = job; */ }
/**

View File

@ -29,6 +29,8 @@ import net.i2p.stat.RateStat;
import net.i2p.util.I2PThread;
import net.i2p.util.Log;
import net.i2p.util.ObjectCounter;
import net.i2p.util.SimpleScheduler;
import net.i2p.util.SimpleTimer;
/**
*
@ -42,7 +44,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
private final Map<Hash, TunnelPool> _clientOutboundPools;
private TunnelPool _inboundExploratory;
private TunnelPool _outboundExploratory;
private BuildExecutor _executor;
private final BuildExecutor _executor;
private boolean _isShutdown;
public TunnelPoolManager(RouterContext ctx) {
@ -263,6 +265,7 @@ public class TunnelPoolManager implements TunnelManagerFacade {
TunnelPool inbound = null;
TunnelPool outbound = null;
// should we share the clientPeerSelector across both inbound and outbound?
// or just one for all clients? why separate?
synchronized (_clientInboundPools) {
inbound = _clientInboundPools.get(dest);
if (inbound == null) {
@ -284,11 +287,22 @@ public class TunnelPoolManager implements TunnelManagerFacade {
}
}
inbound.startup();
try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
outbound.startup();
SimpleScheduler.getInstance().addEvent(new DelayedStartup(outbound), 3*1000);
}
private static class DelayedStartup implements SimpleTimer.TimedEvent {
private TunnelPool pool;
public DelayedStartup(TunnelPool p) {
this.pool = p;
}
public void timeReached() {
this.pool.startup();
}
}
public void removeTunnels(Hash destination) {
if (destination == null) return;
if (_context.clientManager().isLocal(destination)) {
@ -361,12 +375,11 @@ public class TunnelPoolManager implements TunnelManagerFacade {
_inboundExploratory = new TunnelPool(_context, this, inboundSettings, selector);
_inboundExploratory.startup();
try { Thread.sleep(3*1000); } catch (InterruptedException ie) {}
TunnelPoolSettings outboundSettings = new TunnelPoolSettings();
outboundSettings.setIsExploratory(true);
outboundSettings.setIsInbound(false);
_outboundExploratory = new TunnelPool(_context, this, outboundSettings, selector);
_outboundExploratory.startup();
SimpleScheduler.getInstance().addEvent(new DelayedStartup(_outboundExploratory), 3*1000);
// try to build up longer tunnels
_context.jobQueue().addJob(new BootstrapPool(_context, _inboundExploratory));