diff --git a/history.txt b/history.txt index 2ed2526d2..4dd034dab 100644 --- a/history.txt +++ b/history.txt @@ -1,3 +1,22 @@ +2012-03-14 zzz + * Blockfile, i2psnark: Remove static logs + * DHSessionKeyBuilder: + - Move from core to router/transport + - Make non-static, instantiate in TransportManager + - Generate keypair in constructor and make final + to move more processing to the precalc thread + and eliminate races + - Synchronize getSessionKey() to eliminate races + - Comment out unused methods + * Jetty: + - Set default cache-control for webapps and eepsite + - Disable dir listing for console webapps + * UDPTransport: + - Make key builder final in InboundEstablishState to + eliminate rare NPE (ticket #406) + - Remove unused static instance + * YKGenerator: Make non-static, instantiate in ElGamalEngine + 2012-03-13 sponge * Add sponge.i2p :-D bump version. @@ -9,7 +28,7 @@ 2012-03-13 sponge * Plugins: - - Handle 'file://' URLs for installation and updates. + - Handle 'file://' URLs for installation and updates (ticket #429). You must specify the entire path, e.g. file:///home/someone/magicplugin.xpi2p - This works for updates too! diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 71d6b5fa2..538826927 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -30,7 +30,6 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import net.i2p.CoreVersion; -import net.i2p.crypto.DHSessionKeyBuilder; import net.i2p.data.Certificate; import net.i2p.data.DataFormatException; import net.i2p.data.DataHelper; @@ -711,10 +710,8 @@ public class Router implements RouterClock.ClockShiftListener { private void warmupCrypto() { _context.random().nextBoolean(); - // Use restart() to refire the static refiller threads, in case - // we are restarting the router in the same JVM (Android) - DHSessionKeyBuilder.restart(); - _context.elGamalEngine().restart(); + // Instantiate to fire up the YK refiller thread + _context.elGamalEngine(); } private void startupQueue() { @@ -1091,15 +1088,12 @@ public class Router implements RouterClock.ClockShiftListener { // shut down I2PAppContext tasks here - // If there are multiple routers in the JVM, we don't want to do this - // to the DH or YK tasks, as they are singletons. + try { + _context.elGamalEngine().shutdown(); + } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting elGamal", t); } + if (contexts.isEmpty()) { - try { - DHSessionKeyBuilder.shutdown(); - } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting DH", t); } - try { - _context.elGamalEngine().shutdown(); - } catch (Throwable t) { _log.log(Log.CRIT, "Error shutting elGamal", t); } + // any thing else to shut down? } else { _log.logAlways(Log.WARN, "Warning - " + contexts.size() + " routers remaining in this JVM, not releasing all resources"); } diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 7f4edb975..31c35f8c3 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 = 13; + public final static long BUILD = 14; /** for example "-test" */ public final static String EXTRA = ""; diff --git a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java index cb93d5333..4c223d322 100644 --- a/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java +++ b/router/java/src/net/i2p/router/transport/CommSystemFacadeImpl.java @@ -56,9 +56,12 @@ public class CommSystemFacadeImpl extends CommSystemFacade { startTimestamper(); } + /** + * Cannot be restarted. + */ public void shutdown() { if (_manager != null) - _manager.stopListening(); + _manager.shutdown(); } public void restart() { diff --git a/router/java/src/net/i2p/router/transport/TransportManager.java b/router/java/src/net/i2p/router/transport/TransportManager.java index d0a10f31b..a5e3a71fc 100644 --- a/router/java/src/net/i2p/router/transport/TransportManager.java +++ b/router/java/src/net/i2p/router/transport/TransportManager.java @@ -30,6 +30,7 @@ import net.i2p.data.i2np.I2NPMessage; import net.i2p.router.CommSystemFacade; import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; +import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.router.transport.ntcp.NTCPTransport; import net.i2p.router.transport.udp.UDPTransport; import net.i2p.util.Addresses; @@ -45,6 +46,7 @@ public class TransportManager implements TransportEventListener { private final Map _transports; private final RouterContext _context; private final UPnPManager _upnpManager; + private final DHSessionKeyBuilder.PrecalcRunner _dhThread; /** default true */ public final static String PROP_ENABLE_UDP = "i2np.udp.enable"; @@ -67,6 +69,7 @@ public class TransportManager implements TransportEventListener { _upnpManager = new UPnPManager(context, this); else _upnpManager = null; + _dhThread = new DHSessionKeyBuilder.PrecalcRunner(context); } public void addTransport(Transport transport) { @@ -84,12 +87,12 @@ public class TransportManager implements TransportEventListener { private void configTransports() { boolean enableUDP = _context.getBooleanPropertyDefaultTrue(PROP_ENABLE_UDP); if (enableUDP) { - UDPTransport udp = new UDPTransport(_context); + UDPTransport udp = new UDPTransport(_context, _dhThread); addTransport(udp); initializeAddress(udp); } if (isNTCPEnabled(_context)) - addTransport(new NTCPTransport(_context)); + addTransport(new NTCPTransport(_context, _dhThread)); if (_transports.isEmpty()) _log.log(Log.CRIT, "No transports are enabled"); } @@ -135,6 +138,7 @@ public class TransportManager implements TransportEventListener { } public void startListening() { + _dhThread.start(); // For now, only start UPnP if we have no publicly-routable addresses // so we don't open the listener ports to the world. // Maybe we need a config option to force on? Probably not. @@ -161,6 +165,9 @@ public class TransportManager implements TransportEventListener { startListening(); } + /** + * Can be restarted. + */ public void stopListening() { if (_upnpManager != null) _upnpManager.stop(); @@ -170,6 +177,16 @@ public class TransportManager implements TransportEventListener { _transports.clear(); } + + /** + * Cannot be restarted. + * @since 0.9 + */ + public void shutdown() { + stopListening(); + _dhThread.shutdown(); + } + public Transport getTransport(String style) { return _transports.get(style); } diff --git a/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java b/router/java/src/net/i2p/router/transport/crypto/DHSessionKeyBuilder.java similarity index 66% rename from core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java rename to router/java/src/net/i2p/router/transport/crypto/DHSessionKeyBuilder.java index ad09dd6ae..930078ba0 100644 --- a/core/java/src/net/i2p/crypto/DHSessionKeyBuilder.java +++ b/router/java/src/net/i2p/router/transport/crypto/DHSessionKeyBuilder.java @@ -1,4 +1,4 @@ -package net.i2p.crypto; +package net.i2p.router.transport.crypto; /* * free (adj.): unencumbered; not under the control of others @@ -10,17 +10,19 @@ package net.i2p.crypto; */ import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +//import java.io.InputStream; +//import java.io.OutputStream; import java.math.BigInteger; import java.util.concurrent.LinkedBlockingQueue; import net.i2p.I2PAppContext; import net.i2p.I2PException; +import net.i2p.crypto.CryptoConstants; +import net.i2p.crypto.KeyGenerator; +import net.i2p.crypto.SHA256Generator; import net.i2p.data.ByteArray; -import net.i2p.data.DataHelper; +//import net.i2p.data.DataHelper; import net.i2p.data.SessionKey; -import net.i2p.util.Clock; import net.i2p.util.I2PThread; import net.i2p.util.Log; import net.i2p.util.NativeBigInteger; @@ -31,11 +33,10 @@ import net.i2p.util.RandomSource; * constants defined in CryptoConstants, which causes the exchange to create a * 256 bit session key. * - * This class precalcs a set of values on its own thread, using those transparently - * when a new instance is created. By default, the minimum threshold for creating - * new values for the pool is 5, and the max pool size is 10. Whenever the pool has + * This class precalcs a set of values on its own thread. + * Whenever the pool has * less than the minimum, it fills it up again to the max. There is a delay after - * each precalculation so that the CPU isn't hosed during startup (defaulting to 1 second). + * each precalculation so that the CPU isn't hosed during startup. * These three parameters are controlled by java environmental variables and * can be adjusted via: * -Dcrypto.dh.precalc.min=40 -Dcrypto.dh.precalc.max=100 -Dcrypto.dh.precalc.delay=60000 @@ -44,135 +45,54 @@ import net.i2p.util.RandomSource; * * To disable precalculation, set min to 0 * + * @since 0.9 moved from net.i2p.crypto + * * @author jrandom */ public class DHSessionKeyBuilder { - private static I2PAppContext _context = I2PAppContext.getGlobalContext(); - private static Log _log; - private static final int MIN_NUM_BUILDERS; - private static final int MAX_NUM_BUILDERS; - private static final int CALC_DELAY; - private static final LinkedBlockingQueue _builders; - private static Thread _precalcThread; - private static volatile boolean _isRunning; // the data of importance - private BigInteger _myPrivateValue; - private BigInteger _myPublicValue; + private final BigInteger _myPrivateValue; + private final BigInteger _myPublicValue; private BigInteger _peerValue; private SessionKey _sessionKey; - private ByteArray _extraExchangedBytes; // bytes after the session key from the DH exchange + private final ByteArray _extraExchangedBytes; // bytes after the session key from the DH exchange - public final static String PROP_DH_PRECALC_MIN = "crypto.dh.precalc.min"; - public final static String PROP_DH_PRECALC_MAX = "crypto.dh.precalc.max"; - public final static String PROP_DH_PRECALC_DELAY = "crypto.dh.precalc.delay"; - public final static int DEFAULT_DH_PRECALC_MIN = 15; - public final static int DEFAULT_DH_PRECALC_MAX = 40; - public final static int DEFAULT_DH_PRECALC_DELAY = 200; + private final static String PROP_DH_PRECALC_MIN = "crypto.dh.precalc.min"; + private final static String PROP_DH_PRECALC_MAX = "crypto.dh.precalc.max"; + private final static String PROP_DH_PRECALC_DELAY = "crypto.dh.precalc.delay"; + private final static int DEFAULT_DH_PRECALC_MIN = 15; + private final static int DEFAULT_DH_PRECALC_MAX = 40; + private final static int DEFAULT_DH_PRECALC_DELAY = 200; - static { - I2PAppContext ctx = _context; - _log = ctx.logManager().getLog(DHSessionKeyBuilder.class); - ctx.statManager().createRateStat("crypto.dhGeneratePublicTime", "How long it takes to create x and X", "Encryption", new long[] { 60*60*1000 }); - ctx.statManager().createRateStat("crypto.dhCalculateSessionTime", "How long it takes to create the session key", "Encryption", new long[] { 60*60*1000 }); - ctx.statManager().createRateStat("crypto.DHUsed", "Need a DH from the queue", "Encryption", new long[] { 60*60*1000 }); - ctx.statManager().createRateStat("crypto.DHEmpty", "DH queue empty", "Encryption", new long[] { 60*60*1000 }); - - // add to the defaults for every 128MB of RAM, up to 512MB - long maxMemory = Runtime.getRuntime().maxMemory(); - if (maxMemory == Long.MAX_VALUE) - maxMemory = 127*1024*1024l; - int factor = (int) Math.max(1l, Math.min(4l, 1 + (maxMemory / (128*1024*1024l)))); - int defaultMin = DEFAULT_DH_PRECALC_MIN * factor; - int defaultMax = DEFAULT_DH_PRECALC_MAX * factor; - MIN_NUM_BUILDERS = ctx.getProperty(PROP_DH_PRECALC_MIN, defaultMin); - MAX_NUM_BUILDERS = ctx.getProperty(PROP_DH_PRECALC_MAX, defaultMax); - - CALC_DELAY = ctx.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY); - _builders = new LinkedBlockingQueue(MAX_NUM_BUILDERS); - - if (_log.shouldLog(Log.DEBUG)) - _log.debug("DH Precalc (minimum: " + MIN_NUM_BUILDERS + " max: " + MAX_NUM_BUILDERS + ", delay: " - + CALC_DELAY + ")"); - startPrecalc(); + /** + * Create a new public/private value pair for the DH exchange. + * Only for internal use and unit tests. + * Others should get instances from PrecalcRunner.getBuilder() + */ + DHSessionKeyBuilder() { + this(RandomSource.getInstance()); } /** - * Caller must synch on class - * @since 0.8.8 + * Create a new public/private value pair for the DH exchange. + * Only for internal use and unit tests. + * Others should get instances from PrecalcRunner.getBuilder() */ - private static void startPrecalc() { - _context = I2PAppContext.getGlobalContext(); - _log = _context.logManager().getLog(DHSessionKeyBuilder.class); - _precalcThread = new I2PThread(new DHSessionKeyBuilderPrecalcRunner(MIN_NUM_BUILDERS, MAX_NUM_BUILDERS), - "DH Precalc", true); - _precalcThread.setPriority(Thread.MIN_PRIORITY); - _isRunning = true; - _precalcThread.start(); - } - - /** - * Note that this stops the singleton precalc thread. - * You don't want to do this if there are multiple routers in the JVM. - * Fix this if you care. See Router.shutdown(). - * @since 0.8.8 - */ - public static void shutdown() { - _isRunning = false; - _precalcThread.interrupt(); - _builders.clear(); - } - - /** - * Only required if shutdown() previously called. - * @since 0.8.8 - */ - public static void restart() { - synchronized(DHSessionKeyBuilder.class) { - if (!_isRunning) - startPrecalc(); - } - } - - /** - * Construct a new DH key builder - * or pulls a prebuilt one from the queue. - */ - public DHSessionKeyBuilder() { - _context.statManager().addRateData("crypto.DHUsed", 1, 0); - DHSessionKeyBuilder builder = _builders.poll(); - if (builder != null) { - if (_log.shouldLog(Log.DEBUG)) _log.debug("Removing a builder. # left = " + _builders.size()); - _myPrivateValue = builder._myPrivateValue; - _myPublicValue = builder._myPublicValue; - // these two are still null after precalc - //_peerValue = builder._peerValue; - //_sessionKey = builder._sessionKey; - _extraExchangedBytes = builder._extraExchangedBytes; - } else { - if (_log.shouldLog(Log.INFO)) _log.info("No more builders, creating one now"); - _context.statManager().addRateData("crypto.DHEmpty", 1, 0); - // sets _myPrivateValue as a side effect - _myPublicValue = generateMyValue(); - _extraExchangedBytes = new ByteArray(); - } - } - - /** - * Only for internal use - * @parameter usePool unused, just to make it different from other constructor - */ - private DHSessionKeyBuilder(boolean usePool) { + DHSessionKeyBuilder(RandomSource random) { + _myPrivateValue = new NativeBigInteger(KeyGenerator.PUBKEY_EXPONENT_SIZE, random); + _myPublicValue = CryptoConstants.elgg.modPow(_myPrivateValue, CryptoConstants.elgp); _extraExchangedBytes = new ByteArray(); } /** * Conduct a DH exchange over the streams, returning the resulting data. * - * @deprecated unused + * unused * @return exchanged data * @throws IOException if there is an error (but does not close the streams */ +/**** public static DHSessionKeyBuilder exchangeKeys(InputStream in, OutputStream out) throws IOException { DHSessionKeyBuilder builder = new DHSessionKeyBuilder(); @@ -191,10 +111,12 @@ public class DHSessionKeyBuilder { return null; } } +****/ /** - * @deprecated unused + * unused */ +/**** private static BigInteger readBigI(InputStream in) throws IOException { byte Y[] = new byte[256]; int read = DataHelper.read(in, Y); @@ -211,14 +133,16 @@ public class DHSessionKeyBuilder { } return new NativeBigInteger(1, Y); } +****/ /** * Write out the integer as a 256 byte value. This left pads with 0s so * to keep in 2s complement, and if it is already 257 bytes (due to * the sign bit) ignore that first byte. * - * @deprecated unused + * unused */ +/**** private static void writeBigI(OutputStream out, BigInteger val) throws IOException { byte x[] = val.toByteArray(); for (int i = x.length; i < 256; i++) @@ -232,50 +156,22 @@ public class DHSessionKeyBuilder { out.flush(); } +****/ - private static final int getSize() { - return _builders.size(); - } - - /** @return true if successful, false if full */ - private static final boolean addBuilder(DHSessionKeyBuilder builder) { - return _builders.offer(builder); - } - - /** - * Create a new private value for the DH exchange, and return the number to - * be exchanged, leaving the actual private value accessible through getMyPrivateValue() - * - */ - public BigInteger generateMyValue() { - long start = System.currentTimeMillis(); - _myPrivateValue = new NativeBigInteger(KeyGenerator.PUBKEY_EXPONENT_SIZE, _context.random()); - BigInteger myValue = CryptoConstants.elgg.modPow(_myPrivateValue, CryptoConstants.elgp); - long end = System.currentTimeMillis(); - long diff = end - start; - _context.statManager().addRateData("crypto.dhGeneratePublicTime", diff, diff); - if (diff > 1000) { - if (_log.shouldLog(Log.WARN)) - _log.warn("Took more than a second (" + diff + "ms) to generate local DH value"); - } else { - if (_log.shouldLog(Log.DEBUG)) _log.debug("Took " + diff + "ms to generate local DH value"); - } - return myValue; - } - /** * Retrieve the private value used by the local participant in the DH exchange + * unused */ +/* public BigInteger getMyPrivateValue() { return _myPrivateValue; } +*/ /** * Retrieve the public value used by the local participant in the DH exchange, - * generating it if necessary */ public BigInteger getMyPublicValue() { - if (_myPublicValue == null) _myPublicValue = generateMyValue(); return _myPublicValue; } @@ -309,14 +205,17 @@ public class DHSessionKeyBuilder { _peerValue = peerVal; } + /** + * @param val 256 bytes + */ public void setPeerPublicValue(byte val[]) throws InvalidPublicParameterException { if (val.length != 256) throw new IllegalArgumentException("Peer public value must be exactly 256 bytes"); if (1 == (val[0] & 0x80)) { // high bit set, need to inject an additional byte to keep 2s complement - if (_log.shouldLog(Log.DEBUG)) - _log.debug("High bit set"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("High bit set"); byte val2[] = new byte[257]; System.arraycopy(val, 0, val2, 1, 256); val = val2; @@ -329,6 +228,11 @@ public class DHSessionKeyBuilder { return _peerValue; } + /** + * Return a 256 byte representation of his public key, with leading 0s + * if necessary. + * + */ public byte[] getPeerPublicValueBytes() { return toByteArray(getPeerPublicValue()); } @@ -338,10 +242,9 @@ public class DHSessionKeyBuilder { * * @return session key exchanged, or null if the exchange is not complete */ - public SessionKey getSessionKey() { + public synchronized SessionKey getSessionKey() { if (_sessionKey != null) return _sessionKey; if (_peerValue != null) { - if (_myPrivateValue == null) generateMyValue(); _sessionKey = calculateSessionKey(_myPrivateValue, _peerValue); } else { //System.err.println("Not ready yet.. privateValue and peerValue must be set (" @@ -367,7 +270,7 @@ public class DHSessionKeyBuilder { * */ private final SessionKey calculateSessionKey(BigInteger myPrivateValue, BigInteger publicPeerValue) { - long start = System.currentTimeMillis(); + //long start = System.currentTimeMillis(); SessionKey key = new SessionKey(); BigInteger exchangedKey = publicPeerValue.modPow(myPrivateValue, CryptoConstants.elgp); byte buf[] = exchangedKey.toByteArray(); @@ -376,28 +279,28 @@ public class DHSessionKeyBuilder { System.arraycopy(buf, 0, val, 0, buf.length); byte remaining[] = SHA256Generator.getInstance().calculateHash(val).getData(); _extraExchangedBytes.setData(remaining); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Storing " + remaining.length + " bytes from the DH exchange by SHA256 the session key"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Storing " + remaining.length + " bytes from the DH exchange by SHA256 the session key"); } else { // (buf.length >= val.length) System.arraycopy(buf, 0, val, 0, val.length); // feed the extra bytes into the PRNG - _context.random().harvester().feedEntropy("DH", buf, val.length, buf.length-val.length); + RandomSource.getInstance().harvester().feedEntropy("DH", buf, val.length, buf.length-val.length); byte remaining[] = new byte[buf.length - val.length]; System.arraycopy(buf, val.length, remaining, 0, remaining.length); _extraExchangedBytes.setData(remaining); - if (_log.shouldLog(Log.DEBUG)) - _log.debug("Storing " + remaining.length + " bytes from the end of the DH exchange"); + //if (_log.shouldLog(Log.DEBUG)) + // _log.debug("Storing " + remaining.length + " bytes from the end of the DH exchange"); } key.setData(val); - long end = System.currentTimeMillis(); - long diff = end - start; + //long end = System.currentTimeMillis(); + //long diff = end - start; - _context.statManager().addRateData("crypto.dhCalculateSessionTime", diff, diff); - if (diff > 1000) { - if (_log.shouldLog(Log.WARN)) _log.warn("Generating session key took too long (" + diff + " ms"); - } else { - if (_log.shouldLog(Log.DEBUG)) _log.debug("Generating session key " + diff + " ms"); - } + //_context.statManager().addRateData("crypto.dhCalculateSessionTime", diff, diff); + //if (diff > 1000) { + // if (_log.shouldLog(Log.WARN)) _log.warn("Generating session key took too long (" + diff + " ms"); + //} else { + // if (_log.shouldLog(Log.DEBUG)) _log.debug("Generating session key " + diff + " ms"); + //} return key; } @@ -504,19 +407,70 @@ public class DHSessionKeyBuilder { } ******/ - private static class DHSessionKeyBuilderPrecalcRunner implements Runnable { + /** + * @since 0.9 + */ + public interface Factory { + /** + * Construct a new DH key builder + * or pulls a prebuilt one from the queue. + */ + public DHSessionKeyBuilder getBuilder(); + } + + public static class PrecalcRunner extends I2PThread implements Factory { + private final I2PAppContext _context; + private final Log _log; private final int _minSize; private final int _maxSize; + private final int _calcDelay; + private final LinkedBlockingQueue _builders; + private volatile boolean _isRunning; /** check every 30 seconds whether we have less than the minimum */ private long _checkDelay = 30 * 1000; - private DHSessionKeyBuilderPrecalcRunner(int minSize, int maxSize) { - _minSize = minSize; - _maxSize = maxSize; + public PrecalcRunner(I2PAppContext ctx) { + super("DH Precalc"); + _context = ctx; + _log = ctx.logManager().getLog(DHSessionKeyBuilder.class); + ctx.statManager().createRateStat("crypto.dhGeneratePublicTime", "How long it takes to create x and X", "Encryption", new long[] { 60*60*1000 }); + //ctx.statManager().createRateStat("crypto.dhCalculateSessionTime", "How long it takes to create the session key", "Encryption", new long[] { 60*60*1000 }); + ctx.statManager().createRateStat("crypto.DHUsed", "Need a DH from the queue", "Encryption", new long[] { 60*60*1000 }); + ctx.statManager().createRateStat("crypto.DHEmpty", "DH queue empty", "Encryption", new long[] { 60*60*1000 }); + + // add to the defaults for every 128MB of RAM, up to 512MB + long maxMemory = Runtime.getRuntime().maxMemory(); + if (maxMemory == Long.MAX_VALUE) + maxMemory = 127*1024*1024l; + int factor = (int) Math.max(1l, Math.min(4l, 1 + (maxMemory / (128*1024*1024l)))); + int defaultMin = DEFAULT_DH_PRECALC_MIN * factor; + int defaultMax = DEFAULT_DH_PRECALC_MAX * factor; + _minSize = ctx.getProperty(PROP_DH_PRECALC_MIN, defaultMin); + _maxSize = ctx.getProperty(PROP_DH_PRECALC_MAX, defaultMax); + _calcDelay = ctx.getProperty(PROP_DH_PRECALC_DELAY, DEFAULT_DH_PRECALC_DELAY); + + if (_log.shouldLog(Log.DEBUG)) + _log.debug("DH Precalc (minimum: " + _minSize + " max: " + _maxSize + ", delay: " + + _calcDelay + ")"); + _builders = new LinkedBlockingQueue(_maxSize); + setPriority(Thread.MIN_PRIORITY); } + /** + * Note that this stops the singleton precalc thread. + * You don't want to do this if there are multiple routers in the JVM. + * Fix this if you care. See Router.shutdown(). + * @since 0.8.8 + */ + public void shutdown() { + _isRunning = false; + this.interrupt(); + _builders.clear(); + } + public void run() { + _isRunning = true; while (_isRunning) { //long start = System.currentTimeMillis(); int startSize = getSize(); @@ -535,7 +489,7 @@ public class DHSessionKeyBuilder { long curCalc = System.currentTimeMillis() - curStart; // for some relief... try { - Thread.sleep(CALC_DELAY + (curCalc * 3)); + Thread.sleep(_calcDelay + (curCalc * 3)); } catch (InterruptedException ie) { // nop } } @@ -557,11 +511,47 @@ public class DHSessionKeyBuilder { } } - private static DHSessionKeyBuilder precalc() { - DHSessionKeyBuilder builder = new DHSessionKeyBuilder(false); - builder.getMyPublicValue(); + /** + * Construct a new DH key builder + * or pulls a prebuilt one from the queue. + * + * @since 0.9 moved from DHSKB + */ + public DHSessionKeyBuilder getBuilder() { + _context.statManager().addRateData("crypto.DHUsed", 1, 0); + DHSessionKeyBuilder builder = _builders.poll(); + if (builder == null) { + if (_log.shouldLog(Log.INFO)) _log.info("No more builders, creating one now"); + _context.statManager().addRateData("crypto.DHEmpty", 1, 0); + builder = precalc(); + } return builder; } + + private DHSessionKeyBuilder precalc() { + long start = System.currentTimeMillis(); + DHSessionKeyBuilder builder = new DHSessionKeyBuilder(_context.random()); + long end = System.currentTimeMillis(); + long diff = end - start; + _context.statManager().addRateData("crypto.dhGeneratePublicTime", diff, diff); + if (diff > 1000) { + if (_log.shouldLog(Log.WARN)) + _log.warn("Took more than a second (" + diff + "ms) to generate local DH value"); + } else { + if (_log.shouldLog(Log.DEBUG)) _log.debug("Took " + diff + "ms to generate local DH value"); + } + return builder; + } + + /** @return true if successful, false if full */ + private final boolean addBuilder(DHSessionKeyBuilder builder) { + return _builders.offer(builder); + } + + private final int getSize() { + return _builders.size(); + } + } public static class InvalidPublicParameterException extends I2PException { diff --git a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java index ee287fd26..54960e28e 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java +++ b/router/java/src/net/i2p/router/transport/ntcp/EstablishState.java @@ -12,7 +12,6 @@ import java.net.UnknownHostException; import java.nio.ByteBuffer; import net.i2p.I2PAppContext; -import net.i2p.crypto.DHSessionKeyBuilder; import net.i2p.data.Base64; import net.i2p.data.Certificate; import net.i2p.data.DataFormatException; @@ -27,6 +26,7 @@ import net.i2p.data.SigningPrivateKey; import net.i2p.data.SigningPublicKey; import net.i2p.router.Router; import net.i2p.router.RouterContext; +import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.util.Log; /** @@ -70,12 +70,12 @@ import net.i2p.util.Log; * */ class EstablishState { - private RouterContext _context; - private Log _log; + private final RouterContext _context; + private final Log _log; // bob receives (and alice sends) - private byte _X[]; - private byte _hX_xor_bobIdentHash[]; + private final byte _X[]; + private final byte _hX_xor_bobIdentHash[]; private int _aliceIdentSize; /** contains the decrypted aliceIndexSize + aliceIdent + tsA + padding + aliceSig */ private ByteArrayOutputStream _sz_aliceIdent_tsA_padding_aliceSig; @@ -100,7 +100,7 @@ class EstablishState { */ private int _curEncryptedOffset; /** decryption buffer */ - private byte _curDecrypted[]; + private final byte _curDecrypted[]; /** bytes received so far */ private int _received; @@ -109,10 +109,10 @@ class EstablishState { private byte _extra[]; - private DHSessionKeyBuilder _dh; + private final DHSessionKeyBuilder _dh; - private NTCPTransport _transport; - private NTCPConnection _con; + private final NTCPTransport _transport; + private final NTCPConnection _con; private boolean _corrupt; /** error causing the corruption */ private String _err; @@ -127,15 +127,14 @@ class EstablishState { _log = ctx.logManager().getLog(getClass()); _transport = transport; _con = con; - _dh = new DHSessionKeyBuilder(); + _dh = _transport.getDHBuilder(); + _hX_xor_bobIdentHash = new byte[Hash.HASH_LENGTH]; if (_con.isInbound()) { _X = new byte[256]; - _hX_xor_bobIdentHash = new byte[Hash.HASH_LENGTH]; _sz_aliceIdent_tsA_padding_aliceSig = new ByteArrayOutputStream(512); } else { _X = _dh.getMyPublicValueBytes(); _Y = new byte[256]; - _hX_xor_bobIdentHash = new byte[Hash.HASH_LENGTH]; byte hx[] = ctx.sha().calculateHash(_X).getData(); DataHelper.xor(hx, 0, con.getRemotePeer().calculateHash().getData(), 0, _hX_xor_bobIdentHash, 0, hx.length); } diff --git a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java index ac6856a5b..4fb6f8a52 100644 --- a/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java +++ b/router/java/src/net/i2p/router/transport/ntcp/NTCPTransport.java @@ -31,13 +31,14 @@ import net.i2p.router.transport.CommSystemFacadeImpl; import net.i2p.router.transport.Transport; import net.i2p.router.transport.TransportBid; import net.i2p.router.transport.TransportImpl; +import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.util.Addresses; import net.i2p.util.ConcurrentHashSet; import net.i2p.util.Log; import net.i2p.util.Translate; /** - * + * The NIO TCP transport */ public class NTCPTransport extends TransportImpl { private final Log _log; @@ -64,6 +65,7 @@ public class NTCPTransport extends TransportImpl { public static final String PROP_BIND_INTERFACE = "i2np.ntcp.bindInterface"; private final NTCPSendFinisher _finisher; + private final DHSessionKeyBuilder.Factory _dhFactory; private long _lastBadSkew; private static final long[] RATES = { 10*60*1000 }; @@ -71,9 +73,9 @@ public class NTCPTransport extends TransportImpl { //private static final String THINSP = " / "; private static final String THINSP = " / "; - public NTCPTransport(RouterContext ctx) { + public NTCPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) { super(ctx); - + _dhFactory = dh; _log = ctx.logManager().getLog(getClass()); _context.statManager().createRateStat("ntcp.sendTime", "Total message lifetime when sent completely", "ntcp", RATES); @@ -585,6 +587,13 @@ public class NTCPTransport extends TransportImpl { public String getStyle() { return STYLE; } EventPumper getPumper() { return _pumper; } + /** + * @since 0.9 + */ + DHSessionKeyBuilder getDHBuilder() { + return _dhFactory.getBuilder(); + } + /** * how long from initial connection attempt (accept() or connect()) until * the con must be established to avoid premature close()ing diff --git a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java index 0db920117..16386280d 100644 --- a/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java +++ b/router/java/src/net/i2p/router/transport/udp/EstablishmentManager.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import net.i2p.crypto.DHSessionKeyBuilder; import net.i2p.data.Base64; import net.i2p.data.Hash; import net.i2p.data.RouterAddress; @@ -22,6 +21,7 @@ import net.i2p.router.OutNetMessage; import net.i2p.router.Router; import net.i2p.router.RouterContext; import net.i2p.router.networkdb.kademlia.FloodfillNetworkDatabaseFacade; +import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.util.I2PThread; import net.i2p.util.Log; import net.i2p.util.SimpleScheduler; @@ -212,7 +212,7 @@ class EstablishmentManager { } state = new OutboundEstablishState(_context, remAddr, port, msg.getTarget().getIdentity(), - sessionKey, addr); + sessionKey, addr, _transport.getDHBuilder()); OutboundEstablishState oldState = _outboundStates.putIfAbsent(to, state); boolean isNew = oldState == null; if (!isNew) @@ -303,7 +303,8 @@ class EstablishmentManager { } if (!_transport.allowConnection()) return; // drop the packet - state = new InboundEstablishState(_context, from.getIP(), from.getPort(), _transport.getLocalPort()); + state = new InboundEstablishState(_context, from.getIP(), from.getPort(), _transport.getLocalPort(), + _transport.getDHBuilder()); state.receiveSessionRequest(reader.getSessionRequestReader()); InboundEstablishState oldState = _inboundStates.putIfAbsent(from, state); isNew = oldState == null; diff --git a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java index 4212d21cf..e03d61869 100644 --- a/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/InboundEstablishState.java @@ -3,7 +3,6 @@ package net.i2p.router.transport.udp; import java.io.ByteArrayInputStream; import java.io.IOException; -import net.i2p.crypto.DHSessionKeyBuilder; import net.i2p.data.Base64; import net.i2p.data.ByteArray; import net.i2p.data.DataFormatException; @@ -12,6 +11,7 @@ import net.i2p.data.RouterIdentity; import net.i2p.data.SessionKey; import net.i2p.data.Signature; import net.i2p.router.RouterContext; +import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.util.Addresses; import net.i2p.util.Log; @@ -29,8 +29,7 @@ class InboundEstablishState { private byte _receivedX[]; private byte _bobIP[]; private final int _bobPort; - // try to fix NPE in getSentY() ????? - private volatile DHSessionKeyBuilder _keyBuilder; + private final DHSessionKeyBuilder _keyBuilder; // SessionCreated message private byte _sentY[]; private final byte _aliceIP[]; @@ -68,7 +67,8 @@ class InboundEstablishState { /** we are explicitly failing it */ public static final int STATE_FAILED = 5; - public InboundEstablishState(RouterContext ctx, byte remoteIP[], int remotePort, int localPort) { + public InboundEstablishState(RouterContext ctx, byte remoteIP[], int remotePort, int localPort, + DHSessionKeyBuilder dh) { _context = ctx; _log = ctx.logManager().getLog(InboundEstablishState.class); _aliceIP = remoteIP; @@ -77,6 +77,7 @@ class InboundEstablishState { _bobPort = localPort; _currentState = STATE_UNKNOWN; _establishBegin = ctx.clock().now(); + _keyBuilder = dh; } public synchronized int getState() { return _currentState; } @@ -106,7 +107,6 @@ class InboundEstablishState { public synchronized void generateSessionKey() throws DHSessionKeyBuilder.InvalidPublicParameterException { if (_sessionKey != null) return; - _keyBuilder = new DHSessionKeyBuilder(); _keyBuilder.setPeerPublicValue(_receivedX); _sessionKey = _keyBuilder.getSessionKey(); ByteArray extra = _keyBuilder.getExtraBytes(); @@ -130,7 +130,6 @@ class InboundEstablishState { public synchronized byte[] getSentY() { if (_sentY == null) - // Rare NPE seen here... _sentY = _keyBuilder.getMyPublicValueBytes(); return _sentY; } diff --git a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java index 835c74dad..fa35340dc 100644 --- a/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java +++ b/router/java/src/net/i2p/router/transport/udp/OutboundEstablishState.java @@ -4,7 +4,6 @@ import java.net.InetAddress; import java.util.Queue; import java.util.concurrent.LinkedBlockingQueue; -import net.i2p.crypto.DHSessionKeyBuilder; import net.i2p.data.Base64; import net.i2p.data.ByteArray; import net.i2p.data.DataHelper; @@ -13,6 +12,7 @@ import net.i2p.data.SessionKey; import net.i2p.data.Signature; import net.i2p.router.OutNetMessage; import net.i2p.router.RouterContext; +import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.util.Addresses; import net.i2p.util.Log; @@ -26,10 +26,10 @@ class OutboundEstablishState { private final RouterContext _context; private final Log _log; // SessionRequest message - private byte _sentX[]; + private final byte _sentX[]; private byte _bobIP[]; private int _bobPort; - private DHSessionKeyBuilder _keyBuilder; + private final DHSessionKeyBuilder _keyBuilder; // SessionCreated message private byte _receivedY[]; private byte _aliceIP[]; @@ -73,7 +73,8 @@ class OutboundEstablishState { public static final int STATE_PENDING_INTRO = 5; public OutboundEstablishState(RouterContext ctx, InetAddress remoteHost, int remotePort, - RouterIdentity remotePeer, SessionKey introKey, UDPAddress addr) { + RouterIdentity remotePeer, SessionKey introKey, UDPAddress addr, + DHSessionKeyBuilder dh) { _context = ctx; _log = ctx.logManager().getLog(OutboundEstablishState.class); if ( (remoteHost != null) && (remotePort > 0) ) { @@ -92,6 +93,8 @@ class OutboundEstablishState { _establishBegin = ctx.clock().now(); _remoteAddress = addr; _introductionNonce = -1; + _keyBuilder = dh; + _sentX = new byte[UDPPacketReader.SessionRequestReader.X_LENGTH]; prepareSessionRequest(); if ( (addr != null) && (addr.getIntroducerCount() > 0) ) { if (_log.shouldLog(Log.DEBUG)) @@ -128,10 +131,7 @@ class OutboundEstablishState { /** called from constructor, no need to synch */ private void prepareSessionRequest() { - _keyBuilder = new DHSessionKeyBuilder(); byte X[] = _keyBuilder.getMyPublicValue().toByteArray(); - if (_sentX == null) - _sentX = new byte[UDPPacketReader.SessionRequestReader.X_LENGTH]; if (X.length == 257) System.arraycopy(X, 1, _sentX, 0, _sentX.length); else if (X.length == 256) diff --git a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java index e049c0a91..612965bea 100644 --- a/router/java/src/net/i2p/router/transport/udp/UDPTransport.java +++ b/router/java/src/net/i2p/router/transport/udp/UDPTransport.java @@ -34,6 +34,7 @@ import net.i2p.router.RouterContext; import net.i2p.router.transport.Transport; import net.i2p.router.transport.TransportBid; import net.i2p.router.transport.TransportImpl; +import net.i2p.router.transport.crypto.DHSessionKeyBuilder; import net.i2p.router.util.RandomIterator; import net.i2p.util.Addresses; import net.i2p.util.ConcurrentHashSet; @@ -43,7 +44,7 @@ import net.i2p.util.SimpleTimer; import net.i2p.util.Translate; /** - * + * The SSU transport */ public class UDPTransport extends TransportImpl implements TimedWeightedPriorityMessageQueue.FailedListener { private final Log _log; @@ -70,6 +71,7 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority private long _reachabilityStatusLastUpdated; private long _introducersSelectedOn; private long _lastInboundReceivedOn; + private final DHSessionKeyBuilder.Factory _dhFactory; /** do we need to rebuild our external router address asap? */ private boolean _needsRebuild; @@ -178,8 +180,9 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority //private static final String THINSP = " / "; private static final String THINSP = " / "; - public UDPTransport(RouterContext ctx) { + public UDPTransport(RouterContext ctx, DHSessionKeyBuilder.Factory dh) { super(ctx); + _dhFactory = dh; _log = ctx.logManager().getLog(UDPTransport.class); _peersByIdent = new ConcurrentHashMap(128); _peersByRemoteHost = new ConcurrentHashMap(128); @@ -227,7 +230,6 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority _context.statManager().createRateStat("udp.proactiveReestablish", "How long a session was idle for when we proactively reestablished it", "udp", RATES); _context.statManager().createRateStat("udp.dropPeerDroplist", "How many peers currently have their packets dropped outright when a new peer is added to the list?", "udp", RATES); _context.statManager().createRateStat("udp.dropPeerConsecutiveFailures", "How many consecutive failed sends to a peer did we attempt before giving up and reestablishing a new session (lifetime is inactivity perood)", "udp", RATES); - __instance = this; SimpleScheduler.getInstance().addPeriodicEvent(new PingIntroducers(), MIN_EXPIRE_TIMEOUT * 3 / 4); } @@ -1623,24 +1625,13 @@ public class UDPTransport extends TransportImpl implements TimedWeightedPriority return skews; } - private static UDPTransport __instance; - /** **internal, do not use** */ - public static final UDPTransport _instance() { return __instance; } - /** **internal, do not use** return the peers (Hash) of active peers. */ - public List _getActivePeers() { - List peers = new ArrayList(128); - peers.addAll(_peersByIdent.keySet()); - - long now = _context.clock().now(); - for (Iterator iter = peers.iterator(); iter.hasNext(); ) { - Hash peer = iter.next(); - PeerState state = getPeerState(peer); - if (now-state.getLastReceiveTime() > 5*60*1000) - iter.remove(); // don't include old peers - } - return peers; + /** + * @since 0.9 + */ + DHSessionKeyBuilder getDHBuilder() { + return _dhFactory.getBuilder(); } - + private static final int FLAG_ALPHA = 0; private static final int FLAG_IDLE_IN = 1; private static final int FLAG_IDLE_OUT = 2;