diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java index 3f7db9068..55473717f 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPClient.java @@ -455,9 +455,15 @@ public class I2PTunnelHTTPClient extends I2PTunnelHTTPClientBase implements Runn !Boolean.valueOf(getTunnel().getClientOptions().getProperty(PROP_DISABLE_HELPER)).booleanValue()) { pos2 = fragment.indexOf("="); ahelperKey = fragment.substring(pos2 + 1); - // Key contains data, lets not ignore it if (ahelperKey != null) { + if(ahelperKey.endsWith(".b32.i2p")) { + // allow i2paddresshelper=.b32.i2p syntax. + Destination dest = _context.namingService().lookup(ahelperKey); + if(dest==null) + throw new RuntimeException("Could not find destination for "+ahelperKey); + ahelperKey = dest.toBase64(); + } ahelperPresent = true; // ahelperKey will be validated later if (host == null || "i2p".equals(host)) { diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java index debe3e470..c837b643f 100644 --- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java +++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/web/IndexBean.java @@ -8,6 +8,7 @@ package net.i2p.i2ptunnel.web; * */ +import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -31,6 +32,7 @@ import net.i2p.i2ptunnel.I2PTunnelIRCClient; import net.i2p.i2ptunnel.TunnelController; import net.i2p.i2ptunnel.TunnelControllerGroup; import net.i2p.util.ConcurrentHashSet; +import net.i2p.util.FileUtil; import net.i2p.util.Log; /** @@ -41,9 +43,9 @@ import net.i2p.util.Log; * Usage by classes outside of i2ptunnel.war is deprecated. */ public class IndexBean { - protected I2PAppContext _context; - protected Log _log; - protected TunnelControllerGroup _group; + protected final I2PAppContext _context; + protected final Log _log; + protected final TunnelControllerGroup _group; private String _action; private int _tunnel; //private long _prevNonce; @@ -75,8 +77,8 @@ public class IndexBean { private boolean _sharedClient; private boolean _privKeyGenerate; private boolean _removeConfirmed; - private Set _booleanOptions; - private Map _otherOptions; + private final Set _booleanOptions; + private final Map _otherOptions; private int _hashCashValue; private int _certType; private String _certSigner; @@ -199,17 +201,17 @@ public class IndexBean { } private String stopAll() { if (_group == null) return ""; - List msgs = _group.stopAllControllers(); + List msgs = _group.stopAllControllers(); return getMessages(msgs); } private String startAll() { if (_group == null) return ""; - List msgs = _group.startAllControllers(); + List msgs = _group.startAllControllers(); return getMessages(msgs); } private String restartAll() { if (_group == null) return ""; - List msgs = _group.restartAllControllers(); + List msgs = _group.restartAllControllers(); return getMessages(msgs); } private String reloadConfig() { @@ -317,6 +319,10 @@ public class IndexBean { return rv; } + /** + * Stop the tunnel, delete from config, + * rename the private key file if in the default directory + */ private String deleteTunnel() { if (!_removeConfirmed) return "Please confirm removal"; @@ -325,8 +331,38 @@ public class IndexBean { if (cur == null) return "Invalid tunnel number"; - List msgs = _group.removeController(cur); + List msgs = _group.removeController(cur); msgs.addAll(doSave()); + + // Rename private key file if it was a default name in + // the default directory, so it doesn't get reused when a new + // tunnel is created. + // Use configured file name if available, not the one from the form. + String pk = cur.getPrivKeyFile(); + if (pk == null) + pk = _privKeyFile; + if (pk != null && pk.startsWith("i2ptunnel") && pk.endsWith("-privKeys.dat")) { + File pkf = new File(_context.getConfigDir(), pk); + if (pkf.exists()) { + String name = cur.getName(); + if (name == null) { + name = cur.getDescription(); + if (name == null) { + name = cur.getType(); + if (name == null) + name = Long.toString(_context.clock().now()); + } + } + name = "i2ptunnel-deleted-" + name.replace(' ', '_') + "-privkeys.dat"; + File to = new File(_context.getConfigDir(), name); + if (to.exists()) + to = new File(_context.getConfigDir(), name + '-' + _context.clock().now()); + boolean success = FileUtil.rename(pkf, to); + if (success) + msgs.add("Private key file " + pkf.getAbsolutePath() + + " renamed to " + to.getAbsolutePath()); + } + } return getMessages(msgs); } diff --git a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java index a11b8adc5..c7a0b672b 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/NewsFetcher.java @@ -359,7 +359,7 @@ public class NewsFetcher implements Runnable, EepGet.StatusListener { long now = _context.clock().now(); if (_tempFile.exists()) { - boolean copied = FileUtil.copy(_tempFile.getAbsolutePath(), _newsFile.getAbsolutePath(), true); + boolean copied = FileUtil.copy(_tempFile, _newsFile, true, false); if (copied) { _lastUpdated = now; _tempFile.delete(); diff --git a/apps/routerconsole/java/src/net/i2p/router/web/UnsignedUpdateHandler.java b/apps/routerconsole/java/src/net/i2p/router/web/UnsignedUpdateHandler.java index 2eb132b30..90e120af6 100644 --- a/apps/routerconsole/java/src/net/i2p/router/web/UnsignedUpdateHandler.java +++ b/apps/routerconsole/java/src/net/i2p/router/web/UnsignedUpdateHandler.java @@ -93,8 +93,8 @@ public class UnsignedUpdateHandler extends UpdateHandler { _log.log(Log.CRIT, "Corrupt zip file from " + url); return; } - String to = (new File(_context.getRouterDir(), Router.UPDATE_FILE)).getAbsolutePath(); - boolean copied = FileUtil.copy(_updateFile, to, true); + File to = new File(_context.getRouterDir(), Router.UPDATE_FILE); + boolean copied = FileUtil.copy(updFile, to, true, false); if (copied) { updFile.delete(); String policy = _context.getProperty(ConfigUpdateHandler.PROP_UPDATE_POLICY); @@ -124,7 +124,7 @@ public class UnsignedUpdateHandler extends UpdateHandler { } } else { _log.log(Log.CRIT, "Failed copy to " + to); - updateStatus("" + _("Failed copy to {0}", to) + ""); + updateStatus("" + _("Failed copy to {0}", to.getAbsolutePath()) + ""); } } } diff --git a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java index 95a192367..f50a4b5cc 100644 --- a/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java +++ b/apps/streaming/java/src/net/i2p/client/streaming/ConnectionManager.java @@ -165,26 +165,22 @@ class ConnectionManager { _log.warn("Refusing connection since we have exceeded our max of " + _maxConcurrentStreams + " connections"); reject = true; - } else if (shouldRejectConnection(synPacket)) { + } else { // this may not be right if more than one is enabled - String why; - if (_defaultOptions.isAccessListEnabled()) - why = "not whitelisted: "; - else if (_defaultOptions.isBlacklistEnabled()) - why = "blacklisted: "; - else - why = "throttled: "; - _log.error("Refusing connection since peer is " + why + - (synPacket.getOptionalFrom() == null ? "null from" : synPacket.getOptionalFrom().calculateHash().toBase64())); - reject = true; - } else { - while (true) { - Connection oldCon = _connectionByInboundId.putIfAbsent(Long.valueOf(receiveId), con); - if (oldCon == null) { - break; - } else { - // receiveId already taken, try another - receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1; + String why = shouldRejectConnection(synPacket); + if (why != null) { + _log.logAlways(Log.WARN, "Refusing connection since peer is " + why + + (synPacket.getOptionalFrom() == null ? "" : ": " + synPacket.getOptionalFrom().calculateHash().toBase64())); + reject = true; + } else { + while (true) { + Connection oldCon = _connectionByInboundId.putIfAbsent(Long.valueOf(receiveId), con); + if (oldCon == null) { + break; + } else { + // receiveId already taken, try another + receiveId = _context.random().nextLong(Packet.MAX_STREAM_ID-1)+1; + } } } } @@ -308,35 +304,46 @@ class ConnectionManager { return (active >= _maxConcurrentStreams); } - private boolean shouldRejectConnection(Packet syn) { + /** + * @return reason string or null if not rejected + */ + private String shouldRejectConnection(Packet syn) { // unfortunately we don't have access to the router client manager here, // so we can't whitelist local access Destination from = syn.getOptionalFrom(); if (from == null) - return true; + return "null"; Hash h = from.calculateHash(); - boolean throttled = false; + String throttled = null; // always call all 3 to increment all counters if (_minuteThrottler != null && _minuteThrottler.shouldThrottle(h)) { _context.statManager().addRateData("stream.con.throttledMinute", 1, 0); - throttled = true; + throttled = "throttled by per-peer limit of " + _defaultOptions.getMaxConnsPerMinute() + + " or total limit of " + _defaultOptions.getMaxTotalConnsPerMinute() + + " per minute"; } if (_hourThrottler != null && _hourThrottler.shouldThrottle(h)) { _context.statManager().addRateData("stream.con.throttledHour", 1, 0); - throttled = true; + throttled = "throttled by per-peer limit of " + _defaultOptions.getMaxConnsPerHour() + + " or total limit of " + _defaultOptions.getMaxTotalConnsPerHour() + + " per hour"; } if (_dayThrottler != null && _dayThrottler.shouldThrottle(h)) { _context.statManager().addRateData("stream.con.throttledDay", 1, 0); - throttled = true; + throttled = "throttled by per-peer limit of " + _defaultOptions.getMaxConnsPerDay() + + " or total limit of " + _defaultOptions.getMaxTotalConnsPerDay() + + " per day"; } - if (throttled) - return true; + if (throttled != null) + return throttled; // if the sig is absent or bad it will be caught later (in CPH) - if (_defaultOptions.isAccessListEnabled()) - return !_defaultOptions.getAccessList().contains(h); - if (_defaultOptions.isBlacklistEnabled()) - return _defaultOptions.getBlacklist().contains(h); - return false; + if (_defaultOptions.isAccessListEnabled() && + !_defaultOptions.getAccessList().contains(h)) + return "not whitelisted"; + if (_defaultOptions.isBlacklistEnabled() && + _defaultOptions.getBlacklist().contains(h)) + return "blacklisted"; + return null; } diff --git a/core/java/src/freenet/support/CPUInformation/CPUID.java b/core/java/src/freenet/support/CPUInformation/CPUID.java index 439555fce..62b12952a 100644 --- a/core/java/src/freenet/support/CPUInformation/CPUID.java +++ b/core/java/src/freenet/support/CPUInformation/CPUID.java @@ -402,7 +402,7 @@ public class CPUID { } // copy to install dir, ignore failure File newFile = new File(I2PAppContext.getGlobalContext().getBaseDir(), filename); - FileUtil.copy(outFile.getAbsolutePath(), newFile.getAbsolutePath(), false, true); + FileUtil.copy(outFile, newFile, false, true); return true; } diff --git a/core/java/src/net/i2p/client/naming/SingleFileNamingService.java b/core/java/src/net/i2p/client/naming/SingleFileNamingService.java index 6d7926f40..7013fb77a 100644 --- a/core/java/src/net/i2p/client/naming/SingleFileNamingService.java +++ b/core/java/src/net/i2p/client/naming/SingleFileNamingService.java @@ -193,7 +193,7 @@ public class SingleFileNamingService extends NamingService { out.write(d.toBase64()); out.newLine(); out.close(); - boolean success = rename(tmp, _file); + boolean success = FileUtil.rename(tmp, _file); if (success) { for (NamingServiceListener nsl : _listeners) { nsl.entryChanged(this, hostname, d, options); @@ -284,7 +284,7 @@ public class SingleFileNamingService extends NamingService { tmp.delete(); return false; } - success = rename(tmp, _file); + success = FileUtil.rename(tmp, _file); if (success) { for (NamingServiceListener nsl : _listeners) { nsl.entryRemoved(this, hostname); @@ -442,24 +442,6 @@ public class SingleFileNamingService extends NamingService { } } - private static boolean rename(File from, File to) { - boolean success = false; - boolean isWindows = System.getProperty("os.name").startsWith("Win"); - // overwrite fails on windows - if (!isWindows) - success = from.renameTo(to); - if (!success) { - to.delete(); - success = from.renameTo(to); - if (!success) { - // hard way - success = FileUtil.copy(from.getAbsolutePath(), to.getAbsolutePath(), true, true); - from.delete(); - } - } - return success; - } - private void getReadLock() { _fileLock.readLock().lock(); } diff --git a/core/java/src/net/i2p/data/Hash.java b/core/java/src/net/i2p/data/Hash.java index 7512f52f4..a0f63459a 100644 --- a/core/java/src/net/i2p/data/Hash.java +++ b/core/java/src/net/i2p/data/Hash.java @@ -19,7 +19,6 @@ import java.io.InputStream; * @author jrandom */ public class Hash extends SimpleDataStructure { - private volatile String _stringified; private volatile String _base64ed; private int _cachedHashCode; @@ -71,7 +70,6 @@ public class Hash extends SimpleDataStructure { @Override public void setData(byte[] data) { super.setData(data); - _stringified = null; _base64ed = null; _cachedHashCode = super.hashCode(); } @@ -79,7 +77,6 @@ public class Hash extends SimpleDataStructure { @Override public void readBytes(InputStream in) throws DataFormatException, IOException { super.readBytes(in); - _stringified = null; _base64ed = null; _cachedHashCode = super.hashCode(); } diff --git a/core/java/src/net/i2p/util/FileUtil.java b/core/java/src/net/i2p/util/FileUtil.java index de9d354aa..4231eb05d 100644 --- a/core/java/src/net/i2p/util/FileUtil.java +++ b/core/java/src/net/i2p/util/FileUtil.java @@ -400,7 +400,15 @@ public class FileUtil { public static boolean copy(String source, String dest, boolean overwriteExisting, boolean quiet) { File src = new File(source); File dst = new File(dest); + return copy(src, dst, overwriteExisting, quiet); + } + /** + * @param quiet don't log fails to wrapper log if true + * @return true if it was copied successfully + * @since 0.8.8 + */ + public static boolean copy(File src, File dst, boolean overwriteExisting, boolean quiet) { if (dst.exists() && dst.isDirectory()) dst = new File(dst, src.getName()); @@ -429,6 +437,35 @@ public class FileUtil { } } + /** + * Try to rename, if it doesn't work then copy and delete the old. + * Always overwrites any existing "to" file. + * Method moved from SingleFileNamingService. + * + * @return true if it was renamed / copied successfully + * @since 0.8.8 + */ + public static boolean rename(File from, File to) { + if (!from.exists()) + return false; + boolean success = false; + boolean isWindows = System.getProperty("os.name").startsWith("Win"); + // overwrite fails on windows + if (!isWindows) + success = from.renameTo(to); + if (!success) { + to.delete(); + success = from.renameTo(to); + if (!success) { + // hard way + success = copy(from, to, true, true); + if (success) + from.delete(); + } + } + return success; + } + /** * Usage: FileUtil (delete path | copy source dest | unzip path.zip) * diff --git a/core/java/src/net/i2p/util/NativeBigInteger.java b/core/java/src/net/i2p/util/NativeBigInteger.java index b1de68e55..312babe83 100644 --- a/core/java/src/net/i2p/util/NativeBigInteger.java +++ b/core/java/src/net/i2p/util/NativeBigInteger.java @@ -606,7 +606,7 @@ public class NativeBigInteger extends BigInteger { } // copy to install dir, ignore failure File newFile = new File(I2PAppContext.getGlobalContext().getBaseDir(), filename); - FileUtil.copy(outFile.getAbsolutePath(), newFile.getAbsolutePath(), false, true); + FileUtil.copy(outFile, newFile, false, true); return true; } diff --git a/history.txt b/history.txt index 1461bd8a1..30a9410c7 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,10 @@ +2011-07-18 zzz + * FileUtil: Add a rename method and a new copy method + * I2PTunnel: Rename privkey file when deleting tunnel to prevent inadvertent reuse + * Netdb: Update reseed lists + * Streaming: Improve conn limit log message + * UDP: Lower max port below 31000 since wrapper uses that + 2011-07-15 zzz * Shutdown: - Cancel our JVM shutdown hook when shutting down diff --git a/installer/resources/themes/console/light/console_ar.css b/installer/resources/themes/console/light/console_ar.css index a33f03928..3fb8ea7fd 100644 --- a/installer/resources/themes/console/light/console_ar.css +++ b/installer/resources/themes/console/light/console_ar.css @@ -310,8 +310,8 @@ div.sorry hr { } div.main { - margin: 10px 10px 20px 207px; - padding: 0 15px 15px 15px; + margin: 10px 210px 0px 10px; + padding: 7px 0px 30px 20px; background: #ffe; text-align: right; color: #001; @@ -345,8 +345,8 @@ div.main textarea { } div.news { - margin: 0px 10px 5px 207px; - padding: 7px 20px 7px 20px; + margin: 10px 210px 0px 10px; + padding: 7px 0px 30px 20px; border: 1px solid #447; color: #224; border-radius: 4px; @@ -711,8 +711,8 @@ p { h1 { text-align: right; color: #115; - padding: 10px 15px; - margin: 0 10px 10px 207px; + margin: 10px 210px 0px 10px; + padding: 7px 0px 30px 20px; font: normal bold 16pt/120% "Lucida Sans Unicode", "Bitstream Vera Sans", Verdana, Tahoma, Helvetica, sans-serif; letter-spacing: 0.15em; text-transform: uppercase; diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 5a58b19bc..e36cd3b14 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 = 11; + public final static long BUILD = 12; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java index 1fb70e36d..1f6cc3aa5 100644 --- a/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java +++ b/router/java/src/net/i2p/router/networkdb/reseed/Reseeder.java @@ -56,14 +56,22 @@ public class Reseeder { * URLs are constructed, and because SSLEepGet doesn't follow redirects. */ public static final String DEFAULT_SEED_URL = - "http://a.netdb.i2p2.de/,http://c.netdb.i2p2.de/," + - "http://reseed.i2p-projekt.de/,http://forum.i2p2.de/netdb/,http://www.i2pbote.net/netDb/,http://r31453.ovh.net/static_media/files/netDb/"; + "http://a.netdb.i2p2.de/," + + "http://c.netdb.i2p2.de/," + + "http://reseed.i2p-projekt.de/," + + "http://forum.i2p2.de/netdb/," + + "http://www.i2pbote.net/netDb/," + + "http://r31453.ovh.net/static_media/files/netDb/," + + "http://cowpuncher.drollette.com/netdb/"; /** @since 0.8.2 */ public static final String DEFAULT_SSL_SEED_URL = - "https://a.netdb.i2p2.de/,https://c.netdb.i2p2.de/," + - "https://forum.i2p2.de/netdb/,https://www.i2pbote.net/netDb/," + - "https://r31453.ovh.net/static_media/files/netDb/"; + /* "https://a.netdb.i2p2.de/," + bad cert */ + /* "https://c.netdb.i2p2.de/," + no data */ + "https://forum.i2p2.de/netdb/," + + "https://www.i2pbote.net/netDb/," + + "https://r31453.ovh.net/static_media/files/netDb/," + + "https://cowpuncher.drollette.com/netdb/"; private static final String PROP_INPROGRESS = "net.i2p.router.web.ReseedHandler.reseedInProgress"; /** the console shows this message while reseedInProgress == false */ diff --git a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java index b59c40d4b..177309512 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPEndpoint.java @@ -80,11 +80,11 @@ class UDPEndpoint { } ********/ - /** 8998 is monotone, and 32000 is the wrapper, so let's stay between those */ + /** 8998 is monotone, and 31000 is the wrapper outbound, so let's stay between those */ public static final String PROP_MIN_PORT = "i2np.udp.minPort"; public static final String PROP_MAX_PORT = "i2np.udp.maxPort"; private static final int MIN_RANDOM_PORT = 9111; - private static final int MAX_RANDOM_PORT = 31777; + private static final int MAX_RANDOM_PORT = 30777; private static final int MAX_PORT_RETRIES = 20; /** diff --git a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java index ade04a94b..51d757221 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TestJob.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TestJob.java @@ -23,6 +23,10 @@ import net.i2p.stat.Rate; import net.i2p.stat.RateStat; import net.i2p.util.Log; +/** + * Repeatedly test a single tunnel for its entire lifetime, + * or until the pool is shut down or removed from the client manager. + */ class TestJob extends JobImpl { private final Log _log; private final TunnelPool _pool; @@ -35,7 +39,7 @@ class TestJob extends JobImpl { private SessionTag _encryptTag; /** base to randomize the test delay on */ - private static final int TEST_DELAY = 30*1000; + private static final int TEST_DELAY = 40*1000; public TestJob(RouterContext ctx, PooledTunnelCreatorConfig cfg, TunnelPool pool) { super(ctx); @@ -54,7 +58,7 @@ class TestJob extends JobImpl { public String getName() { return "Test tunnel"; } public void runJob() { - if (_pool == null) + if (_pool == null || !_pool.isAlive()) return; long lag = getContext().jobQueue().getMaxLag(); if (lag > 3000) { @@ -155,6 +159,8 @@ class TestJob extends JobImpl { } public void testSuccessful(int ms) { + if (_pool == null || !_pool.isAlive()) + return; getContext().statManager().addRateData("tunnel.testSuccessLength", _cfg.getLength(), 0); getContext().statManager().addRateData("tunnel.testSuccessTime", ms, 0); @@ -182,6 +188,8 @@ class TestJob extends JobImpl { } private void testFailed(long timeToFail) { + if (_pool == null || !_pool.isAlive()) + return; if (_found) { // ok, not really a /success/, but we did find it, even though slowly noteSuccess(timeToFail, _outTunnel); @@ -208,7 +216,7 @@ class TestJob extends JobImpl { } /** randomized time we should wait before testing */ - private int getDelay() { return TEST_DELAY + getContext().random().nextInt(TEST_DELAY); } + private int getDelay() { return TEST_DELAY + getContext().random().nextInt(TEST_DELAY / 3); } /** how long we allow tests to run for before failing them */ private int getTestPeriod() { @@ -234,8 +242,11 @@ class TestJob extends JobImpl { private void scheduleRetest() { scheduleRetest(false); } private void scheduleRetest(boolean asap) { + if (_pool == null || !_pool.isAlive()) + return; if (asap) { - requeue(getContext().random().nextInt(TEST_DELAY)); + if (_cfg.getExpiration() > getContext().clock().now() + (60 * 1000)) + requeue((TEST_DELAY / 4) + getContext().random().nextInt(TEST_DELAY / 4)); } else { int delay = getDelay(); if (_cfg.getExpiration() > getContext().clock().now() + delay + (3 * getTestPeriod())) 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 86c6e1ecb..d9344f0de 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPool.java @@ -23,6 +23,7 @@ import net.i2p.util.Log; /** * A group of tunnels for the router or a particular client, in a single direction. + * Public only for TunnelRenderer in router console. */ public class TunnelPool { private final List _inProgress = new ArrayList(); @@ -43,7 +44,7 @@ public class TunnelPool { private final String _rateName; private static final int TUNNEL_LIFETIME = 10*60*1000; - public TunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPeerSelector sel) { + TunnelPool(RouterContext ctx, TunnelPoolManager mgr, TunnelPoolSettings settings, TunnelPeerSelector sel) { _context = ctx; _log = ctx.logManager().getLog(TunnelPool.class); _manager = mgr; @@ -59,10 +60,18 @@ public class TunnelPool { refreshSettings(); } - public void startup() { + /** + * Warning, this may be called more than once + * (without an intervening shutdown()) if the + * tunnel is stopped and then restarted by the client manager with the same + * Destination (i.e. for servers or clients w/ persistent key) + */ + void startup() { synchronized (_inProgress) { _inProgress.clear(); } + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": Startup() called, was already alive? " + _alive, new Exception()); _alive = true; _started = System.currentTimeMillis(); _lastRateUpdate = _started; @@ -84,7 +93,9 @@ public class TunnelPool { new long[] { 5*60*1000l }); } - public void shutdown() { + void shutdown() { + if (_log.shouldLog(Log.WARN)) + _log.warn(toString() + ": Shutdown called", new Exception()); _alive = false; _lastSelectionPeriod = 0; _lastSelected = null; @@ -132,7 +143,8 @@ public class TunnelPool { * and returns it. * */ - public TunnelInfo selectTunnel() { return selectTunnel(true); } + TunnelInfo selectTunnel() { return selectTunnel(true); } + private TunnelInfo selectTunnel(boolean allowRecurseOnFail) { boolean avoidZeroHop = ((getSettings().getLength() + getSettings().getLengthVariance()) > 0); @@ -239,7 +251,7 @@ public class TunnelPool { * Do we really need more fallbacks? * Used to prevent a zillion of them */ - public boolean needFallback() { + boolean needFallback() { int needed = _settings.getBackupQuantity() + _settings.getQuantity(); int fallbacks = 0; synchronized (_tunnels) { @@ -259,7 +271,8 @@ public class TunnelPool { int getTunnelCount() { synchronized (_tunnels) { return _tunnels.size(); } } public TunnelPoolSettings getSettings() { return _settings; } - public void setSettings(TunnelPoolSettings settings) { + + void setSettings(TunnelPoolSettings settings) { _settings = settings; if (_settings != null) { if (_log.shouldLog(Log.INFO)) @@ -267,8 +280,19 @@ public class TunnelPool { _manager.getExecutor().repoll(); // in case we need more } } - public TunnelPeerSelector getSelector() { return _peerSelector; } - public boolean isAlive() { return _alive; } + + /** + * Is this pool running AND either exploratory, or tracked by the client manager? + * A pool will be alive but not tracked after the client manager removes it + * but before all the tunnels have expired. + */ + public boolean isAlive() { + return _alive && + (_settings.isExploratory() || + (_settings.getDestination() != null && + _context.clientManager().isLocal(_settings.getDestination()))); + } + /** duplicate of getTunnelCount(), let's pick one */ public int size() { synchronized (_tunnels) { @@ -336,17 +360,14 @@ public class TunnelPool { } } - boolean connected = true; - if ( (_settings.getDestination() != null) && (!_context.clientManager().isLocal(_settings.getDestination())) ) - connected = false; - if ( (getTunnelCount() <= 0) && (!connected) ) { + if (getTunnelCount() <= 0 && !isAlive()) { + // this calls both our shutdown() and the other one (inbound/outbound) _manager.removeTunnels(_settings.getDestination()); - return; } } /** This may be called multiple times from TestJob */ - public void tunnelFailed(PooledTunnelCreatorConfig cfg) { + void tunnelFailed(PooledTunnelCreatorConfig cfg) { if (_log.shouldLog(Log.WARN)) _log.warn(toString() + ": Tunnel failed: " + cfg); LeaseSet ls = null; @@ -574,9 +595,8 @@ public class TunnelPool { * the countHowManyToBuild function below) * */ - public int countHowManyToBuild() { - if (_settings.getDestination() != null) { - if (!_context.clientManager().isLocal(_settings.getDestination())) + int countHowManyToBuild() { + if (!isAlive()) { return 0; } int wanted = getSettings().getBackupQuantity() + getSettings().getQuantity(); 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 dc8295572..487e3493d 100644 --- a/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java +++ b/router/java/src/net/i2p/router/tunnel/pool/TunnelPoolManager.java @@ -276,9 +276,9 @@ public class TunnelPoolManager implements TunnelManagerFacade { * Do not use to change settings. */ public void buildTunnels(Destination client, ClientTunnelSettings settings) { - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Building tunnels for the client " + client.calculateHash().toBase64() + ": " + settings); Hash dest = client.calculateHash(); + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Building tunnels for the client " + dest + ": " + settings); settings.getInboundSettings().setDestination(dest); settings.getOutboundSettings().setDestination(dest); TunnelPool inbound = null; @@ -311,7 +311,7 @@ public class TunnelPoolManager implements TunnelManagerFacade { private static class DelayedStartup implements SimpleTimer.TimedEvent { - private TunnelPool pool; + private final TunnelPool pool; public DelayedStartup(TunnelPool p) { this.pool = p; @@ -322,9 +322,14 @@ public class TunnelPoolManager implements TunnelManagerFacade { } } - /** synch with buildTunnels() above */ + /** + * This will be called twice, once by the inbound and once by the outbound pool. + * Synched with buildTunnels() above. + */ public synchronized void removeTunnels(Hash destination) { if (destination == null) return; + if (_log.shouldLog(Log.DEBUG)) + _log.debug("Removing tunnels for the client " + destination); if (_context.clientManager().isLocal(destination)) { if (_log.shouldLog(Log.CRIT)) _log.log(Log.CRIT, "wtf, why are you removing the pool for " + destination.toBase64(), new Exception("i did it"));